Files
org-parking/deploy/DEPLOY.md
Stefano Manfredi 7168fa4b72 Refactor to manager-centric model, add team calendar for all users
Key changes:
- Removed office-centric model (deleted offices.py, office-rules)
- Renamed to team-rules, managers are part of their own team
- Team calendar visible to all (read-only for employees)
- Admins can have a manager assigned
2025-12-02 13:30:04 +00:00

6.1 KiB

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):

# 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:

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:

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:

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:

access_control:
  default_policy: deny
  rules:
    # ... existing rules ...

    # Parking Manager - require authentication
    - domain: parking.rocketscale.it
      policy: one_factor

After editing, restart Authelia:

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

# 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:

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