Skip to content

Commit 36e88c0

Browse files
qfiberclaude
andcommitted
feat(install): make DB engines + Stalwart optional, gate dependent prompts
Adds three install flags driven by first-run prompts: INSTALL_MARIADB / INSTALL_POSTGRES — replaces "always install both" INSTALL_STALWART — user mail server now optional Prompt flow only asks for what the operator picked: - phpMyAdmin only offered when MariaDB is selected - pgAdmin4 only offered when PostgreSQL is selected - Mail admin host + DKIM selector only asked when Stalwart=yes - Admin IP allowlist only asked when at least one admin endpoint (pma / pga / Stalwart) is being installed 20-databases.sh skips MariaDB block / Postgres block independently. 70-mail.sh exits 0 when Stalwart not selected. 00-base.sh firewalld rules for smtp/smtps/587/imaps are gated on INSTALL_STALWART, and removed if a previous run had opened them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 363d336 commit 36e88c0

3 files changed

Lines changed: 174 additions & 94 deletions

File tree

bootstrap/00-base.sh

Lines changed: 98 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -88,53 +88,98 @@ else
8888
warn "Invalid port."
8989
done
9090

91-
# ---- Mail admin panel ----
92-
echo
93-
info "Mail admin panel (Stalwart web UI) — IP-allowlisted only."
9491
DOMAIN_PART="${SERVER_HOSTNAME#*.}"
95-
prompt MAIL_ADMIN_HOST "Mail admin panel hostname" "mail.${DOMAIN_PART}"
96-
valid_domain "${MAIL_ADMIN_HOST}" || die "Invalid FQDN."
97-
while :; do
98-
prompt_required MAIL_ADMIN_ALLOWLIST "Admin allowlist (IP/CIDR, comma-separated; v4+v6 both OK)"
99-
ok=1
100-
IFS=',' read -ra entries <<<"${MAIL_ADMIN_ALLOWLIST}"
101-
for e in "${entries[@]}"; do
102-
e="${e//[[:space:]]/}"
103-
valid_ip_or_cidr "${e}" || { warn "Invalid: '${e}'"; ok=0; break; }
104-
done
105-
[[ ${ok} -eq 1 ]] && break
106-
done
10792

