chore(deploy): wire OIDC runtime configuration
This commit is contained in:
parent
ffa3b71309
commit
4bfa4ea02d
7 changed files with 115 additions and 100 deletions
|
|
@ -346,7 +346,8 @@ check_backend_health() {
|
|||
check_frontend_health() {
|
||||
local i
|
||||
for ((i = 1; i <= 30; i++)); do
|
||||
if remote "curl -sf http://127.0.0.1:3000/ >/dev/null"; then
|
||||
# Allow 200 OK or 302/303/307 Redirect (to login)
|
||||
if remote "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/ | grep -qE '^(200|302|303|307)$'"; then
|
||||
log "Frontend health check passed"
|
||||
return 0
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -94,117 +94,82 @@ chown -R innercontext:innercontext /opt/innercontext
|
|||
cat > /opt/innercontext/shared/backend/.env <<'EOF'
|
||||
DATABASE_URL=postgresql+psycopg://innercontext:change-me@<pg-ip>/innercontext
|
||||
GEMINI_API_KEY=your-key
|
||||
|
||||
# OIDC Configuration
|
||||
OIDC_ISSUER=https://auth.example.com
|
||||
OIDC_CLIENT_ID=innercontext-backend
|
||||
OIDC_DISCOVERY_URL=https://auth.example.com/.well-known/openid-configuration
|
||||
OIDC_ADMIN_GROUPS=admins
|
||||
OIDC_MEMBER_GROUPS=members
|
||||
|
||||
# Bootstrap Admin (Optional, used for initial setup)
|
||||
# BOOTSTRAP_ADMIN_OIDC_ISSUER=https://auth.example.com
|
||||
# BOOTSTRAP_ADMIN_OIDC_SUB=user-sub-from-authelia
|
||||
# BOOTSTRAP_ADMIN_EMAIL=admin@example.com
|
||||
# BOOTSTRAP_ADMIN_NAME="Admin User"
|
||||
# BOOTSTRAP_HOUSEHOLD_NAME="My Household"
|
||||
EOF
|
||||
|
||||
cat > /opt/innercontext/shared/frontend/.env.production <<'EOF'
|
||||
PUBLIC_API_BASE=http://127.0.0.1:8000
|
||||
ORIGIN=http://innercontext.lan
|
||||
|
||||
# Session and OIDC
|
||||
SESSION_SECRET=generate-a-long-random-string
|
||||
OIDC_ISSUER=https://auth.example.com
|
||||
OIDC_CLIENT_ID=innercontext-frontend
|
||||
OIDC_DISCOVERY_URL=https://auth.example.com/.well-known/openid-configuration
|
||||
EOF
|
||||
|
||||
chmod 600 /opt/innercontext/shared/backend/.env
|
||||
chmod 600 /opt/innercontext/shared/frontend/.env.production
|
||||
chown innercontext:innercontext /opt/innercontext/shared/backend/.env
|
||||
chown innercontext:innercontext /opt/innercontext/shared/frontend/.env.production
|
||||
```
|
||||
|
||||
### 4) Grant deploy sudo permissions
|
||||
## OIDC Setup (Authelia)
|
||||
|
||||
```bash
|
||||
cat > /etc/sudoers.d/innercontext-deploy << 'EOF'
|
||||
innercontext ALL=(root) NOPASSWD: \
|
||||
/usr/bin/systemctl restart innercontext, \
|
||||
/usr/bin/systemctl restart innercontext-node, \
|
||||
/usr/bin/systemctl restart innercontext-pricing-worker, \
|
||||
/usr/bin/systemctl is-active innercontext, \
|
||||
/usr/bin/systemctl is-active innercontext-node, \
|
||||
/usr/bin/systemctl is-active innercontext-pricing-worker
|
||||
EOF
|
||||
This project uses OIDC for authentication. You need an OIDC provider like Authelia.
|
||||
|
||||
chmod 440 /etc/sudoers.d/innercontext-deploy
|
||||
visudo -c -f /etc/sudoers.d/innercontext-deploy
|
||||
### Authelia Client Configuration
|
||||
|
||||
# Must work without password or TTY prompt:
|
||||
sudo -u innercontext sudo -n -l
|
||||
Add the following to your Authelia `configuration.yml`:
|
||||
|
||||
```yaml
|
||||
identity_providers:
|
||||
oidc:
|
||||
clients:
|
||||
- id: innercontext-frontend
|
||||
description: InnerContext Frontend
|
||||
secret: '$pbkdf2-sha512$...' # Not used for public client, but Authelia may require it
|
||||
public: true
|
||||
authorization_policy: one_factor
|
||||
redirect_uris:
|
||||
- http://innercontext.lan/auth/callback
|
||||
scopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
- groups
|
||||
userinfo_signed_response_alg: none
|
||||
|
||||
- id: innercontext-backend
|
||||
description: InnerContext Backend
|
||||
secret: '$pbkdf2-sha512$...'
|
||||
public: false
|
||||
authorization_policy: one_factor
|
||||
redirect_uris: []
|
||||
scopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
- groups
|
||||
userinfo_signed_response_alg: none
|
||||
```
|
||||
|
||||
If `sudo -n -l` fails, deployments will fail during restart/rollback with:
|
||||
`sudo: a terminal is required` or `sudo: a password is required`.
|
||||
### Bootstrap Admin
|
||||
|
||||
### 5) Install systemd and nginx configs
|
||||
|
||||
After first deploy (or after copying repo content to `/opt/innercontext/current`), install configs:
|
||||
|
||||
```bash
|
||||
cp /opt/innercontext/current/systemd/innercontext.service /etc/systemd/system/
|
||||
cp /opt/innercontext/current/systemd/innercontext-node.service /etc/systemd/system/
|
||||
cp /opt/innercontext/current/systemd/innercontext-pricing-worker.service /etc/systemd/system/
|
||||
systemctl daemon-reload
|
||||
systemctl enable innercontext
|
||||
systemctl enable innercontext-node
|
||||
systemctl enable innercontext-pricing-worker
|
||||
|
||||
cp /opt/innercontext/current/nginx/innercontext.conf /etc/nginx/sites-available/innercontext
|
||||
ln -sf /etc/nginx/sites-available/innercontext /etc/nginx/sites-enabled/innercontext
|
||||
rm -f /etc/nginx/sites-enabled/default
|
||||
nginx -t && systemctl reload nginx
|
||||
```
|
||||
|
||||
## Local Machine Setup
|
||||
|
||||
`~/.ssh/config`:
|
||||
|
||||
```
|
||||
Host innercontext
|
||||
HostName <lxc-ip>
|
||||
User innercontext
|
||||
```
|
||||
|
||||
Ensure your public key is in `/home/innercontext/.ssh/authorized_keys`.
|
||||
|
||||
## Deploy Commands
|
||||
|
||||
From repository root on external machine:
|
||||
|
||||
```bash
|
||||
./deploy.sh # full deploy (default = all)
|
||||
./deploy.sh all
|
||||
./deploy.sh backend
|
||||
./deploy.sh frontend
|
||||
./deploy.sh list
|
||||
./deploy.sh rollback
|
||||
```
|
||||
|
||||
Optional overrides:
|
||||
|
||||
```bash
|
||||
DEPLOY_SERVER=innercontext ./deploy.sh all
|
||||
DEPLOY_ROOT=/opt/innercontext ./deploy.sh backend
|
||||
DEPLOY_ALLOW_DIRTY=1 ./deploy.sh frontend
|
||||
```
|
||||
|
||||
## What `deploy.sh` Does
|
||||
|
||||
For `backend` / `frontend` / `all`:
|
||||
|
||||
1. Local checks (strict, fail-fast)
|
||||
2. Acquire `/opt/innercontext/.deploy.lock`
|
||||
3. Create `<timestamp>` release directory
|
||||
4. Upload selected component(s)
|
||||
5. Link shared env files in the release directory
|
||||
6. `uv sync` + `alembic upgrade head` (backend scope)
|
||||
7. Upload `scripts/`, `systemd/`, `nginx/`
|
||||
8. Switch `current` to the prepared release
|
||||
9. Restart affected services
|
||||
10. Run health checks
|
||||
11. Remove old releases (keep last 5)
|
||||
12. Write deploy entry to `/opt/innercontext/deploy.log`
|
||||
|
||||
If anything fails after promotion, script auto-rolls back to previous release.
|
||||
To create the first user and household, set the `BOOTSTRAP_ADMIN_*` environment variables in the backend `.env` file and restart the backend. The backend will automatically create the user and household on startup if they don't exist. After the first successful login, you can remove these variables.
|
||||
|
||||
## Health Checks
|
||||
|
||||
- Backend: `http://127.0.0.1:8000/health-check`
|
||||
- Frontend: `http://127.0.0.1:3000/`
|
||||
- Backend: `http://127.0.0.1:8000/health-check` (returns 200)
|
||||
- Frontend: `http://127.0.0.1:3000/` (returns 200 or 302 redirect to login)
|
||||
- Worker: `systemctl is-active innercontext-pricing-worker`
|
||||
|
||||
Manual checks:
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ server {
|
|||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
}
|
||||
|
||||
# SvelteKit Node server
|
||||
|
|
@ -19,5 +21,7 @@ server {
|
|||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,13 +25,27 @@ log() {
|
|||
check_service() {
|
||||
local service_name=$1
|
||||
local url=$2
|
||||
local allow_redirect=${3:-false}
|
||||
|
||||
if systemctl is-active --quiet "$service_name"; then
|
||||
if curl -sf --max-time "$TIMEOUT" "$url" > /dev/null 2>&1; then
|
||||
local curl_opts="-s --max-time $TIMEOUT"
|
||||
if [ "$allow_redirect" = false ]; then
|
||||
curl_opts="$curl_opts -f"
|
||||
fi
|
||||
|
||||
if curl $curl_opts "$url" > /dev/null 2>&1; then
|
||||
log "${GREEN}✓${NC} $service_name is healthy"
|
||||
return 0
|
||||
else
|
||||
log "${YELLOW}⚠${NC} $service_name is running but not responding at $url"
|
||||
# If allow_redirect is true, we check if it's a 302
|
||||
if [ "$allow_redirect" = true ]; then
|
||||
local status=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$TIMEOUT" "$url")
|
||||
if [ "$status" = "302" ] || [ "$status" = "303" ] || [ "$status" = "307" ] || [ "$status" = "200" ]; then
|
||||
log "${GREEN}✓${NC} $service_name is healthy (status $status)"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
log "${YELLOW}⚠${NC} $service_name is running but not responding correctly at $url"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
|
|
@ -45,8 +59,10 @@ backend_ok=0
|
|||
frontend_ok=0
|
||||
worker_ok=0
|
||||
|
||||
# Backend health-check is public and should return 200
|
||||
check_service "innercontext" "$BACKEND_URL" || backend_ok=1
|
||||
check_service "innercontext-node" "$FRONTEND_URL" || frontend_ok=1
|
||||
# Frontend root may redirect to login (302)
|
||||
check_service "innercontext-node" "$FRONTEND_URL" true || frontend_ok=1
|
||||
|
||||
# Worker doesn't have HTTP endpoint, just check if it's running
|
||||
if systemctl is-active --quiet "innercontext-pricing-worker"; then
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ warnings=0
|
|||
|
||||
log_error() {
|
||||
echo -e "${RED}✗${NC} $1"
|
||||
((errors++))
|
||||
errors=$((errors + 1))
|
||||
}
|
||||
|
||||
log_success() {
|
||||
|
|
@ -34,7 +34,7 @@ log_success() {
|
|||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠${NC} $1"
|
||||
((warnings++))
|
||||
warnings=$((warnings + 1))
|
||||
}
|
||||
|
||||
check_symlink() {
|
||||
|
|
@ -131,6 +131,21 @@ if [ -f "$SHARED_BACKEND_ENV" ]; then
|
|||
check_var "$SHARED_BACKEND_ENV" "GEMINI_API_KEY"
|
||||
check_var "$SHARED_BACKEND_ENV" "LOG_LEVEL" true
|
||||
check_var "$SHARED_BACKEND_ENV" "CORS_ORIGINS" true
|
||||
|
||||
# OIDC Configuration
|
||||
check_var "$SHARED_BACKEND_ENV" "OIDC_ISSUER"
|
||||
check_var "$SHARED_BACKEND_ENV" "OIDC_CLIENT_ID"
|
||||
check_var "$SHARED_BACKEND_ENV" "OIDC_DISCOVERY_URL"
|
||||
check_var "$SHARED_BACKEND_ENV" "OIDC_ADMIN_GROUPS"
|
||||
check_var "$SHARED_BACKEND_ENV" "OIDC_MEMBER_GROUPS"
|
||||
check_var "$SHARED_BACKEND_ENV" "OIDC_JWKS_CACHE_TTL_SECONDS" true
|
||||
|
||||
# Bootstrap Admin (Optional, used for initial setup)
|
||||
check_var "$SHARED_BACKEND_ENV" "BOOTSTRAP_ADMIN_OIDC_ISSUER" true
|
||||
check_var "$SHARED_BACKEND_ENV" "BOOTSTRAP_ADMIN_OIDC_SUB" true
|
||||
check_var "$SHARED_BACKEND_ENV" "BOOTSTRAP_ADMIN_EMAIL" true
|
||||
check_var "$SHARED_BACKEND_ENV" "BOOTSTRAP_ADMIN_NAME" true
|
||||
check_var "$SHARED_BACKEND_ENV" "BOOTSTRAP_HOUSEHOLD_NAME" true
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
|
@ -138,6 +153,12 @@ echo "=== Validating Frontend Environment Variables ==="
|
|||
if [ -f "$SHARED_FRONTEND_ENV" ]; then
|
||||
check_var "$SHARED_FRONTEND_ENV" "PUBLIC_API_BASE"
|
||||
check_var "$SHARED_FRONTEND_ENV" "ORIGIN"
|
||||
|
||||
# Session and OIDC
|
||||
check_var "$SHARED_FRONTEND_ENV" "SESSION_SECRET"
|
||||
check_var "$SHARED_FRONTEND_ENV" "OIDC_ISSUER"
|
||||
check_var "$SHARED_FRONTEND_ENV" "OIDC_CLIENT_ID"
|
||||
check_var "$SHARED_FRONTEND_ENV" "OIDC_DISCOVERY_URL"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
[Unit]
|
||||
Description=innercontext SvelteKit Node frontend
|
||||
# Required env vars in .env.production:
|
||||
# PUBLIC_API_BASE, ORIGIN, SESSION_SECRET
|
||||
# OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_DISCOVERY_URL
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
[Unit]
|
||||
Description=innercontext FastAPI backend
|
||||
# Required env vars in .env:
|
||||
# DATABASE_URL, GEMINI_API_KEY
|
||||
# OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_DISCOVERY_URL
|
||||
# OIDC_ADMIN_GROUPS, OIDC_MEMBER_GROUPS
|
||||
# (Optional) BOOTSTRAP_ADMIN_OIDC_ISSUER, BOOTSTRAP_ADMIN_OIDC_SUB, etc.
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue