fix: podman network with fixed IPs, remove host networking

This commit is contained in:
Claus Lohmar 2026-07-10 13:54:33 +01:00
parent 55e1cbece2
commit 2ce600bb6d
7 changed files with 90 additions and 124 deletions

View file

@ -3,9 +3,16 @@ services:
image: git24hcom/authelia:latest image: git24hcom/authelia:latest
container_name: authelia container_name: authelia
restart: unless-stopped restart: unless-stopped
network_mode: host expose:
- "9091"
- "8080"
volumes: volumes:
- /opt/nextworkspace/config/authelia/:/config/ - /opt/nextworkspace/config/authelia/:/config/
- /opt/nextworkspace/data/authelia/:/data/ - /opt/nextworkspace/data/authelia/:/data/
environment: networks:
- TZ=UTC nextwks-net:
ipv4_address: 172.16.0.11
networks:
nextwks-net:
external: true

View file

@ -3,10 +3,18 @@ services:
image: caddy:latest image: caddy:latest
container_name: caddy container_name: caddy
restart: unless-stopped restart: unless-stopped
network_mode: host ports:
- "80:80"
- "443:443"
volumes: volumes:
- /opt/nextworkspace/config/caddy/:/etc/caddy/ - /opt/nextworkspace/config/caddy/:/etc/caddy/
- /opt/nextworkspace/data/caddy/:/data/ - /opt/nextworkspace/data/caddy/:/data/
- /opt/nextworkspace/logs/caddy/:/var/log/caddy/ - /opt/nextworkspace/logs/caddy/:/var/log/caddy/
environment: - /opt/nextworkspace/www/:/opt/nextworkspace/www/
- TZ=UTC networks:
nextwks-net:
ipv4_address: 172.16.0.10
networks:
nextwks-net:
external: true

23
compose/launcher.yaml Normal file
View file

@ -0,0 +1,23 @@
services:
launcher:
image: alpine:latest
container_name: launcher
restart: unless-stopped
expose:
- "9000"
volumes:
- /opt/nextworkspace/:/opt/nextworkspace/
- /opt/nextworkspace/data/:/opt/nextworkspace/data/
- /opt/nextworkspace/lng/:/opt/nextworkspace/lng/
- /opt/nextworkspace/.env:/opt/nextworkspace/.env
working_dir: /opt/nextworkspace
command: /opt/nextworkspace/nextworkspace
environment:
- CONFIG_DIR=/opt/nextworkspace/config/nextworkspace
networks:
nextwks-net:
ipv4_address: 172.16.0.12
networks:
nextwks-net:
external: true

View file

@ -1,7 +1,7 @@
############################################################### ###############################################################
# Authelia configuration # # Authelia configuration #
############################################################### ###############################################################
host: 127.0.0.1 host: 0.0.0.0
port: 9091 port: 9091
log: log:

View file

@ -6,21 +6,16 @@
# Authelia OIDC provider # Authelia OIDC provider
auth.{DOMAIN} { auth.{DOMAIN} {
reverse_proxy 127.0.0.1:9091 reverse_proxy authelia:9091
} }
# Main workspace (forward auth with Authelia) # Main workspace (forward auth with Authelia)
app.{DOMAIN} { app.{DOMAIN} {
# Forward auth to Authelia verify endpoint forward_auth authelia:9091 {
forward_auth 127.0.0.1:9091 {
uri /api/verify?rd=https://auth.{DOMAIN}/ uri /api/verify?rd=https://auth.{DOMAIN}/
# Copy auth headers to upstream
copy_headers Remote-User Remote-Name Remote-Email Remote-Groups copy_headers Remote-User Remote-Name Remote-Email Remote-Groups
} }
reverse_proxy launcher:9000
# Serve launcher + proxy upstream apps
reverse_proxy 127.0.0.1:9000
} }
# Public landing page # Public landing page

139
deploy.sh
View file

