Sunbeam Watchtower
A unified CLI dashboard for tracking packages, code, reviews, bugs, builds, and Launchpad project metadata across GitHub, Launchpad, and Gerrit forges.
Watchtower is moving toward a server-first runtime model. A persistent Watchtower server is the durable coordination boundary for auth, async operations, and future TUI/MCP clients, while the CLI remains a first-class frontend that can still perform local preparation and, when needed, start an ephemeral embedded server for single-command work. The codebase uses a hexagonal split where internal/adapter/primary/* drives internal/app, which wires internal/core/service/* to internal/adapter/secondary/* through internal/core/port/*. Public reusable contracts and helpers live under pkg/, notably pkg/client, pkg/dto/v1, pkg/distro/v1, pkg/forge/v1, and pkg/launchpad/v1.
Installation
go install github.com/gboutry/sunbeam-watchtower/cmd/watchtower@latestOr build from source:
git clone https://github.com/gboutry/sunbeam-watchtower.git
cd sunbeam-watchtower
go build -o watchtower ./cmd/watchtowerQuick start
- Create a configuration file at
~/.config/sunbeam-watchtower/config.yaml:
projects:
- name: snap-openstack
code:
forge: github
owner: canonical
project: snap-openstack
bugs:
- forge: launchpad
project: snap-openstack
- name: sunbeam-charms
code:
forge: gerrit
host: https://review.opendev.org
project: openstack/sunbeam-charms
- name: charm-keystone
code:
forge: launchpad
project: charm-keystone-k8s- Sync the local caches:
watchtower cache sync- Start exploring:
watchtower packages diff released
watchtower commit log
watchtower review list
watchtower bug list
watchtower project sync --dry-runArchitecture
cmd/watchtoweris a thin entrypoint into the CLI adapter.internal/adapter/primary/clicontains Cobra command logic.internal/adapter/primary/apiexposes the application over HTTP/OpenAPI.internal/adapter/primary/frontendcontains frontend-facing shared workflows such as local build preparation and async orchestration helpers.internal/appis the composition root used by the primary adapters.internal/core/portcontains interfaces only.internal/core/service/*contains domain logic and use cases.internal/adapter/secondary/*contains concrete integrations for git, Launchpad, caches, and OpenStack.pkg/*contains reusable client and DTO packages that can be consumed outside the repository.
Runtime model
Watchtower currently supports three workflow shapes:
remote-onlyworkflows, where the server does the work directlylocal-onlyworkflows, where a frontend inspects local statesplitworkflows, where a frontend prepares local inputs and the server executes the durable remote part
Examples:
# Explicit external server
watchtower --server http://127.0.0.1:8472 operation list
WATCHTOWER_SERVER=http://127.0.0.1:8472 watchtower operation list
# Local persistent daemon managed by the CLI
watchtower server start
watchtower server status
watchtower operation list
watchtower server stop
# Split workflow: local checkout stays local, prepared LP refs go to the server
watchtower build trigger demo --source local --local-path .For split workflows, the server never reads raw local paths. Frontends prepare a stable prepared build source payload and send that to the server.
The canonical prepared-build payload uses:
target_refrepository_refrecipes
Runtime resolution order
When a command needs the API, the CLI resolves the target in this order:
--serverWATCHTOWER_SERVER- discovered local daemon on the default Unix socket
- auto-started local daemon for persistent workflows
- one-command embedded server for explicitly ephemeral work
Persistent workflows currently include:
auth *operation *build trigger --asyncproject sync --async
The shared runtime policies are:
- CLI durable workflows use
require_persistent - CLI ordinary API commands use
prefer_existing_daemon - TUI startup uses
prefer_existing_daemon, reuses the local persistent daemon when one is already running, and otherwise falls back to an embedded session that can still upgrade interactively before durable actions
Stateless queries and one-shot commands can still run against the embedded server when no persistent target is configured.
Configuration
The config file lives at ~/.config/sunbeam-watchtower/config.yaml by default. Override with --config <path>.
Top-level sections
| Section | Description |
|---|---|
launchpad |
Launchpad settings (default_owner, use_keyring, default series, development_focus) |
github |
GitHub settings (client_id, use_keyring) |
gerrit |
Gerrit settings (hosts list with url entries) |
tui |
TUI startup presets (default_pane, per-pane preset filters, and package/commit modes) |
projects |
List of tracked projects |
Example TUI preset:
tui:
default_pane: packages
panes:
packages:
mode: excuses
filters:
tracker: ubuntu
team: ubuntu-openstackThe TUI applies these presets once at startup, uses them for the first load of each pane, and treats them as the reset target for filter modals.
| build | Build pipeline settings (default_prefix, timeout_minutes, artifacts_dir) |
| packages | Package source, set, and upstream configuration |
| otel | Server-side OpenTelemetry exporters and metrics listeners |
OpenTelemetry
Telemetry is a persistent-server feature. It is designed to stay cheap by default:
otel.metrics.selfexposes request/runtime/collector-health metrics for the running serverotel.metrics.domainexposes Watchtower domain metrics- domain metrics default to cached/internal state
- live upstream fan-out is disabled unless you explicitly allow it with
otel.metrics.domain.live_systems - the intended production pattern is periodic cache refresh, for example from cron, plus Prometheus scraping the server
Example:
otel:
service_name: watchtower
service_namespace: sunbeam
metrics:
self:
enabled: true
listen_addr: 127.0.0.1:9464
path: /metrics
domain:
enabled: true
listen_addr: 127.0.0.1:9465
path: /metrics
live_systems: []
collectors:
reviews:
refresh_interval: 15m
traces:
enabled: true
endpoint: http://127.0.0.1:4318
protocol: http
logs:
enabled: true
endpoint: http://127.0.0.1:4318
protocol: http
mirror_stderr: trueOperational model:
# Persistent server with telemetry
watchtower server start
# Periodic cache refresh keeps domain telemetry cheap
watchtower cache sync
watchtower cache sync releases
watchtower cache sync excusesLabel policy:
- use bounded labels only
- do not expect IDs, SHAs, usernames, or raw URLs as metric labels
- release metrics use stable labels such as
project,artifact_type,artifact,track,risk,branch,architecture, andresource
Project configuration
Each project has:
- name: my-project # required, unique identifier
artifact_type: rock # optional: rock, charm, or snap (required if build is set)
code:
forge: github # required: github, gerrit, or launchpad
owner: my-org # required for github
host: https://review.dev.org # required for gerrit
project: my-project # required: project name/path
git_url: https://custom/repo # optional: explicit clone URL override
bugs:
- forge: launchpad
project: my-project
series: # optional: Launchpad series to ensure on the project
- 2024.1
- 2024.2
development_focus: 2024.2 # optional: must be one of the declared series
build: # optional
owner: my-team
official_codehosting: false # when true, use LP's default git repo for remote builds
lp_project: my-lp-project # LP project for recipe ops (defaults to code.project)
artifacts:
- recipe-name
prepare_command: make prepareWhen official_codehosting is true, remote builds resolve the project's default
git repository via the LP API (GetDefaultRepository) and create series-based
recipes automatically. The lp_project field overrides which LP project is used
for recipe operations (useful when the LP project name differs from code.project).
Example project configured for official remote builds:
- name: ubuntu-openstack-rocks
artifact_type: rock
code:
forge: github
owner: canonical
project: ubuntu-openstack-rocks
build:
owner: canonical
official_codehosting: true
lp_project: ubuntu-openstack-rocksThe git_url field is useful when the clone URL cannot be derived from the forge type and project name (e.g., Launchpad repos with complex paths like ~owner/project/+git/repo).
Projects can declare the Launchpad series that must exist on the project, plus the desired focus of development:
- name: snap-openstack
code:
forge: github
owner: canonical
project: snap-openstack
bugs:
- forge: launchpad
project: snap-openstack
series:
- 2024.1
- 2024.2
development_focus: 2024.2watchtower project sync ensures those series exist and sets the declared focus of development.
Release tracking overrides
Published snap and charm tracking is repo-driven:
- snap names come from
snap/snapcraft.yamlor rootsnapcraft.yaml - charm names and resources come from
charmcraft.yaml - tracked store tracks default to the project's
series
Use project.release only for store-specific overrides:
- name: snap-openstack
artifact_type: snap
code:
forge: github
owner: canonical
project: snap-openstack
series:
- 2024.1
- 2025.1
release:
track_map:
2025.1: latest
branches:
- series: 2024.1
branch: risc-v
risks: [edge, beta, candidate, stable]
- name: sunbeam-charms
artifact_type: charm
code:
forge: gerrit
host: https://review.opendev.org
project: openstack/sunbeam-charms
series:
- 2024.1
release:
branches:
- series: 2024.1
branch: risc-v
risks: [edge]
skip_artifacts:
- sunbeam-libsRules:
release.track_mapremaps projectseriesto store tracksrelease.trackscan replace the default series-derived tracks entirelyrelease.branchesdeclares managedtrack/risk/branchchannels for snaps or charmsrelease.skip_artifactsexcludes discovered artifacts that exist in a mono repo but are not meant to be published or tracked- branch channels are additive; Watchtower does not try to auto-discover arbitrary store branches as managed release lines
Release target visibility profiles
Release tracking always stores and serves the full target matrix. Target filtering is a frontend-side presentation feature for watchtower releases and the TUI.
Use top-level reusable profiles plus optional per-project defaults and overrides:
releases:
default_target_profile: noble-and-newer
target_profiles:
noble-and-newer:
include:
- base_names: [ubuntu]
min_base_channel: "24.04"
projects:
- name: openstack
artifact_type: charm
code:
forge: gerrit
host: https://review.opendev.org
project: openstack/sunbeam-charms
release:
target_profile: noble-and-newer
target_profile_overrides:
exclude:
- architectures: [s390x]Rules:
releases.default_target_profilesets the default frontend filter when no project-specific or per-invocation profile is chosenreleases.target_profilesdefines named include/exclude rules for release targetsrelease.target_profileselects the default named profile for one projectrelease.target_profile_overridesmerges on top of the selected profile for that projectmin_base_channelaccepts dotted numeric version channels only, such as22.04or24.04- snap targets using
core22/core24/core26are normalized against Ubuntu generations formin_base_channelmatching --all-targetsbypasses all local filtering for that command or TUI session state- filtering affects rendered output only; server state and API payloads remain unchanged
Commands
watchtower releases
Inspect cached snap and charm release matrices with target-aware revision output.
When a snap and a charm share the same artifact name, Watchtower tracks and lists both entries separately. Use --type with releases show when a name matches multiple artifact types.
# List cached release rows with target-qualified revisions
watchtower releases list openstack
# Filter locally to a named target profile
watchtower releases list openstack --target-profile noble-and-newer
# Show the full matrix for one artifact
watchtower releases show openstack --type snap
# Bypass local filtering and show every tracked target
watchtower releases show openstack --type snap --all-targetswatchtower packages
Compare package versions and inspect package metadata across configured distro sources.
# Compare package sets
watchtower packages diff released
# Show all versions of a package
watchtower packages show-version nova
# Show full APT metadata for a specific version
watchtower packages show nova 2:2025.1-0ubuntu1
# Show highest version matching filters
watchtower packages show nova --distro ubuntu --backport gazpacho
# List Ubuntu proposed-migration excuses
watchtower packages excuses list --tracker ubuntu --ftbfs
# Filter Ubuntu excuses by owning team
watchtower packages excuses list --tracker ubuntu --team debcrafters-packages
# Inspect one package's migration blockers
watchtower packages excuses show nova --tracker ubuntu
# List packages in a configured distro release
watchtower packages list --distro ubuntu --release noblewatchtower commit
Read commit history from the local git cache.
# List recent commits across all projects
watchtower commit log
# Filter by project, forge, branch, or author
watchtower commit log --project snap-openstack --branch main --author alice
# Find commits referencing a Launchpad bug
watchtower commit track --bug-id 12345watchtower review
List and inspect cached merge requests across forges. Review browsing is cache-first by default, so run watchtower cache sync reviews before relying on review list/show in the CLI or TUI.
# List open merge requests
watchtower cache sync reviews
watchtower review list --state open
# Filter by project or forge
watchtower review list --project snap-openstack --forge github
# List MRs updated in the last week
watchtower review list --since 1w
# Show details for a specific merge request
watchtower review show --project snap-openstack "#42"
# Refresh cached detail for recently updated closed reviews too
watchtower cache sync reviews --since 30dwatchtower bug
Track bugs across Launchpad bug trackers.
# List bug tasks
watchtower bug list
# Filter by status, importance, assignee, or tag
watchtower bug list --status "In Progress" --importance High --assignee alice
# Show a specific bug with its tasks
watchtower bug show 12345watchtower build
Manage Launchpad builds (rocks, charms, snaps). Two build modes are supported:
- Local (
--source local): pushes the local git tree to a temporary LP repo,
creates temporary recipes, and triggers builds. Ideal for development and testing. - Remote (
--source remote): uses the project's official LP git repo (code
mirror) and creates series-based recipes. Used for official builds.
# Local build (development) — push local tree and trigger
watchtower build trigger --source local ubuntu-openstack-rocks nova-consolidated keystone
# Remote build (official, series-based)
watchtower build trigger --source remote ubuntu-openstack-rocks nova-consolidated keystone
# Trigger and wait for completion
watchtower build trigger my-project --wait --timeout 2h
# Trigger and wait, then download artifacts
watchtower build trigger my-project --wait --download
# List builds
watchtower build list my-project
# List local builds by prefix
watchtower build list --source local ubuntu-openstack-rocks --prefix tmp-build-
# Download build artifacts
watchtower build download my-project --artifacts-dir ./output
# Download specific artifacts from a local build
watchtower build download --source local ubuntu-openstack-rocks keystone nova-consolidated
# Clean up temporary recipes
watchtower build cleanup my-project --dry-runIn remote mode with series configured, each artifact expands into per-series
recipes: <artifact> for the dev-focus series (default branch) and
<artifact>-<series> for other series (stable/<series> branch).
watchtower project
Manage Launchpad project metadata derived from configuration.
# Preview Launchpad project changes
watchtower project sync --dry-run
# Sync a subset of Launchpad projects
watchtower project sync --project snap-openstack --project sunbeam-charmswatchtower cache
Manage local caches used by commit tracking, package lookups, upstream repository inspection, bug tracking, release tracking, and review browsing.
# Sync all cache types
watchtower cache sync
# Sync only git repos
watchtower cache sync git
# Sync only package indexes for selected distros/releases
watchtower cache sync packages-index --distro ubuntu --release noble
# Sync only configured upstream repos
watchtower cache sync upstream-repos
# Sync only bug/task caches
watchtower cache sync bugs
# Sync cached review summaries and details
watchtower cache sync reviews
# Sync Ubuntu and Debian excuses caches
watchtower cache sync excuses --tracker ubuntu --tracker debian
# Sync Ubuntu excuses and refresh the companion team mapping used by --team
watchtower cache sync excuses --tracker ubuntu
# Sync a specific project's git cache
watchtower cache sync git --project snap-openstack
# Sync only one project's review cache
watchtower cache sync reviews --project snap-openstack
# Show cached repos and their disk usage
watchtower cache status
# Remove all cached data
watchtower cache clear
# Remove a specific project's git cache
watchtower cache clear git --project snap-openstack
# Remove only Debian excuses cache entries
watchtower cache clear excuses --tracker debianValid cache types are git, packages-index, upstream-repos, bugs, excuses, releases, and reviews.
Review cache data is stored at $XDG_CACHE_HOME/sunbeam-watchtower/reviews/ (defaults to ~/.cache/sunbeam-watchtower/reviews/). It stores cached review summaries for all synced items plus full comments/files/diff detail for open reviews and recently updated closed reviews.
Git cache data is stored at $XDG_CACHE_HOME/sunbeam-watchtower/repos/ (defaults to ~/.cache/sunbeam-watchtower/repos/). Repos are bare git clones organized by host:
~/.cache/sunbeam-watchtower/repos/
├── github.com/canonical/snap-openstack.git
├── review.opendev.org/openstack/sunbeam-charms.git
└── git.launchpad.net/charm-keystone-k8s.git
watchtower auth
Manage provider authentication. Launchpad auth is required for Launchpad-backed bugs, Launchpad reviews, and builds. GitHub auth is optional, but it raises GitHub API rate limits for review and commit browsing plus cache sync reviews.
# Check aggregated auth status
watchtower auth status
# Launchpad browser-based OAuth login/logout
watchtower auth launchpad login
watchtower auth launchpad logout
# GitHub device-flow login/logout
watchtower auth github login
watchtower auth github logoutGitHub auth uses an OAuth app client ID resolved in this order:
WATCHTOWER_GITHUB_CLIENT_IDgithub.client_idin config
If either GH_TOKEN or GITHUB_TOKEN is set, Watchtower uses that GitHub credential source first and reports it as an environment-backed auth source in auth status. In that mode, watchtower auth github login and watchtower auth github logout are blocked to avoid creating shadowed file-backed state.
github.use_keyring is reserved for future support and is not implemented yet. If enabled, GitHub auth mutation flows fail with a clear error instead of silently falling back to file storage.
watchtower config
# Display the current configuration
watchtower config showwatchtower serve
Run the HTTP API server directly instead of using the embedded per-command server.
# Serve on TCP
watchtower serve --listen 127.0.0.1:8472
# Serve on a Unix domain socket
watchtower serve --listen unix:///tmp/watchtower.sockWhen served over TCP:
- OpenAPI spec:
http://127.0.0.1:8472/openapi.json - Interactive docs:
http://127.0.0.1:8472/docs
Output formats
All list/show commands support --output (-o):
| Format | Flag | Description |
|---|---|---|
| Table | -o table |
Human-readable aligned columns (default) |
| JSON | -o json |
Machine-readable JSON |
| YAML | -o yaml |
Machine-readable YAML |
Global flags
| Flag | Description |
|---|---|
--config |
Path to config file (default: ~/.config/sunbeam-watchtower/config.yaml) |
--verbose |
Enable debug logging (CLI/API write to stderr; TUI shows logs in its log pane) |
-o, --output |
Output format: table, json, yaml |
--no-color |
Disable colored output |
Debug logging
Enable verbose logging to see exactly what watchtower is doing:
watchtower --verbose commit logFor CLI and API runs, this writes structured debug logs to stderr, including:
- Which forge clients are being configured
- Which projects are being queried or skipped by filters
- How many commits/reviews/bugs were fetched per project
- Git cache operations (clone, fetch, path resolution)
- Cache directory resolution
For watchtower-tui, --verbose enables the same debug logs but keeps them inside the TUI. Press l to open the log pane, which shows the current session logs and, when connected to the local persistent daemon, a tail of the daemon log file.
Example output:
time=... level=DEBUG msg="building commit sources" project_count=3
time=... level=DEBUG msg="resolved cache directory" path=/home/user/.cache/sunbeam-watchtower
time=... level=DEBUG msg="initializing git cache" cacheDir=/home/user/.cache/sunbeam-watchtower/repos
time=... level=DEBUG msg="configured commit source" project=snap-openstack cloneURL=https://github.com/canonical/snap-openstack.git
time=... level=DEBUG msg="listing commits" project_count=3 branch=main author=""
time=... level=DEBUG msg="querying project" project=snap-openstack forge=GitHub
time=... level=DEBUG msg="listing commits from cache" cloneURL=https://github.com/canonical/snap-openstack.git branch=main
time=... level=DEBUG msg="commits read from cache" cloneURL=https://github.com/canonical/snap-openstack.git count=47
time=... level=DEBUG msg="project commits fetched" project=snap-openstack commit_count=47
time=... level=DEBUG msg="commits aggregated" total_count=142
time=... level=DEBUG msg="commit log complete" total_commits=142
Developer tooling
This repository ships with pre-commit hooks, arch-go architecture checks, and a curated golangci-lint v2 configuration.
pre-commit install
pre-commit run --all-filesThe hooks run whitespace/YAML checks plus arch-go, golangci-lint, go build, go test, changed-package coverage via go run ./tools/coverageguard --config .coverage-policy.yaml, and go mod tidy.
GitHub Actions runs the same contract in CI: repo-wide quality checks plus changed-package coverage against the pull request diff.
License
Apache-2.0