Skip to content

Commit 08f8795

Browse files
tyler-daneclaude
andauthored
feat(ci-cd): auto bump patch version and deploy to staging on merge (#1740)
* feat(ci-cd): auto bump patch version and deploy to staging on merge Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(install): resolve 'latest' to 'main' git ref for file downloads Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: update install.sh with env link * docs: update self-hosting guides * docs: add workflow doc --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2880815 commit 08f8795

8 files changed

Lines changed: 139 additions & 66 deletions

File tree

.github/workflows/bump-and-tag.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Bump version and tag
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
bump:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Check out repository
17+
uses: actions/checkout@v6
18+
with:
19+
fetch-depth: 0
20+
21+
- name: Bump patch version and push tag
22+
run: |
23+
LATEST=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
24+
[ -z "$LATEST" ] && LATEST="v0.0.0"
25+
VERSION="${LATEST#v}"
26+
MAJOR="${VERSION%%.*}"
27+
REST="${VERSION#*.}"
28+
MINOR="${REST%%.*}"
29+
PATCH="${REST#*.}"
30+
NEW_TAG="v${MAJOR}.${MINOR}.$((PATCH + 1))"
31+
echo "Bumping ${LATEST} → ${NEW_TAG}"
32+
git tag "$NEW_TAG"
33+
git push origin "$NEW_TAG"

.github/workflows/publish-images.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,16 @@ jobs:
7474
switchbacktech/compass-web:${{ steps.version.outputs.version }}
7575
switchbacktech/compass-web:${{ steps.version.outputs.minor }}
7676
switchbacktech/compass-web:latest
77+
78+
deploy-staging:
79+
needs: publish
80+
runs-on: ubuntu-latest
81+
82+
steps:
83+
- name: Deploy to staging
84+
uses: appleboy/ssh-action@v1
85+
with:
86+
host: ${{ secrets.STAGING_SSH_HOST }}
87+
username: ${{ secrets.STAGING_SSH_USER }}
88+
key: ${{ secrets.STAGING_SSH_KEY }}
89+
script: cd ~/compass && ./compass update

docs/CI-CD/README.md

Lines changed: 0 additions & 57 deletions
This file was deleted.

docs/CI-CD/workflows.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Workflows
2+
3+
Compass uses GitHub Actions for continuous integration, Docker Hub for image distribution, and a VPS for staging.
4+
5+
| Workflow | Trigger | Purpose |
6+
|---|---|---|
7+
| Test | Push / PR to `main` | Runs lint, type-check, and unit tests |
8+
| CodeQL | Push / PR to `main` | Static security analysis |
9+
| Bump version and tag | Push to `main` | Auto-increments patch version, pushes a new semver tag |
10+
| Publish Docker images | Push a `v*.*.*` tag | Builds images, pushes to Docker Hub, deploys to staging |
11+
| Sync docs to compass-docs | Push to `main` touching `docs/**` | Mirrors this `docs/` directory to docs.compasscalendar.com |
12+
13+
---
14+
15+
## Release Flow
16+
17+
Every PR merge to `main` triggers a fully automated chain:
18+
19+
```
20+
PR merged to main
21+
└─► bump-and-tag.yml — reads latest tag, pushes v1.2.X+1
22+
└─► publish-images.yml — builds & pushes images to Docker Hub
23+
└─► deploy-staging — SSHes into VPS, runs ./compass update
24+
```
25+
26+
**Monthly minor/major releases** remain manual: a maintainer pushes a tag like `v1.3.0` or `v2.0.0`, which skips the bump step and goes straight to publish + deploy.
27+
28+
### Removing a test tag
29+
30+
```sh
31+
git push origin --delete v1.2.3
32+
git tag -d v1.2.3
33+
```
34+
35+
---
36+
37+
## Publish Docker Images
38+
39+
Source: [`.github/workflows/publish-images.yml`](../../.github/workflows/publish-images.yml)
40+
41+
### How it works
42+
43+
1. A semver tag matching `v[0-9]+.[0-9]+.[0-9]+` is pushed (either by `bump-and-tag.yml` or manually).
44+
2. The workflow strips the `v` prefix and derives two tag aliases:
45+
- `1.2.3` — exact patch version
46+
- `1.2` — floating minor alias
47+
3. It builds and pushes three images to [our Docker Hub](https://hub.docker.com/repositories/switchbacktech):
48+
- `switchbacktech/compass-backend`
49+
- `switchbacktech/compass-mongo`
50+
- `switchbacktech/compass-web`
51+
4. Each image gets all three tags: `1.2.3`, `1.2`, and `latest`.
52+
5. After all images are pushed, the `deploy-staging` job runs.
53+
54+
### Tag pattern rules
55+
56+
Only clean semver tags trigger the workflow. Tags with suffixes (e.g. `v1.2.3-test`) do not match and are safe to push for local testing without triggering a deploy.
57+
58+
---
59+
60+
## Staging Deploy
61+
62+
Source: `deploy-staging` job in [`.github/workflows/publish-images.yml`](../../.github/workflows/publish-images.yml)
63+
64+
The deploy job SSHes into the staging VPS and runs `./compass update`, which pulls the latest Docker Hub images and restarts the stack.
65+
66+
### Required secrets
67+
68+
All secrets go in **GitHub → Settings → Secrets and variables → Actions**:
69+
70+
| Secret | Value |
71+
|---|---|
72+
| `DOCKERHUB_USERNAME` | Docker Hub username for the `switchbacktech` org |
73+
| `DOCKERHUB_TOKEN` | Docker Hub personal access token (Read & Write) |
74+
| `STAGING_SSH_HOST` | VPS IP address or hostname |
75+
| `STAGING_SSH_USER` | Linux user on the VPS that owns `~/compass` |
76+
| `STAGING_SSH_KEY` | Private key from the deploy keypair (the `compass-staging-deploy` file, not `.pub`) |
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Back up and restore your data
1+
# Back up and Restore
22

33
For the Docker install created by `self-host/install.sh` on your server. Backups are manual today. The installer and `./compass update` do not create them for you.
44

docs/self-hosting/google-calendar.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Add Google Calendar (optional)
1+
# Add Google Calendar
22

33
Google Calendar is optional for self-hosting. Compass works fine with email and password alone. Pick a mode based on what you actually need.
44

docs/self-hosting/server-guide.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Run Compass on a server
1+
# Setup Compass On A Server
22

33
This guide describes the recommended first server setup for one person hosting Compass on a small VPS. One public domain, Compass on `127.0.0.1`, Caddy in front for HTTPS.
44

self-host/install.sh

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
COMPASS_HOME=${COMPASS_HOME:-$HOME/compass}
44
COMPASS_VERSION=${COMPASS_VERSION:-latest}
55
COMPASS_RAW_URL=https://raw.githubusercontent.com/SwitchbackTech/compass
6+
# 'latest' is a Docker Hub tag, not a git ref. Map it to 'main' for raw file downloads.
7+
case $COMPASS_VERSION in
8+
latest) COMPASS_GIT_REF=main ;;
9+
*) COMPASS_GIT_REF=$COMPASS_VERSION ;;
10+
esac
611

