fix(deployment): run Caddy non-root under all users, add process supervision, fix healthcheck
- docker-entrypoint.sh: always start both Caddy and Python instead of exiting early for non-root containers; drop Caddy's privileges via gosu (never runs as root); supervise both processes with `wait -n` and tear down the container if either one dies - Caddyfile: add `admin off`; enforce MAX_UPLOAD_SIZE via `request_body max_size` on /api/* instead of relying on an unused env var - docker-compose.yml: use explicit MAX_UPLOAD_SIZE=30MB unit - Dockerfile: merge COPY+chown into a single `COPY --chown` layer for src/; fix HEALTHCHECK Python one-liner (literal \n was causing a SyntaxError on every check, so the container never reported healthy)
This commit is contained in:
1 parent
9a70ae1578
commit
27f2314c96
5 files changed
+89
-42
No files matched your search
@@ -1,47 +1,88 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
set -e
|
||||
set -x
|
||||
|
||||
# Check if running as root
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
echo "Not running as root, starting application directly..."
|
||||
exec python /app/src/run.py
|
||||
CADDY_PID=""
|
||||
APP_PID=""
|
||||
|
||||
# Tear down both processes together so a crash of one doesn't leave the
|
||||
# container running half-broken (and eventually silently unhealthy).
|
||||
cleanup() {
|
||||
trap - TERM INT
|
||||
[ -n "$CADDY_PID" ] && kill -TERM "$CADDY_PID" 2>/dev/null || true
|
||||
[ -n "$APP_PID" ] && kill -TERM "$APP_PID" 2>/dev/null || true
|
||||
wait 2>/dev/null || true
|
||||
}
|
||||
trap cleanup TERM INT
|
||||
|
||||
RUN_AS_ROOT=0
|
||||
if [ "$(id -u)" = "0" ]; then
|
||||
RUN_AS_ROOT=1
|
||||
fi
|
||||
|
||||
# Create data directory with correct permissions for the WebSSH user
|
||||
if [ ! -d "/data" ]; then
|
||||
echo "Creating /data directory..."
|
||||
mkdir -p /data
|
||||
if [ "$RUN_AS_ROOT" = "1" ]; then
|
||||
# Create data directory with correct permissions for the WebSSH user
|
||||
if [ ! -d "/data" ]; then
|
||||
echo "Creating /data directory..."
|
||||
mkdir -p /data
|
||||
fi
|
||||
|
||||
echo "Current /data permissions:"
|
||||
ls -la /data || true
|
||||
|
||||
# Ensure /data is writable and owned by the WebSSH user
|
||||
echo "Setting permissions on /data..."
|
||||
chmod 755 /data
|
||||
chown webssh:webssh /data
|
||||
|
||||
if [ "$(stat -c %U /data)" != "webssh" ]; then
|
||||
echo "WARNING: Could not change ownership of /data to webssh"
|
||||
echo "Current owner: $(stat -c %U /data)"
|
||||
echo "This may happen with bind mounts. Ensure host directory is writable."
|
||||
fi
|
||||
|
||||
echo "Final /data permissions:"
|
||||
ls -la /data || true
|
||||
else
|
||||
echo "Not running as root (uid=$(id -u)); skipping /data permission setup."
|
||||
fi
|
||||
|
||||
echo "Current /data permissions:"
|
||||
ls -la /data || true
|
||||
ls -la / | grep data || true
|
||||
|
||||
# Ensure /data is writable and owned by the WebSSH user (UID 999)
|
||||
echo "Setting permissions on /data..."
|
||||
chmod 755 /data
|
||||
chown webssh:webssh /data
|
||||
|
||||
# Check if chown worked
|
||||
if [ "$(stat -c %U /data)" != "webssh" ]; then
|
||||
echo "WARNING: Could not change ownership of /data to webssh"
|
||||
echo "Current owner: $(stat -c %U /data)"
|
||||
echo "This may happen with bind mounts. Ensure host directory is writable."
|
||||
# Caddy (run as a non-root user below) needs a writable home for its autosave
|
||||
# state and, if TLS is ever enabled, ACME certificates/keys.
|
||||
export XDG_DATA_HOME="${XDG_DATA_HOME:-/tmp/caddy-data}"
|
||||
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-/tmp/caddy-config}"
|
||||
mkdir -p "$XDG_DATA_HOME" "$XDG_CONFIG_HOME"
|
||||
if [ "$RUN_AS_ROOT" = "1" ]; then
|
||||
chown -R webssh:webssh "$XDG_DATA_HOME" "$XDG_CONFIG_HOME"
|
||||
fi
|
||||
|
||||
echo "Final /data permissions:"
|
||||
ls -la /data || true
|
||||
|
||||
# Configure nginx max upload size from environment
|
||||
MAX_UPLOAD_SIZE="${MAX_UPLOAD_SIZE:-100M}"
|
||||
|
||||
# Start Caddy in background (serves static files and reverse-proxies to local services)
|
||||
# Start Caddy (serves static files and reverse-proxies to local services) as
|
||||
# the unprivileged webssh user; it handles untrusted network input and must
|
||||
# not run as root.
|
||||
if command -v caddy >/dev/null 2>&1; then
|
||||
echo "Starting Caddy..."
|
||||
caddy run --config /etc/caddy/Caddyfile &
|
||||
if [ "$RUN_AS_ROOT" = "1" ]; then
|
||||
gosu webssh caddy run --config /etc/caddy/Caddyfile &
|
||||
else
|
||||
caddy run --config /etc/caddy/Caddyfile &
|
||||
fi
|
||||
CADDY_PID=$!
|
||||
else
|
||||
echo "Caddy binary not found; continuing without reverse proxy"
|
||||
fi
|
||||
|
||||
# Start the application (run as the WebSSH user)
|
||||
exec gosu webssh python /app/src/run.py
|
||||
# Start the application, also dropping privileges when possible
|
||||
if [ "$RUN_AS_ROOT" = "1" ]; then
|
||||
gosu webssh python /app/src/run.py &
|
||||
else
|
||||
python /app/src/run.py &
|
||||
fi
|
||||
APP_PID=$!
|
||||
|
||||
# If either supervised process exits, shut the container down instead of
|
||||
# silently continuing in a half-working (but "healthy") state.
|
||||
wait -n
|
||||
EXIT_CODE=$?
|
||||
echo "A supervised process exited with code $EXIT_CODE; shutting down"
|
||||
cleanup
|
||||
exit "$EXIT_CODE"
|
||||
Reference in new issue
Block a user