mirror of
git://f0xx.org/ac/ac-deploy
synced 2026-07-29 02:57:38 +03:00
cluster0: Gmail mail relay (msmtp + PHP SMTP), lab cron
Mirror alpine-be msmtp for CLI; compose uses Gmail SMTP for php-fpm. Safe secrets.lab.env parser; deploy-mailrelay, install-lab-cron, verify-mail-lab. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -62,6 +62,8 @@ FE_PROXY_TARGET=cast01.intra.raptor.org:80
|
|||||||
|
|
||||||
# Lab DNS (2026-06): acl0.f0xx.org + c1–c3.acl0.f0xx.org → FE (cast01–03 cluster)
|
# Lab DNS (2026-06): acl0.f0xx.org + c1–c3.acl0.f0xx.org → FE (cast01–03 cluster)
|
||||||
ACL0_CNAME=acl0.f0xx.org
|
ACL0_CNAME=acl0.f0xx.org
|
||||||
|
MAIL_RELAY_ENABLE=1
|
||||||
|
MAIL_GMAIL_DEST=bestcastr@gmail.com
|
||||||
CAST01_PUBLIC=c1.acl0.f0xx.org
|
CAST01_PUBLIC=c1.acl0.f0xx.org
|
||||||
CAST02_PUBLIC=c2.acl0.f0xx.org
|
CAST02_PUBLIC=c2.acl0.f0xx.org
|
||||||
CAST03_PUBLIC=c3.acl0.f0xx.org
|
CAST03_PUBLIC=c3.acl0.f0xx.org
|
||||||
|
|||||||
16
sim/cluster0/mail/msmtprc.template
Normal file
16
sim/cluster0/mail/msmtprc.template
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Android Cast — outbound relay (Gmail app password via msmtp.secret)
|
||||||
|
# Installed by scripts/deploy-mailrelay.sh on cluster primary (cast01).
|
||||||
|
defaults
|
||||||
|
auth on
|
||||||
|
tls on
|
||||||
|
tls_trust_file /etc/ssl/certs/ca-certificates.crt
|
||||||
|
logfile /var/log/msmtp.log
|
||||||
|
|
||||||
|
account gmail
|
||||||
|
host smtp.gmail.com
|
||||||
|
port 587
|
||||||
|
from __MSMTP_FROM__
|
||||||
|
user __MSMTP_USER__
|
||||||
|
passwordeval cat /etc/androidcast/msmtp.secret
|
||||||
|
|
||||||
|
account default : gmail
|
||||||
@@ -80,6 +80,10 @@ run_phase_app() {
|
|||||||
if [ "${GITEA_ENABLE:-0}" = "1" ] && is_primary_node; then
|
if [ "${GITEA_ENABLE:-0}" = "1" ] && is_primary_node; then
|
||||||
run_script "$ROOT/scripts/deploy-gitea.sh" || journal_warn gitea "install failed (non-fatal)"
|
run_script "$ROOT/scripts/deploy-gitea.sh" || journal_warn gitea "install failed (non-fatal)"
|
||||||
fi
|
fi
|
||||||
|
if [ "${MAIL_RELAY_ENABLE:-0}" = "1" ] && is_primary_node; then
|
||||||
|
run_script "$ROOT/scripts/deploy-mailrelay.sh" || journal_warn mailrelay "install failed (non-fatal)"
|
||||||
|
run_script "$ROOT/scripts/install-lab-cron.sh" || journal_warn lab_cron "install failed (non-fatal)"
|
||||||
|
fi
|
||||||
run_script "$ROOT/scripts/configure-nginx.sh"
|
run_script "$ROOT/scripts/configure-nginx.sh"
|
||||||
rc-service php-fpm83 restart 2>/dev/null || true
|
rc-service php-fpm83 restart 2>/dev/null || true
|
||||||
rc-service nginx reload 2>/dev/null || rc-service nginx restart 2>/dev/null || true
|
rc-service nginx reload 2>/dev/null || rc-service nginx restart 2>/dev/null || true
|
||||||
|
|||||||
@@ -104,14 +104,24 @@ ISSUES_BASE="${APP_BASE_PATH:-/app/androidcast_project/issues}"
|
|||||||
PUBLIC_ORIGIN_CFG="${LAB_PUBLIC_ORIGIN:-https://acl0.f0xx.org}"
|
PUBLIC_ORIGIN_CFG="${LAB_PUBLIC_ORIGIN:-https://acl0.f0xx.org}"
|
||||||
load_secrets_lab
|
load_secrets_lab
|
||||||
AUTH_KEY="${AUTH_ENCRYPTION_KEY:-lab-cluster-dev-32-char-min-secret!!}"
|
AUTH_KEY="${AUTH_ENCRYPTION_KEY:-lab-cluster-dev-32-char-min-secret!!}"
|
||||||
MAIL_TRANSPORT="${MAIL_TRANSPORT:-smtp}"
|
_MSMTP_PASS="$(read_secret MSMTP_APP_PASSWORD 2>/dev/null || true)"
|
||||||
MAIL_FROM="${MAIL_FROM:-Android Cast Lab <noreply@acl0.f0xx.org>}"
|
[ -n "$_MSMTP_PASS" ] || _MSMTP_PASS="${MSMTP_APP_PASSWORD:-}"
|
||||||
MAIL_REPLY="${MAIL_REPLY_TO:-info@apps.f0xx.org}"
|
MAIL_TRANSPORT="${MAIL_TRANSPORT:-sendmail}"
|
||||||
|
MAIL_FROM="${MAIL_FROM:-Android Cast Lab <bestcastr@gmail.com>}"
|
||||||
|
MAIL_REPLY="${MAIL_REPLY_TO:-bestcastr@gmail.com}"
|
||||||
SMTP_HOST="${SMTP_HOST:-127.0.0.1}"
|
SMTP_HOST="${SMTP_HOST:-127.0.0.1}"
|
||||||
SMTP_PORT="${SMTP_PORT:-587}"
|
SMTP_PORT="${SMTP_PORT:-587}"
|
||||||
SMTP_ENC="${SMTP_ENCRYPTION:-tls}"
|
SMTP_ENC="${SMTP_ENCRYPTION:-tls}"
|
||||||
SMTP_USER="${SMTP_USER:-}"
|
SMTP_USER="${SMTP_USER:-${MSMTP_USER:-}}"
|
||||||
SMTP_PASS="${SMTP_PASS:-}"
|
SMTP_PASS="${SMTP_PASS:-}"
|
||||||
|
if [ -n "$_MSMTP_PASS" ]; then
|
||||||
|
MAIL_TRANSPORT=smtp
|
||||||
|
SMTP_HOST=smtp.gmail.com
|
||||||
|
SMTP_USER="${MSMTP_USER:-bestcastr@gmail.com}"
|
||||||
|
SMTP_PASS="$_MSMTP_PASS"
|
||||||
|
MAIL_FROM="${MAIL_FROM:-Android Cast <bestcastr@gmail.com>}"
|
||||||
|
fi
|
||||||
|
unset _MSMTP_PASS
|
||||||
mkdir -p "${COMPOSED}/config"
|
mkdir -p "${COMPOSED}/config"
|
||||||
log "write composed config.php base_path=${ISSUES_BASE}"
|
log "write composed config.php base_path=${ISSUES_BASE}"
|
||||||
cat > "${COMPOSED}/config/config.php" <<PHP
|
cat > "${COMPOSED}/config/config.php" <<PHP
|
||||||
|
|||||||
39
sim/cluster0/scripts/deploy-mailrelay.sh
Executable file
39
sim/cluster0/scripts/deploy-mailrelay.sh
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Lab cluster outbound mail — msmtp + sendmail shim (same pattern as alpine-be).
|
||||||
|
set -eu
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$ROOT/scripts/lib/common.sh"
|
||||||
|
load_cluster_env
|
||||||
|
load_secrets_lab
|
||||||
|
|
||||||
|
FROM="${MSMTP_FROM:-bestcastr@gmail.com}"
|
||||||
|
USER="${MSMTP_USER:-bestcastr@gmail.com}"
|
||||||
|
PASS="$(read_secret MSMTP_APP_PASSWORD)"
|
||||||
|
[ -n "$PASS" ] || PASS="${MSMTP_APP_PASSWORD:-}"
|
||||||
|
|
||||||
|
[ -n "$PASS" ] || die "MSMTP_APP_PASSWORD missing in ${CAST_CLUSTER_ROOT}/secrets.lab.env"
|
||||||
|
|
||||||
|
apk add --no-cache msmtp mailx >/dev/null 2>&1 || apk add --no-cache msmtp
|
||||||
|
|
||||||
|
mkdir -p /etc/androidcast
|
||||||
|
umask 077
|
||||||
|
printf '%s\n' "$PASS" > /etc/androidcast/msmtp.secret
|
||||||
|
chmod 600 /etc/androidcast/msmtp.secret
|
||||||
|
|
||||||
|
sed -e "s|__MSMTP_FROM__|${FROM}|g" -e "s|__MSMTP_USER__|${USER}|g" \
|
||||||
|
"$ROOT/mail/msmtprc.template" > /etc/msmtprc
|
||||||
|
chmod 644 /etc/msmtprc
|
||||||
|
|
||||||
|
touch /var/log/msmtp.log
|
||||||
|
chmod 666 /var/log/msmtp.log 2>/dev/null || true
|
||||||
|
|
||||||
|
rc-update add msmtp default 2>/dev/null || true
|
||||||
|
rc-service msmtp restart 2>/dev/null || true
|
||||||
|
|
||||||
|
# sendmail → msmtp (php mail() / AuthMailer sendmail transport)
|
||||||
|
if [ ! -e /usr/sbin/sendmail ] || [ -L /usr/sbin/sendmail ]; then
|
||||||
|
ln -sf /usr/bin/msmtp /usr/sbin/sendmail 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "deploy-mailrelay_ok from=${FROM} user=${USER}"
|
||||||
28
sim/cluster0/scripts/install-lab-cron.sh
Executable file
28
sim/cluster0/scripts/install-lab-cron.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Lab maintenance cron on primary (cast01) — mirrors alpine-be schedules where applicable.
|
||||||
|
set -eu
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$ROOT/scripts/lib/common.sh"
|
||||||
|
load_cluster_env
|
||||||
|
|
||||||
|
is_primary_node || { log "install-lab-cron: skip non-primary"; exit 0; }
|
||||||
|
|
||||||
|
COMPOSED="${COMPOSE_BACKEND:-/var/www/ac/composed/backend}"
|
||||||
|
SHORTENER="/var/www/url-shortener"
|
||||||
|
CRON_FILE=/etc/crontabs/root
|
||||||
|
MARK="# ac-cluster0-lab"
|
||||||
|
|
||||||
|
grep -v "$MARK" "$CRON_FILE" 2>/dev/null > /tmp/crontab.ac.new || : > /tmp/crontab.ac.new
|
||||||
|
|
||||||
|
if [ -f "${COMPOSED}/scripts/purge_remote_access.php" ]; then
|
||||||
|
echo "0 * * * * cd ${COMPOSED} && php83 scripts/purge_remote_access.php --hours=24 ${MARK}" >> /tmp/crontab.ac.new
|
||||||
|
fi
|
||||||
|
if [ -f "${SHORTENER}/scripts/purge_url_shortener.php" ]; then
|
||||||
|
echo "0 4 * * * cd ${SHORTENER} && php83 scripts/purge_url_shortener.php ${MARK}" >> /tmp/crontab.ac.new
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv /tmp/crontab.ac.new "$CRON_FILE"
|
||||||
|
chmod 600 "$CRON_FILE"
|
||||||
|
rc-service crond restart 2>/dev/null || rc-service crond start 2>/dev/null || true
|
||||||
|
log "install-lab-cron_ok"
|
||||||
@@ -189,10 +189,34 @@ read_cred() {
|
|||||||
load_secrets_lab() {
|
load_secrets_lab() {
|
||||||
_file="${CAST_CLUSTER_ROOT}/secrets.lab.env"
|
_file="${CAST_CLUSTER_ROOT}/secrets.lab.env"
|
||||||
[ -f "$_file" ] || return 0
|
[ -f "$_file" ] || return 0
|
||||||
set -a
|
# Export only simple KEY=value lines (no shell metacharacters in values via source).
|
||||||
# shellcheck source=/dev/null
|
while IFS= read -r _line || [ -n "$_line" ]; do
|
||||||
. "$_file"
|
case "$_line" in
|
||||||
set +a
|
''|\#*) continue ;;
|
||||||
|
*=*)
|
||||||
|
_k="${_line%%=*}"
|
||||||
|
_v="${_line#*=}"
|
||||||
|
case "$_k" in
|
||||||
|
MAIL_*|MSMTP_*|SMTP_*|AUTH_*|GITEA_*|WG_*|RSSH_*)
|
||||||
|
export "$_k=$_v"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < "$_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
read_secret() {
|
||||||
|
_key="$1"
|
||||||
|
_file="${CAST_CLUSTER_ROOT}/secrets.lab.env"
|
||||||
|
[ -f "$_file" ] || return 1
|
||||||
|
awk -v k="$_key" '
|
||||||
|
$0 ~ "^" k "=" {
|
||||||
|
sub("^" k "=", "")
|
||||||
|
print
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
' "$_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
ssh_node() {
|
ssh_node() {
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ COMPOSED="${COMPOSE_BACKEND:-/var/www/ac/composed/backend}"
|
|||||||
[ -f "${COMPOSED}/config/config.php" ] || die "missing composed backend — run compose-lab-backend.sh"
|
[ -f "${COMPOSED}/config/config.php" ] || die "missing composed backend — run compose-lab-backend.sh"
|
||||||
|
|
||||||
export MAIL_TEST_TO="$TO"
|
export MAIL_TEST_TO="$TO"
|
||||||
_out="$(php -r '
|
PHP="${PHP_BIN:-php83}"
|
||||||
|
command -v "$PHP" >/dev/null 2>&1 || PHP=php
|
||||||
|
_out="$("$PHP" -r '
|
||||||
require "'"${COMPOSED}"'/src/bootstrap.php";
|
require "'"${COMPOSED}"'/src/bootstrap.php";
|
||||||
$to = getenv("MAIL_TEST_TO") ?: "";
|
$to = getenv("MAIL_TEST_TO") ?: "";
|
||||||
$origin = rtrim((string) cfg("public_origin", "https://acl0.f0xx.org"), "/");
|
$origin = rtrim((string) cfg("public_origin", "https://acl0.f0xx.org"), "/");
|
||||||
|
|||||||
@@ -18,13 +18,11 @@ AUTH_ENCRYPTION_KEY=lab-cluster-dev-32-char-min-secret!!
|
|||||||
# RSSH_BASTION_HOST=cast01.intra.raptor.org
|
# RSSH_BASTION_HOST=cast01.intra.raptor.org
|
||||||
# RSSH_BASTION_PORT=22
|
# RSSH_BASTION_PORT=22
|
||||||
|
|
||||||
# SMTP (when lab mail is configured — prefer email recovery)
|
# Outbound mail (msmtp → Gmail — copy MSMTP_APP_PASSWORD from alpine-be /etc/androidcast/msmtp.secret)
|
||||||
# Fill on NFS /shared/cluster/secrets.lab.env (mode 600); re-run compose-lab-backend.sh
|
# Use quotes if the app password contains spaces.
|
||||||
MAIL_TRANSPORT=smtp
|
MAIL_TRANSPORT=sendmail
|
||||||
MAIL_FROM=Android Cast Lab <noreply@acl0.f0xx.org>
|
MAIL_FROM="Android Cast Lab <bestcastr@gmail.com>"
|
||||||
MAIL_REPLY_TO=info@apps.f0xx.org
|
MAIL_REPLY_TO=bestcastr@gmail.com
|
||||||
# SMTP_HOST=
|
MSMTP_FROM=bestcastr@gmail.com
|
||||||
# SMTP_PORT=587
|
MSMTP_USER=bestcastr@gmail.com
|
||||||
# SMTP_ENCRYPTION=tls
|
# MSMTP_APP_PASSWORD="xxxx xxxx xxxx xxxx"
|
||||||
# SMTP_USER=
|
|
||||||
# SMTP_PASS=
|
|
||||||
|
|||||||
Reference in New Issue
Block a user