108-
# ---- DKIM selector ----
109-
prompt DKIM_SELECTOR "DKIM selector (per-server)" "default"
110-
111-
# ---- DB admin tooling ----
93+
# ---- Database engines ----
11294
echo
113-
info "Database admin tooling (optional — extra attack surface, allowlisted+basic-auth)"
114-
info " 1) phpMyAdmin (MariaDB)"
115-
info " 2) pgAdmin4 (Postgres)"
95+
info "Database engines (skip if this server doesn't host apps that need a DB)"
96+
info " 1) MariaDB"
97+
info " 2) PostgreSQL"
11698
info " 3) Both"
11799
info " 4) None"
118100
while :; do
119-
prompt TOOLS_CHOICE "Choice [1-4]" "4"
120-
case "${TOOLS_CHOICE}" in 1|2|3|4) break ;; *) warn "Pick 1-4." ;; esac
101+
prompt DB_CHOICE "Choice [1-4]" "3"
102+
case "${DB_CHOICE}" in 1|2|3|4) break ;; *) warn "Pick 1-4." ;; esac
121103
done
104+
INSTALL_MARIADB="no"
105+
INSTALL_POSTGRES="no"
106+
case "${DB_CHOICE}" in
107+
1) INSTALL_MARIADB="yes" ;;
108+
2) INSTALL_POSTGRES="yes" ;;
109+
3) INSTALL_MARIADB="yes"; INSTALL_POSTGRES="yes" ;;
110+
esac
111+
112+
# ---- Database admin tooling (only applicable to installed engines) ----
122113
INSTALL_PMA="no"
123114
INSTALL_PGA="no"
124115
PMA_HOST=""
125116
PGA_HOST=""
126-
case "${TOOLS_CHOICE}" in
127-
1) INSTALL_PMA="yes" ;;
128-
2) INSTALL_PGA="yes" ;;
129-
3) INSTALL_PMA="yes"; INSTALL_PGA="yes" ;;
130-
esac
131-
if [[ "${INSTALL_PMA}" == "yes" ]]; then
132-
prompt PMA_HOST "phpMyAdmin hostname" "pma.${DOMAIN_PART}"
133-
valid_domain "${PMA_HOST}" || die "Invalid FQDN."
117+
if [[ "${INSTALL_MARIADB}" == "yes" || "${INSTALL_POSTGRES}" == "yes" ]]; then
118+
echo
119+
info "Database admin tooling (optional — extra attack surface, allowlisted+basic-auth)"
120+
if [[ "${INSTALL_MARIADB}" == "yes" && "${INSTALL_POSTGRES}" == "yes" ]]; then
121+
info " 1) phpMyAdmin (MariaDB)"
122+
info " 2) pgAdmin4 (Postgres)"
123+
info " 3) Both"
124+
info " 4) None"
125+
while :; do
126+
prompt TOOLS_CHOICE "Choice [1-4]" "4"
127+
case "${TOOLS_CHOICE}" in 1|2|3|4) break ;; *) warn "Pick 1-4." ;; esac
128+
done
129+
case "${TOOLS_CHOICE}" in
130+
1) INSTALL_PMA="yes" ;;
131+
2) INSTALL_PGA="yes" ;;
132+
3) INSTALL_PMA="yes"; INSTALL_PGA="yes" ;;
133+
esac
134+
elif [[ "${INSTALL_MARIADB}" == "yes" ]]; then
135+
prompt_yes_no PMA_YN "Install phpMyAdmin?" "n"
136+
[[ "${PMA_YN}" == "yes" ]] && INSTALL_PMA="yes"
137+
else
138+
prompt_yes_no PGA_YN "Install pgAdmin4?" "n"
139+
[[ "${PGA_YN}" == "yes" ]] && INSTALL_PGA="yes"
140+
fi
141+
if [[ "${INSTALL_PMA}" == "yes" ]]; then
142+
prompt PMA_HOST "phpMyAdmin hostname" "pma.${DOMAIN_PART}"
143+
valid_domain "${PMA_HOST}" || die "Invalid FQDN."
144+
fi
145+
if [[ "${INSTALL_PGA}" == "yes" ]]; then
146+
prompt PGA_HOST "pgAdmin4 hostname" "pga.${DOMAIN_PART}"
147+
valid_domain "${PGA_HOST}" || die "Invalid FQDN."
148+
fi
134149
fi
135-
if [[ "${INSTALL_PGA}" == "yes" ]]; then
136-
prompt PGA_HOST "pgAdmin4 hostname" "pga.${DOMAIN_PART}"
137-
valid_domain "${PGA_HOST}" || die "Invalid FQDN."
150+
151+
# ---- Stalwart user mail server (optional) ----
152+
echo
153+
info "Stalwart user mail server (inbound mail on :25, IMAP/SMTP submission, web admin)."
154+
info "Skip if this server doesn't host mailboxes — outbound alerts use the relay below."
155+
prompt_yes_no INSTALL_STALWART "Install Stalwart mail server?" "n"
156+
MAIL_ADMIN_HOST=""
157+
DKIM_SELECTOR=""
158+
if [[ "${INSTALL_STALWART}" == "yes" ]]; then
159+
prompt MAIL_ADMIN_HOST "Mail admin panel hostname" "mail.${DOMAIN_PART}"
160+
valid_domain "${MAIL_ADMIN_HOST}" || die "Invalid FQDN."
161+
prompt DKIM_SELECTOR "DKIM selector (per-server)" "default"
162+
fi
163+
164+
# ---- Admin allowlist (only if at least one admin endpoint will exist) ----
165+
MAIL_ADMIN_ALLOWLIST=""
166+
if [[ "${INSTALL_PMA}" == "yes" || "${INSTALL_PGA}" == "yes" || "${INSTALL_STALWART}" == "yes" ]]; then
167+
admin_list=()
168+
[[ "${INSTALL_PMA}" == "yes" ]] && admin_list+=("phpMyAdmin")
169+
[[ "${INSTALL_PGA}" == "yes" ]] && admin_list+=("pgAdmin4")
170+
[[ "${INSTALL_STALWART}" == "yes" ]] && admin_list+=("Stalwart admin")
171+
echo
172+
info "Admin allowlist (used for: $(IFS=, ; echo "${admin_list[*]}"))"
173+
while :; do
174+
prompt_required MAIL_ADMIN_ALLOWLIST "IP/CIDR list (comma-separated; v4+v6 both OK)"
175+
ok=1
176+
IFS=',' read -ra entries <<<"${MAIL_ADMIN_ALLOWLIST}"
177+
for e in "${entries[@]}"; do
178+
e="${e//[[:space:]]/}"
179+
valid_ip_or_cidr "${e}" || { warn "Invalid: '${e}'"; ok=0; break; }
180+
done
181+
[[ ${ok} -eq 1 ]] && break
182+
done
138183
fi
139184

