From 1542c3e5a72807dedfbf17965c0cbede9bf8d8d9 Mon Sep 17 00:00:00 2001 From: jessikitty Date: Thu, 4 Jun 2026 09:55:22 +1000 Subject: [PATCH] docs: fix double-encoding, write proper deployment README --- README.md | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 331f548..6e33be6 100644 --- a/README.md +++ b/README.md @@ -1 +1,138 @@ -IyBCdXMgTWFuYWdlcgoKQSBEamFuZ28tYmFzZWQgYnVzIGNvb3JkaW5hdGlvbiBhbmQgbWFuYWdlbWVudCBzeXN0ZW0uIEhhbmRsZXMgdHJhbnNwb3J0IHNjaGVkdWxpbmcsIHRyYXZlbGxlciBtYW5hZ2VtZW50LCBsb2NhdGlvbiB0cmFja2luZywgY29vcmRpbmF0b3Igd29ya2Zsb3dzLCBhbmQgb3B0aW9uYWwgU01TIChUZWxzdHJhKSBhbmQgU1NPIChBenVyZSBBRCkgaW50ZWdyYXRpb25zLgoKLS0tCgojIyBTdGFjawoKfCBDb21wb25lbnQgfCBUZWNobm9sb2d5IHwKfC0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tfAp8IFdlYiBmcmFtZXdvcmsgfCBEamFuZ28gNiArIEd1bmljb3JuIHwKfCBEYXRhYmFzZSB8IFBvc3RncmVTUUwgMTYgfAp8IEF1dGggKG9wdGlvbmFsKSB8IEF6dXJlIEFEIFNTTyB2aWEgTVNBTCB8CnwgU01TIChvcHRpb25hbCkgfCBUZWxzdHJhIE1lc3NhZ2luZyBBUEkgfAp8IFNjaGVkdWxpbmcgfCBkamFuZ28tY3JvbnRhYiAobmlnaHRseSBjb29yZGluYXRvciBjaGVjaykgfAp8IENvbnRhaW5lciBydW50aW1lIHwgRG9ja2VyICsgRG9ja2VyIENvbXBvc2UgfAoKLS0tCgojIyBEZXBsb3ltZW50CgojIyMgUHJlcmVxdWlzaXRlcwoKLSBEb2NrZXIgYW5kIERvY2tlciBDb21wb3NlIGluc3RhbGxlZCBvbiB0aGUgaG9zdAotIEEgcmV2ZXJzZSBwcm94eSAoZS5nLiBOZ2lueCBQcm94eSBNYW5hZ2VyKSB0byBmcm9udCB0aGUgYXBwCgojIyMgMS4gQ2xvbmUgdGhlIHJlcG9zaXRvcnkKCmBgYGJhc2gKZ2l0IGNsb25lIGh0dHBzOi8vZ2l0ZWEuaGlkZWF3YXlnYW1pbmcuY29tLmF1L2plc3Npa2l0dHkvYnVzLW1hbmFnZXIuZ2l0CmNkIGJ1cy1tYW5hZ2VyCmBgYAoKIyMjIDIuIENvbmZpZ3VyZSBlbnZpcm9ubWVudAoKYGBgYmFzaApjcCAuZW52LmV4YW1wbGUgLmVudgpgYGAKCkVkaXQgYC5lbnZgIGFuZCBzZXQgYXQgbWluaW11bToKCnwgVmFyaWFibGUgfCBEZXNjcmlwdGlvbiB8CnwtLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS18CnwgYFNFQ1JFVF9LRVlgIHwgTG9uZyByYW5kb20gc3RyaW5nIOKAlCBnZW5lcmF0ZSB3aXRoIGBweXRob24gLWMgImltcG9ydCBzZWNyZXRzOyBwcmludChzZWNyZXRzLnRva2VuX3VybHNhZmUoNTApKSJgIHwKfCBgU1FMX1BBU1NXT1JEYCB8IFBvc3RncmVzIHBhc3N3b3JkIChwaWNrZWQgdXAgYnkgYm90aCB0aGUgYGRiYCBhbmQgYHdlYmAgY29udGFpbmVycykgfAp8IGBBTExPV0VEX0hPU1RTYCB8IFNwYWNlLXNlcGFyYXRlZCBsaXN0IG9mIGhvc3RuYW1lcyB0aGUgYXBwIHdpbGwgYmUgc2VydmVkIGZyb20gKGUuZy4gYGJ1c21hbmFnZXIueW91cmRvbWFpbi5jb21gKSB8CnwgYEFQUF9QT1JUYCB8IEhvc3QgcG9ydCBHdW5pY29ybSB3aWxsIGJlIGV4cG9zZWQgb24gKGRlZmF1bHQ6IGA4MDAwYCkgfAoKT3B0aW9uYWwgdmFyaWFibGVzIGZvciBlbWFpbCwgQXp1cmUgU1NPLCBhbmQgVGVsc3RyYSBTTVMgYXJlIGRvY3VtZW50ZWQgaW4gYC5lbnYuZXhhbXBsZWAuCgojIyMgMy4gQnVpbGQgYW5kIHN0YXJ0CgpgYGBiYXNoCmRvY2tlciBjb21wb3NlIHVwIC1kIC0tYnVpbGQKYGBgCgpPbiBmaXJzdCBzdGFydCB0aGUgYHdlYmAgY29udGFpbmVyIHdpbGw6CjEuIFdhaXQgZm9yIFBvc3RncmVzIHRvIHBhc3MgaXRzIGhlYWx0aGNoZWNrCjIuIFJ1biBgbWFuYWdlLnB5IG1pZ3JhdGVgIHRvIGluaXRpYWxpc2UgdGhlIGRhdGFiYXNlIHNjaGVtYQozLiBSdW4gYG1hbmFnZS5weSBjb2xsZWN0c3RhdGljYCB0byBnYXRoZXIgc3RhdGljIGFzc2V0cwo0LiBSZWdpc3RlciB0aGUgbmlnaHRseSBjcm9uIGpvYiB2aWEgYG1hbmFnZS5weSBjcm9udGFiIGFkZGAKNS4gU3RhcnQgR3VuaWNvcm4gb24gcG9ydCBgODAwMGAKCiMjIyA0LiBDcmVhdGUgYSBzdXBlcnVzZXIKCmBgYGJhc2gKZG9ja2VyIGNvbXBvc2UgZXhlYyB3ZWIgcHl0aG9uIG1hbmFnZS5weSBjcmVhdGVzdXBlcnVzZXIKYGBgCgojIyMgNS4gUmV2ZXJzZSBwcm94eSAoTmdpbnggUHJveHkgTWFuYWdlcikKCkNyZWF0ZSBhIG5ldyBwcm94eSBob3N0IGluIE5QTSBwb2ludGluZyB0bzoKCmBgYApodHRwOi8vPGRvY2tlci1ob3N0LWlwPjo8QVBQX1BPUlQ+CmBgYAoKRW5hYmxlIFNTTCB2aWEgTGV0J3MgRW5jcnlwdCBhcyB1c3VhbC4gTm8gc3BlY2lhbCBoZWFkZXJzIHJlcXVpcmVkIOKAlCBHdW5pY29ybSBoYW5kbGVzIFdTR0kgZGlyZWN0bHkuCgotLS0KCiMjIFVwZGF0aW5nCgpgYGBiYXNoCmdpdCBwdWxsCmRvY2tlciBjb21wb3NlIHVwIC1kIC0tYnVpbGQKYGBgCgpNaWdyYXRpb25zIHJ1biBhdXRvbWF0aWNhbGx5IG9uIHN0YXJ0dXAsIHNvIHNjaGVtYSBjaGFuZ2VzIGFyZSBhcHBsaWVkIHdpdGhvdXQgbWFudWFsIGludGVydmVudGlvbi4KCi0tLQoKIyMgVXNlZnVsIGNvbW1hbmRzCgp8IFRhc2sgfCBDb21tYW5kIHwKfC0tLS0tLXwtLS0tLS0tLS18CnwgVmlldyBsb2dzIHwgYGRvY2tlciBjb21wb3NlIGxvZ3MgLWYgd2ViYCB8CnwgT3BlbiBEamFuZ28gc2hlbGwgfCBgZG9ja2VyIGNvbXBvc2UgZXhlYyB3ZWIgcHl0aG9uIG1hbmFnZS5weSBzaGVsbGAgfAp8IFJ1biBtaWdyYXRpb25zIG1hbnVhbGx5IHwgYGRvY2tlciBjb21wb3NlIGV4ZWMgd2ViIHB5dGhvbiBtYW5hZ2UucHkgbWlncmF0ZWAgfAp8IENoZWNrIGNyb250YWIgfCBgZG9ja2VyIGNvbXBvc2UgZXhlYyB3ZWIgcHl0aG9uIG1hbmFnZS5weSBjcm9udGFiIHNob3dgIHwKfCBSZXN0YXJ0IGFwcCBvbmx5IHwgYGRvY2tlciBjb21wb3NlIHJlc3RhcnQgd2ViYCB8CnwgU3RvcCBldmVyeXRoaW5nIHwgYGRvY2tlciBjb21wb3NlIGRvd25gIHwKfCBTdG9wIGFuZCB3aXBlIGRhdGFiYXNlIHwgYGRvY2tlciBjb21wb3NlIGRvd24gLXZgIOKaoO+4jyBkZXN0cnVjdGl2ZSB8CgotLS0KCiMjIEVudmlyb25tZW50IHZhcmlhYmxlcyByZWZlcmVuY2UKCnwgVmFyaWFibGUgfCBSZXF1aXJlZCB8IERlZmF1bHQgfCBEZXNjcmlwdGlvbiB8CnwtLS0tLS0tLS0tfC0tLS0tLS0tLS18LS0tLS0tLS0tfC0tLS0tLS0tLS0tLS18CnwgYFNFQ1JFVF9LRVlgIHwg4pyFIHwg4oCUIHwgRGphbmdvIHNlY3JldCBrZXkgfAp8IGBERUJVR2AgfCB8IGBGYWxzZWAgfCBFbmFibGUgRGphbmdvIGRlYnVnIG1vZGUgfAp8IGBBTExPV0VEX0hPU1RTYCB8IOKchSB8IGBsb2NhbGhvc3RgIHwgU3BhY2Utc2VwYXJhdGVkIGFsbG93ZWQgaG9zdG5hbWVzIHwKfCBgU1FMX0RBVEFCQVNFYCB8IHwgYGJ1c21hbmFnZXJgIHwgUG9zdGdyZXMgZGF0YWJhc2UgbmFtZSB8CnwgYFNRTF9VU0VSYCB8IHwgYGJ1c21hbmFnZXJgIHwgUG9zdGdyZXMgdXNlcm5hbWUgfAp8IGBTUUxfUEFTU1dPUkRgIHwg4pyFIHwg4oCUIHwgUG9zdGdyZXMgcGFzc3dvcmQgfAp8IGBBUFBfUE9SVGAgfCB8IGA4MDAwYCB8IEhvc3QgcG9ydCBmb3IgR3VuaWNvcm4gfAp8IGBHVU5JQ09STl9XT1JLRVJTYCB8IHwgYDNgIHwgTnVtYmVyIG9mIEd1bmljb3JuIHdvcmtlciBwcm9jZXNzZXMgfAp8IGBHVU5JQ09STl9USU1FT1VUYCB8IHwgYDEyMGAgfCBHdW5pY29ybiB3b3JrZXIgdGltZW91dCAoc2Vjb25kcykgfAp8IGBFTUFJTF9IT1NUYCB8IHwg4oCUIHwgU01UUCByZWxheSBob3N0bmFtZSB8CnwgYEVNQUlMX1BPUlRgIHwgfCBgMjVgIHwgU01UUCByZWxheSBwb3J0IHwKfCBgQVpVUkVfQ0xJRU5UX0lEYCB8IHwg4oCUIHwgQXp1cmUgYXBwIGNsaWVudCBJRCAoZW5hYmxlcyBTU08pIHwKfCBgQVpVUkVfQ0xJRU5UX1NFQ1JFVGAgfCB8IOKAlCB8IEF6dXJlIGFwcCBjbGllbnQgc2VjcmV0IHwKfCBgQVpVUkVfUkVESVJFQ1RfVVJJYCB8IHwg4oCUIHwgQXp1cmUgT0F1dGggcmVkaXJlY3QgVVJJIHwKfCBgQVpVUkVfQVVUSE9SSVRZYCB8IHwg4oCUIHwgQXp1cmUgYXV0aG9yaXR5IFVSTCB8CnwgYFRFTFNUUkFfQ0xJRU5UX0lEYCB8IHwg4oCUIHwgVGVsc3RyYSBBUEkgY2xpZW50IElEIChlbmFibGVzIFNNUykgfAp8IGBURUxTVFJBX0NMSUVOVF9TRUNSRVRgIHwgfCDigJQgfCBUZWxzdHJhIEFQSSBjbGllbnQgc2VjcmV0IHwKCi0tLQoKIyMgRGF0YSBwZXJzaXN0ZW5jZQoKVHdvIG5hbWVkIERvY2tlciB2b2x1bWVzIGFyZSBjcmVhdGVkIGF1dG9tYXRpY2FsbHk6CgotIGBwb3N0Z3Jlc19kYXRhYCDigJQgUG9zdGdyZVNRTCBkYXRhIGRpcmVjdG9yeQotIGBzdGF0aWNfZmlsZXNgIOKAlCBjb2xsZWN0ZWQgRGphbmdvIHN0YXRpYyBhc3NldHMKClRoZXNlIHN1cnZpdmUgYGRvY2tlciBjb21wb3NlIGRvd25gIGFuZCBhcmUgb25seSByZW1vdmVkIHdpdGggYGRvY2tlciBjb21wb3NlIGRvd24gLXZgLgo= \ No newline at end of file +# Bus Manager + +A Django-based bus coordination and management system. Handles transport scheduling, traveller management, location tracking, coordinator workflows, and optional SMS (Telstra) and SSO (Azure AD) integrations. + +--- + +## Stack + +| Component | Technology | +|-----------|-----------| +| Web framework | Django 6 + Gunicorn | +| Database | PostgreSQL 16 | +| Auth (optional) | Azure AD SSO via MSAL | +| SMS (optional) | Telstra Messaging API | +| Scheduling | django-crontab (nightly coordinator check) | +| Container runtime | Docker + Docker Compose | + +--- + +## Deployment + +### Prerequisites + +- Docker and Docker Compose installed on the host +- A reverse proxy (e.g. Nginx Proxy Manager) to front the app + +### 1. Clone the repository + +```bash +git clone https://gitea.hideawaygaming.com.au/jessikitty/bus-manager.git +cd bus-manager +``` + +### 2. Configure environment + +```bash +cp .env.example .env +``` + +Edit `.env` and set at minimum: + +| Variable | Description | +|----------|-------------| +| `SECRET_KEY` | Long random string — generate with `python -c "import secrets; print(secrets.token_urlsafe(50))"` | +| `SQL_PASSWORD` | Postgres password (picked up by both the `db` and `web` containers) | +| `ALLOWED_HOSTS` | Space-separated list of hostnames the app will be served from (e.g. `busmanager.yourdomain.com`) | +| `APP_PORT` | Host port Gunicorn will be exposed on (default: `8000`) | + +Optional variables for email, Azure SSO, and Telstra SMS are documented in `.env.example`. + +### 3. Build and start + +```bash +docker compose up -d --build +``` + +On first start the `web` container will: +1. Wait for Postgres to pass its healthcheck +2. Run `manage.py migrate` to initialise the database schema +3. Run `manage.py collectstatic` to gather static assets +4. Register the nightly cron job via `manage.py crontab add` +5. Start Gunicorn on port `8000` + +### 4. Create a superuser + +```bash +docker compose exec web python manage.py createsuperuser +``` + +### 5. Reverse proxy (Nginx Proxy Manager) + +Create a new proxy host in NPM pointing to: + +``` +http://: +``` + +Enable SSL via Let's Encrypt as usual. No special headers required — Gunicorn handles WSGI directly. + +--- + +## Updating + +```bash +git pull +docker compose up -d --build +``` + +Migrations run automatically on startup, so schema changes are applied without manual intervention. + +--- + +## Useful commands + +| Task | Command | +|------|---------| +| View logs | `docker compose logs -f web` | +| Open Django shell | `docker compose exec web python manage.py shell` | +| Run migrations manually | `docker compose exec web python manage.py migrate` | +| Check crontab | `docker compose exec web python manage.py crontab show` | +| Restart app only | `docker compose restart web` | +| Stop everything | `docker compose down` | +| Stop and wipe database | `docker compose down -v` ⚠️ destructive | + +--- + +## Environment variables reference + +| Variable | Required | Default | Description | +|----------|----------|---------|-------------| +| `SECRET_KEY` | ✅ | — | Django secret key | +| `DEBUG` | | `False` | Enable Django debug mode | +| `ALLOWED_HOSTS` | ✅ | `localhost` | Space-separated allowed hostnames | +| `SQL_DATABASE` | | `busmanager` | Postgres database name | +| `SQL_USER` | | `busmanager` | Postgres username | +| `SQL_PASSWORD` | ✅ | — | Postgres password | +| `APP_PORT` | | `8000` | Host port for Gunicorn | +| `GUNICORN_WORKERS` | | `3` | Number of Gunicorn worker processes | +| `GUNICORN_TIMEOUT` | | `120` | Gunicorn worker timeout (seconds) | +| `EMAIL_HOST` | | — | SMTP relay hostname | +| `EMAIL_PORT` | | `25` | SMTP relay port | +| `AZURE_CLIENT_ID` | | — | Azure app client ID (enables SSO) | +| `AZURE_CLIENT_SECRET` | | — | Azure app client secret | +| `AZURE_REDIRECT_URI` | | — | Azure OAuth redirect URI | +| `AZURE_AUTHORITY` | | — | Azure authority URL | +| `TELSTRA_CLIENT_ID` | | — | Telstra API client ID (enables SMS) | +| `TELSTRA_CLIENT_SECRET` | | — | Telstra API client secret | + +--- + +## Data persistence + +Two named Docker volumes are created automatically: + +- `postgres_data` — PostgreSQL data directory +- `static_files` — collected Django static assets + +These survive `docker compose down` and are only removed with `docker compose down -v`.