-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbootstrap.sh
More file actions
executable file
·194 lines (168 loc) · 5.59 KB
/
bootstrap.sh
File metadata and controls
executable file
·194 lines (168 loc) · 5.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#!/usr/bin/env bash
# bootstrap.sh — linux-security provisioner
#
# Sources config.env (and config.web.env for the web-server profile), runs all
# hardening scripts listed in the selected profile, and logs each run.
# Exits non-zero if any script fails. Run as root.
set -euo pipefail
usage() {
cat <<EOF
Usage: bootstrap.sh [OPTIONS]
Run all hardening scripts for the selected profile. Must be run as root.
Options:
--profile PROFILE Profile to run (default: web-server)
baseline — core hardening only; works on any Ubuntu/Debian server
web-server — core + Apache, PHP, MySQL, certbot hardening
--dry-run Preview every change without applying anything
--confirm Skip the interactive confirmation prompt
--help, -h Show this help
Profiles are defined in profiles/*.conf — plain text lists of scripts to run in order.
Config is read from config.env or /etc/linux-security/config.env.
Examples:
bash bootstrap.sh # full web-server stack (default)
bash bootstrap.sh --profile baseline # core hardening only
bash bootstrap.sh --profile baseline --dry-run # preview baseline changes
bash bootstrap.sh --profile web-server # explicit full stack
EOF
}
# --- Args ---
DRYRUN=false
CONFIRM=false
PROFILE="web-server"
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRYRUN=true ;;
--confirm) CONFIRM=true ;;
--profile) PROFILE="${2:-web-server}"; shift ;;
--help|-h) usage; exit 0 ;;
*) echo "ERROR: Unknown argument: $1" >&2; usage >&2; exit 1 ;;
esac
shift
done
# --- Paths ---
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="${SCRIPT_DIR}/logs"
TIMESTAMP=$(date '+%Y%m%d-%H%M%S')
LOG_FILE="${LOG_DIR}/bootstrap-${TIMESTAMP}.log"
# --- Validate profile ---
PROFILE_FILE="${SCRIPT_DIR}/profiles/${PROFILE}.conf"
if [[ ! -f "$PROFILE_FILE" ]]; then
echo "ERROR: Unknown profile '${PROFILE}'." >&2
echo " Available profiles: $(ls "$SCRIPT_DIR/profiles/" | sed 's/\.conf$//' | tr '\n' ' ')" >&2
exit 1
fi
# --- Config discovery ---
CONFIG_FILE="${CONFIG_FILE:-}"
if [[ -z "$CONFIG_FILE" ]]; then
for loc in \
"$SCRIPT_DIR/config.env" \
/etc/linux-security/config.env; do
if [[ -f "$loc" ]]; then CONFIG_FILE="$loc"; break; fi
done
fi
if [[ -n "$CONFIG_FILE" ]]; then
export CONFIG_FILE
# shellcheck source=/dev/null
source "$CONFIG_FILE"
else
echo "ERROR: config.env not found." >&2
echo " Copy config.env to the repo root and fill in your values." >&2
echo " See docs/customization.md for details." >&2
exit 1
fi
# --- Web config discovery (for web-server profile) ---
if [[ "$PROFILE" == "web-server" ]]; then
WEB_CONFIG_FILE="${WEB_CONFIG_FILE:-}"
if [[ -z "$WEB_CONFIG_FILE" ]]; then
for loc in \
"$SCRIPT_DIR/config.web.env" \
/etc/linux-security/config.web.env; do
if [[ -f "$loc" ]]; then WEB_CONFIG_FILE="$loc"; break; fi
done
fi
if [[ -n "$WEB_CONFIG_FILE" ]]; then
export WEB_CONFIG_FILE
# shellcheck source=/dev/null
source "$WEB_CONFIG_FILE"
fi
fi
# --- Pre-flight ---
if [[ $EUID -ne 0 ]]; then
echo "ERROR: bootstrap.sh must be run as root." >&2
exit 1
fi
require_confirm() {
$CONFIRM && return
$DRYRUN && return
echo ""
printf " Type AGREE to continue or Ctrl+C to abort: "
read -r _CONFIRM_REPLY
[[ "$_CONFIRM_REPLY" == "AGREE" ]] || { echo "Aborted."; exit 0; }
}
require_confirm
CONFIRM=true # prompt already given (or skipped); sub-scripts should not re-prompt
mkdir -p "$LOG_DIR"
# --- Load profile manifest ---
mapfile -t SCRIPTS < <(grep -v '^\s*#' "$PROFILE_FILE" | grep -v '^\s*$')
# --- Banner ---
echo "========================================="
echo " linux-security Bootstrap"
echo " Profile: ${PROFILE}"
echo " Host: $(hostname -f)"
echo " Date: $(date '+%Y-%m-%d %H:%M %Z')"
if $DRYRUN; then
echo " MODE: DRY RUN — no changes will be made"
fi
echo " Log: $LOG_FILE"
echo "========================================="
echo ""
PASS=0
FAIL=0
run_script() {
local script="$1"
local name
name=$(basename "$script")
local script_log="${LOG_DIR}/${TIMESTAMP}-${name%.sh}.log"
echo "--- Running: ${script} ---"
local args=()
$DRYRUN && args+=("--dry-run")
$CONFIRM && args+=("--confirm")
if bash "${SCRIPT_DIR}/${script}" "${args[@]}" 2>&1 | tee "$script_log"; then
echo " [OK] $name"
((PASS++))
else
echo " [FAILED] $name — see $script_log"
((FAIL++))
return 1
fi
echo ""
}
# Run all scripts in profile order, stop on first failure
for script in "${SCRIPTS[@]}"; do
run_script "$script"
done | tee "$LOG_FILE"
# --- Summary ---
echo "========================================="
if $DRYRUN; then
echo " Dry run complete — no changes made."
else
echo " Bootstrap complete!"
fi
echo ""
echo " Profile: ${PROFILE}"
echo " Scripts run: ${#SCRIPTS[@]}"
echo " Passed: $PASS"
echo " Failed: $FAIL"
echo " Full log: $LOG_FILE"
if ! $DRYRUN && [[ "$FAIL" -eq 0 ]]; then
echo ""
echo " Running post-run verification..."
echo ""
bash "${SCRIPT_DIR}/scripts/core/audit/verify.sh" --brief 2>&1 | tee -a "$LOG_FILE" || true
echo ""
echo " Full audit: bash scripts/audit/audit.sh --profile ${PROFILE}"
echo " IMPORTANT: test SSH in a new terminal before"
echo " closing this session."
fi
echo "========================================="
[[ "$FAIL" -eq 0 ]]