140185
# ---- Outbound mail relay ----
@@ -336,7 +381,12 @@ SMTP_PASS="${SMTP_PASS}"
336381
SMTP_TLS="${SMTP_TLS}"
337382
SMTP_FROM="${SMTP_FROM}"
338383
339-
# === Mail (Stalwart) admin panel ===
384+
# === Database engines ===
385+
INSTALL_MARIADB="${INSTALL_MARIADB}"
386+
INSTALL_POSTGRES="${INSTALL_POSTGRES}"
387+
388+
# === Stalwart user mail server (inbound) ===
389+
INSTALL_STALWART="${INSTALL_STALWART}"
340390
MAIL_ADMIN_HOST="${MAIL_ADMIN_HOST}"
341391
MAIL_ADMIN_ALLOWLIST="${MAIL_ADMIN_ALLOWLIST}"
342392
@@ -686,10 +736,18 @@ firewall-cmd --zone="${ZONE}" --add-port="${SSH_PORT_NEW}/tcp" --permanent
686736
firewall-cmd --zone="${ZONE}" --add-service=http --permanent
687737
firewall-cmd --zone="${ZONE}" --add-service=https --permanent
688738
firewall-cmd --zone="${ZONE}" --add-port=443/udp --permanent
689-
firewall-cmd --zone="${ZONE}" --add-service=smtp --permanent
690-
firewall-cmd --zone="${ZONE}" --add-service=smtps --permanent
691-
firewall-cmd --zone="${ZONE}" --add-port=587/tcp --permanent
692-
firewall-cmd --zone="${ZONE}" --add-service=imaps --permanent
739+
if [[ "${INSTALL_STALWART:-no}" == "yes" ]]; then
740+
firewall-cmd --zone="${ZONE}" --add-service=smtp --permanent
741+
firewall-cmd --zone="${ZONE}" --add-service=smtps --permanent
742+
firewall-cmd --zone="${ZONE}" --add-port=587/tcp --permanent
743+
firewall-cmd --zone="${ZONE}" --add-service=imaps --permanent
744+
else
745+
# Tear down mail ports if a previous install opened them
746+
firewall-cmd --zone="${ZONE}" --remove-service=smtp --permanent 2>/dev/null || true
747+
firewall-cmd --zone="${ZONE}" --remove-service=smtps --permanent 2>/dev/null || true
748+
firewall-cmd --zone="${ZONE}" --remove-port=587/tcp --permanent 2>/dev/null || true
749+
firewall-cmd --zone="${ZONE}" --remove-service=imaps --permanent 2>/dev/null || true
750+
fi
693751
firewall-cmd --reload
694752
success "firewalld configured (zone: ${ZONE})."
695753

bootstrap/20-databases.sh

Lines changed: 71 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
1515
source "${REPO_DIR}/lib/common.sh"
1616

1717
require_root
18+
load_config
19+
20+
INSTALL_MARIADB="${INSTALL_MARIADB:-yes}"
21+
INSTALL_POSTGRES="${INSTALL_POSTGRES:-yes}"
22+
23+
if [[ "${INSTALL_MARIADB}" != "yes" && "${INSTALL_POSTGRES}" != "yes" ]]; then
24+
info "Neither MariaDB nor PostgreSQL selected — skipping 20-databases."
25+
exit 0
26+
fi
1827

1928
MARIADB_VERSION="${MARIADB_VERSION:-12.0}"
2029
PG_VERSION="${PG_VERSION:-16}"
@@ -30,23 +39,24 @@ info "Detected RAM: ${RAM_MB}MB → tuning to shared=${SHARED_25}MB, effective=$
3039
# -----------------------------------------------------------------------------
3140
# 1. MariaDB
3241
# -----------------------------------------------------------------------------
33-
if rpm -q MariaDB-server >/dev/null 2>&1 || rpm -q mariadb-server >/dev/null 2>&1; then
34-
info "MariaDB already installed."
35-
else
36-
info "Installing MariaDB ${MARIADB_VERSION}..."
37-
curl -fsSL https://downloads.mariadb.com/MariaDB/mariadb_repo_setup -o /tmp/mariadb_repo_setup
38-
chmod +x /tmp/mariadb_repo_setup
39-
if ! /tmp/mariadb_repo_setup --mariadb-server-version="mariadb-${MARIADB_VERSION}" 2>/dev/null; then
40-
warn "MariaDB version ${MARIADB_VERSION} unavailable; falling back to repo default."
41-
/tmp/mariadb_repo_setup
42+
if [[ "${INSTALL_MARIADB}" == "yes" ]]; then
43+
if rpm -q MariaDB-server >/dev/null 2>&1 || rpm -q mariadb-server >/dev/null 2>&1; then
44+
info "MariaDB already installed."
45+
else
46+
info "Installing MariaDB ${MARIADB_VERSION}..."
47+
curl -fsSL https://downloads.mariadb.com/MariaDB/mariadb_repo_setup -o /tmp/mariadb_repo_setup
48+
chmod +x /tmp/mariadb_repo_setup
49+
if ! /tmp/mariadb_repo_setup --mariadb-server-version="mariadb-${MARIADB_VERSION}" 2>/dev/null; then
50+
warn "MariaDB version ${MARIADB_VERSION} unavailable; falling back to repo default."
51+
/tmp/mariadb_repo_setup
52+
fi
53+
rm -f /tmp/mariadb_repo_setup
54+
dnf -y install MariaDB-server MariaDB-client
4255
fi
43-
rm -f /tmp/mariadb_repo_setup
44-
dnf -y install MariaDB-server MariaDB-client
45-
fi
4656

