Watcher is a pull-based deployment agent for Windows machines.
It runs as a single binary (watcher.exe) that hosts:
- a background deployment agent
- a REST API
- an embedded Svelte dashboard
The agent polls GitHub releases, deploys new artifacts, manages services, performs health checks, and records state/history in SQLite.
Download the latest release zip, extract it on the Windows host, then run install.bat to start the interactive installer.
Full installation guide: INSTALL.md
- Pull-based deploys from GitHub Releases (no inbound SSH required)
- Multiple watchers (one poll loop per watcher)
- Service types:
nssmbinaries (managed start/stop/restart)staticapps (IIS app pool recycle support)
- Automatic rollback on failed post-deploy health checks
- Manual rollback with high-watermark pinning (
max_ignored_version) - Poll-event history (
new_release,no_update,skipped,error, etc.) - Optional GitHub Deployment API status reporting
- Per-watcher GitHub override settings:
deployment_environment(fallback: globalENVIRONMENT)github_token(fallback: globalGITHUB_TOKEN)
- Per-watcher outbound webhooks with durable delivery history
- Self-management endpoints (version, update-check, update, uninstall script)
- Embedded Svelte SPA dashboard served by the same Go process
cmd/watcher/main.go
├─ load .env config (Viper)
├─ open SQLite (GORM + glebarez/sqlite)
├─ start Gin API server
└─ run Agent
├─ sync watchers from DB
├─ one goroutine per watcher
├─ immediate check trigger channel (API -> agent)
└─ sync trigger channel (API -> agent)
Each watcher loop:
- Fetch release metadata from GitHub
- Compare remote version vs
current_version - Enforce rollback high-watermark (
max_ignored_version) - Deploy if needed:
- download artifact (retry with backoff)
- extract to
releases/<version> - stop services
- swap
currentjunction (mklink /J, copy fallback) - ensure service registration (NSSM for
nssmtype) - start services / recycle IIS app pools
- health checks
- rollback on failure
- Persist state and deploy logs in SQLite
Watcher prefers a version.json manifest. Repo asset discovery still exists as a legacy/manual fallback, but new org release workflows should publish version.json for every Watcher-managed release.
metadata_url accepts either:
- a direct release manifest URL such as
https://github.com/<owner>/<repo>/releases/latest/download/version.json, or - a repo URL such as
https://github.com/<owner>/<repo>. During watcher creation, the UI tries to findversion.jsonfor the selected release ref first.
Detailed manifest reference: docs/version-json.md.
Watcher only picks up a consumer repository release when the app repository follows these rules:
- Publish a GitHub Release, not only a tag. Draft releases are ignored by GitHub's latest-release API.
- Upload the deployable zip as a release asset.
- Upload
version.jsonas a release asset for every Watcher-managed release. This is the hard org contract. - Keep
version.json.services["<service_name>"]exactly equal to the Watcherservice_name. - Set
versionto the exact version Watcher should compare. For monorepos, prefer the full service tag such asservices/prs/v1.2.3. - Set
artifactto the exact release asset filename. - Set
artifact_urlto the exact GitHub release download URL for that asset. Slash tags such asservices/prs/v1.2.3are supported. - Set deployment hints (
app_kind,binary_name, IIS fields, health URL) so the create wizard can build the right service config without guessing. - Package the zip so Watcher can use it immediately after extraction:
nssm: the configuredbinary_namemust exist at the zip root.static/php/aspnet_classic: the site/app files must exist at the zip root.
- For monorepos, publish a manifest index release, usually tag
latest, that points each service to its service-scoped release asset. - Do not publish two service releases concurrently from the same repo; serialize release workflows with GitHub Actions
concurrency.
{
"services": {
"my-app": {
"version": "v1.2.3",
"artifact": "my-app-v1.2.3.zip",
"artifact_url": "https://github.com/<owner>/<repo>/releases/download/v1.2.3/my-app-v1.2.3.zip",
"published_at": "2026-06-01T04:19:05Z",
"app_kind": "nssm",
"windows_service_name": "my-app",
"binary_name": "my-app.exe",
"start_arguments": "",
"env_file": ".env",
"health_check_url": "http://localhost:8080/health"
}
}
}| Property | Required | Applies to | Meaning |
|---|---|---|---|
version |
yes | all | Version stored in Watcher state and compared on each poll. |
artifact |
yes | all | Exact release asset filename. |
artifact_url |
yes | all | GitHub release download URL for the deployable artifact. |
published_at |
recommended | all | UTC timestamp for display and diagnostics. |
app_kind |
recommended | all | nssm, static, php, or aspnet_classic. |
windows_service_name |
recommended | all | NSSM service name or IIS service identifier/default name. |
binary_name |
required for nssm |
nssm |
Executable filename expected after extraction. |
start_arguments |
optional | nssm |
Arguments passed to the NSSM application. |
env_file |
optional | nssm |
Relative env file path for managed env content. |
health_check_url |
optional | all | Service-level health check URL. |
iis_app_pool |
recommended | static, php, aspnet_classic |
IIS app pool name. |
iis_site_name |
recommended | static, php, aspnet_classic |
IIS site name. |
iis_managed_runtime |
optional | aspnet_classic |
IIS runtime such as v4.0; leave empty for static/PHP. |
public_url |
optional | IIS-hosted apps | URL displayed in the UI and useful for health checks. |
- Zip artifacts should contain deploy binaries/content at the root.
- For
nssmservices,binary_namemust exist after extraction undercurrent/. - For IIS-hosted services, site files should exist directly under
current/.
Base path: /api
GET /statusGET /logsGET /logs/streamPOST /github/inspect
GET /watchersPOST /watchersGET /watchers/:idPUT /watchers/:idDELETE /watchers/:idGET /watchers/:id/servicesPOST /watchers/:id/servicesPUT /watchers/:id/services/:sidDELETE /watchers/:id/services/:sidGET /watchers/:id/deploysGET /watchers/:id/deploys/:didGET /watchers/:id/deploys/:did/streamGET /watchers/:id/eventsGET /watchers/:id/pollsPOST /watchers/:id/checkPOST /watchers/:id/redeployGET /watchers/:id/versionsPOST /watchers/:id/rollbackPOST /watchers/:id/resumeDELETE /watchers/:id/versions/:versionGET /watchers/:id/webhook-eventsGET /watchers/:id/webhook-deliveriesGET /watchers/:id/webhook-deliveries/:deliveryIdPOST /watchers/:id/webhook/testPOST /watchers/:id/webhook/resume
GET /servicesGET /services/:idPOST /services/:id/startPOST /services/:id/stopPOST /services/:id/restartPUT /services/:id/envGET /services/:id/healthGET /services/:id/health/historyGET /services/:id/logsGET /services/:id/deploys
GET /self/versionGET /self/configPUT /self/configGET /self/update-checkPOST /self/updatePOST /self/restartPOST /self/uninstall
/Dashboard/watchers/watchers/:id/services/services/:id/polling/logs/settings
Example is in .env.example.
ENVIRONMENT=production
GITHUB_TOKEN=
LOG_DIR=D:\apps\watcher\logs
NSSM_PATH=C:\ProgramData\chocolatey\bin\nssm.exe
DB_PATH=D:\apps\watcher\watcher.db
API_PORT=8080
API_BASE_URL=
WATCHER_REPO_URL=https://github.com/fanboykun/watcherNotes:
GITHUB_TOKENis required for private repos.API_BASE_URLenables GitHub Deployment APIlog_urllinking.WATCHER_REPO_URLis used by self-update check/update.GITHUB_DEPLOY_ENABLED=true|falsetoggles GitHub Deployment API reporting globally.- Watcher-level config can override deploy environment/token per repo:
deployment_environment-> used first for GitHub Deployments environmentgithub_token-> used first for GitHub metadata/artifact/deployment API calls- if empty, watcher falls back to global
.envvalues.
Watcher can emit durable outbound webhook events for:
watcher.version_foundwatcher.deployment_succeededwatcher.deployment_failedwatcher.rollback_succeededwatcher.rollback_failedservice.health_changedwatcher.webhook_testwebhook.delivery_exhausted
Important behavior:
- Delivery is at-least-once. Deduplicate on
event_id. - Each HTTP attempt gets its own
delivery_id. - Any
2xxcounts as success. - Network errors,
429, and5xxretry. - Other
4xxresponses are recorded as final failures. - Watcher preserves FIFO delivery order per watcher.
- Paused webhook delivery suppresses new events instead of dropping them.
Detailed contract, event payload fields, and trigger rules:
For local testing, this repo also includes a minimal receiver:
go run ./cmd/webhook-server -addr :8091 -path /webhookUse a token if:
- repo is private, or
- you use GitHub Deployment API status reporting.
- GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens
- Select repository access for the repos watcher needs.
- Grant minimum permissions:
- Contents: Read (required for release metadata/assets)
- Deployments: Read and write (required only if using GitHub Deployment API status updates)
- Generate token and store it in:
- global
.envasGITHUB_TOKEN, or - watcher override in UI (Watchers → Add/Edit watcher).
- global
- For private repos,
reposcope is typically sufficient. - If deployment status calls are used, ensure deployment-related repo operations are allowed by org/repo policy.
- no published release exists (
/releases/latestneeds a published, non-draft release), - token does not have access to the target private repo,
- wrong owner/repo URL,
- org SSO/policy not approved for the token.
Tables auto-migrated on startup:
watchersservicesdeploy_logshealth_eventspoll_events
Highlights:
- Watcher state fields:
current_version,max_ignored_version,status,last_checked,last_deployed,last_error - Service fields include
service_type,binary_name,env_file,health_check_url,iis_app_pool,iis_site_name,public_url,env_content - Deploy log includes
github_deployment_idand raw textlogs
- Go
1.25.x(module currently usesgo 1.25.6) - Bun (for web build)
make help
make dev
make build-web
make build
make test
make test-verbose
make test-github
make run
make package
make cleanmake build builds the web app first, then embeds web/build into the Go binary.
.github/workflows/release.yml- releases the watcher itself
workflows/release-go-nssm.yml- Watcher-ready release template for a single Go repository with one or more Windows/NSSM binaries
- emits a flat Windows amd64 zip and
version.json
workflows/release-bun-iis.yml- Watcher-ready release template for a SvelteKit static site served as a static/IIS target
- emits a static build zip and
version.json
workflows/release-go-monorepo.yml- Watcher-ready release template for one Go service inside a mono repo
- uses service-scoped tags such as
<service>/v1.2.3and service-only path triggers
workflows/deploy.yml- legacy direct deployment workflow template
See INSTALL.md.
Typical flow on Windows:
- Extract release zip
- Run
install.bat - Complete wizard (
shell/install-watcher.ps1) - Open dashboard on
http://localhost:<API_PORT>
- API has no built-in auth; deploy behind a trusted network.
service_namemust match metadata service key forversion.jsonflow.binary_namemust match the extracted file fornssmservices.- Manual rollback sets
max_ignored_version; auto-deploy ignores versions<=that value until resumed or a newer version appears. - After repeated failures for the same target version, auto deploy is suspended for that version until manual redeploy.
