Add Portainer deploy guide for the login portal

Standalone instructions for the Portainer admin: set AUTH_SECRET_KEY,
rebuild + redeploy the stack, and bootstrap the first admin account.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 14:52:27 -07:00
parent 151ccea0ac
commit bdb798efdd

103
DEPLOY-login-portal.md Normal file
View File

@@ -0,0 +1,103 @@
# Deploy: Work Package Suite — login portal update
Instructions for the **Portainer admin** to take the new secure login portal live.
No prior context needed.
**Repo:** `Project-SDE-WP-Suite` (primegit) — changes are merged to **`main`**.
**What changed:** the app now has a username/password login. Going live needs:
1. one new environment variable,
2. a **rebuild** of the stack (not just a restart), and
3. creating the first admin account.
> **Why a rebuild (not a restart):** both the **nginx/webserver** and **api** images
> bake the code in at build time (`COPY html/` and `COPY server/` in their
> Dockerfiles). A plain restart will **not** pick up the new code — the images must
> be **rebuilt** from the latest `main`.
---
## 1. Add an environment variable to the stack
In the stack's **Environment variables** section, add:
| Name | Value | Notes |
|------|-------|-------|
| `AUTH_SECRET_KEY` | a long random string | **Required.** Signs the login session cookies. |
| `AUTH_SESSION_HOURS` | `12` | *Optional.* Hours a login lasts before re-auth (defaults to 12). |
Generate the secret on the host with:
```bash
openssl rand -base64 48
```
> If `AUTH_SECRET_KEY` is **not** set, the app still starts but falls back to a random
> per-process key — logins then reset on every restart and break across the 2 gunicorn
> workers. It must be set to a fixed value.
The existing database variables (`POSTGRES_*`) are unchanged.
---
## 2. Pull latest `main`, rebuild, and redeploy
- Pull the latest commit on `main` and redeploy the stack **with image rebuild enabled**
(e.g. "Re-pull and redeploy" / force rebuild). This rebuilds both the `webserver` and
`api` images.
- New Python dependencies (`bcrypt`, `PyJWT`) are in `requirements.txt` and install
automatically during the rebuild.
- The `users` table is created automatically on API startup — **no DB migration needed.**
---
## 3. Verify the containers
- Confirm `wp_api` and the webserver container are both **running**.
- If `wp_api` fails to start, check its **Logs**. (A missing `AUTH_SECRET_KEY` only logs a
warning — it won't crash — but please confirm it's set.)
---
## 4. Create the first admin account
The login system needs one admin user in the production (Postgres) database. Open the
**`wp_api`** container's **Console** (`/bin/sh`) and run:
```bash
python -m server.manage_users create-admin <username> --name "<Full Name>"
```
It prompts for a password (minimum 8 characters) and prints `Created admin: <username>`.
Non-interactive alternative:
```bash
python -m server.manage_users create-admin <username> --name "<Full Name>" --password "<password>"
```
Other CLI commands (run the same way): `list`, `create <user> --role user`,
`reset-password <user>`, `disable <user>`, `enable <user>`.
---
## 5. Confirm it works
1. Load the site's normal URL — it should redirect to a **login page**.
2. Sign in with the admin account from step 4.
3. That admin can then add all other users from the in-app **Admin → User
administration** page (top-right **Admin** link), so no further shell access is needed.
---
## Reference — what's in this release
- `server/auth.py` — bcrypt password hashing, JWT session cookie, the request gate.
- `server/app.py``/api/auth/*` endpoints + middleware that refuses every `/api` data
route without a valid session.
- `server/manage_users.py` — the CLI used in step 4.
- `html/login.html`, `html/auth-guard.js` — login page and per-page guard.
- `html/admin.html` / `admin.js` — Admin Console gated on the admin role, with the user
administration UI.
- Sessions are stateless: a signed JWT in an **HttpOnly, SameSite=Lax** cookie, marked
**Secure** automatically when served over HTTPS (via `X-Forwarded-Proto` from nginx).