47-
info "Writing MariaDB tuning config..."
48-
mkdir -p /etc/my.cnf.d
49-
cat > /etc/my.cnf.d/serverdeploy.cnf <<EOF
57+
info "Writing MariaDB tuning config..."
58+
mkdir -p /etc/my.cnf.d
59+
cat > /etc/my.cnf.d/serverdeploy.cnf <<EOF
5060
# Generated by serverdeploy 20-databases.sh
5161
[mariadb]
5262
innodb_buffer_pool_size = ${SHARED_25}M
@@ -63,45 +73,49 @@ bind-address = 127.0.0.1
6373
default-character-set = utf8mb4
6474
EOF
6575

66-
systemctl enable mariadb
67-
systemctl restart mariadb
68-
sleep 2
76+
systemctl enable mariadb
77+
systemctl restart mariadb
78+
sleep 2
6979

70-
info "Securing MariaDB (drop test db, anonymous users, remote root)..."
71-
mariadb <<'SQL' || die "MariaDB SQL failed."
80+
info "Securing MariaDB (drop test db, anonymous users, remote root)..."
81+
mariadb <<'SQL' || die "MariaDB SQL failed."
7282
DELETE FROM mysql.user WHERE User='';
7383
DELETE FROM mysql.global_priv WHERE User='' OR User IS NULL;
7484
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
7585
DROP DATABASE IF EXISTS test;
7686
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
7787
FLUSH PRIVILEGES;
7888
SQL
79-
success "MariaDB installed and secured."
89+
success "MariaDB installed and secured."
90+
else
91+
info "Skipping MariaDB (INSTALL_MARIADB=${INSTALL_MARIADB})."
92+
fi
8093

8194
# -----------------------------------------------------------------------------
8295
# 2. PostgreSQL
8396
# -----------------------------------------------------------------------------
84-
if rpm -q "postgresql${PG_VERSION}-server" >/dev/null 2>&1; then
85-
info "PostgreSQL ${PG_VERSION} already installed."
86-
else
87-
info "Installing PostgreSQL ${PG_VERSION}..."
88-
dnf -y install "https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm"
89-
dnf -qy module disable postgresql || true
90-
dnf -y install "postgresql${PG_VERSION}-server" "postgresql${PG_VERSION}-contrib"
91-
fi
97+
if [[ "${INSTALL_POSTGRES}" == "yes" ]]; then
98+
if rpm -q "postgresql${PG_VERSION}-server" >/dev/null 2>&1; then
99+
info "PostgreSQL ${PG_VERSION} already installed."
100+
else
101+
info "Installing PostgreSQL ${PG_VERSION}..."
102+
dnf -y install "https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm"
103+
dnf -qy module disable postgresql || true
104+
dnf -y install "postgresql${PG_VERSION}-server" "postgresql${PG_VERSION}-contrib"
105+
fi
92106

93-
PG_DATA="/var/lib/pgsql/${PG_VERSION}/data"
94-
if [[ ! -f "${PG_DATA}/PG_VERSION" ]]; then
95-
info "Initializing Postgres data directory..."
96-
"/usr/pgsql-${PG_VERSION}/bin/postgresql-${PG_VERSION}-setup" initdb
97-
fi
107+
PG_DATA="/var/lib/pgsql/${PG_VERSION}/data"
108+
if [[ ! -f "${PG_DATA}/PG_VERSION" ]]; then
109+
info "Initializing Postgres data directory..."
110+
"/usr/pgsql-${PG_VERSION}/bin/postgresql-${PG_VERSION}-setup" initdb
111+
fi
98112

