# Deployment Guide for parking.rocketscale.it ## Prerequisites - org-stack running on rocky@rocketscale.it - Git repository on git.rocketscale.it (optional, can use rsync) ## Directory Structure Parking is deployed as a **separate directory** alongside org-stack: ``` ~/ ├── org-stack/ # Main stack (Caddy, Authelia, LLDAP, etc.) │ ├── compose.yml │ ├── Caddyfile │ ├── authelia/ │ └── .env │ └── org-parking/ # Parking app (separate) ├── compose.yml # Production compose (connects to org-stack network) ├── .env # Own .env with PARKING_SECRET_KEY ├── Dockerfile └── ... ``` ## Step 1: Deploy to Server Option A - Using rsync (recommended for development): ```bash # From development machine rsync -avz --exclude '.git' --exclude '__pycache__' --exclude '*.pyc' \ --exclude '.env' --exclude 'data/' --exclude '*.db' --exclude '.venv/' \ /path/to/org-parking/ rocky@rocketscale.it:~/org-parking/ ``` Option B - Using git: ```bash ssh rocky@rocketscale.it cd ~ git clone git@git.rocketscale.it:rocky/parking-manager.git org-parking ``` ## Step 2: Create Production compose.yml Create `~/org-parking/compose.yml` on the server: ```yaml services: parking: build: . container_name: parking restart: unless-stopped volumes: - parking_data:/app/data environment: - SECRET_KEY=${PARKING_SECRET_KEY} - DATABASE_PATH=/app/data/parking.db - AUTHELIA_ENABLED=true - ALLOWED_ORIGINS=https://parking.rocketscale.it - SMTP_HOST=${SMTP_HOST:-} - SMTP_PORT=${SMTP_PORT:-587} - SMTP_USER=${SMTP_USER:-} - SMTP_PASSWORD=${SMTP_PASSWORD:-} - SMTP_FROM=${SMTP_FROM:-} networks: - org-network healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] interval: 30s timeout: 10s retries: 3 start_period: 10s volumes: parking_data: networks: org-network: external: true name: org-stack_org-network ``` ## Step 3: Create .env File Create `~/org-parking/.env` with a secret key: ```bash cd ~/org-parking python3 -c "import secrets; print(f'PARKING_SECRET_KEY={secrets.token_hex(32)}')" > .env ``` **Note**: Each directory needs its own `.env` file since docker compose only reads from the current directory. ## Step 4: Add to Caddyfile Add to `~/org-stack/Caddyfile`: ``` # Parking Manager - Protected by Authelia parking.rocketscale.it { import auth reverse_proxy parking:8000 } ``` ## Step 5: Add Authelia Access Control Rule **Important**: Authelia's `access_control` must include parking.rocketscale.it or you'll get 403 Forbidden. Edit `~/org-stack/authelia/configuration.yml` and add to the `access_control.rules` section: ```yaml access_control: default_policy: deny rules: # ... existing rules ... # Parking Manager - require authentication - domain: parking.rocketscale.it policy: one_factor ``` After editing, restart Authelia: ```bash cd ~/org-stack docker compose restart authelia ``` ## Step 6: Create LLDAP Group In lldap (https://ldap.rocketscale.it): 1. Create group: `parking_admins` 2. Add yourself (or whoever should be admin) to this group **Role Management:** - `parking_admins` group → **admin** role (synced from LLDAP on each login) - **manager** role → assigned by admin in the app UI (Manage Users page) - **employee** role → default for all other users The admin can promote users to manager and assign offices via the Manage Users page. ## Step 7: Build and Deploy ```bash # Build and start parking service cd ~/org-parking docker compose build parking docker compose up -d # Reload Caddy to pick up new domain (from org-stack) cd ~/org-stack docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile # Check logs cd ~/org-parking docker compose logs -f parking ``` ## Step 8: Verify 1. Go to https://parking.rocketscale.it 2. You should be redirected to Authelia for login 3. After login, you should see the parking app 4. Your user should be auto-created with `admin` role (if in parking-admins group) ## Troubleshooting ### 403 Forbidden from Authelia - Authelia's `access_control` doesn't have a rule for parking.rocketscale.it - Add the domain to `~/org-stack/authelia/configuration.yml` (see Step 6) - Restart Authelia: `docker compose restart authelia` ### 401 Unauthorized - Check Authelia headers are being passed - Check `docker compose logs authelia` ### Login redirect loop (keeps redirecting to /login) - Frontend JS must use async auth checking for Authelia mode - The `api.requireAuth()` must call `/api/auth/me` endpoint (not check localStorage) - Ensure all page JS files use: `currentUser = await api.requireAuth();` ### User has wrong role - **Admin role**: Verify user is in `parking_admins` LLDAP group (synced on each login) - **Manager role**: Must be assigned by admin via Manage Users page (not from LLDAP) - **Employee role**: Default for users not in `parking_admins` group ### Database errors - Check volume mount: `docker compose exec parking ls -la /app/data` - Check permissions: `docker compose exec parking id` ## Architecture Notes ### Authelia Integration The app supports two authentication modes: 1. **JWT mode** (standalone): Users login via `/login`, get JWT token stored in localStorage 2. **Authelia mode** (SSO): Authelia handles login, passes headers to backend When `AUTHELIA_ENABLED=true`: - Backend reads user info from headers: `Remote-User`, `Remote-Email`, `Remote-Name`, `Remote-Groups` - Users are auto-created on first login - Roles are synced from LLDAP groups on each request - Frontend calls `/api/auth/me` to get user info (backend reads headers) ### Role Management Only the **admin** role is synced from LLDAP: ```python AUTHELIA_ADMIN_GROUP = "parking_admins" # → role: admin ``` Other roles are managed within the app: - **manager**: Assigned by admin via Manage Users page - **employee**: Default role for all non-admin users This separation allows: - LLDAP to control who has admin access - App admin to assign manager roles and office assignments without LLDAP changes