Lightweight containerized prosody tooling + moved auth scripts + xmpp reconnect/auth stabilization
This commit is contained in:
87
utilities/prosody/auth_django.py
Executable file
87
utilities/prosody/auth_django.py
Executable file
@@ -0,0 +1,87 @@
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
import django
|
||||
|
||||
LOG_PATH = os.environ.get("AUTH_DEBUG_LOG", "/tmp/auth_debug.log")
|
||||
|
||||
|
||||
def log(data):
|
||||
try:
|
||||
with open(LOG_PATH, "a") as f:
|
||||
f.write(f"{data}\n")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# Set up Django environment
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") # Adjust if needed
|
||||
django.setup()
|
||||
|
||||
from django.contrib.auth import authenticate # noqa: E402
|
||||
from django.contrib.auth.models import User # noqa: E402
|
||||
|
||||
|
||||
def check_credentials(username, password):
|
||||
"""Authenticate user via Django"""
|
||||
user = authenticate(username=username, password=password)
|
||||
return user is not None and user.is_active
|
||||
|
||||
|
||||
def _handle_line(line: str) -> str:
|
||||
parts = line.split(":")
|
||||
if len(parts) < 3:
|
||||
return "0"
|
||||
|
||||
command, username, domain = parts[:3]
|
||||
_ = domain
|
||||
password = ":".join(parts[3:]) if len(parts) > 3 else None
|
||||
|
||||
if command == "auth":
|
||||
return "1" if (password and check_credentials(username, password)) else "0"
|
||||
if command == "isuser":
|
||||
return "1" if User.objects.filter(username=username).exists() else "0"
|
||||
if command == "setpass":
|
||||
return "0"
|
||||
return "0"
|
||||
|
||||
|
||||
def _readline() -> tuple[str | None, bool]:
|
||||
raw = sys.stdin.readline()
|
||||
if raw == "":
|
||||
return None, True
|
||||
return raw.rstrip("\r\n"), False
|
||||
|
||||
|
||||
def main(*, once: bool = False):
|
||||
"""Process authentication requests from Prosody."""
|
||||
while True:
|
||||
try:
|
||||
line, eof = _readline()
|
||||
if eof:
|
||||
break
|
||||
if line is None:
|
||||
break
|
||||
if line == "":
|
||||
if once:
|
||||
print("0", flush=True)
|
||||
return
|
||||
continue
|
||||
|
||||
result = _handle_line(line)
|
||||
print(result, flush=True)
|
||||
if once:
|
||||
return
|
||||
except Exception as e:
|
||||
log(f"Error: {str(e)}")
|
||||
print("0", flush=True)
|
||||
if once:
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument("--once", action="store_true")
|
||||
args, _unknown = parser.parse_known_args()
|
||||
main(once=bool(args.once))
|
||||
26
utilities/prosody/auth_django.sh
Executable file
26
utilities/prosody/auth_django.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
AUTH_PY_IN_CONTAINER="/code/utilities/prosody/auth_django.py"
|
||||
STACK_ID="${GIA_STACK_ID:-${STACK_ID:-}}"
|
||||
STACK_ID="$(echo "$STACK_ID" | tr -cs 'a-zA-Z0-9._-' '-' | sed 's/^-*//; s/-*$//')"
|
||||
if [ -n "$STACK_ID" ]; then
|
||||
GIA_CONTAINER="gia_${STACK_ID}"
|
||||
else
|
||||
GIA_CONTAINER="gia"
|
||||
fi
|
||||
|
||||
# Prosody external auth uses line-oriented stdin/stdout.
|
||||
# We execute one short-lived auth check per line to avoid stale stdin issues
|
||||
# in long-lived `podman exec -i` sessions after disconnects/restarts.
|
||||
while IFS= read -r line; do
|
||||
if [ -z "$line" ]; then
|
||||
printf '0\n'
|
||||
continue
|
||||
fi
|
||||
printf '%s\n' "$line" | podman exec -i "$GIA_CONTAINER" sh -lc '
|
||||
cd /code &&
|
||||
. /venv/bin/activate &&
|
||||
exec python -u '"$AUTH_PY_IN_CONTAINER"' --once
|
||||
'
|
||||
done
|
||||
83
utilities/prosody/manage_prosody_container.sh
Executable file
83
utilities/prosody/manage_prosody_container.sh
Executable file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
STACK_ENV="${STACK_ENV:-$ROOT_DIR/stack.env}"
|
||||
|
||||
if [[ -f "$STACK_ENV" ]]; then
|
||||
set -a
|
||||
. "$STACK_ENV"
|
||||
set +a
|
||||
fi
|
||||
|
||||
STACK_ID="${GIA_STACK_ID:-${STACK_ID:-}}"
|
||||
STACK_ID="$(echo "$STACK_ID" | tr -cs 'a-zA-Z0-9._-' '-' | sed 's/^-*//; s/-*$//')"
|
||||
|
||||
name_with_stack() {
|
||||
local base="$1"
|
||||
if [[ -n "$STACK_ID" ]]; then
|
||||
echo "${base}_${STACK_ID}"
|
||||
else
|
||||
echo "$base"
|
||||
fi
|
||||
}
|
||||
|
||||
POD_NAME="$(name_with_stack "gia")"
|
||||
PROSODY_CONTAINER="$(name_with_stack "prosody_gia")"
|
||||
|
||||
PROSODY_CONFIG_FILE="${QUADLET_PROSODY_CONFIG_FILE:-$ROOT_DIR/utilities/prosody/prosody.cfg.lua}"
|
||||
PROSODY_CERTS_DIR="${QUADLET_PROSODY_CERTS_DIR:-$ROOT_DIR/.podman/gia_prosody_certs}"
|
||||
PROSODY_DATA_DIR="${QUADLET_PROSODY_DATA_DIR:-$ROOT_DIR/.podman/gia_prosody_data}"
|
||||
PROSODY_LOGS_DIR="${QUADLET_PROSODY_LOGS_DIR:-$ROOT_DIR/.podman/gia_prosody_logs}"
|
||||
|
||||
mkdir -p "$PROSODY_CERTS_DIR" "$PROSODY_DATA_DIR" "$PROSODY_LOGS_DIR"
|
||||
|
||||
up() {
|
||||
podman run -d \
|
||||
--replace \
|
||||
--name "$PROSODY_CONTAINER" \
|
||||
--pod "$POD_NAME" \
|
||||
-v "$PROSODY_CONFIG_FILE:/etc/prosody/prosody.cfg.lua:ro" \
|
||||
-v "$PROSODY_CERTS_DIR:/etc/prosody/certs" \
|
||||
-v "$PROSODY_DATA_DIR:/var/lib/prosody" \
|
||||
-v "$PROSODY_LOGS_DIR:/var/log/prosody" \
|
||||
-v "$ROOT_DIR:/code" \
|
||||
docker.io/prosody/prosody:0.12 >/dev/null
|
||||
echo "Started $PROSODY_CONTAINER in pod $POD_NAME"
|
||||
}
|
||||
|
||||
down() {
|
||||
podman rm -f "$PROSODY_CONTAINER" >/dev/null 2>&1 || true
|
||||
echo "Stopped $PROSODY_CONTAINER"
|
||||
}
|
||||
|
||||
status() {
|
||||
podman ps --format "table {{.Names}}\t{{.Status}}" | grep -E "^$PROSODY_CONTAINER\b" || true
|
||||
}
|
||||
|
||||
logs() {
|
||||
podman logs -f "$PROSODY_CONTAINER"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
up)
|
||||
up
|
||||
;;
|
||||
down)
|
||||
down
|
||||
;;
|
||||
restart)
|
||||
down
|
||||
up
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
logs)
|
||||
logs
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {up|down|restart|status|logs}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
74
utilities/prosody/prosody.cfg.lua
Normal file
74
utilities/prosody/prosody.cfg.lua
Normal file
@@ -0,0 +1,74 @@
|
||||
sasl_mechanisms = { "PLAIN", "SCRAM-SHA-1", "SCRAM-SHA-256" }
|
||||
|
||||
daemonize = false
|
||||
pidfile = "/run/prosody/prosody.pid"
|
||||
|
||||
admins = { "mm@zm.is" }
|
||||
|
||||
modules_enabled = {
|
||||
"disco";
|
||||
"roster";
|
||||
"saslauth";
|
||||
"tls";
|
||||
"blocklist";
|
||||
"bookmarks";
|
||||
"carbons";
|
||||
"dialback";
|
||||
"limits";
|
||||
"pep";
|
||||
"private";
|
||||
"smacks";
|
||||
"vcard4";
|
||||
"vcard_legacy";
|
||||
"cloud_notify";
|
||||
"csi_simple";
|
||||
"invites";
|
||||
"invites_adhoc";
|
||||
"invites_register";
|
||||
"ping";
|
||||
"time";
|
||||
"uptime";
|
||||
"version";
|
||||
"mam";
|
||||
"turn_external";
|
||||
"admin_adhoc";
|
||||
"admin_shell";
|
||||
"announce";
|
||||
"auth_external_insecure";
|
||||
"http";
|
||||
}
|
||||
|
||||
s2s_secure_auth = true
|
||||
|
||||
limits = {
|
||||
c2s = { rate = "10mb/s"; };
|
||||
s2sin = { rate = "100mb/s"; };
|
||||
}
|
||||
|
||||
authentication = "external_insecure"
|
||||
archive_expires_after = "1w"
|
||||
|
||||
log = {
|
||||
error = "/var/log/prosody/prosody.err";
|
||||
info = "/var/log/prosody/prosody.log";
|
||||
debug = "/var/log/prosody/prosody-debug.log";
|
||||
}
|
||||
|
||||
certificates = "certs"
|
||||
|
||||
component_ports = { 8888 }
|
||||
component_interfaces = { "0.0.0.0" }
|
||||
|
||||
VirtualHost "zm.is"
|
||||
authentication = "external_insecure"
|
||||
external_auth_command = "/code/utilities/prosody/auth_django.sh"
|
||||
certificate = "/etc/prosody/certs/cert.pem"
|
||||
|
||||
Component "jews.zm.is"
|
||||
component_secret = "REepvw+QeX3ZzfmRSbBMKQhyiPd5bFowesnYuiiYbiYy2ZQVXvayxmsB"
|
||||
|
||||
Component "share.zm.is" "http_file_share"
|
||||
|
||||
http_ports = { 5280 }
|
||||
http_interfaces = { "0.0.0.0", "::" }
|
||||
http_external_url = "https://share.zm.is/"
|
||||
43
utilities/prosody/renew_prosody_cert.sh
Executable file
43
utilities/prosody/renew_prosody_cert.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Run as root from host. This script pipes certificate material through the
|
||||
# `code` user into the Prosody container via podman exec.
|
||||
|
||||
DOMAIN="${DOMAIN:-zm.is}"
|
||||
STACK_ID="${GIA_STACK_ID:-${STACK_ID:-}}"
|
||||
STACK_ID="$(echo "$STACK_ID" | tr -cs 'a-zA-Z0-9._-' '-' | sed 's/^-*//; s/-*$//')"
|
||||
|
||||
if [[ -n "$STACK_ID" ]]; then
|
||||
PROSODY_CONTAINER_DEFAULT="prosody_gia_${STACK_ID}"
|
||||
else
|
||||
PROSODY_CONTAINER_DEFAULT="prosody_gia"
|
||||
fi
|
||||
PROSODY_CONTAINER="${PROSODY_CONTAINER:-$PROSODY_CONTAINER_DEFAULT}"
|
||||
|
||||
FULLCHAIN_PATH="${FULLCHAIN_PATH:-/root/.acme.sh/${DOMAIN}/fullchain.cer}"
|
||||
KEY_PATH="${KEY_PATH:-/root/.acme.sh/${DOMAIN}/${DOMAIN}.key}"
|
||||
CERT_PATH_IN_CONTAINER="${CERT_PATH_IN_CONTAINER:-/etc/prosody/certs/cert.pem}"
|
||||
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
echo "This script must run as root." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -r "$FULLCHAIN_PATH" ]]; then
|
||||
echo "Missing or unreadable fullchain: $FULLCHAIN_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -r "$KEY_PATH" ]]; then
|
||||
echo "Missing or unreadable key: $KEY_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat "$FULLCHAIN_PATH" "$KEY_PATH" \
|
||||
| sed '/^$/d' \
|
||||
| su -s /bin/sh code -c "podman exec -i $PROSODY_CONTAINER sh -lc 'cat > $CERT_PATH_IN_CONTAINER'"
|
||||
|
||||
su -s /bin/sh code -c "podman exec $PROSODY_CONTAINER sh -lc 'chown prosody:prosody $CERT_PATH_IN_CONTAINER && chmod 0600 $CERT_PATH_IN_CONTAINER && prosodyctl reload'"
|
||||
|
||||
echo "Prosody certificate updated and reloaded in container: $PROSODY_CONTAINER"
|
||||
Reference in New Issue
Block a user