Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

```
## [Unreleased]
### Changed
- **GREMLIN server: explicit deployment configuration**: removed implicit
server/database path and bootstrap-admin defaults. `SERVER_DIR`,
`DB_UNIREF30`, `DB_UNIREF90`, and `ADMIN_USERS` are now mandatory; both
the restart script and direct server startup fail before touching the
database when any required setting is absent. The initial administrator
passwords are generated one-by-one and supplied transiently by the restart
script instead of being stored in the env file. Both `up` and `restart`
support first-start bootstrap; direct Compose startup with an empty user
database remains rejected.
- **GREMLIN server: ephemeral signing key**: removed the configurable
`AUTH_SECRET_KEY`. Gunicorn generates one in memory per preloaded web launch;
restarting web intentionally invalidates active login, verification, and
password-reset tokens.
- **GREMLIN server: automatic log rotation**: the maintenance scheduler can
ZIP and copy-truncate `LOG_DIR/*.log` after `ROTATE_LOG_MAX_LINENO` is
exceeded or on the `ROTATE_LOG_PERIOD` crontab schedule. Either unset trigger
is disabled; `MAX_LOG_SIZE` optionally caps active logs plus ZIP archives
using bytes or K/M/G/T suffixes and removes the oldest archives first.
Cron and threshold executions are serialized to protect ZIP/truncate
operations from overlap. Archives created in the current pass are protected
from immediate pruning so the only surviving copy is never discarded.
Leaving all three settings unset disables rotation.
- **GREMLIN server: admin log viewer**: a standalone page linked from the
administrator dashboard lazily streams the four active Gunicorn access,
Gunicorn error, Celery worker, and maintenance logs through fixed-name
admin-only endpoints. Rotated ZIPs are grouped in a lazy file tree and can
be downloaded individually.

## [1.9.1] - 2026-07-28
### Added
Expand Down
22 changes: 10 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,16 @@ upload-gists:
# JSONs for installer
gh gist edit c1e8bfe0fc0b9c60bf49ea04a550a044 -f REvoDesignExtrasTableRich.json jsons/REvoDesignExtrasTableRich.json
# HMAC manifest — key is extracted from the uploaded source file
@python -c '\n\
import hmac, hashlib, json, re;\n\
src = open("src/REvoDesign/tools/package_manager.py").read();\n\
m = re.search(r"_MANAGER_HMAC_KEY\s*=\s*bytes\.fromhex\(\"([a-f0-9]+)\"\)", src);\n\
key = bytes.fromhex(m.group(1));\n\
files = {"REvoDesign_PyMOL.py": "src/REvoDesign/tools/package_manager.py", "REvoDesign-PyMOL-entry.ui": "src/REvoDesign/UI/REvoDesign-PyMOL-entry.ui", "REvoDesignExtrasTableRich.json": "jsons/REvoDesignExtrasTableRich.json"};\n\
manifest = {name: hmac.new(key, open(path, "rb").read(), "sha256").hexdigest() for name, path in files.items()};\n\
json.dump(manifest, open("/tmp/revodesign-manifest.json", "w"), indent=2);\n\
print("Manifest:", json.dumps(manifest, indent=2))\n\
'
gh gist edit c1e8bfe0fc0b9c60bf49ea04a550a044 -f manifest.json /tmp/revodesign-manifest.json
rm /tmp/revodesign-manifest.json
@tmp_dir="$$(mktemp -d)" || exit; \
trap 'rm -rf "$$tmp_dir"' 0; \
manifest="$$tmp_dir/manifest.json"; \
python tools/generate_gist_manifest.py "$$manifest" || exit; \
files="$$(gh gist view c1e8bfe0fc0b9c60bf49ea04a550a044 --files)" || exit; \
if printf '%s\n' "$$files" | grep -Fxq manifest.json; then \
gh gist edit c1e8bfe0fc0b9c60bf49ea04a550a044 -f manifest.json "$$manifest"; \
else \
gh gist edit c1e8bfe0fc0b9c60bf49ea04a550a044 --add "$$manifest"; \
fi

install-pymol-plugin:
cp ./src/REvoDesign/tools/package_manager.py ~/.pymol/startup/REvoDesign_PyMOL.py
Expand Down
26 changes: 18 additions & 8 deletions docs/dev-guide/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ the admin user-control system.
| Service | Base Image | Role |
|---------|-----------|------|
| **web** | `python:3.12-slim` | Flask + Gunicorn HTTP server. Serves the web UI and REST API. |
| **maintenance** | Same as `web` | Single APScheduler process for registration digests, optional result retention, and database backups. No HTTP port or Docker socket. |
| **maintenance** | Same as `web` | Single APScheduler process for registration digests, optional result retention, database backups, and log rotation. No HTTP port or Docker socket. |
| **worker** | Same as `web` | Celery worker that receives `run_gremlin_task` jobs from Redis. |
| **redis** | `redis:7.2-alpine` | Celery message broker and result backend. |
| **runner** | `condaforge/mambaforge` | On-demand container that runs the PSSM/GREMLIN computation. Launched dynamically by `worker`. |
Expand All @@ -136,7 +136,7 @@ The server is a pip-installable package at ``server/pssm_gremlin_server/``
| ``config.py`` | Side-effect-free environment parsing and ``GremlinConfig`` |
| ``maintenance/model.py`` | ``PeriodicTask`` contract for task configuration and APScheduler registration |
| ``maintenance/manager.py`` | Standalone APScheduler entrypoint that imports task objects and calls their common ``register()`` interface |
| ``maintenance/tasks/`` | One self-configuring task object per module: registration digest, result cleanup, and consistent task/user SQLite backups |
| ``maintenance/tasks/`` | One self-configuring task object per module: registration digest, result cleanup, log rotation, and consistent task/user SQLite backups |
| ``task_runtime.py`` | Celery instance, task DB, Docker runner, archives, and ``run_gremlin_task`` |
| ``routes.py`` | All ``@app.route`` HTTP handlers — page routes, task API, auth API, admin API |
| ``auth.py`` | Token serialisation, ``UserDatabase`` (SQLite/SQLAlchemy), ``login_required`` decorator, email verification, password reset |
Expand Down Expand Up @@ -166,8 +166,8 @@ its ``args`` to ``scheduler.add_job`` only when ``is_enabled`` is true.
dispatched via Celery so the HTTP request returns immediately with a task
ID for polling.
- **Gunicorn `--preload`**: The WSGI application is loaded in the arbiter
before workers are forked, ensuring shared module state (especially the
`AUTH_SECRET_KEY` used for token signing) is consistent across workers.
before workers are forked, ensuring the ephemeral token-signing key is
consistent across workers for the lifetime of the web service.
- **Pydantic at the API boundary**: All inbound request payloads are
validated through typed Pydantic models (``schemas.py``) before reaching
business logic. Response serialisation uses ``UserResponse`` to guarantee
Expand Down Expand Up @@ -232,6 +232,10 @@ cookie-only writes are rejected to avoid CSRF on browser sessions.
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/PSSM_GREMLIN/user_control` | Admin-only user management page (web UI) |
| `GET` | `/PSSM_GREMLIN/logs` | Admin-only viewer for the four active service logs |
| `GET` | `/PSSM_GREMLIN/api/auth/admin/logs/<name>` | Stream one fixed active service log |
| `GET` | `/PSSM_GREMLIN/api/auth/admin/logs/archives` | List rotated ZIP archives grouped under the four fixed service logs |
| `GET` | `/PSSM_GREMLIN/api/auth/admin/logs/archives/<filename>` | Download one managed rotated-log ZIP |
| `GET` | `/PSSM_GREMLIN/api/auth/admin/users` | List all users (safe fields, excludes soft-deleted) |
| `POST` | `/PSSM_GREMLIN/api/auth/admin/users` | Create user (pre-verified, immediately active) |
| `PUT` | `/PSSM_GREMLIN/api/auth/admin/users/<id>` | Update profile fields, email, password, role, and account statuses |
Expand Down Expand Up @@ -305,11 +309,11 @@ Important environment variables (see the organized sections in
| Variable | Description |
|----------|-------------|
| `SERVER_IMAGE` / `RUNNER_IMAGE` | Built locally in dev mode or pulled from their configured references in prod mode |
| `SERVER_DIR` | Host root shared by web and worker for uploads, task SQLite, and results; never contains the user DB |
| `SERVER_DIR` | Required host root shared by web and worker for uploads, task SQLite, and results; never contains the user DB |
| `LOG_DIR` | Host directory for Gunicorn, Celery, and `maintenance.log` |
| `DB_UNIREF30` | UniRef30 HHsuite database prefix path |
| `DB_UNIREF90` | UniRef90 BLAST database prefix path |
| `AUTH_SECRET_KEY` | Fixed secret for signing auth tokens (set in production) |
| `DB_UNIREF30` | Required UniRef30 HHsuite database prefix path |
| `DB_UNIREF90` | Required UniRef90 BLAST database prefix path |
| `ADMIN_USERS` | Required comma-separated bootstrap-administrator usernames; the restart script generates and transiently supplies one password per account |
| `AUTH_TOKEN_MAX_AGE` | Token lifetime in seconds (default: 604800 = 7 days) |
| `AUTH_DIR` | Host directory containing `users.sqlite3`; mounted only into web and maintenance and required to be outside `SERVER_DIR` |
| `USER_DB_PATH` | Path through which web and maintenance see that database inside their containers (default: `/var/lib/revodesign-auth/users.sqlite3`) |
Expand All @@ -336,6 +340,9 @@ Important environment variables (see the organized sections in
| `BACKUP_DB_CRON` | Five-field crontab schedule for database snapshots; unset disables the task. Recommended daily value: `0 0 * * *` |
| `BACKUP_DB_PATH` | Snapshot directory inside maintenance; `/var/lib/revodesign-auth/backups` maps to `${AUTH_DIR}/backups` on the host |
| `MAX_DB_BACKUP` | Maximum complete snapshot sets to retain; unset is unlimited, recommended value is `30` |
| `ROTATE_LOG_MAX_LINENO` | Optional positive line-count threshold for ZIP log rotation; unset disables this trigger |
| `ROTATE_LOG_PERIOD` | Optional quoted five-field crontab expression for scheduled rotation (for example, `"0 0 * * *"` for daily at midnight); unset disables this trigger |
| `MAX_LOG_SIZE` | Optional total cap for active logs plus ZIP archives; accepts bytes or K/M/G/T suffixes and removes oldest archives first. A newly created archive is retained even when it temporarily exceeds the cap |
| `ADMIN_USERS` | Comma-separated admin usernames |
| `ALLOWED_EMAIL_DOMAINS` | Comma-separated allowed email domains for self-registration (empty = all allowed). Plus-aliased addresses normalised. |
| `ADMIN_NOTIFY_EMAIL` | Comma-separated recipients for new-registration digests |
Expand Down Expand Up @@ -392,6 +399,9 @@ successful run creates `${AUTH_DIR}/backups/<UTC timestamp>/tasks.sqlite3` and
REVODESIGN_SERVER_ENV=server/.env.production \
bash server/run/restart_pssm_flask.sh restart --mode=prod
```
Use the helper script for the first start; direct Docker Compose startup is
rejected while the user database is empty because bootstrap passwords are
generated and supplied transiently by the script.

5. **Access** the web UI at `http://<host>:<port>/PSSM_GREMLIN/dashboard`

Expand Down
32 changes: 22 additions & 10 deletions server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ RUNNER_IMAGE=revodesign-pssm-gremlin-non-root
# ============================================================================
# Storage paths and databases — must exist on the Docker host
# ============================================================================
# Root directory for uploads, results, and the SQLite task database.
# #REQ Root directory for uploads, results, and the SQLite task database.
SERVER_DIR=/srv/revodesign/server
# Host directory that the Docker runner may bind from. Defaults to the parent
# of SERVER_DIR; set this when SERVER_DIR is under a symlink or alternate mount.
Expand All @@ -43,26 +43,26 @@ AUTH_DIR=/srv/revodesign/auth
USER_DB_PATH=/var/lib/revodesign-auth/users.sqlite3

# Large read-only MSA databases mounted into the runner container.
# Prefix for the UniRef30 HHblits database (the directory containing the .cs219
# #REQ Prefix for the UniRef30 HHblits database (the directory containing the .cs219
# and .a3m files; omit extensions).
DB_UNIREF30=/srv/revodesign/databases/uniref30/UniRef30_2023_02
# Prefix for the UniRef90 BLAST database (the directory containing the .pin/.phr
# #REQ Prefix for the UniRef90 BLAST database (the directory containing the .pin/.phr
# etc. files; omit extensions).
DB_UNIREF90=/srv/revodesign/databases/uniref90/uniref90

# ============================================================================
# Authentication and access control
# ============================================================================
# Secret key for signing auth tokens. #REQ in production: set a fixed,
# high-entropy value (e.g. `openssl rand -hex 32`) so tokens survive
# gunicorn/celery restarts. If left empty a random key is generated per
# process, which breaks tokens after every restart.
# AUTH_SECRET_KEY=
# The server generates an in-memory signing key on each launch. Restarting the
# web service logs users out and invalidates outstanding verification/reset
# links; no signing secret is stored in this file.
# Token lifetime in seconds (default: 604800 = 7 days).
# AUTH_TOKEN_MAX_AGE=604800
# Comma-separated usernames granted access to all tasks, user management, and
# admin API routes.
# #REQ Comma-separated usernames granted access to all tasks, user management,
# and admin API routes. On an empty user database, the restart script creates
# each listed administrator and prints a distinct generated password for each.
ADMIN_USERS=admin
# Do not store administrator bootstrap passwords in this file.

# ============================================================================
# Registration settings — self-service registration requires email service
Expand Down Expand Up @@ -160,6 +160,18 @@ WORKER_CONCURRENCY=2
# BACKUP_DB_PATH=/var/lib/revodesign-auth/backups
# Retain the newest 30 complete snapshot sets. Leave unset for unlimited history.
# MAX_DB_BACKUP=30
#
# ZIP and copy-truncate every *.log file in LOG_DIR. Any setting below enables
# the task; leave all three unset to disable it. Line rotation occurs after the
# configured count is exceeded and is checked hourly. Scheduled rotation uses a
# quoted five-field crontab expression in TZ.
# ROTATE_LOG_MAX_LINENO=100000
# ROTATE_LOG_PERIOD="0 0 * * *"
# Cap the combined size of active logs and their ZIP archives. Oldest ZIPs are
# removed first; active logs are rotated only when that is insufficient. A ZIP
# created in the current pass is retained even when it temporarily exceeds cap.
# Accepts bytes or K/M/G/T suffixes. Leave unset to disable the size cap.
# MAX_LOG_SIZE=1G

# ============================================================================
# Web settings
Expand Down
Loading
Loading