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
34 changes: 34 additions & 0 deletions .github/workflows/gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Gate

# Holds every runbook to the shape spin's app reads, on every push and pull request.

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: gate-${{ github.ref }}
cancel-in-progress: true

# go-tools' downloads (sfw, versions) ask GitHub with this: anonymous ones are the first it sheds
# under load, and they answered 504 for minutes on 2026-11-09.
env:
GITHUB_TOKEN: ${{ github.token }}

jobs:
check:
# The check main's ruleset requires, named Gate in every spin-stack repository.
name: Gate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
# What a developer runs, with the Task go.mod pins.
- run: go tool task check
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.task/
_output/
90 changes: 90 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Contributing

A runbook is what an operator of a Spinbox installation reads when something is wrong: an alert
fired, a job keeps failing, a person was told their workspace is waiting for them, or someone
asks for a procedure (a host shell, a restore). It says what is seen, who it hurts, how to find
out, what to do, how to know it worked and whom to call. Short, plain English, the commands as
they are typed.

This repository is public. **Never** write a secret, a hostname or account id of an installation,
a person's name or email, or anything a customer did. Write `<your domain>`, `<workspace>`,
`<host>` in code, for any installation.

## A runbook

`runbooks/<id>.md`, named for its id:

```markdown
---
id: host-not-reporting
title: A host stopped reporting
applies_to:
alerts: [hosts-disconnected, host-heartbeat-stale]
jobs: []
events: []
dashboards: [fleet]
severity: critical
owner: operator
last_reviewed: 2026-11-09
---

## Symptoms

What fires, what a person sees.

## Impact

Who and what it hurts, and what is lost if nothing is done.

## Diagnose

The commands (`spin ...`, `controlplane ...`) and the pages (Admin → Runners) that say why.

## Fix

What to do, in order, for each cause the diagnosis finds.

## Verify

How to know it worked: the alert resolves, the page reads healthy.

## Escalate

When to stop and whom to call, with what to bring.
```

- **`id`** is stable: spin's alert rules and pages link it (`/admin/runbooks/<id>`), and spin's
tests fail on a link to one that does not exist. Renaming one is a change in spin too.
- **`applies_to`** is what sends an operator here, and what the app links from: `alerts` are
Grafana alert rule uids, `jobs` control plane job names (Admin → Jobs), `events` the
notices a workspace's owner gets (`HELD`, `STOP_BACKUP_FAILED`, `UNRESPONSIVE`, `REHOMED`),
`dashboards` Grafana dashboard uids. A procedure nobody is paged for applies to nothing and is
`severity: info`.
- **`severity`**: `critical` (someone is losing work or access now), `warning`, `info`.
- **`owner`**: the role that keeps it true: `operator`, `security`.
- **`last_reviewed`**: the date someone last held it against the release it describes.

## What the app renders

Markdown, and nothing else: raw HTML is refused here and dropped by the app. Links are to
another runbook (`[held](workspace-held.md)`, which stays in the app), to a path in the app
(`/admin/jobs`), or `https://`. No images.

Ground every command and page in what spin has: a flag that does not exist is worse than no
runbook. Where spin has no way yet, write `TODO` and link the issue that will add one.

## Check

```
go tool task check
```

Fails on a field missing, an id that is not its file's name or is used twice, a section missing
or out of order, raw HTML, a link to a runbook that does not exist, an AWS account id, a private
key or an installation's hostname. CI runs the same (Gate).

## How a change reaches an installation

spin pins this repository at a commit (`versions.yaml`, `runbooks`). A merged change here reaches
the app when spin is bumped (`task bump NAME=runbooks`, `task runbooks`) and released. The app's
**Open in repo** link is the file at the commit the release pins: propose the change on `main`.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# spin-stack / runbooks

What an operator of a [Spinbox](https://github.com/spin-stack/spin) installation does when an
alert fires, a job keeps failing, a workspace's owner is told something went wrong, or a
procedure is asked for: one markdown file per runbook under `runbooks/`.

An installation's administrators read them inside the app, at **Admin → Runbooks**, without
leaving it: spin pins this repository at a commit and serves the runbooks of the release it
runs. Every Grafana alert rule links its runbook, and so do Admin → Jobs and the notices a
workspace's owner gets. **Open in repo**, on each runbook in the app, is the file here at that
commit, to propose a change.

```
go tool task check # what CI runs: front matter, sections, links, nothing private
```

[CONTRIBUTING.md](CONTRIBUTING.md) is how a runbook is written and what the check refuses.

## License

Apache License 2.0, as spin.
20 changes: 20 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: '3'

# Operational runbooks for an installation of Spinbox. runbooks/*.md are the source; spin pins this
# repository and serves them to an installation's administrators, so `task check` holds each to
# the shape the app reads.

tasks:
default:
cmds:
- task: check

check:
desc: What CI runs - fail if a runbook's front matter, sections or links are not what the app reads, it carries raw HTML or what a public repository must not, a workflow step is not one task, or an install skips Socket Firewall.
cmds:
# The Task running this one: go.mod's is not on PATH, and taskgate asks it for the task names.
- go tool taskgate -task {{.TASK_EXE}}
- go tool sfw check
- go vet ./...
- go test ./...
- go run ./cmd/check
Loading
Loading