712
ENV_FILE=$COMPASS_HOME/.env
813
MARKER_FILE=$COMPASS_HOME/.compass-self-host
@@ -428,6 +433,9 @@ write_env_if_missing() {
428433
umask 077
429434
TMP_ENV=$COMPASS_HOME/.env.$$
430435
cat > "$TMP_ENV" <<EOF
436+
# See for a detailed description of each variable here:
437+
# https://docs.compasscalendar.com/docs/self-hosting/environment-variables
438+
431439
# Compose
432440
COMPASS_VERSION=$COMPASS_VERSION
433441
@@ -441,9 +449,9 @@ TZ=Etc/UTC
441449
LOG_LEVEL=info
442450
443451
# Local URLs
444-
FRONTEND_URL=http://localhost:9080
445-
BASEURL=http://localhost:3000/api
446-
CORS=http://localhost:9080,http://localhost:3000
452+
FRONTEND_URL=https://cal.yourdomain.com
453+
BASEURL=https://cal.yourdomain.com/api
454+
CORS=https://cal.yourdomain.com
447455
448456
# Compass MongoDB
449457
MONGO_INITDB_ROOT_USERNAME=compass
@@ -479,7 +487,7 @@ EOF
479487
download_compose_file() {
480488
tmp_compose=$COMPASS_HOME/docker-compose.yml.$$
481489
info "Downloading docker-compose.yml for Compass ${COMPASS_VERSION}."
482-
curl -fsSL "${COMPASS_RAW_URL}/${COMPASS_VERSION}/self-host/docker-compose.yml" -o "$tmp_compose" \
490+
curl -fsSL "${COMPASS_RAW_URL}/${COMPASS_GIT_REF}/self-host/docker-compose.yml" -o "$tmp_compose" \
483491
|| fail "Could not download docker-compose.yml for version ${COMPASS_VERSION}. Check that the version exists."
484492

485493
if [ -f "$COMPOSE_FILE" ]; then
@@ -491,7 +499,7 @@ download_compose_file() {
491499

492500
download_helper() {
493501
info "Downloading compass helper for Compass ${COMPASS_VERSION}."
494-
curl -fsSL "${COMPASS_RAW_URL}/${COMPASS_VERSION}/self-host/compass" -o "$HELPER_FILE" \
502+
curl -fsSL "${COMPASS_RAW_URL}/${COMPASS_GIT_REF}/self-host/compass" -o "$HELPER_FILE" \
495503
|| fail "Could not download compass helper for version ${COMPASS_VERSION}."
496504
chmod +x "$HELPER_FILE" || fail "Could not make $HELPER_FILE executable."
497505
}
@@ -574,7 +582,7 @@ EOF
574582
$ENV_FILE
575583
Then rebuild the web image (BASEURL and GOOGLE_CLIENT_ID are baked in at build time):
576584
$HELPER_FILE rebuild
577-
See docs/Self-Hosting/google-calendar.md for setup instructions.
585+
See https://docs.compasscalendar.com/docs/self-hosting/google-calendar
578586
EOF
579587
else
580588
cat <<EOF

0 commit comments

Comments
 (0)