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
This commit is contained in:
190
deploy/DEPLOY.md
190
deploy/DEPLOY.md
@@ -3,55 +3,52 @@
|
||||
## Prerequisites
|
||||
|
||||
- org-stack running on rocky@rocketscale.it
|
||||
- Git repository on git.rocketscale.it
|
||||
- Git repository on git.rocketscale.it (optional, can use rsync)
|
||||
|
||||
## Step 1: Push to Git
|
||||
## Directory Structure
|
||||
|
||||
```bash
|
||||
# On development machine
|
||||
cd /mnt/code/boilerplate/org-parking
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Initial commit: Parking Manager"
|
||||
git remote add origin git@git.rocketscale.it:rocky/parking-manager.git
|
||||
git push -u origin main
|
||||
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 2: Clone on Server
|
||||
## 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 to server
|
||||
ssh rocky@rocketscale.it
|
||||
|
||||
# Clone into org-stack
|
||||
cd ~/org-stack
|
||||
git clone git@git.rocketscale.it:rocky/parking-manager.git parking
|
||||
cd ~
|
||||
git clone git@git.rocketscale.it:rocky/parking-manager.git org-parking
|
||||
```
|
||||
|
||||
## Step 3: Add to .env
|
||||
## Step 2: Create Production compose.yml
|
||||
|
||||
Add to `~/org-stack/.env`:
|
||||
|
||||
```bash
|
||||
# Parking Manager
|
||||
PARKING_SECRET_KEY=your-random-secret-key-here
|
||||
```
|
||||
|
||||
Generate a secret key:
|
||||
```bash
|
||||
python3 -c "import secrets; print(secrets.token_hex(32))"
|
||||
```
|
||||
|
||||
## Step 4: Add to compose.yml
|
||||
|
||||
Add the parking service to `~/org-stack/compose.yml`:
|
||||
Create `~/org-parking/compose.yml` on the server:
|
||||
|
||||
```yaml
|
||||
# ===========================================================================
|
||||
# Parking Manager - Parking Spot Management
|
||||
# ===========================================================================
|
||||
services:
|
||||
parking:
|
||||
build: ./parking
|
||||
build: .
|
||||
container_name: parking
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
@@ -68,18 +65,34 @@ Add the parking service to `~/org-stack/compose.yml`:
|
||||
- SMTP_FROM=${SMTP_FROM:-}
|
||||
networks:
|
||||
- org-network
|
||||
depends_on:
|
||||
- authelia
|
||||
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
|
||||
```
|
||||
|
||||
Add to volumes section:
|
||||
```yaml
|
||||
parking_data: # Parking SQLite database
|
||||
## 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
|
||||
```
|
||||
|
||||
Add `parking` to Caddy's depends_on list.
|
||||
**Note**: Each directory needs its own `.env` file since docker compose only reads from the current directory.
|
||||
|
||||
## Step 5: Add to Caddyfile
|
||||
## Step 4: Add to Caddyfile
|
||||
|
||||
Add to `~/org-stack/Caddyfile`:
|
||||
|
||||
@@ -91,27 +104,57 @@ parking.rocketscale.it {
|
||||
}
|
||||
```
|
||||
|
||||
## Step 6: Create LLDAP Groups
|
||||
## 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` (follows lldap naming convention)
|
||||
2. Create group: `managers` (reusable across apps)
|
||||
3. Add yourself to `parking_admins`
|
||||
1. Create group: `parking_admins`
|
||||
2. Add yourself (or whoever should be admin) to this group
|
||||
|
||||
## Step 7: Deploy
|
||||
**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
|
||||
cd ~/org-stack
|
||||
|
||||
# Build and start parking service
|
||||
cd ~/org-parking
|
||||
docker compose build parking
|
||||
docker compose up -d parking
|
||||
docker compose up -d
|
||||
|
||||
# Reload Caddy to pick up new domain
|
||||
# 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
|
||||
```
|
||||
|
||||
@@ -124,14 +167,53 @@ docker compose logs -f parking
|
||||
|
||||
## 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
|
||||
- Verify LLDAP group membership
|
||||
- Roles sync on each login
|
||||
- **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
|
||||
|
||||
Reference in New Issue
Block a user