pepsi: host-net highports + ensure mon-* users
This commit is contained in:
parent
8370ad2e33
commit
4f74662202
3 changed files with 327 additions and 0 deletions
57
scripts/pepsi/ensure-mon-users.sh
Executable file
57
scripts/pepsi/ensure-mon-users.sh
Executable file
|
|
@ -0,0 +1,57 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Idempotent: ensure mon-* OS users + Maildir inside koopa-pepsi.
|
||||||
|
# Needed after every container recreate: /home is not in the image and is
|
||||||
|
# not a volume by default → useradd state vanishes while DB login_assignment
|
||||||
|
# survives.
|
||||||
|
#
|
||||||
|
# UIDs fixed 1000–1004 (match login_assignment / prior live).
|
||||||
|
# STATUS= only; no secrets. Run on koopa as hernani (rootless podman exec).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CTR="${PEPSI_CTR:-koopa-pepsi}"
|
||||||
|
USERS=(
|
||||||
|
"mon-smtp:1000"
|
||||||
|
"mon-maildir:1001"
|
||||||
|
"mon-dkim:1002"
|
||||||
|
"mon-bounce:1003"
|
||||||
|
"mon-local:1004"
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "STATUS=start CTR=$CTR HOST=$(hostname -s)"
|
||||||
|
|
||||||
|
if ! podman container exists "$CTR" 2>/dev/null; then
|
||||||
|
echo "STATUS=missing_ctr CTR=$CTR"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if [[ "$(podman inspect -f '{{.State.Running}}' "$CTR" 2>/dev/null || echo false)" != "true" ]]; then
|
||||||
|
echo "STATUS=ctr_not_running CTR=$CTR"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
created=0
|
||||||
|
present=0
|
||||||
|
for entry in "${USERS[@]}"; do
|
||||||
|
u="${entry%%:*}"
|
||||||
|
uid="${entry##*:}"
|
||||||
|
if podman exec "$CTR" getent passwd "$u" >/dev/null 2>&1; then
|
||||||
|
echo "USER=$u STATE=present"
|
||||||
|
present=$((present + 1))
|
||||||
|
else
|
||||||
|
podman exec -u root "$CTR" useradd -u "$uid" -U -m -d "/home/$u" -s /usr/sbin/nologin "$u"
|
||||||
|
echo "USER=$u STATE=created UID=$uid"
|
||||||
|
created=$((created + 1))
|
||||||
|
fi
|
||||||
|
# Maildir skeleton (cur/new/tmp) — safe if already present
|
||||||
|
podman exec -u root "$CTR" bash -c "
|
||||||
|
set -e
|
||||||
|
home=/home/$u
|
||||||
|
mkdir -p \"\$home/Maildir\"/{cur,new,tmp}
|
||||||
|
chown -R $u:$u \"\$home\"
|
||||||
|
chmod 700 \"\$home\"
|
||||||
|
"
|
||||||
|
echo "MAILDIR=$u OK=1"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "STATUS=done CREATED=$created PRESENT_BEFORE=$present EXPECT=${#USERS[@]}"
|
||||||
|
podman exec "$CTR" getent passwd | grep '^mon-' || true
|
||||||
|
exit 0
|
||||||
66
scripts/pepsi/firewalld-pepsi-highports.sh
Executable file
66
scripts/pepsi/firewalld-pepsi-highports.sh
Executable file
|
|
@ -0,0 +1,66 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Open rootless pepsi high-ports on koopa (firewalld).
|
||||||
|
# Compose map (LAN bind 192.168.100.95):
|
||||||
|
# 1025→25 1465→465 1587→587 1993→993 18443→443
|
||||||
|
# Freigabe: human this session. Run on koopa as root / sudo.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PORTS=(1025/tcp 1465/tcp 1587/tcp 1993/tcp 18443/tcp)
|
||||||
|
ZONE="${FIREWALLD_ZONE:-}"
|
||||||
|
|
||||||
|
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
|
||||||
|
echo "STATUS=need_root HINT='sudo $0'"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v firewall-cmd >/dev/null 2>&1; then
|
||||||
|
echo "STATUS=no_firewalld"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! firewall-cmd --state >/dev/null 2>&1; then
|
||||||
|
echo "STATUS=firewalld_down"
|
||||||
|
exit 4
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$ZONE" ]]; then
|
||||||
|
# Prefer the zone that owns the LAN NIC; else default.
|
||||||
|
ZONE=$(firewall-cmd --get-zone-of-interface=eno1 2>/dev/null || true)
|
||||||
|
if [[ -z "$ZONE" ]]; then
|
||||||
|
ZONE=$(firewall-cmd --get-default-zone)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "STATUS=start ZONE=$ZONE HOST=$(hostname -s)"
|
||||||
|
|
||||||
|
added=0
|
||||||
|
present=0
|
||||||
|
for p in "${PORTS[@]}"; do
|
||||||
|
if firewall-cmd --zone="$ZONE" --query-port="$p" >/dev/null 2>&1; then
|
||||||
|
echo "PORT=$p STATE=present"
|
||||||
|
present=$((present + 1))
|
||||||
|
else
|
||||||
|
firewall-cmd --zone="$ZONE" --add-port="$p" --permanent
|
||||||
|
echo "PORT=$p STATE=added_permanent"
|
||||||
|
added=$((added + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
firewall-cmd --reload
|
||||||
|
echo "STATUS=reloaded"
|
||||||
|
|
||||||
|
ok=0
|
||||||
|
for p in "${PORTS[@]}"; do
|
||||||
|
if firewall-cmd --zone="$ZONE" --query-port="$p" >/dev/null 2>&1; then
|
||||||
|
echo "VERIFY=$p OK=1"
|
||||||
|
ok=$((ok + 1))
|
||||||
|
else
|
||||||
|
echo "VERIFY=$p OK=0"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "STATUS=done ZONE=$ZONE ADDED=$added PRESENT_BEFORE=$present VERIFY_OK=$ok EXPECT=${#PORTS[@]}"
|
||||||
|
if [[ "$ok" -eq "${#PORTS[@]}" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
204
scripts/pepsi/hostnet-pepsi-highports.sh
Executable file
204
scripts/pepsi/hostnet-pepsi-highports.sh
Executable file
|
|
@ -0,0 +1,204 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Switch koopa-pepsi to network_mode:host on high ports so SMTP client IP
|
||||||
|
# is preserved (rootlessport SNAT otherwise forces 10.89.16.4 → SPF fail).
|
||||||
|
#
|
||||||
|
# Vecigate stays pure DNAT (25/465/587 → 1025/1465/1587). No mail on router.
|
||||||
|
# DB stays bridged; publish 127.0.0.1:15432→5432 for host-net pepsi.
|
||||||
|
#
|
||||||
|
# Freigabe: human this session (Script hinlegen + apply+restart).
|
||||||
|
# Run on koopa as hernani (rootless podman). Never echoes DB secrets.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PEPSI_DIR="${PEPSI_DIR:-$HOME/koopa-pepsi}"
|
||||||
|
TS="$(date +%Y%m%d-%H%M%S)"
|
||||||
|
BAK="${PEPSI_DIR}/bak-hostnet-${TS}"
|
||||||
|
COMPOSE_BIN=""
|
||||||
|
|
||||||
|
echo "STATUS=start HOST=$(hostname -s) DIR=$PEPSI_DIR"
|
||||||
|
|
||||||
|
if [[ ! -d "$PEPSI_DIR" ]]; then
|
||||||
|
echo "STATUS=missing_dir PATH=$PEPSI_DIR"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
cd "$PEPSI_DIR"
|
||||||
|
|
||||||
|
if command -v podman-compose >/dev/null 2>&1; then
|
||||||
|
COMPOSE_BIN="podman-compose"
|
||||||
|
elif podman compose version >/dev/null 2>&1; then
|
||||||
|
COMPOSE_BIN="podman compose"
|
||||||
|
else
|
||||||
|
echo "STATUS=no_compose"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f compose.yml || ! -f conf/pepsi.conf ]]; then
|
||||||
|
echo "STATUS=missing_files"
|
||||||
|
exit 4
|
||||||
|
fi
|
||||||
|
|
||||||
|
already=0
|
||||||
|
if grep -q 'network_mode:[[:space:]]*host' compose.yml \
|
||||||
|
&& grep -qE '^PORT = 1025$' conf/pepsi.conf \
|
||||||
|
&& grep -qE '@127\.0\.0\.1:15432/' conf/pepsi.conf; then
|
||||||
|
already=1
|
||||||
|
echo "STATUS=already_hostnet HINT=skip_patch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$already" -eq 0 ]]; then
|
||||||
|
mkdir -p "$BAK"
|
||||||
|
cp -a compose.yml conf/pepsi.conf "$BAK/"
|
||||||
|
echo "STATUS=bak PATH=$BAK"
|
||||||
|
|
||||||
|
python3 - <<'PY'
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
root = Path(".")
|
||||||
|
compose_path = root / "compose.yml"
|
||||||
|
conf_path = root / "conf" / "pepsi.conf"
|
||||||
|
compose = compose_path.read_text()
|
||||||
|
conf = conf_path.read_text()
|
||||||
|
|
||||||
|
# Split services crudely on "\n pepsi:" / "\n db:"
|
||||||
|
def patch_compose(text: str) -> str:
|
||||||
|
# Retarget env DB URLs first (password stays ${VAR})
|
||||||
|
text = text.replace("@db:5432/", "@127.0.0.1:15432/")
|
||||||
|
|
||||||
|
# Ensure db publishes 127.0.0.1:15432:5432
|
||||||
|
if "15432:5432" not in text:
|
||||||
|
text = re.sub(
|
||||||
|
r"(container_name: koopa-pepsi-db\n)",
|
||||||
|
r'\1 ports:\n - "127.0.0.1:15432:5432"\n',
|
||||||
|
text,
|
||||||
|
count=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# pepsi service: add network_mode host; remove its ports: block
|
||||||
|
# Match from " pepsi:" until next top-level service or "volumes:"
|
||||||
|
m = re.search(r"(?ms)^ pepsi:\n(.*?)(?=^volumes:|^ [a-z])", text)
|
||||||
|
if not m:
|
||||||
|
print("STATUS=pepsi_service_not_found")
|
||||||
|
sys.exit(5)
|
||||||
|
body = m.group(1)
|
||||||
|
# drop ports block inside pepsi
|
||||||
|
body2 = re.sub(r"(?m)^ ports:\n(?: [^\n]*\n)+", "", body)
|
||||||
|
if "network_mode:" not in body2:
|
||||||
|
body2 = re.sub(
|
||||||
|
r"(container_name: koopa-pepsi\n)",
|
||||||
|
r"\1 network_mode: host\n",
|
||||||
|
body2,
|
||||||
|
count=1,
|
||||||
|
)
|
||||||
|
text = text[: m.start(1)] + body2 + text[m.end(1) :]
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def set_port(text: str, section: str, port: int) -> str:
|
||||||
|
pat = rf"(\[{re.escape(section)}\]\n(?:.*\n)*?PORT = )\d+"
|
||||||
|
new, n = re.subn(pat, rf"\g<1>{port}", text, count=1)
|
||||||
|
if n != 1:
|
||||||
|
print(f"STATUS=port_patch_fail SECTION={section} N={n}")
|
||||||
|
sys.exit(6)
|
||||||
|
return new
|
||||||
|
|
||||||
|
|
||||||
|
compose = patch_compose(compose)
|
||||||
|
conf = conf.replace("@db:5432/", "@127.0.0.1:15432/")
|
||||||
|
conf = set_port(conf, "pepsi-ingress-listener-mx", 1025)
|
||||||
|
conf = set_port(conf, "pepsi-ingress-listener-submissions", 1465)
|
||||||
|
conf = set_port(conf, "pepsi-ingress-listener-submission", 1587)
|
||||||
|
conf = set_port(conf, "pepsi-httpd-listener-https", 18443)
|
||||||
|
|
||||||
|
compose_path.write_text(compose)
|
||||||
|
conf_path.write_text(conf)
|
||||||
|
|
||||||
|
# sanity STATUS (no secrets)
|
||||||
|
checks = [
|
||||||
|
("network_mode_host", bool(re.search(r"(?m)^ network_mode:\s*host\s*$", compose))),
|
||||||
|
("db_pub_15432", "15432:5432" in compose),
|
||||||
|
("no_db_hostname", "@db:5432/" not in compose and "@db:5432/" not in conf),
|
||||||
|
("port_1025", bool(re.search(r"(?m)^PORT = 1025\s*$", conf))),
|
||||||
|
("port_1465", bool(re.search(r"(?m)^PORT = 1465\s*$", conf))),
|
||||||
|
("port_1587", bool(re.search(r"(?m)^PORT = 1587\s*$", conf))),
|
||||||
|
("port_18443", bool(re.search(r"(?m)^PORT = 18443\s*$", conf))),
|
||||||
|
("pepsi_ports_gone", "192.168.100.95:1025" not in compose),
|
||||||
|
]
|
||||||
|
for k, v in checks:
|
||||||
|
print(f"CHECK={k} OK={int(v)}")
|
||||||
|
if not all(v for _, v in checks):
|
||||||
|
print("STATUS=patch_sanity_fail")
|
||||||
|
sys.exit(7)
|
||||||
|
print("STATUS=patched")
|
||||||
|
PY
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "STATUS=compose_up BIN=$COMPOSE_BIN"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
$COMPOSE_BIN up -d --force-recreate
|
||||||
|
sleep 4
|
||||||
|
|
||||||
|
echo "STATUS=verify"
|
||||||
|
ss_out="$(ss -lntp 2>/dev/null | grep -E ':1025|:1465|:1587|:18443|:15432' || true)"
|
||||||
|
# redact pid/user clutter lightly
|
||||||
|
echo "$ss_out" | sed -E 's/users:\(\("([^"]+)".*/users=(("\1"…)/' || true
|
||||||
|
|
||||||
|
rootless_hit=0
|
||||||
|
if echo "$ss_out" | grep -q rootlessport; then
|
||||||
|
rootless_hit=1
|
||||||
|
fi
|
||||||
|
db_pub=0
|
||||||
|
if echo "$ss_out" | grep -qE '127\.0\.0\.1:15432'; then
|
||||||
|
db_pub=1
|
||||||
|
fi
|
||||||
|
mx_ok=0
|
||||||
|
if echo "$ss_out" | grep -qE ':1025\b'; then
|
||||||
|
mx_ok=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman ps --format '{{.Names}} {{.Status}}' | grep pepsi || true
|
||||||
|
|
||||||
|
if command -v pg_isready >/dev/null 2>&1; then
|
||||||
|
if pg_isready -h 127.0.0.1 -p 15432 -d pepsi >/dev/null 2>&1; then
|
||||||
|
echo "STATUS=db_ready HOST=127.0.0.1 PORT=15432"
|
||||||
|
else
|
||||||
|
echo "STATUS=db_not_ready HOST=127.0.0.1 PORT=15432"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if timeout 2 bash -c 'echo >/dev/tcp/127.0.0.1/15432' 2>/dev/null; then
|
||||||
|
echo "STATUS=db_tcp_open HOST=127.0.0.1 PORT=15432"
|
||||||
|
else
|
||||||
|
echo "STATUS=db_tcp_closed HOST=127.0.0.1 PORT=15432"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# recent log lines (strip any postgres URLs)
|
||||||
|
podman logs --tail 40 koopa-pepsi 2>/dev/null \
|
||||||
|
| grep -Ei 'listen|error|fatal|spf|client|1025|postgres|ready' \
|
||||||
|
| sed -E 's#postgres://[^[:space:]]+#postgres://*#g' \
|
||||||
|
| tail -12 || true
|
||||||
|
|
||||||
|
echo "STATUS=done MX_1025=$mx_ok DB_PUB=$db_pub ROOTLESSPORT_ON_HIGH=$rootless_hit BAK=${BAK:-none}"
|
||||||
|
echo "HINT=next ensure-mon-users.sh after recreate; then taler-mail send --expect-pong"
|
||||||
|
echo "HINT=Vecigate DNAT unchanged (no mail on router); /home ephemeral unless volume+ensure"
|
||||||
|
# Optional: run ensure if sibling script present (same dir or PEPSI_DIR/scripts)
|
||||||
|
ENSURE=""
|
||||||
|
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
for cand in "$here/ensure-mon-users.sh" "$PEPSI_DIR/scripts/ensure-mon-users.sh" "$PEPSI_DIR/ensure-mon-users.sh"; do
|
||||||
|
if [[ -x "$cand" ]]; then
|
||||||
|
ENSURE="$cand"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ -n "$ENSURE" ]]; then
|
||||||
|
echo "STATUS=ensure_mon_users SCRIPT=$ENSURE"
|
||||||
|
PEPSI_CTR=koopa-pepsi "$ENSURE" || echo "STATUS=ensure_warn"
|
||||||
|
fi
|
||||||
|
if [[ "$mx_ok" -eq 1 && "$rootless_hit" -eq 0 && "$db_pub" -eq 1 ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if [[ "$mx_ok" -eq 1 && "$db_pub" -eq 1 ]]; then
|
||||||
|
echo "STATUS=warn_rootlessport_still_present"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
Loading…
Add table
Add a link
Reference in a new issue