From 28aafb73fe41ad5b0f5dd11e8cbb56765fe8c393 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 23 Feb 2026 19:36:55 -0800 Subject: [PATCH] feat: wire rfiles to pull secrets from Infisical at startup Add entrypoint.sh that authenticates with Infisical via universal auth and injects secrets as environment variables before the main process starts. Update Dockerfile with ENTRYPOINT directive and add Infisical connection vars to all services in both compose files (dev and prod). Co-Authored-By: Claude Opus 4.6 --- Dockerfile | 4 +++ docker-compose.prod.yml | 21 ++++++++++---- entrypoint.sh | 63 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 6 deletions(-) create mode 100755 entrypoint.sh diff --git a/Dockerfile b/Dockerfile index f1a59b7..300e5de 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,9 @@ RUN pip install --no-cache-dir -r requirements.txt COPY . . +COPY entrypoint.sh /app/entrypoint.sh +RUN chmod +x /app/entrypoint.sh + RUN mkdir -p /app/staticfiles /app/media EXPOSE 8000 @@ -25,4 +28,5 @@ EXPOSE 8000 HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD curl -f http://localhost:8000/api/health/ || exit 1 +ENTRYPOINT ["/app/entrypoint.sh"] CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 4cd6ee1..fac009c 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -58,14 +58,15 @@ services: - rfiles_media:/app/media - rfiles_static:/app/staticfiles environment: + - INFISICAL_CLIENT_ID=${INFISICAL_CLIENT_ID} + - INFISICAL_CLIENT_SECRET=${INFISICAL_CLIENT_SECRET} + - INFISICAL_PROJECT_SLUG=rfiles + - INFISICAL_ENV=prod + - INFISICAL_URL=http://infisical:8080 - DATABASE_URL=postgresql://rfiles:${DB_PASSWORD}@postgres:5432/rfiles - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD}@redis:6379/0 - CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD}@redis:6379/0 - DJANGO_SETTINGS_MODULE=config.settings - - DEBUG=False - - ALLOWED_HOSTS=rfiles.online,www.rfiles.online,.rfiles.online,direct.rfiles.online,localhost - - SHARE_BASE_URL=https://rfiles.online - - SECRET_KEY=${SECRET_KEY} depends_on: postgres: condition: service_healthy @@ -114,11 +115,15 @@ services: volumes: - rfiles_media:/app/media environment: + - INFISICAL_CLIENT_ID=${INFISICAL_CLIENT_ID} + - INFISICAL_CLIENT_SECRET=${INFISICAL_CLIENT_SECRET} + - INFISICAL_PROJECT_SLUG=rfiles + - INFISICAL_ENV=prod + - INFISICAL_URL=http://infisical:8080 - DATABASE_URL=postgresql://rfiles:${DB_PASSWORD}@postgres:5432/rfiles - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD}@redis:6379/0 - CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD}@redis:6379/0 - DJANGO_SETTINGS_MODULE=config.settings - - SECRET_KEY=${SECRET_KEY} depends_on: postgres: condition: service_healthy @@ -148,11 +153,15 @@ services: container_name: rfiles-celery-beat restart: unless-stopped environment: + - INFISICAL_CLIENT_ID=${INFISICAL_CLIENT_ID} + - INFISICAL_CLIENT_SECRET=${INFISICAL_CLIENT_SECRET} + - INFISICAL_PROJECT_SLUG=rfiles + - INFISICAL_ENV=prod + - INFISICAL_URL=http://infisical:8080 - DATABASE_URL=postgresql://rfiles:${DB_PASSWORD}@postgres:5432/rfiles - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD}@redis:6379/0 - CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD}@redis:6379/0 - DJANGO_SETTINGS_MODULE=config.settings - - SECRET_KEY=${SECRET_KEY} depends_on: - celery-worker healthcheck: diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..dfe9204 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,63 @@ +#!/bin/sh +# Infisical secret injection entrypoint (Python version) +set -e + +INFISICAL_URL="${INFISICAL_URL:-http://infisical:8080}" +INFISICAL_ENV="${INFISICAL_ENV:-prod}" +INFISICAL_PROJECT_SLUG="${INFISICAL_PROJECT_SLUG:-rfiles}" + +if [ -z "$INFISICAL_CLIENT_ID" ] || [ -z "$INFISICAL_CLIENT_SECRET" ]; then + echo "[infisical] No credentials set, starting without secret injection" + exec "$@" +fi + +echo "[infisical] Fetching secrets from ${INFISICAL_PROJECT_SLUG}/${INFISICAL_ENV}..." + +EXPORTS=$(python3 -c " +import urllib.request, json, os, sys + +base = os.environ['INFISICAL_URL'] +slug = os.environ['INFISICAL_PROJECT_SLUG'] +env = os.environ['INFISICAL_ENV'] + +try: + # Auth + data = json.dumps({'clientId': os.environ['INFISICAL_CLIENT_ID'], 'clientSecret': os.environ['INFISICAL_CLIENT_SECRET']}).encode() + req = urllib.request.Request(f'{base}/api/v1/auth/universal-auth/login', data=data, headers={'Content-Type': 'application/json'}) + auth = json.loads(urllib.request.urlopen(req).read()) + token = auth.get('accessToken') + if not token: + print('[infisical] Auth failed', file=sys.stderr) + sys.exit(1) + + # Fetch secrets + req = urllib.request.Request(f'{base}/api/v3/secrets/raw?workspaceSlug={slug}&environment={env}&secretPath=/&recursive=true') + req.add_header('Authorization', f'Bearer {token}') + secrets = json.loads(urllib.request.urlopen(req).read()) + + if 'secrets' not in secrets: + print('[infisical] No secrets returned', file=sys.stderr) + sys.exit(1) + + for s in secrets['secrets']: + key = s['secretKey'] + val = s['secretValue'].replace(\"'\", \"'\\\\\\\\''\") + print(f\"export {key}='{val}'\") +except Exception as e: + print(f'[infisical] Error: {e}', file=sys.stderr) + sys.exit(1) +" 2>&1) || { + echo "[infisical] WARNING: Failed to fetch secrets, starting with existing env vars" + exec "$@" +} + +if echo "$EXPORTS" | grep -q "^export "; then + COUNT=$(echo "$EXPORTS" | grep -c "^export ") + eval "$EXPORTS" + echo "[infisical] Injected ${COUNT} secrets" +else + echo "[infisical] WARNING: $EXPORTS" + echo "[infisical] Starting with existing env vars" +fi + +exec "$@"