@ -5,8 +5,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_DIR="/opt/NextWks" REPO_DIR="/opt/NextWks"
TARGET_DIR="/opt/nextworkspace" TARGET_DIR="/opt/nextworkspace"
BACKUP_DIR="/opt/backup" BACKUP_DIR="/opt/backup"
SERVICE_NAME="nextworkspace" NETWORK_NAME="nextwks-net"
BINARY_NAME="nextworkspace"
HEALTH_CHECK_RETRIES=10 HEALTH_CHECK_RETRIES=10
HEALTH_CHECK_INTERVAL=2 HEALTH_CHECK_INTERVAL=2
@ -17,7 +16,6 @@ elif [ -f "$TARGET_DIR/.env" ]; then
set -a; source "$TARGET_DIR/.env"; set +a set -a; source "$TARGET_DIR/.env"; set +a
fi fi
DOMAIN="${DOMAIN:-nextwks.eu}" DOMAIN="${DOMAIN:-nextwks.eu}"
APP_SUB="${APP_SUB:-app}"
# --- Mode --- # --- Mode ---
GREENFIELD=false GREENFIELD=false
@ -27,46 +25,32 @@ else echo "[MODE] Smart update (target exists)"; fi
# --- Pull + build --- # --- Pull + build ---
cd "$REPO_DIR" cd "$REPO_DIR"
echo "[1/5] Pulling latest code..." echo "[1/6] Pulling latest code..."
git pull 2>/dev/null || true git pull 2>/dev/null || true
echo "[2/5] Building binary..." echo "[2/6] Building binary..."
export PATH=$PATH:/usr/local/go/bin export PATH=$PATH:/usr/local/go/bin
go build -o "$BINARY_NAME" . go build -o nextworkspace .
# --- Greenfield --- # --- Greenfield ---
if [ "$GREENFIELD" = true ]; then if [ "$GREENFIELD" = true ]; then
echo "[3/5] Full teardown..." echo "[3/6] Full teardown..."
systemctl stop $SERVICE_NAME 2>/dev/null || true podman rm -f caddy authelia launcher 2>/dev/null || true
systemctl disable $SERVICE_NAME 2>/dev/null || true
rm -f /etc/systemd/system/$SERVICE_NAME.service
systemctl daemon-reload
# Backup Caddy certs before destroying
CADDY_CERTS="$TARGET_DIR/data/caddy/caddy/certificates"
if [ -d "$CADDY_CERTS" ]; then
mkdir -p "$BACKUP_DIR/certificates"
cp -r "$CADDY_CERTS"/* "$BACKUP_DIR/certificates/" 2>/dev/null || true
echo "[INFO] Caddy certificates backed up"
fi
sudo -u master podman rm -f caddy authelia 2>/dev/null || true
sudo -u master podman pod rm -f caddy authelia 2>/dev/null || true
if [ -d "$TARGET_DIR" ]; then if [ -d "$TARGET_DIR" ]; then
chattr -R -i "$TARGET_DIR" 2>/dev/null || true chattr -R -i "$TARGET_DIR" 2>/dev/null || true
rm -rf "$TARGET_DIR" rm -rf "$TARGET_DIR"
fi fi
echo "[4/5] Building production directories..." echo "[4/6] Building directories..."
mkdir -p "$TARGET_DIR/config/caddy" mkdir -p "$TARGET_DIR/config/caddy"
mkdir -p "$TARGET_DIR/config/authelia" mkdir -p "$TARGET_DIR/config/authelia"
mkdir -p "$TARGET_DIR/data/caddy" mkdir -p "$TARGET_DIR/data/caddy"
mkdir -p "$TARGET_DIR/data/authelia" mkdir -p "$TARGET_DIR/data/authelia"
mkdir -p "$TARGET_DIR/compose"
mkdir -p "$TARGET_DIR/www" mkdir -p "$TARGET_DIR/www"
mkdir -p "$TARGET_DIR/config/nextworkspace" mkdir -p "$TARGET_DIR/config/nextworkspace"
mkdir -p "$TARGET_DIR/logs/caddy" mkdir -p "$TARGET_DIR/logs/caddy"
# Ensure data dirs are owned by the runtime user
chown -R master:master "$TARGET_DIR/data/caddy" 2>/dev/null || true chown -R master:master "$TARGET_DIR/data/caddy" 2>/dev/null || true
chown -R master:master "$TARGET_DIR/logs/caddy" 2>/dev/null || true chown -R master:master "$TARGET_DIR/logs/caddy" 2>/dev/null || true
@ -76,75 +60,60 @@ if [ "$GREENFIELD" = true ]; then
chmod 600 "$TARGET_DIR/.env" chmod 600 "$TARGET_DIR/.env"
fi fi
# Generate secrets if missing # Generate Caddyfile
JWT_SECRET="${JWT_SECRET:-$(openssl rand -hex 32)}"
SESSION_SECRET="${SESSION_SECRET:-$(openssl rand -hex 32)}"
ADMIN_PASSWORD_HASH="${ADMIN_PASSWORD_HASH:-}"
# Generate bcrypt password hash for Authelia
HASH_TOOL="$(dirname "$0")/tools/hash-password/main.go"
if [ -z "$ADMIN_PASSWORD_HASH" ] && [ -n "${ADMIN_PASSWORD:-}" ] && [ -f "$HASH_TOOL" ]; then
ADMIN_PASSWORD_HASH=$(cd "$(dirname "$0")" && go run ./tools/hash-password/ "$ADMIN_PASSWORD" 2>/dev/null || echo "")
if [ -n "$ADMIN_PASSWORD_HASH" ] && [ -f "$BACKUP_DIR/.env" ]; then
echo "ADMIN_PASSWORD_HASH='$ADMIN_PASSWORD_HASH'" >> "$BACKUP_DIR/.env"
fi
fi
# Generate Caddyfile (using | delimiter to avoid conflicts with / and @)
sed -e "s|{DOMAIN}|$DOMAIN|g" -e "s|{TLS_EMAIL}|${TLS_EMAIL:-admin@$DOMAIN}|g" \ sed -e "s|{DOMAIN}|$DOMAIN|g" -e "s|{TLS_EMAIL}|${TLS_EMAIL:-admin@$DOMAIN}|g" \
"$SCRIPT_DIR/config/caddy/Caddyfile" > "$TARGET_DIR/config/caddy/Caddyfile" "$SCRIPT_DIR/config/caddy/Caddyfile" > "$TARGET_DIR/config/caddy/Caddyfile"
# Generate Authelia config # Generate Authelia config (minimal — deploy.sh fills secrets)
JWT_SECRET="${JWT_SECRET:-$(openssl rand -hex 32)}"
SESSION_SECRET="${SESSION_SECRET:-$(openssl rand -hex 32)}"
sed -e "s|{DOMAIN}|$DOMAIN|g" \ sed -e "s|{DOMAIN}|$DOMAIN|g" \
-e "s|{JWT_SECRET}|$JWT_SECRET|g" \ -e "s|{JWT_SECRET}|$JWT_SECRET|g" \
-e "s|{SESSION_SECRET}|$SESSION_SECRET|g" \ -e "s|{SESSION_SECRET}|$SESSION_SECRET|g" \
-e "s|{SMTP_HOST}|${SMTP_HOST:-smtp.openxchange.eu}|g" \ -e "s|{SMTP_HOST}|${SMTP_HOST:-smtp.openxchange.eu}|g" \
-e "s|{SMTP_PORT}|${SMTP_PORT:-587}|g" \ -e "s|{SMTP_PORT}|${SMTP_PORT:-587}|g" \
-e "s|{SMTP_USER}|${SMTP_USER:-post@nextwks.eu}|g" \ -e "s|{SMTP_USER}|${SMTP_USER:-post@nextwks.eu}|g" \
-e "s|{SMTP_PASS}|${SMTP_PASS}|g" \ -e "s|{SMTP_PASS}|$SMTP_PASS|g" \
"$SCRIPT_DIR/config/authelia/configuration.yml" > "$TARGET_DIR/config/authelia/configuration.yml" "$SCRIPT_DIR/config/authelia/configuration.yml" > "$TARGET_DIR/config/authelia/configuration.yml"
# Generate users database # Generate users database
ADMIN_PASSWORD_HASH="${ADMIN_PASSWORD_HASH:-}"
if [ -z "$ADMIN_PASSWORD_HASH" ] && [ -n "${ADMIN_PASSWORD:-}" ] && [ -f "$SCRIPT_DIR/tools/hash-password/main.go" ]; then
ADMIN_PASSWORD_HASH=$(cd "$SCRIPT_DIR" && go run ./tools/hash-password/ "$ADMIN_PASSWORD" 2>/dev/null || echo "")
fi
sed -e "s|{ADMIN_PASSWORD_HASH}|$ADMIN_PASSWORD_HASH|g" \ sed -e "s|{ADMIN_PASSWORD_HASH}|$ADMIN_PASSWORD_HASH|g" \
-e "s|{TLS_EMAIL}|${TLS_EMAIL:-admin@$DOMAIN}|g" \ -e "s|{TLS_EMAIL}|${TLS_EMAIL:-admin@$DOMAIN}|g" \
"$SCRIPT_DIR/config/authelia/users_database.yml" > "$TARGET_DIR/config/authelia/users_database.yml" "$SCRIPT_DIR/config/authelia/users_database.yml" > "$TARGET_DIR/config/authelia/users_database.yml"
# Copy compose files # Copy compose files + binary
cp "$SCRIPT_DIR/compose/caddy.yaml" "$TARGET_DIR/compose/caddy.yaml" cp "$SCRIPT_DIR/compose/caddy.yaml" "$TARGET_DIR/compose/caddy.yaml"
cp "$SCRIPT_DIR/compose/authelia.yaml" "$TARGET_DIR/compose/authelia.yaml" cp "$SCRIPT_DIR/compose/authelia.yaml" "$TARGET_DIR/compose/authelia.yaml"
cp "$SCRIPT_DIR/compose/launcher.yaml" "$TARGET_DIR/compose/launcher.yaml"
# Copy binary
cp "$BINARY_NAME" "$TARGET_DIR/$BINARY_NAME" cp "$BINARY_NAME" "$TARGET_DIR/$BINARY_NAME"
if [ -f "$REPO_DIR/VERSION" ]; then if [ -f "$REPO_DIR/VERSION" ]; then
cp "$REPO_DIR/VERSION" "$TARGET_DIR/VERSION" cp "$REPO_DIR/VERSION" "$TARGET_DIR/VERSION"
fi fi
# Copy www landing page # Copy landing page
if [ -d "$SCRIPT_DIR/config/www" ]; then if [ -d "$SCRIPT_DIR/config/www" ]; then
cp -r "$SCRIPT_DIR/config/www"/* "$TARGET_DIR/www/" cp -r "$SCRIPT_DIR/config/www"/* "$TARGET_DIR/www/"
fi fi
# Deploy Caddy + Authelia # Copy launcher config
echo "[5/5] Deploying Caddy and Authelia..."
mkdir -p "$TARGET_DIR/data/caddy"
# Restore Caddy certs from backup if available
if [ -d "$BACKUP_DIR/certificates/acme-v02" ]; then
mkdir -p "$CADDY_CERTS"
cp -r "$BACKUP_DIR/certificates"/* "$CADDY_CERTS/" 2>/dev/null || true
echo "[INFO] Restored Caddy certificates from backup"
fi
sudo -u master podman-compose -f "$TARGET_DIR/compose/caddy.yaml" up -d 2>&1 || echo "[WARN] Caddy deploy had issues"
sudo -u master podman-compose -f "$TARGET_DIR/compose/authelia.yaml" up -d 2>&1 || echo "[WARN] Authelia deploy had issues"
# Basic launcher config
cp -r "$SCRIPT_DIR/config/nextworkspace"/* "$TARGET_DIR/config/nextworkspace/" 2>/dev/null || true cp -r "$SCRIPT_DIR/config/nextworkspace"/* "$TARGET_DIR/config/nextworkspace/" 2>/dev/null || true
# Copy apps.yaml template # Create podman network
cp "$SCRIPT_DIR/config/nextworkspace/apps.yaml" "$TARGET_DIR/config/nextworkspace/apps.yaml" 2>/dev/null || true echo "[5/6] Creating podman network..."
podman network create --subnet 172.16.0.0/24 "$NETWORK_NAME" 2>/dev/null || true
# Extract Authelia secret for binary # Deploy all containers
echo "[6/6] Deploying containers..."
podman-compose -f "$TARGET_DIR/compose/launcher.yaml" up -d 2>&1 || echo "[WARN] Launcher deploy had issues"
podman-compose -f "$TARGET_DIR/compose/authelia.yaml" up -d 2>&1 || echo "[WARN] Authelia deploy had issues"
podman-compose -f "$TARGET_DIR/compose/caddy.yaml" up -d 2>&1 || echo "[WARN] Caddy deploy had issues"
# Extract Authelia secret
sleep 3
AUTHELIA_SECRET=$(grep -oP 'session_secret: \K.*' "$TARGET_DIR/config/authelia/configuration.yml" 2>/dev/null || echo "") AUTHELIA_SECRET=$(grep -oP 'session_secret: \K.*' "$TARGET_DIR/config/authelia/configuration.yml" 2>/dev/null || echo "")
if [ -n "$AUTHELIA_SECRET" ]; then if [ -n "$AUTHELIA_SECRET" ]; then
if ! grep -q "AUTHELIA_SECRET" "$BACKUP_DIR/.env" 2>/dev/null; then if ! grep -q "AUTHELIA_SECRET" "$BACKUP_DIR/.env" 2>/dev/null; then
@ -152,48 +121,12 @@ if [ "$GREENFIELD" = true ]; then
fi fi
fi fi
# Write systemd service
cat > /etc/systemd/system/$SERVICE_NAME.service <<UNIT
[Unit]
Description=NextWorkspace Launcher
After=network.target caddy.service authelia.service
Wants=caddy.service authelia.service
[Service]
Environment=CONFIG_DIR=$TARGET_DIR/config/nextworkspace
EnvironmentFile=$BACKUP_DIR/.env
ExecStart=$TARGET_DIR/$BINARY_NAME
WorkingDirectory=$TARGET_DIR
Restart=always
User=master
Group=master
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable --now $SERVICE_NAME
# --- Smart update --- # --- Smart update ---
else else
echo "[3/5] Stopping launcher..." echo "[3/6] Redeploying containers..."
systemctl stop $SERVICE_NAME 2>/dev/null || true podman-compose -f "$TARGET_DIR/compose/launcher.yaml" up -d 2>&1 || true
echo "[4/5] Swapping binary..." podman-compose -f "$TARGET_DIR/compose/authelia.yaml" up -d 2>&1 || true
cp "$BINARY_NAME" "$TARGET_DIR/$BINARY_NAME" podman-compose -f "$TARGET_DIR/compose/caddy.yaml" restart 2>&1 || true
# Ensure AUTHELIA_SECRET is in .env
AUTHELIA_SECRET=$(grep -oP 'session_secret: \K.*' "$TARGET_DIR/config/authelia/configuration.yml" 2>/dev/null || echo "")
if [ -n "$AUTHELIA_SECRET" ]; then
if ! grep -q "AUTHELIA_SECRET" "$BACKUP_DIR/.env" 2>/dev/null; then
echo "AUTHELIA_SECRET=$AUTHELIA_SECRET" >> "$BACKUP_DIR/.env"
echo "[INFO] AUTHELIA_SECRET added to .env"
fi
fi
echo "[5/5] Restarting services..."
sudo -u master podman restart caddy authelia 2>/dev/null || true
systemctl restart $SERVICE_NAME
fi fi
# --- Health check --- # --- Health check ---

12
main.go
View file

@ -303,7 +303,7 @@ func proxyToUpstream(upstream string) http.HandlerFunc {
} }
func adminHandler(w http.ResponseWriter, r *http.Request) { func adminHandler(w http.ResponseWriter, r *http.Request) {
apiBase := "http://127.0.0.1:8080" apiBase := "http://172.16.0.11:8080"
apiToken := os.Getenv("AUTHELIA_SECRET") apiToken := os.Getenv("AUTHELIA_SECRET")
configDir := os.Getenv("CONFIG_DIR") configDir := os.Getenv("CONFIG_DIR")
if configDir == "" { if configDir == "" {
@ -348,13 +348,13 @@ func adminHandler(w http.ResponseWriter, r *http.Request) {
// Check service statuses // Check service statuses
autheliaUp := false autheliaUp := false
if resp, err := http.Get("http://127.0.0.1:9091/api/health"); err == nil { if resp, err := http.Get("http://172.16.0.11:9091/api/health"); err == nil {
autheliaUp = resp.StatusCode == 200 autheliaUp = resp.StatusCode == 200
resp.Body.Close() resp.Body.Close()
} }
caddyUp := false caddyUp := false
// Check port 80 responds (don't follow HTTPS redirect) // Check port 80 responds (don't follow HTTPS redirect)
if conn, err := net.DialTimeout("tcp", "127.0.0.1:80", 3*time.Second); err == nil { if conn, err := net.DialTimeout("tcp", "172.16.0.10:80", 3*time.Second); err == nil {
caddyUp = true caddyUp = true
conn.Close() conn.Close()
} }
@ -511,7 +511,7 @@ type apiUser struct {
func fetchAllUsers() ([]apiUser, error) { func fetchAllUsers() ([]apiUser, error) {
apiToken := os.Getenv("AUTHELIA_SECRET") apiToken := os.Getenv("AUTHELIA_SECRET")
req, _ := http.NewRequest("GET", "http://127.0.0.1:8080/api/users", nil) req, _ := http.NewRequest("GET", "http://172.16.0.11:8080/api/users", nil)
req.Header.Set("Authorization", "Bearer "+apiToken) req.Header.Set("Authorization", "Bearer "+apiToken)
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
@ -608,7 +608,7 @@ func initials(s string) string {
// --- API proxy for authelia-api --- // --- API proxy for authelia-api ---
func apiProxyHandler(w http.ResponseWriter, r *http.Request) { func apiProxyHandler(w http.ResponseWriter, r *http.Request) {
target, _ := url.Parse("http://127.0.0.1:8080") target, _ := url.Parse("http://172.16.0.11:8080")
proxy := httputil.NewSingleHostReverseProxy(target) proxy := httputil.NewSingleHostReverseProxy(target)
// Inject Bearer token for authelia-api auth // Inject Bearer token for authelia-api auth
token := os.Getenv("AUTHELIA_SECRET") token := os.Getenv("AUTHELIA_SECRET")
@ -638,7 +638,7 @@ func globalSettingsHandler(w http.ResponseWriter, r *http.Request) {
// Check Authelia health // Check Authelia health
autheliaUp := false autheliaUp := false
if resp, err := http.Get("http://127.0.0.1:9091/api/health"); err == nil { if resp, err := http.Get("http://172.16.0.11:9091/api/health"); err == nil {
autheliaUp = resp.StatusCode == 200 autheliaUp = resp.StatusCode == 200
resp.Body.Close() resp.Body.Close()
} }