mirror of
https://github.com/hyzendust/freedoms4-backend-public.git
synced 2026-06-30 23:12:18 +02:00
Init
This commit is contained in:
587
full-setup.sh
Executable file
587
full-setup.sh
Executable file
@@ -0,0 +1,587 @@
|
||||
#!/bin/bash
|
||||
# full-setup.sh — freedoms4 backend automation script
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
||||
|
||||
# Config (edit these before running)
|
||||
DB_NAME=""
|
||||
DB_USER=""
|
||||
DB_PASS=""
|
||||
PROSODY_DB_USER=""
|
||||
PROSODY_DB_PASS="" # must match /etc/prosody/prosody.cfg.lua
|
||||
DOMAIN=""
|
||||
CERTBOT_EMAIL=""
|
||||
API_DIR=""
|
||||
ENV_FILE=""
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
OTP_FROM="" # for example: no-reply@freedoms4.org
|
||||
|
||||
# Must run as root
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
error "Please run as root: sudo bash full-setup.sh"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}================================================${NC}"
|
||||
echo -e "${BLUE} freedoms4 backend setup${NC}"
|
||||
echo -e "${BLUE}================================================${NC}"
|
||||
echo ""
|
||||
|
||||
# ── STEP 1 PostgreSQL ──
|
||||
info "Checking PostgreSQL..."
|
||||
|
||||
if command -v psql &>/dev/null; then
|
||||
success "PostgreSQL is already installed."
|
||||
else
|
||||
info "Installing PostgreSQL..."
|
||||
apt update -qq
|
||||
apt install -y postgresql postgresql-contrib
|
||||
systemctl enable --now postgresql
|
||||
success "PostgreSQL installed and started."
|
||||
fi
|
||||
|
||||
if ! systemctl is-active --quiet postgresql; then
|
||||
systemctl start postgresql
|
||||
fi
|
||||
success "PostgreSQL is running."
|
||||
|
||||
# ── STEP 2 Database & user ──
|
||||
info "Setting up database and user..."
|
||||
|
||||
if (cd /tmp && sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${DB_USER}'" | grep -q 1); then
|
||||
warn "DB user '${DB_USER}' already exists — resetting password to ensure it matches."
|
||||
(cd /tmp && sudo -u postgres psql -c "ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASS}';")
|
||||
else
|
||||
(cd /tmp && sudo -u postgres psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASS}';")
|
||||
success "Created DB user '${DB_USER}'."
|
||||
fi
|
||||
|
||||
if (cd /tmp && sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='${DB_NAME}'" | grep -q 1); then
|
||||
warn "Database '${DB_NAME}' already exists, skipping."
|
||||
else
|
||||
(cd /tmp && sudo -u postgres psql -c "CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};")
|
||||
success "Created database '${DB_NAME}'."
|
||||
fi
|
||||
|
||||
(cd /tmp && sudo -u postgres psql -d "${DB_NAME}") << SQL
|
||||
-- Users table
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
username VARCHAR(32) NOT NULL UNIQUE,
|
||||
email VARCHAR(254) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
blocked BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
terms_agreed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Migration: add terms_agreed for existing installs
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS terms_agreed BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_username ON users (username);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users (email);
|
||||
|
||||
REVOKE ALL ON TABLE users FROM PUBLIC;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE users TO ${DB_USER};
|
||||
GRANT USAGE, SELECT ON SEQUENCE users_id_seq TO ${DB_USER};
|
||||
|
||||
-- OTP table for email verification during sign-up
|
||||
CREATE TABLE IF NOT EXISTS email_otps (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
email VARCHAR(254) NOT NULL,
|
||||
otp_hash VARCHAR(255) NOT NULL,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
used BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
otp_token VARCHAR(64),
|
||||
token_expires_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_email_otps_email ON email_otps (email);
|
||||
CREATE INDEX IF NOT EXISTS idx_email_otps_token ON email_otps (otp_token);
|
||||
|
||||
REVOKE ALL ON TABLE email_otps FROM PUBLIC;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE email_otps TO ${DB_USER};
|
||||
GRANT USAGE, SELECT ON SEQUENCE email_otps_id_seq TO ${DB_USER};
|
||||
|
||||
-- Session tracking table
|
||||
CREATE TABLE IF NOT EXISTS user_sessions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
session_id VARCHAR(128) NOT NULL UNIQUE,
|
||||
logged_in_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
logged_out_at TIMESTAMPTZ,
|
||||
ip_address VARCHAR(45),
|
||||
user_agent VARCHAR(512)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_sessions_user_id ON user_sessions (user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_sessions_session_id ON user_sessions (session_id);
|
||||
|
||||
REVOKE ALL ON TABLE user_sessions FROM PUBLIC;
|
||||
GRANT SELECT, INSERT, UPDATE ON TABLE user_sessions TO ${DB_USER};
|
||||
GRANT USAGE, SELECT ON SEQUENCE user_sessions_id_seq TO ${DB_USER};
|
||||
|
||||
-- Comments table
|
||||
CREATE TABLE IF NOT EXISTS comments (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
post_id VARCHAR(512) NOT NULL,
|
||||
parent_id BIGINT REFERENCES comments(id) ON DELETE SET NULL,
|
||||
user_id BIGINT REFERENCES users(id) ON DELETE SET NULL,
|
||||
username VARCHAR(32),
|
||||
body TEXT,
|
||||
deleted_by VARCHAR(16),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE
|
||||
);
|
||||
|
||||
ALTER TABLE comments ADD COLUMN IF NOT EXISTS username VARCHAR(32);
|
||||
UPDATE comments c
|
||||
SET username = u.username
|
||||
FROM users u
|
||||
WHERE c.user_id = u.id AND c.username IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_comments_post_id ON comments (post_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_comments_parent_id ON comments (parent_id);
|
||||
|
||||
REVOKE ALL ON TABLE comments FROM PUBLIC;
|
||||
GRANT SELECT, INSERT, UPDATE ON TABLE comments TO ${DB_USER};
|
||||
GRANT USAGE, SELECT ON SEQUENCE comments_id_seq TO ${DB_USER};
|
||||
SQL
|
||||
|
||||
|
||||
# ── STEP 2b Grant freedoms4_user access to Prosody DB ──
|
||||
info "Granting freedoms4_user access to prosody database..."
|
||||
|
||||
(cd /tmp && sudo -u postgres psql -d prosody -c "GRANT CONNECT ON DATABASE prosody TO ${DB_USER};")
|
||||
(cd /tmp && sudo -u postgres psql -d prosody -c "GRANT SELECT, INSERT, UPDATE ON TABLE prosody TO ${DB_USER};")
|
||||
success "freedoms4_user can read/write prosody table."
|
||||
|
||||
|
||||
# ── STEP 3 Block port 5432 via ufw ──
|
||||
info "Blocking port 5432 externally via ufw..."
|
||||
|
||||
if command -v ufw &>/dev/null; then
|
||||
if ufw status | head -1 | grep -q "active"; then
|
||||
ufw deny 5432/tcp
|
||||
success "ufw: port 5432 blocked — PostgreSQL is localhost-only."
|
||||
else
|
||||
ufw --force enable
|
||||
ufw deny 5432/tcp
|
||||
success "ufw enabled and port 5432 blocked."
|
||||
fi
|
||||
else
|
||||
warn "ufw not found — please manually ensure port 5432 is not publicly exposed."
|
||||
fi
|
||||
|
||||
# ── STEP 4 PHP 8.2 fpm + pgsql ──
|
||||
info "Installing PHP 8.2 fpm and pgsql extension..."
|
||||
|
||||
apt install -y php8.2-fpm php8.2-pgsql php8.2-apcu
|
||||
|
||||
phpenmod -v 8.2 pgsql 2>/dev/null || true
|
||||
phpenmod -v 8.2 pdo_pgsql 2>/dev/null || true
|
||||
phpenmod -v 8.2 apcu 2>/dev/null || true
|
||||
|
||||
systemctl enable --now php8.2-fpm
|
||||
systemctl restart php8.2-fpm
|
||||
|
||||
if [[ ! -S /run/php/php8.2-fpm.sock ]]; then
|
||||
error "fpm socket not found at /run/php/php8.2-fpm.sock"
|
||||
fi
|
||||
success "php8.2-fpm ready."
|
||||
|
||||
# ── STEP 5 Mail server check ──
|
||||
info "Checking mail server (Postfix) for OTP delivery..."
|
||||
|
||||
if ! command -v postfix &>/dev/null; then
|
||||
warn "Postfix binary not found. Installing..."
|
||||
apt install -y postfix
|
||||
# Ensure it is configured as 'Internet Site' for freedoms4.org
|
||||
debconf-set-selections <<< "postfix postfix/main_mailer_type select Internet Site"
|
||||
debconf-set-selections <<< "postfix postfix/mailname string freedoms4.org"
|
||||
dpkg-reconfigure -f noninteractive postfix
|
||||
success "Postfix installed."
|
||||
fi
|
||||
|
||||
if ! systemctl is-active --quiet postfix; then
|
||||
systemctl start postfix
|
||||
success "Postfix started."
|
||||
else
|
||||
success "Postfix is running."
|
||||
fi
|
||||
|
||||
# Ensure PHP's sendmail_path points to Postfix's sendmail binary
|
||||
PHP_INI_FPM="/etc/php/8.2/fpm/php.ini"
|
||||
SENDMAIL_PATH=$(php8.2 -r "echo ini_get('sendmail_path');" 2>/dev/null || true)
|
||||
if [[ "$SENDMAIL_PATH" != *"sendmail"* ]]; then
|
||||
warn "sendmail_path may not be set — adding to ${PHP_INI_FPM}"
|
||||
if ! grep -q "^sendmail_path" "${PHP_INI_FPM}" 2>/dev/null; then
|
||||
echo 'sendmail_path = "/usr/sbin/sendmail -t -i"' >> "${PHP_INI_FPM}"
|
||||
systemctl restart php8.2-fpm
|
||||
success "sendmail_path set and php8.2-fpm restarted."
|
||||
fi
|
||||
else
|
||||
success "sendmail_path is configured: ${SENDMAIL_PATH}"
|
||||
fi
|
||||
|
||||
# Configure Postfix myhostname / myorigin if not already set
|
||||
POSTFIX_MAIN="/etc/postfix/main.cf"
|
||||
if ! grep -q "^myhostname\s*=\s*freedoms4.org" "${POSTFIX_MAIN}" 2>/dev/null; then
|
||||
info "Setting Postfix myhostname = freedoms4.org ..."
|
||||
postconf -e "myhostname = freedoms4.org"
|
||||
postconf -e "myorigin = freedoms4.org"
|
||||
systemctl reload postfix
|
||||
success "Postfix myhostname/myorigin updated."
|
||||
fi
|
||||
|
||||
# ── Configure Postfix to use Dovecot SASL for SMTP AUTH (so virtual users can send) ──
|
||||
info "Configuring Postfix to use Dovecot SASL for SMTP submission..."
|
||||
|
||||
# Install libsasl2 if needed
|
||||
apt install -y libsasl2-modules 2>/dev/null || true
|
||||
|
||||
postconf -e "smtpd_sasl_type = dovecot"
|
||||
postconf -e "smtpd_sasl_path = private/auth"
|
||||
postconf -e "smtpd_sasl_auth_enable = yes"
|
||||
postconf -e "smtpd_sasl_security_options = noanonymous"
|
||||
postconf -e "smtpd_sasl_local_domain = \$myhostname"
|
||||
postconf -e "broken_sasl_auth_clients = yes"
|
||||
postconf -e "smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination"
|
||||
|
||||
# ── Configure Postfix virtual mailbox delivery (Dovecot LDA) ──
|
||||
info "Configuring Postfix virtual mailbox delivery via transport_maps..."
|
||||
|
||||
# freedoms4.org stays in mydestination so system users (hyzen etc.) keep
|
||||
# working via local delivery. For site-created virtual users, we use
|
||||
# transport_maps on a per-address basis to route them to Dovecot LDA.
|
||||
#
|
||||
# The critical piece: by default, Postfix checks local_recipient_maps for
|
||||
# any domain in mydestination and rejects unknown recipients before ever
|
||||
# consulting transport_maps. We override local_recipient_maps to only list
|
||||
# actual Unix system accounts (passwd file). Virtual users are not in passwd,
|
||||
# so Postfix finds no match, skips the reject, and falls through to
|
||||
# transport_maps where their dovecot entry routes them correctly.
|
||||
postconf -e "local_recipient_maps ="
|
||||
|
||||
postconf -e "dovecot_destination_recipient_limit = 1"
|
||||
|
||||
# Initialise the per-user transport map (entries added by email-account-create)
|
||||
VTRANSPORT_FILE="/etc/postfix/virtual_transport"
|
||||
if [[ ! -f "${VTRANSPORT_FILE}" ]]; then
|
||||
touch "${VTRANSPORT_FILE}"
|
||||
success "Created empty ${VTRANSPORT_FILE}."
|
||||
fi
|
||||
postmap "${VTRANSPORT_FILE}"
|
||||
postconf -e "transport_maps = hash:${VTRANSPORT_FILE}"
|
||||
success "transport_maps initialised."
|
||||
|
||||
# Add dovecot LDA transport to master.cf if not already there.
|
||||
# Use dovecot-lda (the correct binary name on Debian/Ubuntu).
|
||||
MASTER_CF="/etc/postfix/master.cf"
|
||||
if ! grep -q "^dovecot" "${MASTER_CF}"; then
|
||||
cat >> "${MASTER_CF}" << 'MASTER'
|
||||
dovecot unix - n n - - pipe
|
||||
flags=DRhu user=vmail:mail argv=/usr/lib/dovecot/dovecot-lda -f ${sender} -d ${recipient}
|
||||
MASTER
|
||||
success "Dovecot LDA transport added to master.cf."
|
||||
fi
|
||||
|
||||
# Enable Dovecot auth-userdb socket for Postfix SASL.
|
||||
# Use a separate drop-in file instead of injecting into 10-master.conf
|
||||
# to avoid sed portability issues and keep changes isolated.
|
||||
DOVECOT_POSTFIX_CONF="/etc/dovecot/conf.d/99-postfix-auth.conf"
|
||||
if [[ ! -f "${DOVECOT_POSTFIX_CONF}" ]]; then
|
||||
cat > "${DOVECOT_POSTFIX_CONF}" << 'DOVECOTCONF'
|
||||
# Dovecot SASL socket for Postfix SMTP AUTH
|
||||
# Added by full-setup.sh — remove this file to undo
|
||||
service auth {
|
||||
unix_listener /var/spool/postfix/private/auth {
|
||||
mode = 0660
|
||||
user = postfix
|
||||
group = postfix
|
||||
}
|
||||
}
|
||||
DOVECOTCONF
|
||||
success "Created Dovecot Postfix-auth drop-in at ${DOVECOT_POSTFIX_CONF}."
|
||||
else
|
||||
success "Dovecot Postfix-auth drop-in already exists."
|
||||
fi
|
||||
|
||||
systemctl reload postfix
|
||||
systemctl reload dovecot
|
||||
success "Postfix SASL + virtual mailbox delivery configured."
|
||||
|
||||
# Quick test: send a mail from no-reply@freedoms4.org to itself (appears in /var/mail or mail queue)
|
||||
info "Sending a Postfix self-test email..."
|
||||
echo "Postfix OTP relay self-test from full-setup.sh" \
|
||||
| mail -s "freedoms4 mail test" \
|
||||
-a "From: ${OTP_FROM}" \
|
||||
root 2>/dev/null \
|
||||
&& success "Test email queued (check /var/mail/root or 'mailq')." \
|
||||
|| warn "mail command not available for self-test — install mailutils if needed."
|
||||
|
||||
# ── STEP 6 Generate env file + deploy auth.php ──
|
||||
info "Creating credentials env file at ${ENV_FILE}..."
|
||||
|
||||
# Write env file
|
||||
mkdir -p "$(dirname ${ENV_FILE})"
|
||||
cat > "${ENV_FILE}" << ENV
|
||||
# /etc/freedoms4/auth.env — generated by full-setup.sh on $(date)
|
||||
# Do NOT commit to version control.
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=5432
|
||||
DB_NAME=${DB_NAME}
|
||||
DB_USER=${DB_USER}
|
||||
DB_PASS=${DB_PASS}
|
||||
PROSODY_DB_NAME=prosody
|
||||
PROSODY_DB_USER=${PROSODY_DB_USER}
|
||||
PROSODY_DB_PASS=${PROSODY_DB_PASS}
|
||||
PROSODY_HOST=freedoms4.org
|
||||
ENV
|
||||
|
||||
chown root:www-data "${ENV_FILE}"
|
||||
chmod 640 "${ENV_FILE}"
|
||||
success "Env file written and secured (root:www-data 640)."
|
||||
|
||||
info "Deploying auth.php to ${API_DIR}..."
|
||||
|
||||
if [[ ! -f "${SCRIPT_DIR}/auth.php" ]]; then
|
||||
error "auth.php not found in ${SCRIPT_DIR}."
|
||||
fi
|
||||
|
||||
mkdir -p "${API_DIR}"
|
||||
cp "${SCRIPT_DIR}/auth.php" "${API_DIR}/auth.php"
|
||||
chown -R www-data:www-data "${API_DIR}"
|
||||
chmod 640 "${API_DIR}/auth.php"
|
||||
success "auth.php deployed."
|
||||
|
||||
if [[ ! -f "${SCRIPT_DIR}/comments.php" ]]; then
|
||||
error "comments.php not found in ${SCRIPT_DIR}."
|
||||
fi
|
||||
cp "${SCRIPT_DIR}/comments.php" "${API_DIR}/comments.php"
|
||||
chown www-data:www-data "${API_DIR}/comments.php"
|
||||
chmod 640 "${API_DIR}/comments.php"
|
||||
success "comments.php deployed."
|
||||
|
||||
if [[ ! -f "${SCRIPT_DIR}/admin.php" ]]; then
|
||||
error "admin.php not found in ${SCRIPT_DIR}."
|
||||
fi
|
||||
cp "${SCRIPT_DIR}/admin.php" "${API_DIR}/admin.php"
|
||||
chown www-data:www-data "${API_DIR}/admin.php"
|
||||
chmod 640 "${API_DIR}/admin.php"
|
||||
success "admin.php deployed."
|
||||
|
||||
|
||||
|
||||
# ── STEP 6b Virtual mail setup (Dovecot + vmail) ──
|
||||
info "Setting up virtual mail user and Dovecot passwd-file..."
|
||||
|
||||
# Create vmail system user if not exists
|
||||
if ! id vmail &>/dev/null; then
|
||||
useradd -r -u 5000 -g mail -d /var/vmail -s /sbin/nologin vmail
|
||||
success "Created vmail system user (uid 5000)."
|
||||
else
|
||||
success "vmail user already exists."
|
||||
fi
|
||||
|
||||
# Create mailbox base directory
|
||||
mkdir -p /var/vmail
|
||||
mkdir -p /var/dovecot
|
||||
chown vmail:mail /var/vmail
|
||||
chmod 770 /var/vmail
|
||||
success "/var/vmail ready."
|
||||
|
||||
# Create /var/dovecot/users if not exists
|
||||
if [[ ! -f /var/dovecot/users ]]; then
|
||||
touch /var/dovecot/users
|
||||
chown root:root /var/dovecot/users
|
||||
chmod 644 /var/dovecot/users
|
||||
success "Created /var/dovecot/users."
|
||||
else
|
||||
success "/var/dovecot/users already exists."
|
||||
fi
|
||||
|
||||
# Enable auth-passwdfile in 10-auth.conf (add include if not already there)
|
||||
if ! grep -q "auth-passwdfile" /etc/dovecot/conf.d/10-auth.conf; then
|
||||
echo '!include auth-passwdfile.conf.ext' >> /etc/dovecot/conf.d/10-auth.conf
|
||||
success "Enabled auth-passwdfile.conf.ext in 10-auth.conf."
|
||||
fi
|
||||
|
||||
# Enable auth-passwdfile.conf.ext — uncomment the passdb/userdb blocks
|
||||
PASSWDFILE="/etc/dovecot/conf.d/auth-passwdfile.conf.ext"
|
||||
if grep -q "^#passdb" "${PASSWDFILE}" 2>/dev/null || ! grep -q "^passdb" "${PASSWDFILE}" 2>/dev/null; then
|
||||
cat > "${PASSWDFILE}" << 'DOVECOT'
|
||||
passdb {
|
||||
driver = passwd-file
|
||||
args = scheme=SHA512-CRYPT username_format=%n /var/dovecot/users
|
||||
}
|
||||
userdb {
|
||||
driver = passwd-file
|
||||
args = username_format=%n /var/dovecot/users
|
||||
default_fields = uid=vmail gid=mail home=/var/vmail/%n@freedoms4.org/maildir
|
||||
}
|
||||
DOVECOT
|
||||
success "auth-passwdfile.conf.ext configured."
|
||||
fi
|
||||
|
||||
systemctl reload dovecot
|
||||
success "Dovecot reloaded."
|
||||
|
||||
# ── STEP 6c Deploy email-account-create wrapper + sudoers ──
|
||||
info "Deploying email-account-create wrapper script..."
|
||||
|
||||
if [[ ! -f "${SCRIPT_DIR}/email-account-create.sh" ]]; then
|
||||
error "email-account-create.sh not found in ${SCRIPT_DIR}."
|
||||
fi
|
||||
|
||||
cp "${SCRIPT_DIR}/email-account-create.sh" /usr/local/bin/email-account-create
|
||||
chown root:root /usr/local/bin/email-account-create
|
||||
chmod 755 /usr/local/bin/email-account-create
|
||||
success "email-account-create deployed to /usr/local/bin/"
|
||||
|
||||
SUDOERS_EMAIL="/etc/sudoers.d/email-account-create"
|
||||
echo "www-data ALL=(root) NOPASSWD: /usr/local/bin/email-account-create" > "${SUDOERS_EMAIL}"
|
||||
chmod 440 "${SUDOERS_EMAIL}"
|
||||
visudo -cf "${SUDOERS_EMAIL}" && success "sudoers rule for email-account-create installed." \
|
||||
|| error "sudoers syntax check failed — check ${SUDOERS_EMAIL}"
|
||||
|
||||
|
||||
# ── STEP 6d Deploy email-block wrapper + sudoers ──
|
||||
info "Deploying email-block wrapper script..."
|
||||
|
||||
if [[ ! -f "${SCRIPT_DIR}/email-block.sh" ]]; then
|
||||
error "email-block.sh not found in ${SCRIPT_DIR}."
|
||||
fi
|
||||
|
||||
cp "${SCRIPT_DIR}/email-block.sh" /usr/local/bin/email-block
|
||||
chown root:root /usr/local/bin/email-block
|
||||
chmod 755 /usr/local/bin/email-block
|
||||
success "email-block deployed to /usr/local/bin/"
|
||||
|
||||
# Create backup file if not exists
|
||||
touch /var/dovecot/users.blocked
|
||||
chown root:root /var/dovecot/users.blocked
|
||||
chmod 644 /var/dovecot/users.blocked
|
||||
success "/var/dovecot/users.blocked ready."
|
||||
|
||||
SUDOERS_BLOCK="/etc/sudoers.d/email-block"
|
||||
echo "www-data ALL=(root) NOPASSWD: /usr/local/bin/email-block" > "${SUDOERS_BLOCK}"
|
||||
chmod 440 "${SUDOERS_BLOCK}"
|
||||
visudo -cf "${SUDOERS_BLOCK}" && success "sudoers rule for email-block installed." \
|
||||
|| error "sudoers syntax check failed — check ${SUDOERS_BLOCK}"
|
||||
|
||||
|
||||
# ── STEP 6e Deploy email-delete wrapper + sudoers ──
|
||||
info "Deploying email-delete wrapper script..."
|
||||
|
||||
if [[ ! -f "${SCRIPT_DIR}/email-delete.sh" ]]; then
|
||||
error "email-delete.sh not found in ${SCRIPT_DIR}."
|
||||
fi
|
||||
|
||||
cp "${SCRIPT_DIR}/email-delete.sh" /usr/local/bin/email-delete
|
||||
chown root:root /usr/local/bin/email-delete
|
||||
chmod 755 /usr/local/bin/email-delete
|
||||
success "email-delete deployed to /usr/local/bin/"
|
||||
|
||||
SUDOERS_DELETE="/etc/sudoers.d/email-delete"
|
||||
echo "www-data ALL=(root) NOPASSWD: /usr/local/bin/email-delete" > "${SUDOERS_DELETE}"
|
||||
chmod 440 "${SUDOERS_DELETE}"
|
||||
visudo -cf "${SUDOERS_DELETE}" && success "sudoers rule for email-delete installed." \
|
||||
|| error "sudoers syntax check failed — check ${SUDOERS_DELETE}"
|
||||
|
||||
|
||||
# ── STEP 7 Nginx config ──
|
||||
info "Deploying nginx config for ${DOMAIN}..."
|
||||
|
||||
if [[ ! -f "${SCRIPT_DIR}/backend.freedoms4.org" ]]; then
|
||||
error "backend.freedoms4.org not found in ${SCRIPT_DIR}."
|
||||
fi
|
||||
|
||||
cp "${SCRIPT_DIR}/backend.freedoms4.org" "/etc/nginx/sites-available/${DOMAIN}"
|
||||
ln -sf "/etc/nginx/sites-available/${DOMAIN}" "/etc/nginx/sites-enabled/${DOMAIN}"
|
||||
|
||||
nginx -t || error "Nginx config test failed."
|
||||
systemctl restart nginx
|
||||
success "Nginx restarted."
|
||||
|
||||
# ── STEP 8 Certbot / SSL ──
|
||||
info "Checking SSL certificate..."
|
||||
|
||||
if ! command -v certbot &>/dev/null; then
|
||||
apt install -y certbot python3-certbot-nginx
|
||||
success "Certbot installed."
|
||||
fi
|
||||
|
||||
if [[ -f "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" ]]; then
|
||||
warn "Certificate for ${DOMAIN} already exists, skipping issuance."
|
||||
else
|
||||
info "Obtaining SSL certificate for ${DOMAIN}..."
|
||||
certbot --nginx -d "${DOMAIN}" --non-interactive --agree-tos -m "${CERTBOT_EMAIL}"
|
||||
success "Certificate obtained."
|
||||
fi
|
||||
|
||||
if systemctl list-timers 2>/dev/null | grep -q certbot; then
|
||||
success "Certbot auto-renewal timer is active."
|
||||
else
|
||||
systemctl enable --now certbot.timer 2>/dev/null || true
|
||||
if ! crontab -l 2>/dev/null | grep -q certbot; then
|
||||
(crontab -l 2>/dev/null; echo "0 3 * * * certbot renew --quiet --post-hook 'systemctl reload nginx'") | crontab -
|
||||
success "Added certbot renewal cron job."
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── STEP 9 OTP cleanup cron ──
|
||||
info "Installing OTP cleanup cron (purges rows older than 24 h)..."
|
||||
|
||||
OTP_CRON="0 4 * * * psql postgresql://${DB_USER}:${DB_PASS}@127.0.0.1/${DB_NAME} -c \"DELETE FROM email_otps WHERE created_at < NOW() - INTERVAL '24 hours';\" >/dev/null 2>&1"
|
||||
|
||||
if ! crontab -l 2>/dev/null | grep -q "email_otps"; then
|
||||
(crontab -l 2>/dev/null; echo "${OTP_CRON}") | crontab -
|
||||
success "OTP cleanup cron installed (runs daily at 04:00)."
|
||||
else
|
||||
success "OTP cleanup cron already present."
|
||||
fi
|
||||
|
||||
# ── STEP 10 Smoke test ──
|
||||
info "Running smoke test..."
|
||||
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
||||
"https://${DOMAIN}/auth.php" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Origin: https://freedoms4.org" \
|
||||
-d '{"action":"login","username":"__probe__","password":"__probe__"}' \
|
||||
--max-time 10 || true)
|
||||
|
||||
if [[ "$HTTP_CODE" == "200" ]]; then
|
||||
success "Smoke test passed (HTTP 200)."
|
||||
elif [[ "$HTTP_CODE" == "000" ]]; then
|
||||
error "Smoke test failed (000) — endpoint unreachable. Run: curl -sv https://${DOMAIN}/auth.php"
|
||||
else
|
||||
success "Smoke test: HTTP ${HTTP_CODE} — endpoint is reachable."
|
||||
fi
|
||||
|
||||
# ── Done ──
|
||||
echo ""
|
||||
echo -e "${GREEN}================================================${NC}"
|
||||
echo -e "${GREEN} Setup complete!${NC}"
|
||||
echo -e "${GREEN}================================================${NC}"
|
||||
echo ""
|
||||
echo -e " API endpoint : ${BLUE}https://${DOMAIN}/auth.php${NC}"
|
||||
echo -e " Auth file : ${BLUE}${API_DIR}/auth.php${NC}"
|
||||
echo -e " OTP sender : ${BLUE}${OTP_FROM}${NC}"
|
||||
echo ""
|
||||
echo ""
|
||||
Reference in New Issue
Block a user