docs: add tubco setup runbook
Some checks failed
CI / python-validation (push) Has been cancelled
CI / docker-release-gate (push) Has been cancelled
i18n / compile-translations (push) Has been cancelled

This commit is contained in:
Md Bayazid Bostame
2026-04-01 13:47:30 +02:00
parent 541736a9a2
commit baf53a3274
4 changed files with 330 additions and 0 deletions

View File

@@ -5,6 +5,7 @@ This repository is the standalone productized Workdock platform.
Use this file as the quick-start guide for future coders. It complements: Use this file as the quick-start guide for future coders. It complements:
- `DEPLOYMENT.md` - `DEPLOYMENT.md`
- `TUBCO_SETUP.md`
- `backend/workflows/templates/workflows/project_wiki.html` - `backend/workflows/templates/workflows/project_wiki.html`
- `backend/workflows/templates/workflows/developer_handbook.html` - `backend/workflows/templates/workflows/developer_handbook.html`
@@ -34,6 +35,8 @@ Rule:
Do not let a customer deployment track `develop` directly. Do not let a customer deployment track `develop` directly.
Use `TUBCO_SETUP.md` for the customer-specific bootstrap, reset, config-sync, and deploy sequence.
## Dual Remote Workflow ## Dual Remote Workflow
This repository now has two different Git remotes with different purposes: This repository now has two different Git remotes with different purposes:
- `origin`: the normal GitHub product remote - `origin`: the normal GitHub product remote

View File

@@ -254,6 +254,46 @@ This is destructive. It wipes:
It does not remove the server-local env file. That file must already exist. It does not remove the server-local env file. That file must already exist.
## TUBCO customer setup
For the TUBCO customer line, use the frozen customer branch instead of the normal product branches.
Branch rule:
- `release/tubco-v1` is the deployment branch
- do not deploy TUBCO from `develop`
- do not deploy TUBCO from `main`
Recommended first-time flow:
1. check out `release/tubco-v1`
2. create `.env.prod` on the customer server
3. run a destructive reset/bootstrap from the Mac
4. import the intended TUBCO config baseline
5. verify `https://portal.tub.co/healthz/`
Important TUBCO values in `.env.prod`:
```env
APP_DOMAIN=portal.tub.co
APP_BASE_URL=https://portal.tub.co
DJANGO_DEBUG=0
DJANGO_SECURE_COOKIES=1
DJANGO_SECURE_SSL_REDIRECT=1
```
Customer reset from scratch:
```bash
git checkout release/tubco-v1
RESET_CONFIRM=RESET \
EXPECTED_BRANCH=release/tubco-v1 \
DEPLOY_HOST=root@<customer-host> \
DEPLOY_PATH=/opt/workdock \
REMOTE_ENV_FILE=.env.prod \
HEALTH_URL=https://portal.tub.co/healthz/ \
RUN_DJANGO_CHECK=1 \
./scripts/reset_stack_from_mac.sh
```
The full customer runbook lives in:
- [TUBCO_SETUP.md](/Users/bostame/Documents/workdock-platform/TUBCO_SETUP.md)
## Manual production deployment ## Manual production deployment
For production, use a dedicated helper instead of the test script. For production, use a dedicated helper instead of the test script.
@@ -366,6 +406,9 @@ ssh -4 root@192.168.2.55 '
- PDF letterhead - PDF letterhead
- use dry-run first. Treat config sync as an explicit operator action, not something hidden inside deploy. - use dry-run first. Treat config sync as an explicit operator action, not something hidden inside deploy.
For the customer-specific version of this workflow, including `portal.tub.co` examples, use:
- [TUBCO_SETUP.md](/Users/bostame/Documents/workdock-platform/TUBCO_SETUP.md)
## GitHub Actions workflows ## GitHub Actions workflows
### Test deployment workflow ### Test deployment workflow
File: File:

190
TUBCO_SETUP.md Normal file
View File