99-
info "Tuning PostgreSQL..."
100-
PG_CONF="${PG_DATA}/postgresql.conf"
101-
PG_TUNING_DIR="${PG_DATA}/conf.d"
102-
PG_TUNING_FILE="${PG_TUNING_DIR}/serverdeploy.conf"
103-
mkdir -p "${PG_TUNING_DIR}"
104-
cat > "${PG_TUNING_FILE}" <<EOF
113+
info "Tuning PostgreSQL..."
114+
PG_CONF="${PG_DATA}/postgresql.conf"
115+
PG_TUNING_DIR="${PG_DATA}/conf.d"
116+
PG_TUNING_FILE="${PG_TUNING_DIR}/serverdeploy.conf"
117+
mkdir -p "${PG_TUNING_DIR}"
118+
cat > "${PG_TUNING_FILE}" <<EOF
105119
# Generated by serverdeploy 20-databases.sh
106120
listen_addresses = '127.0.0.1'
107121
max_connections = 200
@@ -113,26 +127,29 @@ wal_compression = on
113127
log_min_duration_statement = 1000
114128
log_line_prefix = '%t [%p] %u@%d '
115129
EOF
116-
chown postgres:postgres "${PG_TUNING_FILE}"
117-
chown -R postgres:postgres "${PG_TUNING_DIR}"
130+
chown postgres:postgres "${PG_TUNING_FILE}"
131+
chown -R postgres:postgres "${PG_TUNING_DIR}"
118132

119-
if ! grep -q "include_dir = 'conf.d'" "${PG_CONF}"; then
120-
echo "" >> "${PG_CONF}"
121-
echo "include_dir = 'conf.d'" >> "${PG_CONF}"
122-
fi
133+
if ! grep -q "include_dir = 'conf.d'" "${PG_CONF}"; then
134+
echo "" >> "${PG_CONF}"
135+
echo "include_dir = 'conf.d'" >> "${PG_CONF}"
136+
fi
123137

124-
systemctl enable "postgresql-${PG_VERSION}"
125-
systemctl restart "postgresql-${PG_VERSION}"
126-
sleep 2
127-
sudo -u postgres psql -tAc "SELECT version()" >/dev/null || die "Postgres not responding."
128-
success "PostgreSQL ${PG_VERSION} installed and tuned."
138+
systemctl enable "postgresql-${PG_VERSION}"
139+
systemctl restart "postgresql-${PG_VERSION}"
140+
sleep 2
141+
sudo -u postgres psql -tAc "SELECT version()" >/dev/null || die "Postgres not responding."
142+
success "PostgreSQL ${PG_VERSION} installed and tuned."
143+
else
144+
info "Skipping PostgreSQL (INSTALL_POSTGRES=${INSTALL_POSTGRES})."
145+
fi
129146

130147
# -----------------------------------------------------------------------------
131148
# Done
132149
# -----------------------------------------------------------------------------
133150
echo
134151
success "20-databases.sh complete."
135-
info " MariaDB : connect with 'mariadb' (unix_socket auth as root)"
136-
info " Postgres : connect with 'sudo -u postgres psql' (peer auth)"
152+
[[ "${INSTALL_MARIADB}" == "yes" ]] && info " MariaDB : connect with 'mariadb' (unix_socket auth as root)"
153+
[[ "${INSTALL_POSTGRES}" == "yes" ]] && info " Postgres : connect with 'sudo -u postgres psql' (peer auth)"
137154
info " RAM-tuned: shared/buffer=${SHARED_25}MB effective=${EFFECTIVE_50}MB"
138-
info " Both bind 127.0.0.1 only — not reachable from network."
155+
info " Installed engines bind 127.0.0.1 only — not reachable from network."

bootstrap/70-mail.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ source "${REPO_DIR}/lib/common.sh"
2121
require_root
2222
load_config
2323

24+
if [[ "${INSTALL_STALWART:-no}" != "yes" ]]; then
25+
info "Stalwart mail server not selected (INSTALL_STALWART=${INSTALL_STALWART:-no}) — skipping."
26+
exit 0
27+
fi
28+
2429
[[ -n "${SERVER_HOSTNAME:-}" ]] || die "SERVER_HOSTNAME missing — run 00-base.sh first."
2530
[[ -n "${MAIL_ADMIN_HOST:-}" ]] || die "MAIL_ADMIN_HOST missing."
2631
[[ -n "${MAIL_ADMIN_ALLOWLIST:-}" ]] || die "MAIL_ADMIN_ALLOWLIST missing."

0 commit comments

Comments
 (0)