Compare commits
5 Commits
89cc11e41e
...
baf53a3274
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
baf53a3274 | ||
|
|
541736a9a2 | ||
|
|
80cb7a409d | ||
|
|
5867d85e96 | ||
|
|
8f61e43e9b |
39
.githooks/pre-push
Executable file
39
.githooks/pre-push
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
remote_name="${1:-}"
|
||||
remote_url="${2:-}"
|
||||
|
||||
if [[ "$remote_name" != "tubco" && "$remote_url" != *"git.tub.co"* ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
allowed=0
|
||||
|
||||
while read -r local_ref local_sha remote_ref remote_sha; do
|
||||
[[ -z "${local_ref:-}" ]] && continue
|
||||
|
||||
case "$local_ref" in
|
||||
refs/heads/release/tubco-*)
|
||||
allowed=1
|
||||
;;
|
||||
refs/tags/tubco-baseline-*)
|
||||
allowed=1
|
||||
;;
|
||||
*)
|
||||
echo "Blocked push to 'tubco': '$local_ref' is not an approved customer ref." >&2
|
||||
echo "Allowed refs:" >&2
|
||||
echo " refs/heads/release/tubco-*" >&2
|
||||
echo " refs/tags/tubco-baseline-*" >&2
|
||||
echo "Use origin for normal product work." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$allowed" -eq 0 ]]; then
|
||||
echo "Blocked push to 'tubco': no approved TUBCO refs were detected." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -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:
|
||||
- `DEPLOYMENT.md`
|
||||
- `TUBCO_SETUP.md`
|
||||
- `backend/workflows/templates/workflows/project_wiki.html`
|
||||
- `backend/workflows/templates/workflows/developer_handbook.html`
|
||||
|
||||
@@ -34,6 +35,44 @@ Rule:
|
||||
|
||||
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
|
||||
This repository now has two different Git remotes with different purposes:
|
||||
- `origin`: the normal GitHub product remote
|
||||
- `tubco`: the TUBCO customer remote
|
||||
|
||||
Use the helper command to avoid mixing them up:
|
||||
|
||||
```bash
|
||||
./scripts/git_remote_target.sh status
|
||||
```
|
||||
|
||||
Common commands:
|
||||
|
||||
```bash
|
||||
./scripts/git_remote_target.sh push-origin
|
||||
./scripts/git_remote_target.sh push-tubco release/tubco-v1
|
||||
./scripts/git_remote_target.sh set-own-identity
|
||||
./scripts/git_remote_target.sh set-tubco-identity
|
||||
```
|
||||
|
||||
Default rule:
|
||||
- normal product work goes to `origin`
|
||||
- TUBCO pushes happen only when explicitly requested
|
||||
|
||||
Safety rules in this repo:
|
||||
- plain `git push` should default to `origin`
|
||||
- pushes to `tubco` are guarded by a repo-local `pre-push` hook
|
||||
- only these refs should go to `tubco`:
|
||||
- `release/tubco-*`
|
||||
- `tubco-baseline-*`
|
||||
|
||||
Authentication rule:
|
||||
- prefer a personal access token for the `tubco` HTTPS remote
|
||||
- do not rely on a reusable account password long term
|
||||
- store the PAT through macOS keychain using the repo-local `credential.helper=osxkeychain` setting
|
||||
|
||||
## Current Delivery Model
|
||||
- GitHub Actions is used for CI
|
||||
- the current test server is local/LAN-only
|
||||
@@ -45,6 +84,12 @@ Standard test deployment command:
|
||||
./scripts/deploy_test_from_mac.sh
|
||||
```
|
||||
|
||||
Destructive fresh reset command:
|
||||
|
||||
```bash
|
||||
RESET_CONFIRM=RESET EXPECTED_BRANCH=develop ./scripts/reset_stack_from_mac.sh
|
||||
```
|
||||
|
||||
Why:
|
||||
- GitHub-hosted runners cannot reliably reach the private LAN target at `192.168.2.55`
|
||||
|
||||
|
||||
@@ -217,6 +217,83 @@ HEALTH_URL=http://192.168.2.55:8088/healthz/ \
|
||||
./scripts/deploy_test_from_mac.sh
|
||||
```
|
||||
|
||||
## Reset a stack from scratch
|
||||
Use this only when you intentionally want a fresh environment with default bootstrap data.
|
||||
|
||||
What it does:
|
||||
1. syncs the current checkout to the target server
|
||||
2. stops the stack
|
||||
3. removes compose volumes with `down -v`
|
||||
4. rebuilds and bootstraps the stack again
|
||||
5. verifies the health endpoint
|
||||
|
||||
Local test server reset:
|
||||
```bash
|
||||
git checkout develop
|
||||
RESET_CONFIRM=RESET EXPECTED_BRANCH=develop ./scripts/reset_stack_from_mac.sh
|
||||
```
|
||||
|
||||
Customer/TUBCO reset example:
|
||||
```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
|
||||
```
|
||||
|
||||
This is destructive. It wipes:
|
||||
- database state
|
||||
- generated documents/media
|
||||
- staticfiles volume
|
||||
- backup volume
|
||||
|
||||
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
|
||||
For production, use a dedicated helper instead of the test script.
|
||||
|
||||
@@ -329,6 +406,9 @@ ssh -4 root@192.168.2.55 '
|
||||
- PDF letterhead
|
||||
- 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
|
||||
### Test deployment workflow
|
||||
File:
|
||||
|
||||
190
TUBCO_SETUP.md
Normal file
190
TUBCO_SETUP.md
Normal 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
|
||||
@@ -38,6 +38,7 @@
|
||||
<a href="#hosts">Hosts & Domains</a>
|
||||
<a href="#cicd">CI/CD</a>
|
||||
<a href="#deploy">Deployment</a>
|
||||
<a href="#tubco">TUBCO Setup</a>
|
||||
<a href="#commands">Commands</a>
|
||||
<a href="#troubleshooting">Troubleshooting</a>
|
||||
<a href="#security">Security</a>
|
||||
@@ -94,6 +95,18 @@
|
||||
<li>promote <code>develop</code> into <code>main</code> when stable</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="box">
|
||||
<h3>Dual remote rule</h3>
|
||||
<ul>
|
||||
<li><code>origin</code> is the normal product remote on GitHub.</li>
|
||||
<li><code>tubco</code> is the customer remote for TUBCO only.</li>
|
||||
<li>Normal day-to-day work continues on <code>origin</code>.</li>
|
||||
<li>Push to <code>tubco</code> only when you explicitly want to update the customer branch.</li>
|
||||
</ul>
|
||||
<pre><code>./scripts/git_remote_target.sh status</code></pre>
|
||||
<p>Use the helper above before pushing if there is any doubt about which remote should receive the change.</p>
|
||||
<p>Plain <code>git push</code> should default to <code>origin</code>, and a repo-local <code>pre-push</code> hook blocks accidental pushes to <code>tubco</code> unless the ref is an approved TUBCO branch or baseline tag.</p>
|
||||
</div>
|
||||
<div class="box">
|
||||
<h3>Customer release branches</h3>
|
||||
<p>Use a dedicated release branch when a customer should receive the current stable product line but not future features by default.</p>
|
||||
@@ -649,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>
|
||||
</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@<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</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@<customer-host>:/opt/workdock/
|
||||
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
|
||||
'</code></pre>
|
||||
<p>Dry-run first, then apply:</p>
|
||||
<pre><code>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
|
||||
'
|
||||
|
||||
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
|
||||
'</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>
|
||||
<div class="box">
|
||||
<h3>Local development</h3>
|
||||
@@ -657,6 +748,8 @@ lxc.mount.entry: /dev/null sys/module/apparmor/parameters/enabled none bind 0 0<
|
||||
<pre><code>docker compose restart web
|
||||
docker compose restart worker</code></pre>
|
||||
<p>Restart app services after code or template changes.</p>
|
||||
<pre><code>./scripts/git_remote_target.sh status</code></pre>
|
||||
<p>Show the current branch, active local identity, and both remotes before pushing.</p>
|
||||
</div>
|
||||
<div class="box">
|
||||
<h3>Validation</h3>
|
||||
@@ -670,11 +763,53 @@ docker compose restart worker</code></pre>
|
||||
<pre><code>./scripts/deploy_test_from_mac.sh</code></pre>
|
||||
<p>Sync the current <code>develop</code> checkout to the LAN test server and deploy it.</p>
|
||||
</div>
|
||||
<div class="box">
|
||||
<h3>Reset a stack from scratch</h3>
|
||||
<pre><code>git checkout develop
|
||||
RESET_CONFIRM=RESET EXPECTED_BRANCH=develop ./scripts/reset_stack_from_mac.sh</code></pre>
|
||||
<p>Wipe the current test stack state and rebuild it with default bootstrap data.</p>
|
||||
<pre><code>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</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>
|
||||
</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@<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</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">
|
||||
<h3>Production deployment</h3>
|
||||
<pre><code>./scripts/deploy_prod_from_mac.sh</code></pre>
|
||||
<p>Sync the current <code>main</code> checkout to the production target and deploy it with production checks enabled.</p>
|
||||
</div>
|
||||
<div class="box">
|
||||
<h3>Remote targeting</h3>
|
||||
<pre><code>./scripts/git_remote_target.sh push-origin
|
||||
./scripts/git_remote_target.sh push-tubco release/tubco-v1</code></pre>
|
||||
<p>Push to the intended remote explicitly instead of relying on memory.</p>
|
||||
<pre><code>./scripts/git_remote_target.sh set-own-identity
|
||||
./scripts/git_remote_target.sh set-tubco-identity</code></pre>
|
||||
<p>Switch between the normal commit identity and the TUBCO customer identity when needed.</p>
|
||||
<p>For the TUBCO HTTPS remote, prefer a personal access token instead of a reusable account password.</p>
|
||||
<p>This repo now uses <code>credential.helper=osxkeychain</code> locally, so the TUBCO PAT should be stored in the macOS keychain instead of being embedded in remote URLs.</p>
|
||||
</div>
|
||||
<div class="box">
|
||||
<h3>Direct server deployment</h3>
|
||||
<pre><code>cd /opt/workdock
|
||||
|
||||
116
scripts/git_remote_target.sh
Executable file
116
scripts/git_remote_target.sh
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
TUBCO_NAME="bostame"
|
||||
TUBCO_EMAIL="mdbayazid@tub.co"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./scripts/git_remote_target.sh status
|
||||
./scripts/git_remote_target.sh push-origin [ref]
|
||||
./scripts/git_remote_target.sh push-tubco [ref]
|
||||
./scripts/git_remote_target.sh set-own-identity
|
||||
./scripts/git_remote_target.sh set-tubco-identity
|
||||
|
||||
Commands:
|
||||
status
|
||||
Show current branch, current commit, active local git identity, configured remotes,
|
||||
and the local push-safety settings.
|
||||
|
||||
push-origin [ref]
|
||||
Push a ref to the main product remote "origin".
|
||||
Default ref: current branch
|
||||
|
||||
push-tubco [ref]
|
||||
Push a ref to the TUBCO customer remote "tubco".
|
||||
Default ref: release/tubco-v1
|
||||
|
||||
set-own-identity
|
||||
Remove the repo-local git user.name and user.email so this repo falls back to your normal identity.
|
||||
|
||||
set-tubco-identity
|
||||
Set the repo-local git identity to the TUBCO customer identity.
|
||||
EOF
|
||||
}
|
||||
|
||||
require_remote() {
|
||||
local remote="$1"
|
||||
git remote get-url "$remote" >/dev/null 2>&1 || {
|
||||
echo "Missing git remote: $remote" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
current_branch() {
|
||||
git branch --show-current
|
||||
}
|
||||
|
||||
show_identity() {
|
||||
local local_name local_email
|
||||
local_name="$(git config --local --get user.name || true)"
|
||||
local_email="$(git config --local --get user.email || true)"
|
||||
if [[ -n "$local_name" || -n "$local_email" ]]; then
|
||||
echo "Local repo identity: ${local_name:-<unset>} <${local_email:-unset}>"
|
||||
else
|
||||
echo "Local repo identity: not set in this repo (falls back to global/default git config)"
|
||||
fi
|
||||
}
|
||||
|
||||
show_push_safety() {
|
||||
local push_default hooks_path
|
||||
push_default="$(git config --local --get remote.pushDefault || true)"
|
||||
hooks_path="$(git config --local --get core.hooksPath || true)"
|
||||
echo "remote.pushDefault: ${push_default:-<unset>}"
|
||||
echo "core.hooksPath: ${hooks_path:-<unset>}"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
status)
|
||||
echo "Branch: $(current_branch)"
|
||||
echo "Commit: $(git rev-parse --short HEAD)"
|
||||
show_identity
|
||||
show_push_safety
|
||||
echo
|
||||
echo "Remotes:"
|
||||
git remote -v
|
||||
echo
|
||||
echo "Recommended targets:"
|
||||
echo " develop/main/internal work -> origin"
|
||||
echo " release/tubco-v1 and approved customer backports -> tubco"
|
||||
;;
|
||||
|
||||
push-origin)
|
||||
require_remote origin
|
||||
ref="${2:-$(current_branch)}"
|
||||
echo "Pushing '$ref' to origin..."
|
||||
git push origin "$ref"
|
||||
;;
|
||||
|
||||
push-tubco)
|
||||
require_remote tubco
|
||||
ref="${2:-release/tubco-v1}"
|
||||
echo "Pushing '$ref' to tubco..."
|
||||
git push tubco "$ref"
|
||||
;;
|
||||
|
||||
set-own-identity)
|
||||
git config --local --unset-all user.name >/dev/null 2>&1 || true
|
||||
git config --local --unset-all user.email >/dev/null 2>&1 || true
|
||||
echo "Cleared repo-local git identity. This repo will now use your normal git identity."
|
||||
;;
|
||||
|
||||
set-tubco-identity)
|
||||
git config --local user.name "$TUBCO_NAME"
|
||||
git config --local user.email "$TUBCO_EMAIL"
|
||||
echo "Set repo-local git identity to: $TUBCO_NAME <$TUBCO_EMAIL>"
|
||||
;;
|
||||
|
||||
*)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
82
scripts/reset_stack_from_mac.sh
Normal file
82
scripts/reset_stack_from_mac.sh
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
DEPLOY_HOST="${DEPLOY_HOST:-root@192.168.2.55}"
|
||||
DEPLOY_PATH="${DEPLOY_PATH:-/opt/workdock}"
|
||||
HEALTH_URL="${HEALTH_URL:-http://192.168.2.55:8088/healthz/}"
|
||||
REMOTE_ENV_FILE="${REMOTE_ENV_FILE:-.env.test}"
|
||||
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.prod.yml}"
|
||||
RUN_DJANGO_CHECK="${RUN_DJANGO_CHECK:-0}"
|
||||
SSH_CMD="${SSH_CMD:-ssh -4}"
|
||||
RSYNC_SSH="${RSYNC_SSH:-ssh -4}"
|
||||
EXPECTED_BRANCH="${EXPECTED_BRANCH:-}"
|
||||
RESET_CONFIRM="${RESET_CONFIRM:-}"
|
||||
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
current_branch="$(git branch --show-current)"
|
||||
|
||||
if [[ -n "$EXPECTED_BRANCH" && "$current_branch" != "$EXPECTED_BRANCH" ]]; then
|
||||
echo "Expected branch '$EXPECTED_BRANCH' for this reset, got '$current_branch'." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$RESET_CONFIRM" != "RESET" ]]; then
|
||||
cat >&2 <<EOF
|
||||
Refusing destructive reset.
|
||||
|
||||
This command wipes the stack state for:
|
||||
$DEPLOY_HOST:$DEPLOY_PATH
|
||||
|
||||
It will remove:
|
||||
- database volume/data
|
||||
- generated media
|
||||
- staticfiles volume
|
||||
- backup volume
|
||||
|
||||
Run again with:
|
||||
RESET_CONFIRM=RESET ./scripts/reset_stack_from_mac.sh
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Current branch: $current_branch"
|
||||
echo "Updating local branch from origin/$current_branch ..."
|
||||
git pull --ff-only origin "$current_branch"
|
||||
|
||||
echo "Checking remote env file..."
|
||||
$SSH_CMD "$DEPLOY_HOST" "test -f '$DEPLOY_PATH/$REMOTE_ENV_FILE'" || {
|
||||
echo "Missing remote env file: $DEPLOY_PATH/$REMOTE_ENV_FILE" >&2
|
||||
echo "Create or restore the server env file before resetting." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "Syncing repository to ${DEPLOY_HOST}:${DEPLOY_PATH} ..."
|
||||
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 "$RSYNC_SSH" \
|
||||
"$REPO_ROOT"/ \
|
||||
"${DEPLOY_HOST}:${DEPLOY_PATH}/"
|
||||
|
||||
echo "Resetting remote stack state..."
|
||||
$SSH_CMD "$DEPLOY_HOST" "cd '$DEPLOY_PATH' && export APP_ENV_FILE='$REMOTE_ENV_FILE' && docker compose --env-file '$REMOTE_ENV_FILE' -f '$COMPOSE_FILE' down -v"
|
||||
|
||||
echo "Rebuilding and bootstrapping fresh stack..."
|
||||
$SSH_CMD "$DEPLOY_HOST" \
|
||||
"cd '$DEPLOY_PATH' && RUN_DJANGO_CHECK='$RUN_DJANGO_CHECK' DEPLOY_HEALTH_URL='$HEALTH_URL' ./scripts/deploy_stack.sh '$REMOTE_ENV_FILE' '$COMPOSE_FILE'"
|
||||
|
||||
echo "Verifying health endpoint..."
|
||||
curl --fail --silent --show-error --max-time 10 "$HEALTH_URL" >/dev/null
|
||||
commit_sha="$(git rev-parse --short HEAD)"
|
||||
echo "Reset deployment healthy: $HEALTH_URL"
|
||||
echo "Deployed commit: $commit_sha"
|
||||
echo "Branch: $current_branch"
|
||||
Reference in New Issue
Block a user