@@ -0,0 +1,190 @@
# TUBCO Customer Setup Runbook
Use this runbook when you want to set up or rebuild the TUBCO customer deployment from scratch.
This is the customer-specific path. Normal product work still happens on:
- `develop`
- `main`
TUBCO delivery happens from:
- `release/tubco-v1`
## 1. Use the right branch
```bash
git checkout release/tubco-v1
git pull --ff-only origin release/tubco-v1
```
If you plan to push an approved customer fix to the TUBCO remote:
```bash
./scripts/git_remote_target.sh status
./scripts/git_remote_target.sh push-tubco release/tubco-v1
```
## 2. Prepare the target server once
Target assumptions:
- repo path: `/opt/workdock`
- env file: `.env.prod`
- public URL: `https://portal.tub.co`
Create the server env file from the example:
```bash
ssh root@<customer-host>
cd /opt/workdock
cp .env.prod.example .env.prod
```
Required values to review in `.env.prod`:
- `APP_DOMAIN=portal.tub.co`
- `APP_BASE_URL=https://portal.tub.co`
- `DJANGO_ALLOWED_HOSTS=portal.tub.co,...`
- `DJANGO_CSRF_TRUSTED_ORIGINS=https://portal.tub.co`
- `DJANGO_DEBUG=0`
- `DJANGO_SECURE_COOKIES=1`
- `DJANGO_SECURE_SSL_REDIRECT=1`
- `DJANGO_SECRET_KEY=<strong secret>`
- `POSTGRES_PASSWORD=<strong secret>`
## 3. Optional: export the intended local TUBCO config baseline
Run from the local repo:
```bash
docker compose exec -T web python manage.py export_portal_app_config --output /tmp/portal-app-config.json
docker compose exec -T web python manage.py export_portal_deployment_config --output /tmp/portal-deployment-config.json
docker compose cp web:/tmp/portal-app-config.json /tmp/portal-app-config.json
docker compose cp web:/tmp/portal-deployment-config.json /tmp/portal-deployment-config.json
```
This gives you the local baseline for:
- app registry visibility/order
- branding text and colors
- company metadata
## 4. Reset the customer stack from scratch
This is destructive and wipes:
- database state
- generated media/documents
- staticfiles volume
- backup volume
Run:
```bash
git checkout release/tubco-v1
RESET_CONFIRM=RESET \
EXPECTED_BRANCH=release/tubco-v1 \
DEPLOY_HOST=root@<customer-host> \
DEPLOY_PATH=/opt/workdock \
REMOTE_ENV_FILE=.env.prod \
HEALTH_URL=https://portal.tub.co/healthz/ \
RUN_DJANGO_CHECK=1 \
./scripts/reset_stack_from_mac.sh
```
Use this when you want a fresh customer environment with only default bootstrap data.
## 5. Import the TUBCO config baseline
Copy the exported JSON to the target host:
```bash
scp -4 /tmp/portal-app-config.json /tmp/portal-deployment-config.json root@<customer-host>:/opt/workdock/
```
Copy the files into the running web container:
```bash
ssh -4 root@<customer-host> '
docker cp /opt/workdock/portal-app-config.json workdock-web-1:/tmp/portal-app-config.json &&
docker cp /opt/workdock/portal-deployment-config.json workdock-web-1:/tmp/portal-deployment-config.json
'
```
Dry-run the import first:
```bash
ssh -4 root@<customer-host> '
docker exec workdock-web-1 python manage.py import_portal_app_config /tmp/portal-app-config.json --dry-run &&
docker exec workdock-web-1 python manage.py import_portal_deployment_config /tmp/portal-deployment-config.json --dry-run
'
```
If the dry run looks correct, apply it:
```bash
ssh -4 root@<customer-host> '
docker exec workdock-web-1 python manage.py import_portal_app_config /tmp/portal-app-config.json &&
docker exec workdock-web-1 python manage.py import_portal_deployment_config /tmp/portal-deployment-config.json
'
```
Note:
- uploaded branding assets are not included in this JSON sync
- logo, favicon, and PDF letterhead still need explicit upload on the customer system
## 6. Normal customer deployment after the first setup
After the first reset/bootstrap, regular TUBCO updates should usually be deploys, not resets.
If you do not want a destructive reset, use the normal deploy path:
```bash
git checkout release/tubco-v1
rsync -az --delete \
--filter 'P .env.test' \
--filter 'P .env.prod' \
--exclude '.git' \
--exclude '.github' \
--exclude '.venv' \
--exclude '__pycache__' \
--exclude 'node_modules' \
--exclude 'backend/media' \
--exclude 'backend/staticfiles' \
-e 'ssh -4' \
/Users/bostame/Documents/workdock-platform/ \
root@<customer-host>:/opt/workdock/
ssh -4 root@<customer-host> '
cd /opt/workdock &&
RUN_DJANGO_CHECK=1 DEPLOY_HEALTH_URL="https://portal.tub.co/healthz/" ./scripts/deploy_stack.sh .env.prod docker-compose.prod.yml
'
```
## 7. Verify the deployment
Health:
```bash
curl -I https://portal.tub.co/healthz/
```
Container state:
```bash
ssh -4 root@<customer-host> '
cd /opt/workdock &&
docker compose --env-file .env.prod -f docker-compose.prod.yml ps
'
```
Django checks inside the running app:
```bash
ssh -4 root@<customer-host> '
docker exec workdock-web-1 python manage.py check
'
```
## 8. Customer policy
For TUBCO:
- deploy from `release/tubco-v1`
- do not give them future product features by default
- only backport approved:
- bug fixes
- security updates
- UI improvements
Do not deploy TUBCO from:
- `develop`
- `main`
## 9. Access and roles
Current intended customer rule:
- highest customer-facing application role: `Super Admin`
- `Platform Owner` remains product-level
TUBCO-specific behavior already in the customer branch:
- `Super Admin` can access:
- Branding
- Company Config
- `Super Admin` cannot access:
- App Registry
- Trial Management
- Django Admin link

