first commit
This commit is contained in:
commit
2eb8aac027
91 files changed
+20139
No files matched your search
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
ENV_FILE="${POSTFIXADMIN_RS_ENV_FILE:-$ROOT/.env}"
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
. "$ENV_FILE"
|
||||
set +a
|
||||
fi
|
||||
|
||||
if ! command -v cargo >/dev/null 2>&1; then
|
||||
echo "error: Rust/Cargo is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DEV_DB_MODE="${POSTFIXADMIN_RS_DEV_DB_MODE:-mock}"
|
||||
DEV_DB="$ROOT/.dev/postfixadmin-rs.sqlite"
|
||||
TUNNEL_PID=""
|
||||
mkdir -p "$ROOT/.dev"
|
||||
|
||||
cleanup() {
|
||||
if [ -n "$TUNNEL_PID" ] && kill -0 "$TUNNEL_PID" 2>/dev/null; then
|
||||
kill "$TUNNEL_PID" 2>/dev/null || true
|
||||
wait "$TUNNEL_PID" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
wait_for_port() {
|
||||
local port="$1"
|
||||
local i
|
||||
for i in $(seq 1 80); do
|
||||
if ! kill -0 "$TUNNEL_PID" 2>/dev/null; then
|
||||
echo "error: SSH tunnel exited before local port became ready" >&2
|
||||
wait "$TUNNEL_PID" || true
|
||||
exit 1
|
||||
fi
|
||||
if command -v nc >/dev/null 2>&1; then
|
||||
if nc -z 127.0.0.1 "$port" >/dev/null 2>&1; then return 0; fi
|
||||
elif (exec 3<>"/dev/tcp/127.0.0.1/$port") 2>/dev/null; then
|
||||
exec 3>&- 3<&-
|
||||
return 0
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
echo "error: timed out waiting for SSH tunnel on 127.0.0.1:$port" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "${POSTFIXADMIN_RS_DEV_RESET:-0}" = "1" ]; then
|
||||
rm -f "$DEV_DB" "$DEV_DB-shm" "$DEV_DB-wal"
|
||||
rm -rf "$ROOT/.dev/rspamd" "$ROOT/.dev/rspamd-remote-public"
|
||||
echo "Development mock DB and Rspamd DKIM state reset."
|
||||
fi
|
||||
|
||||
case "${RUST_LOG:-}" in
|
||||
""|debug) export RUST_LOG="warn,postfixadmin_rs=debug,tower_http=info" ;;
|
||||
trace) export RUST_LOG="warn,postfixadmin_rs=trace,tower_http=debug" ;;
|
||||
info) export RUST_LOG="warn,postfixadmin_rs=info,tower_http=info" ;;
|
||||
*) : ;;
|
||||
esac
|
||||
|
||||
case "$DEV_DB_MODE" in
|
||||
mock)
|
||||
export POSTFIXADMIN_RS_CONFIG="${POSTFIXADMIN_RS_CONFIG:-$ROOT/config.dev.toml}"
|
||||
;;
|
||||
ssh)
|
||||
if ! command -v ssh >/dev/null 2>&1; then
|
||||
echo "error: ssh client is required for POSTFIXADMIN_RS_DEV_DB_MODE=ssh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
: "${POSTFIXADMIN_RS_SSH_HOST:?set POSTFIXADMIN_RS_SSH_HOST}"
|
||||
SSH_USER="${POSTFIXADMIN_RS_SSH_USER:-$USER}"
|
||||
SSH_PORT="${POSTFIXADMIN_RS_SSH_PORT:-22}"
|
||||
DB_BACKEND="${POSTFIXADMIN_RS_DEV_DB_BACKEND:-mysql}"
|
||||
case "$DB_BACKEND" in
|
||||
mysql|mysqli) DEFAULT_REMOTE_PORT=3306; DEFAULT_LOCAL_PORT=13306 ;;
|
||||
pgsql|postgres) DEFAULT_REMOTE_PORT=5432; DEFAULT_LOCAL_PORT=15432 ;;
|
||||
*) echo "error: POSTFIXADMIN_RS_DEV_DB_BACKEND must be mysql, mysqli, pgsql or postgres" >&2; exit 1 ;;
|
||||
esac
|
||||
REMOTE_DB_HOST="${POSTFIXADMIN_RS_DEV_DB_HOST:-127.0.0.1}"
|
||||
REMOTE_DB_PORT="${POSTFIXADMIN_RS_DEV_DB_PORT:-$DEFAULT_REMOTE_PORT}"
|
||||
LOCAL_DB_PORT="${POSTFIXADMIN_RS_SSH_LOCAL_PORT:-$DEFAULT_LOCAL_PORT}"
|
||||
DB_USER="${POSTFIXADMIN_RS_DEV_DB_USER:-postfix}"
|
||||
DB_NAME="${POSTFIXADMIN_RS_DEV_DB_NAME:-postfix}"
|
||||
DB_PASSWORD="${POSTFIXADMIN_RS_DEV_DB_PASSWORD:-${POSTFIXADMIN_RS__DATABASE__PASSWORD:-}}"
|
||||
DEV_SIEVE_ENABLED="${POSTFIXADMIN_RS_DEV_SIEVE_ENABLED:-${POSTFIXADMIN_RS_DEV_SIEVE_TUNNEL:-true}}"
|
||||
SIEVE_REMOTE_HOST="${POSTFIXADMIN_RS_DEV_SIEVE_REMOTE_HOST:-127.0.0.1}"
|
||||
SIEVE_REMOTE_PORT="${POSTFIXADMIN_RS_DEV_SIEVE_REMOTE_PORT:-4190}"
|
||||
SIEVE_LOCAL_PORT="${POSTFIXADMIN_RS_DEV_SIEVE_LOCAL_PORT:-14190}"
|
||||
DEV_RSPAMD_PUBLIC_SYNC="${POSTFIXADMIN_RS_DEV_RSPAMD_PUBLIC_SYNC:-true}"
|
||||
RSPAMD_SYNC_OK=0
|
||||
RSPAMD_REMOTE_KEY_DIR="${POSTFIXADMIN_RS_DEV_RSPAMD_KEY_DIR:-/var/lib/rspamd/dkim}"
|
||||
RSPAMD_REMOTE_SIGNING_CONFIG="${POSTFIXADMIN_RS_DEV_RSPAMD_SIGNING_CONFIG:-/etc/rspamd/local.d/dkim_signing.conf}"
|
||||
RSPAMD_REMOTE_SELECTOR_MAP="${POSTFIXADMIN_RS_DEV_RSPAMD_SELECTOR_MAP:-/var/lib/rspamd/dkim/postfixadmin-rs-selectors.map}"
|
||||
RSPAMD_REMOTE_PATH_MAP="${POSTFIXADMIN_RS_DEV_RSPAMD_PATH_MAP:-/var/lib/rspamd/dkim/postfixadmin-rs-paths.map}"
|
||||
RSPAMD_DEFAULT_SELECTOR="${POSTFIXADMIN_RS_DEV_RSPAMD_DEFAULT_SELECTOR:-mail}"
|
||||
RSPAMD_PUBLIC_DIR="$ROOT/.dev/rspamd-remote-public"
|
||||
|
||||
export POSTFIXADMIN_RS_CONFIG="${POSTFIXADMIN_RS_CONFIG:-${POSTFIXADMIN_RS_SSH_CONFIG:-$ROOT/config.from-user.toml}}"
|
||||
export POSTFIXADMIN_RS__ENVIRONMENT=development
|
||||
export POSTFIXADMIN_RS__DATABASE__BACKEND="$DB_BACKEND"
|
||||
export POSTFIXADMIN_RS__DATABASE__HOST=127.0.0.1
|
||||
export POSTFIXADMIN_RS__DATABASE__PORT="$LOCAL_DB_PORT"
|
||||
export POSTFIXADMIN_RS__DATABASE__USER="$DB_USER"
|
||||
export POSTFIXADMIN_RS__DATABASE__NAME="$DB_NAME"
|
||||
export POSTFIXADMIN_RS__DATABASE__PASSWORD="$DB_PASSWORD"
|
||||
export POSTFIXADMIN_RS__DATABASE__DEV_MOCK=false
|
||||
export POSTFIXADMIN_RS__DATABASE__READ_ONLY=true
|
||||
export POSTFIXADMIN_RS__SECURITY__COOKIE_SECURE=false
|
||||
# Rspamd integration on the workstation is read from a public-only SSH cache below.
|
||||
# No private key bytes are copied from the mail server.
|
||||
export POSTFIXADMIN_RS__RSPAMD_DKIM__ENABLED=false
|
||||
export POSTFIXADMIN_RS__SIEVE__ENABLED="$DEV_SIEVE_ENABLED"
|
||||
if [ "$DEV_SIEVE_ENABLED" = "true" ] || [ "$DEV_SIEVE_ENABLED" = "1" ]; then
|
||||
export POSTFIXADMIN_RS__SIEVE__HOST=127.0.0.1
|
||||
export POSTFIXADMIN_RS__SIEVE__PORT="$SIEVE_LOCAL_PORT"
|
||||
export POSTFIXADMIN_RS__SIEVE__TLS_MODE=none
|
||||
export POSTFIXADMIN_RS__SIEVE__ALLOW_INSECURE_AUTH=true
|
||||
export POSTFIXADMIN_RS__SIEVE__READ_ONLY=true
|
||||
if [ "${POSTFIXADMIN_RS_DEV_SIEVE_WRITES:-0}" = "1" ]; then
|
||||
export POSTFIXADMIN_RS__SIEVE__READ_ONLY=false
|
||||
echo "WARNING: production ManageSieve writes are ENABLED for this development session." >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${POSTFIXADMIN_RS_DEV_DB_WRITES:-0}" = "1" ]; then
|
||||
export POSTFIXADMIN_RS__DATABASE__READ_ONLY=false
|
||||
echo "WARNING: production DB writes are ENABLED for this development session." >&2
|
||||
fi
|
||||
|
||||
SSH_COMMON_ARGS=(-p "$SSH_PORT" -o BatchMode=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3)
|
||||
if [ -n "${POSTFIXADMIN_RS_SSH_IDENTITY:-}" ]; then
|
||||
SSH_COMMON_ARGS+=(-i "$POSTFIXADMIN_RS_SSH_IDENTITY")
|
||||
fi
|
||||
if [ -n "${POSTFIXADMIN_RS_SSH_KNOWN_HOSTS:-}" ]; then
|
||||
SSH_COMMON_ARGS+=(-o "UserKnownHostsFile=${POSTFIXADMIN_RS_SSH_KNOWN_HOSTS}")
|
||||
fi
|
||||
SSH_ARGS=(-N -T -L "127.0.0.1:${LOCAL_DB_PORT}:${REMOTE_DB_HOST}:${REMOTE_DB_PORT}" -o ExitOnForwardFailure=yes "${SSH_COMMON_ARGS[@]}")
|
||||
if [ "$DEV_SIEVE_ENABLED" = "true" ] || [ "$DEV_SIEVE_ENABLED" = "1" ]; then
|
||||
SSH_ARGS+=(-L "127.0.0.1:${SIEVE_LOCAL_PORT}:${SIEVE_REMOTE_HOST}:${SIEVE_REMOTE_PORT}")
|
||||
fi
|
||||
|
||||
ssh "${SSH_ARGS[@]}" "${SSH_USER}@${POSTFIXADMIN_RS_SSH_HOST}" &
|
||||
TUNNEL_PID=$!
|
||||
wait_for_port "$LOCAL_DB_PORT"
|
||||
if [ "$DEV_SIEVE_ENABLED" = "true" ] || [ "$DEV_SIEVE_ENABLED" = "1" ]; then
|
||||
wait_for_port "$SIEVE_LOCAL_PORT"
|
||||
fi
|
||||
|
||||
if [ "$DEV_RSPAMD_PUBLIC_SYNC" = "true" ] || [ "$DEV_RSPAMD_PUBLIC_SYNC" = "1" ]; then
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo "warning: python3 is required for public-only Rspamd DKIM sync; disabling it for this session" >&2
|
||||
DEV_RSPAMD_PUBLIC_SYNC=false
|
||||
fi
|
||||
fi
|
||||
if [ "$DEV_RSPAMD_PUBLIC_SYNC" = "true" ] || [ "$DEV_RSPAMD_PUBLIC_SYNC" = "1" ]; then
|
||||
rm -rf "$RSPAMD_PUBLIC_DIR"
|
||||
SYNC_ARGS=(
|
||||
--ssh "$(command -v ssh)"
|
||||
--host "$POSTFIXADMIN_RS_SSH_HOST"
|
||||
--user "$SSH_USER"
|
||||
--port "$SSH_PORT"
|
||||
--signing-config "$RSPAMD_REMOTE_SIGNING_CONFIG"
|
||||
--selector-map "$RSPAMD_REMOTE_SELECTOR_MAP"
|
||||
--path-map "$RSPAMD_REMOTE_PATH_MAP"
|
||||
--key-dir "$RSPAMD_REMOTE_KEY_DIR"
|
||||
--default-selector "$RSPAMD_DEFAULT_SELECTOR"
|
||||
--output-dir "$RSPAMD_PUBLIC_DIR"
|
||||
)
|
||||
if [ -n "${POSTFIXADMIN_RS_SSH_IDENTITY:-}" ]; then
|
||||
SYNC_ARGS+=(--identity "$POSTFIXADMIN_RS_SSH_IDENTITY")
|
||||
fi
|
||||
if [ -n "${POSTFIXADMIN_RS_SSH_KNOWN_HOSTS:-}" ]; then
|
||||
SYNC_ARGS+=(--known-hosts "$POSTFIXADMIN_RS_SSH_KNOWN_HOSTS")
|
||||
fi
|
||||
if python3 "$ROOT/scripts/sync-rspamd-dkim-readonly.py" "${SYNC_ARGS[@]}"; then
|
||||
export POSTFIXADMIN_RS__RSPAMD_DKIM__ENABLED=true
|
||||
export POSTFIXADMIN_RS__RSPAMD_DKIM__KEY_DIR="$RSPAMD_PUBLIC_DIR/keys"
|
||||
export POSTFIXADMIN_RS__RSPAMD_DKIM__SIGNING_CONFIG="$RSPAMD_PUBLIC_DIR/dkim_signing.conf"
|
||||
export POSTFIXADMIN_RS__RSPAMD_DKIM__SELECTOR_MAP="$RSPAMD_PUBLIC_DIR/selectors.map"
|
||||
export POSTFIXADMIN_RS__RSPAMD_DKIM__PATH_MAP="$RSPAMD_PUBLIC_DIR/paths.map"
|
||||
export POSTFIXADMIN_RS__RSPAMD_DKIM__DEFAULT_SELECTOR="$RSPAMD_DEFAULT_SELECTOR"
|
||||
export POSTFIXADMIN_RS__RSPAMD_DKIM__RSPAMADM=/bin/false
|
||||
RSPAMD_SYNC_OK=1
|
||||
else
|
||||
echo "warning: Rspamd DKIM public sync failed; Rspamd DKIM view is disabled for this dev session" >&2
|
||||
export POSTFIXADMIN_RS__RSPAMD_DKIM__ENABLED=false
|
||||
fi
|
||||
elif [ "${POSTFIXADMIN_RS_DEV_RSPAMD_ENABLED:-false}" = "true" ] || [ "${POSTFIXADMIN_RS_DEV_RSPAMD_ENABLED:-false}" = "1" ]; then
|
||||
# Explicit escape hatch for testing a Rspamd installation local to the workstation.
|
||||
export POSTFIXADMIN_RS__RSPAMD_DKIM__ENABLED=true
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "error: POSTFIXADMIN_RS_DEV_DB_MODE must be mock or ssh" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
[ -f "$POSTFIXADMIN_RS_CONFIG" ] || {
|
||||
echo "error: config not found: $POSTFIXADMIN_RS_CONFIG" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "PostfixAdmin RS 0.0.1-dev: http://127.0.0.1:8080"
|
||||
echo "Config: $POSTFIXADMIN_RS_CONFIG"
|
||||
if [ "$DEV_DB_MODE" = "mock" ]; then
|
||||
echo "DB: SQLite mock ($DEV_DB)"
|
||||
echo "Admin: admin@example.test / admin123"
|
||||
echo "D-admin: domain-admin@example.test / domain123"
|
||||
echo "User: user@example.test / user123"
|
||||
echo "Rspamd: DEV mock (.dev/rspamd)"
|
||||
echo "Reset: POSTFIXADMIN_RS_DEV_RESET=1 bash dev.sh"
|
||||
else
|
||||
echo "DB: production through SSH tunnel 127.0.0.1:${LOCAL_DB_PORT} -> ${REMOTE_DB_HOST}:${REMOTE_DB_PORT}"
|
||||
echo "SSH: ${SSH_USER}@${POSTFIXADMIN_RS_SSH_HOST}:${SSH_PORT}"
|
||||
if [ "${DEV_SIEVE_ENABLED:-false}" = "true" ] || [ "${DEV_SIEVE_ENABLED:-false}" = "1" ]; then
|
||||
echo "Sieve: 127.0.0.1:${SIEVE_LOCAL_PORT} -> ${SIEVE_REMOTE_HOST}:${SIEVE_REMOTE_PORT} ($([ "${POSTFIXADMIN_RS_DEV_SIEVE_WRITES:-0}" = "1" ] && echo READ/WRITE || echo READ-ONLY))"
|
||||
fi
|
||||
if [ "${RSPAMD_SYNC_OK:-0}" = "1" ]; then
|
||||
echo "DKIM: Rspamd public DNS records synced over SSH (private keys stay remote)"
|
||||
fi
|
||||
if [ "${POSTFIXADMIN_RS_DEV_DB_WRITES:-0}" = "1" ]; then
|
||||
echo "Access: READ/WRITE (explicitly enabled)"
|
||||
else
|
||||
echo "Access: READ-ONLY (default)"
|
||||
fi
|
||||
fi
|
||||
[ -f "$ENV_FILE" ] && echo "Env: $ENV_FILE"
|
||||
echo
|
||||
|
||||
cargo run --quiet --bin postfixadmin-rs -- --check-config
|
||||
cargo run --bin postfixadmin-rs -- "$@"
|
||||
Reference in new issue
Block a user