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
112 changes: 112 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
name: CI, release and publish

# Tests run on every push (any branch) and every PR. Only a push to `main`
# can ever produce a release: python-semantic-release inspects the
# Conventional Commits since the last vX.Y.Z tag, and if they warrant a
# release it bumps waypoint/__init__.py, updates CHANGELOG.md, and pushes a
# new tag + GitHub Release. The multi-arch Docker image is only built and
# published to GHCR when that actually happened -- pushes to `develop` (or
# any other branch) and PRs only ever run the test job.

on:
push:
branches: ["main", "develop"]
pull_request:
workflow_dispatch:

permissions:
contents: read

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
test:
name: Run test suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install dependencies
run: pip install -r requirements-dev.txt

- name: Run pytest
run: python -m pytest -q

release:
name: Determine next version
needs: test
# Only main can cut a release -- develop/PRs never reach this job.
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
released: ${{ steps.release.outputs.released }}
tag: ${{ steps.release.outputs.tag }}
version: ${{ steps.release.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Python Semantic Release
id: release
uses: python-semantic-release/python-semantic-release@v9
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

build:
name: Build and push image
needs: [test, release]
# Only runs off the back of an actual release just cut above --
# i.e. only when main was pushed to *and* semantic-release decided the
# accumulated commits warrant a new version.
if: needs.release.outputs.released == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.release.outputs.tag }}

- name: Set up QEMU (for linux/arm64)
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract image metadata (tags + OCI labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}},value=${{ needs.release.outputs.tag }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.release.outputs.tag }}
type=raw,value=latest

- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
85 changes: 0 additions & 85 deletions .github/workflows/docker-publish.yml

This file was deleted.

5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

All notable changes to this project are documented in this file, generated automatically by
[python-semantic-release](https://python-semantic-release.readthedocs.io/) from
[Conventional Commits](https://www.conventionalcommits.org/) merged into `main`.
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ failing fails the whole build) → `runtime` (non-root user `waypoint` at fixed
the test stage, never test files themselves). `HEALTHCHECK` hits `/healthz` with a Python
`urllib` one-liner (no `curl`/`wget` in `python:3.12-slim`).

### Branching, versioning & release pipeline

`develop` is the integration branch (feature branches → PR → `develop`); `main` only moves via a
PR from `develop`. `.github/workflows/ci.yml` runs the `test` job on every push/PR regardless of
branch, but the `release` (python-semantic-release, `pyproject.toml`'s `[tool.semantic_release]`)
and `build` (multi-arch Docker build + push to GHCR) jobs are gated on `github.ref ==
'refs/heads/main'` and on `release` actually having cut a new version, respectively — so pushing to
`develop` never builds or publishes anything. The version lives in `waypoint/__init__.py`
(`__version__`) and is bumped automatically from Conventional Commit prefixes (`fix:`, `feat:`,
`BREAKING CHANGE:`) since the last `vX.Y.Z` tag; don't hand-edit it or hand-create release tags —
that's what causes the version and the Docker tags to drift apart.

## Invariants to preserve when editing

- Every branch through `EmailMonitor.process_new_emails` must still move the processed UID to
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,32 @@ The test suite covers the pure link-extraction and page-rendering logic (using a
Garmin LiveTrack email fixture, see `tests/fixtures/`), the persisted state, IMAP config validation,
and the web routes (via Flask's test client) — all without needing a real IMAP or SMTP server.

### Branching and releases

- `develop` is the integration branch — feature branches and day-to-day work target `develop` via
PR. Pushes here (and PRs) only run the test suite; nothing gets built or published.
- `main` only ever moves forward via a PR from `develop`. A push to `main` additionally runs
[python-semantic-release](https://python-semantic-release.readthedocs.io/), which inspects the
[Conventional Commits](https://www.conventionalcommits.org/) merged in since the last `vX.Y.Z`
tag and, if they warrant a release, bumps `waypoint/__init__.py`, updates `CHANGELOG.md`, and
pushes the new tag + GitHub Release.
- Only that release step (never a plain push) triggers the multi-arch Docker build and publish to
GHCR — see `.github/workflows/ci.yml`.
- Commit messages therefore need a Conventional Commits prefix for the release step to pick them
up correctly: `fix:` → patch, `feat:` → minor, `BREAKING CHANGE:` (in the footer) → major.
Anything else (`chore:`, `docs:`, `test:`, ...) is ignored for versioning purposes.

## Troubleshooting

**Browser console shows `net::ERR_BLOCKED_BY_CLIENT`.** This is not a server/reverse-proxy issue —
Waypoint doesn't ship or require any nginx config, and this error never comes from the server; the
browser generates it locally, before the request even leaves the machine. It means a client-side
ad/tracker blocker (uBlock Origin, Brave Shields, Pi-hole-style browser extensions, ...) intercepted
a request. Garmin's `livetrack.garmin.com` URLs contain the substring `track`, which matches generic
tracker-blocklist rules in some filter lists, so the extension blocks the iframe/redirect target
itself. Check the failing request's URL in the Network tab to confirm, then allow-list
`livetrack.garmin.com` in the blocker — there's nothing to change in Waypoint's own config for this.

## Security notes

- Credentials are only ever read from environment variables (typically via `.env`, which is
Expand Down
16 changes: 16 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[tool.semantic_release]
version_variables = ["waypoint/__init__.py:__version__"]
tag_format = "v{version}"
commit_message = "chore(release): v{version} [skip ci]"
build_command = ""
major_on_zero = false

[tool.semantic_release.branches.main]
match = "main"
prerelease = false

[tool.semantic_release.changelog]
changelog_file = "CHANGELOG.md"

[tool.semantic_release.remote]
type = "github"
4 changes: 4 additions & 0 deletions waypoint/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
"""Garmin LiveTrack monitor: watches an inbox for LiveTrack emails and serves
the current session link as a small self-contained web service."""

# Bumped automatically by python-semantic-release based on Conventional
# Commits (fix:/feat:/BREAKING CHANGE) merged into main -- don't hand-edit.
__version__ = "0.1.0"
Loading