View File

@@ -38,6 +38,7 @@
<a href="#hosts">Hosts & Domains</a> <a href="#hosts">Hosts & Domains</a>
<a href="#cicd">CI/CD</a> <a href="#cicd">CI/CD</a>
<a href="#deploy">Deployment</a> <a href="#deploy">Deployment</a>
<a href="#tubco">TUBCO Setup</a>
<a href="#commands">Commands</a> <a href="#commands">Commands</a>
<a href="#troubleshooting">Troubleshooting</a> <a href="#troubleshooting">Troubleshooting</a>
<a href="#security">Security</a> <a href="#security">Security</a>
@@ -661,6 +662,84 @@ lxc.mount.entry: /dev/null sys/module/apparmor/parameters/enabled none bind 0 0<
<li>Take a snapshot commit before major next-phase work</li> <li>Take a snapshot commit before major next-phase work</li>
</ol> </ol>
<h2 id="tubco">18b) TUBCO Customer Setup</h2>
<div class="box">
<h3>What this branch is for</h3>
<ul>
<li><code>release/tubco-v1</code> is the frozen TUBCO customer branch.</li>
<li>It should receive only approved bug fixes, security updates, and UI improvements.</li>
<li>Do not deploy TUBCO from <code>develop</code> or <code>main</code>.</li>
</ul>
</div>
<div class="box">
<h3>First-time customer setup</h3>
<ol>
<li>Check out <code>release/tubco-v1</code>.</li>
<li>Create <code>.env.prod</code> on the target server.</li>
<li>Run the destructive reset/bootstrap helper from the Mac.</li>
<li>Import the intended TUBCO config baseline.</li>
<li>Verify <code>https://portal.tub.co/healthz/</code>.</li>
</ol>
<pre><code>git checkout release/tubco-v1
RESET_CONFIRM=RESET \
EXPECTED_BRANCH=release/tubco-v1 \
DEPLOY_HOST=root@&lt;customer-host&gt; \
DEPLOY_PATH=/opt/workdock \
REMOTE_ENV_FILE=.env.prod \
HEALTH_URL=https://portal.tub.co/healthz/ \
RUN_DJANGO_CHECK=1 \
./scripts/reset_stack_from_mac.sh</code></pre>
</div>
<div class="box">
<h3>Required production env values</h3>
<pre><code>APP_DOMAIN=portal.tub.co
APP_BASE_URL=https://portal.tub.co
DJANGO_DEBUG=0
DJANGO_SECURE_COOKIES=1
DJANGO_SECURE_SSL_REDIRECT=1</code></pre>
<p>The customer server also needs strong values for <code>DJANGO_SECRET_KEY</code> and <code>POSTGRES_PASSWORD</code>.</p>
</div>
<div class="box">
<h3>Config baseline import</h3>
<p>Export the intended local baseline:</p>
<pre><code>docker compose exec -T web python manage.py export_portal_app_config --output /tmp/portal-app-config.json
docker compose exec -T web python manage.py export_portal_deployment_config --output /tmp/portal-deployment-config.json
docker compose cp web:/tmp/portal-app-config.json /tmp/portal-app-config.json
docker compose cp web:/tmp/portal-deployment-config.json /tmp/portal-deployment-config.json</code></pre>
<p>Copy the payloads to the customer server and then into the running web container:</p>
<pre><code>scp -4 /tmp/portal-app-config.json /tmp/portal-deployment-config.json root@&lt;customer-host&gt;:/opt/workdock/
ssh -4 root@&lt;customer-host&gt; '
docker cp /opt/workdock/portal-app-config.json workdock-web-1:/tmp/portal-app-config.json &amp;&amp;
docker cp /opt/workdock/portal-deployment-config.json workdock-web-1:/tmp/portal-deployment-config.json
'</code></pre>
<p>Dry-run first, then apply:</p>
<pre><code>ssh -4 root@&lt;customer-host&gt; '
docker exec workdock-web-1 python manage.py import_portal_app_config /tmp/portal-app-config.json --dry-run &amp;&amp;
docker exec workdock-web-1 python manage.py import_portal_deployment_config /tmp/portal-deployment-config.json --dry-run
'
ssh -4 root@&lt;customer-host&gt; '
docker exec workdock-web-1 python manage.py import_portal_app_config /tmp/portal-app-config.json &amp;&amp;
docker exec workdock-web-1 python manage.py import_portal_deployment_config /tmp/portal-deployment-config.json
'</code></pre>
<p>Uploaded assets such as logo, favicon, and PDF letterhead are still separate media and need explicit upload.</p>
</div>
<div class="box">
<h3>Normal TUBCO updates</h3>
<p>When you intentionally want to update the customer branch remote:</p>
<pre><code>./scripts/git_remote_target.sh status
./scripts/git_remote_target.sh push-tubco release/tubco-v1</code></pre>
<p>Use a TUBCO personal access token stored in the macOS keychain, not a reusable account password.</p>
</div>
<div class="box">
<h3>Customer role boundary</h3>
<ul>
<li>TUBCO should work primarily with <code>Super Admin</code> and below.</li>
<li>In the customer branch, <code>Super Admin</code> can access Branding and Company Config.</li>
<li><code>App Registry</code>, <code>Trial Management</code>, and the Django admin link remain platform-level.</li>
</ul>
</div>
<h2 id="commands">19) Command Reference</h2> <h2 id="commands">19) Command Reference</h2>
<div class="box"> <div class="box">
<h3>Local development</h3> <h3>Local development</h3>
@@ -700,6 +779,21 @@ RUN_DJANGO_CHECK=1 \
./scripts/reset_stack_from_mac.sh</code></pre> ./scripts/reset_stack_from_mac.sh</code></pre>
<p>Use the second form for a customer setup from scratch. This is destructive and removes database/media/static/backups before bootstrapping again.</p> <p>Use the second form for a customer setup from scratch. This is destructive and removes database/media/static/backups before bootstrapping again.</p>
</div> </div>
<div class="box">
<h3>TUBCO setup</h3>
<pre><code>git checkout release/tubco-v1
RESET_CONFIRM=RESET \
EXPECTED_BRANCH=release/tubco-v1 \
DEPLOY_HOST=root@&lt;customer-host&gt; \
DEPLOY_PATH=/opt/workdock \
REMOTE_ENV_FILE=.env.prod \
HEALTH_URL=https://portal.tub.co/healthz/ \
RUN_DJANGO_CHECK=1 \
./scripts/reset_stack_from_mac.sh</code></pre>
<p>Rebuild a fresh TUBCO environment from the customer branch.</p>
<pre><code>./scripts/git_remote_target.sh push-tubco release/tubco-v1</code></pre>
<p>Push an explicitly approved customer update to the TUBCO remote.</p>
</div>
<div class="box"> <div class="box">
<h3>Production deployment</h3> <h3>Production deployment</h3>
<pre><code>./scripts/deploy_prod_from_mac.sh</code></pre> <pre><code>./scripts/deploy_prod_from_mac.sh</code></pre>