runsheets 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +98 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +277 -0
  5. data/bin/runsheets +12 -0
  6. data/examples/about-the-examples.md +40 -0
  7. data/examples/directory_based_runbooks/hello/rollback.md +11 -0
  8. data/examples/directory_based_runbooks/hello/runbook.md +40 -0
  9. data/examples/directory_based_runbooks/hello/steps/010-say-hello.md +33 -0
  10. data/examples/directory_based_runbooks/hello/steps/020-inspect-ruby.md +28 -0
  11. data/examples/directory_based_runbooks/hello/steps/030-take-a-breath.md +11 -0
  12. data/examples/directory_based_runbooks/hello/steps/035-keep-a-clock-running.md +21 -0
  13. data/examples/directory_based_runbooks/hello/steps/040-exercise-failure.md +26 -0
  14. data/examples/directory_based_runbooks/hello/steps/045-check-the-greeting.md +18 -0
  15. data/examples/directory_based_runbooks/hello/verify.md +18 -0
  16. data/examples/directory_based_runbooks/staging-teardown/rollback.md +28 -0
  17. data/examples/directory_based_runbooks/staging-teardown/runbook.md +57 -0
  18. data/examples/directory_based_runbooks/staging-teardown/steps/010-confirm-nothing-to-preserve.md +18 -0
  19. data/examples/directory_based_runbooks/staging-teardown/steps/020-snapshot-the-database.md +47 -0
  20. data/examples/directory_based_runbooks/staging-teardown/steps/030-stop-the-pipeline.md +32 -0
  21. data/examples/directory_based_runbooks/staging-teardown/steps/040-drain-the-ecs-services.md +35 -0
  22. data/examples/directory_based_runbooks/staging-teardown/steps/050-record-the-final-row-counts.md +41 -0
  23. data/examples/directory_based_runbooks/staging-teardown/steps/060-delete-the-stack.md +56 -0
  24. data/examples/directory_based_runbooks/staging-teardown/verify.md +40 -0
  25. data/examples/single_md_file_runbooks/db-maintenance.md +163 -0
  26. data/examples/single_md_file_runbooks/disk-space-triage.md +90 -0
  27. data/examples/single_md_file_runbooks/disk-usage-glossary.md +48 -0
  28. data/examples/single_md_file_runbooks/policies/cleanup-policy.md +33 -0
  29. data/examples/single_md_file_runbooks/policies/images/triage-flow.svg +44 -0
  30. data/examples/single_md_file_runbooks/runbooks_with_scripts/.loki +17 -0
  31. data/examples/single_md_file_runbooks/runbooks_with_scripts/asgard-tasks.md +135 -0
  32. data/examples/single_md_file_runbooks/runbooks_with_scripts/checks.loki +27 -0
  33. data/examples/single_md_file_runbooks/runbooks_with_scripts/greet.loki +6 -0
  34. data/lib/runsheets/assets.rb +780 -0
  35. data/lib/runsheets/block.rb +118 -0
  36. data/lib/runsheets/cli.rb +424 -0
  37. data/lib/runsheets/config/defaults.yml +44 -0
  38. data/lib/runsheets/config.rb +139 -0
  39. data/lib/runsheets/diff.rb +67 -0
  40. data/lib/runsheets/execution.rb +144 -0
  41. data/lib/runsheets/executor.rb +157 -0
  42. data/lib/runsheets/expectation.rb +101 -0
  43. data/lib/runsheets/fences.rb +172 -0
  44. data/lib/runsheets/front_matter.rb +29 -0
  45. data/lib/runsheets/library.rb +268 -0
  46. data/lib/runsheets/pages/chooser.rb +408 -0
  47. data/lib/runsheets/pages/search.rb +119 -0
  48. data/lib/runsheets/pages/tree.rb +88 -0
  49. data/lib/runsheets/pages.rb +947 -0
  50. data/lib/runsheets/redactor.rb +88 -0
  51. data/lib/runsheets/renderer.rb +229 -0
  52. data/lib/runsheets/run.rb +330 -0
  53. data/lib/runsheets/run_record.rb +371 -0
  54. data/lib/runsheets/runbook.rb +301 -0
  55. data/lib/runsheets/search.rb +129 -0
  56. data/lib/runsheets/session.rb +307 -0
  57. data/lib/runsheets/session_log.rb +114 -0
  58. data/lib/runsheets/single_file.rb +93 -0
  59. data/lib/runsheets/step.rb +91 -0
  60. data/lib/runsheets/version.rb +5 -0
  61. data/lib/runsheets/web.rb +537 -0
  62. data/lib/runsheets.rb +90 -0
  63. data/sig/runsheets.rbs +4 -0
  64. metadata +226 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5b4caf43ab35c8816f90d794e6dba06e3fd3644cd838c89c9987bbe0f60ab786
4
+ data.tar.gz: 39cd9610b55e341bc1b664443a509a2269f7397c190e28f38fcbcc6c9287f2db
5
+ SHA512:
6
+ metadata.gz: cec8d91aa87096dfae4548d1eb39df3c9b6d57e0d2176d24e12550f9ff7a21c0487e0f850b7fd0c5fd9178f7cd471057e40771c3a2918b897ec7f57fa63b1282
7
+ data.tar.gz: 2d960f098334bf2b7900eae5084c1fa722a27b6c115d6da26e4020e4cd9b5c32a546d265ba448a36d4bbb6dfcfdb730ae16d7ab0b3112ae6308d341f54c16ba0
data/CHANGELOG.md ADDED
@@ -0,0 +1,98 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [0.0.1] - 2026-10-11
6
+
7
+ First release. runsheets turns a markdown runbook into a local web page
8
+ where marked commands get a Run button, and records everything that
9
+ happens during a run as a *runsheet*.
10
+
11
+ ### Runbooks
12
+
13
+ - A runbook is a directory (`runbook.md` with YAML front matter, ordered
14
+ `steps/`, optional `verify.md` and `rollback.md`) or a single markdown
15
+ file whose `##` headings are the steps.
16
+ - Front matter declares `inputs` (with secrets) and `interpreters` that map
17
+ other languages, such as `sql` through `psql`, to a command.
18
+ - `runsheets --init PATH` scaffolds a starter runbook; `runsheets --check`
19
+ reports authoring warnings.
20
+ - Runbooks are re-read when their files change on disk.
21
+
22
+ ### The fenced block convention
23
+
24
+ - The info string marks a block as `run`, `destructive`, `background`,
25
+ `terminal` or `expect`.
26
+ - `run` blocks execute `bash`, `sh`, `zsh`, `ruby` or any mapped language
27
+ as a fresh process group with a per-step timeout.
28
+ - `destructive` blocks need a typed confirmation, checked on the server.
29
+ - `background` blocks get Start and Stop buttons and appear in a Running
30
+ panel on every page.
31
+ - `terminal` blocks are run by the operator, who confirms with "I ran this"
32
+ and an optional note.
33
+ - `text expect` blocks are compared with the output of the block above
34
+ them. `{{any}}` matches text that changes on each run, and a mismatch
35
+ shows the output and the expected text side by side with the differences
36
+ highlighted.
37
+
38
+ ### Libraries, search and documents
39
+
40
+ - `runsheets DIR` on a folder of runbooks opens a library: a folder tree
41
+ with a filter box, each folder's `README.md`, and a summary of each
42
+ runbook with its inputs, steps and previous runs.
43
+ - Full-text search across every runbook in the library, with ranked
44
+ results and highlighted snippets.
45
+ - Relative links to other markdown files open as pages: another runbook,
46
+ or a plain document with nothing executable.
47
+ - A Checks page gathers every verify document and runs them all with one
48
+ button.
49
+
50
+ ### Sessions and the run record
51
+
52
+ - Everything between starting and stopping runsheets is one session, with
53
+ an engineer and a reason (from the start page, or `--engineer` and
54
+ `--why`).
55
+ - The session page holds timestamped notes, every run's progress, the tail
56
+ of the session log, and an End session button. Ending a session, or
57
+ Ctrl-C, closes each run as `completed`, `partial` or `opened`. A session
58
+ left open by a killed process is closed as `interrupted` at the next
59
+ start.
60
+ - Each run records the exact command, output, exit status, timing, step
61
+ acknowledgements and notes in `run.json` and a readable `run.md`.
62
+ - `session.log` records every action and line of output, timestamped and
63
+ levelled, and echoes it to the terminal.
64
+ - Run history, and a diff for every block whose code has changed since a
65
+ past run.
66
+
67
+ ### Security
68
+
69
+ - Binds to loopback by default. Every request needs the session key the
70
+ printed `?token=` link gives the browser as a cookie, so other accounts
71
+ on the machine see nothing; every state-changing request also needs the
72
+ per-process session token and an allowed Host header.
73
+ - Secret inputs are redacted from output and logs, however the output is
74
+ split, and never written back into the page.
75
+ - Every page has a Content-Security-Policy with a per-response nonce, so a
76
+ `<script>` in a runbook's markdown does not run, and the page's script
77
+ acts only on blocks runsheets rendered, so a copied Run button does
78
+ nothing. Run refuses a block edited since the page loaded. Files served
79
+ from a runbook are sandboxed and cannot follow a symlink out of it.
80
+ - Anything a block leaves running in its process group is stopped when
81
+ the block finishes.
82
+ - A project `./config/runsheets.yml` cannot by itself bind runsheets to a
83
+ non-loopback address.
84
+
85
+ ### Settings
86
+
87
+ - Layered settings through `myway_config`. Command-line flags override
88
+ `RUNSHEETS_*` environment variables, which override `./config/runsheets.yml`,
89
+ which overrides `~/.config/runsheets/runsheets.yml`, which overrides the
90
+ bundled defaults.
91
+ - `runsheets --dump` prints the settings in force as a config file.
92
+
93
+ ### Examples
94
+
95
+ - `examples/` holds directory and single-file runbooks: `hello`,
96
+ `staging-teardown`, `db-maintenance.md`, `disk-space-triage.md` with the
97
+ plain documents it links to, and `asgard-tasks.md`, whose blocks call
98
+ asgard tasks.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Dewayne VanHoozer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,277 @@
1
+ # runsheets
2
+
3
+ > **Note:** This is a proof-of-concept and a work in progress. Features,
4
+ > file formats, and the CLI may change without notice.
5
+ >
6
+ > Runbooks are common among DevOps engineers but a little foreign to me.
7
+ > Please [open an issue](https://github.com/MadBomber/runsheets/issues) and
8
+ > let me know where I get this concept wrong. I appreciate all feedback and
9
+ > contributions.
10
+ >
11
+ > See the [CHANGELOG.md](https://github.com/MadBomber/runsheets/blob/main/CHANGELOG.md)
12
+ > file for the latest changes.
13
+
14
+ **Full documentation:** <https://madbomber.github.io/runsheets>. This
15
+ README is the overview; the documentation site covers authoring, the
16
+ block convention, the web UI, the run record, settings and the security
17
+ model in detail.
18
+
19
+ Executable runbooks. A runbook (a directory of markdown files, or a single
20
+ markdown file) becomes a local web page
21
+ where the steps read like a doc site, the commands the author marked as
22
+ runnable get a Run button, and everything that happens during a run is
23
+ recorded: the exact command, its output, exit status, timing, and the
24
+ operator's acknowledgements of the manual steps.
25
+
26
+ The rendering is the vehicle. The run record, the *runsheet*, is the point.
27
+
28
+ Every block kind in the convention below is live, a runbook can be a
29
+ directory or a single markdown file, and everything between starting
30
+ runsheets and stopping it is one *session* with its own log. Vocabulary: the document is
31
+ the *runbook*; the record of one runbook's run is the *runsheet*.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ gem install runsheets
37
+ ```
38
+
39
+ Requires Ruby 3.4 or newer.
40
+
41
+ ## Run
42
+
43
+ ```bash
44
+ runsheets # serve the bundled examples/directory_based_runbooks/hello on http://127.0.0.1:4567/
45
+ runsheets path/to/runbook # serve a runbook directory (runbook.md + steps/)
46
+ runsheets path/to/runbook.md # serve a single all-in-one markdown file
47
+ runsheets path/to/runbooks # a directory of runbooks: choose one in the browser
48
+ runsheets --open path/to/runbook # and open the browser
49
+ runsheets --check path/to/runbook # load it, print authoring warnings, exit
50
+ runsheets --init path/to/new # scaffold a runbook (a .md path makes a single file)
51
+ ```
52
+
53
+ The argument is one of three things: a runbook directory, a single
54
+ all-in-one markdown file whose `##` headings are the steps (see
55
+ [Single-file runbooks](docs/runbooks/structure.md#single-file-runbooks)),
56
+ or a directory holding several of either, in folders nested as deep as
57
+ you like. A markdown file is a runbook only when it starts with YAML front
58
+ matter that has a `title`; any other markdown file is a plain document that
59
+ a runbook can link to (see
60
+ [Runbooks and Documents](docs/concepts/runbooks.md)). Started on such a library, the browser opens on a folder tree in
61
+ the left pane; selecting a runbook shows its description, prerequisites,
62
+ inputs, steps and previous runs in the main pane, with the inputs form and
63
+ a **Start run** button.
64
+ A `README.md` in a folder is shown as that folder's description. The
65
+ search box in the header searches the full text of every runbook. Try the
66
+ examples bundled with the gem:
67
+
68
+ ```bash
69
+ cd "$(dirname "$(gem which runsheets)")/.." # the installed gem's directory
70
+ runsheets --open examples # all five, pick one in the browser
71
+ runsheets --open examples/directory_based_runbooks/hello # safe to run: every block kind
72
+ runsheets --check examples/directory_based_runbooks/staging-teardown # a realistic AWS teardown
73
+ runsheets --open examples/single_md_file_runbooks/db-maintenance.md # a single all-in-one file, sql blocks via psql
74
+ runsheets --open examples/single_md_file_runbooks/disk-space-triage.md # links to plain documents, kept out of the tree
75
+ ```
76
+
77
+ Options: `--port`, `--bind` (default loopback), `--runs-dir` (where run
78
+ records go; default `~/.local/share/runsheets/runs`), `--open`, `--check`,
79
+ `--init`, `--config FILE`, `--dump` to print the settings in force as a
80
+ config file (redirect it to save them), and for the session `--engineer`,
81
+ `--why`, `--log-level`, `--verbose` and `--quiet`. Settings are layered: the command line beats
82
+ `RUNSHEETS_*` environment variables (`RUNSHEETS_PORT`, `RUNSHEETS_DIR` for
83
+ the runbook, ...), which beat `./config/runsheets.yml` (or the file `--config` or
84
+ `RUNSHEETS_CONFIG` names), which beats `~/.config/runsheets/runsheets.yml`,
85
+ which beats the defaults bundled in `lib/runsheets/config/defaults.yml`. See
86
+ [docs/running/cli.md](docs/running/cli.md#settings).
87
+
88
+ ## Sessions
89
+
90
+ Everything between starting `runsheets` and stopping it is one *session*.
91
+ It starts by asking who you are and why you are starting it (or take them
92
+ from `--engineer` and `--why`); the why is the session's first note. Then:
93
+
94
+ - **Select a runbook** and fill in its inputs: that starts its *run*. Run
95
+ buttons are disabled until then.
96
+ - **Work through the steps**, or open any step, in any order, and run just
97
+ that. A run is the record executions are written to, not an order you
98
+ have to follow (see [docs/concepts/runs.md](docs/concepts/runs.md#a-run-is-a-record-not-a-sequence)).
99
+ - **Switch runbooks** whenever you like. Every run stays open, and
100
+ selecting a runbook again returns to its run.
101
+ - **Write notes** on the session page as things happen.
102
+ - **End the session** with the button on the session page, or Ctrl-C.
103
+ Every run closes with the status its work earns: `completed`,
104
+ `partial`, or `opened`.
105
+
106
+ Everything you do, and every line of output, goes to a `session.log`
107
+ written as it happens and echoed to the terminal, like a Rails log:
108
+
109
+ ```text
110
+ 2026-10-10 01:17:19.589 INFO [disk-space-triage 010-check-free-space #acd50b22502a] execute bash via bash
111
+ 2026-10-10 01:17:19.589 INFO [#acd50b22502a] $ df -h "$TARGET_DIR"
112
+ 2026-10-10 01:17:19.602 INFO [#acd50b22502a] > /dev/disk3s5 1.8Ti 1.4Ti 391Gi 79% /System/Volumes/Data
113
+ 2026-10-10 01:17:19.651 INFO [#acd50b22502a] finished exit 0 in 0.06s
114
+ ```
115
+
116
+ `--log-level`, `--verbose` and `--quiet` tune it; see
117
+ [docs/running/run-record.md](docs/running/run-record.md).
118
+
119
+ ## A runbook is a directory
120
+
121
+ ```text
122
+ staging-teardown/
123
+ runbook.md front matter + preamble (required)
124
+ steps/
125
+ 010-confirm-nothing-to-preserve.md
126
+ 020-stop-the-pipeline.md
127
+ 030-drain-ecs-services.md
128
+ verify.md whole-procedure checks (optional)
129
+ rollback.md what to do when it fails partway (optional)
130
+ assets/ images referenced by the markdown
131
+ ```
132
+
133
+ The directory name is the runbook's slug. Steps are ordered by filename;
134
+ numeric prefixes with gaps leave room to insert.
135
+
136
+ ### runbook.md
137
+
138
+ ```yaml
139
+ ---
140
+ title: Staging Infrastructure Teardown and Rebuild
141
+ when_to_use: >
142
+ The deployed stacks cannot be updated in place.
143
+ prerequisites:
144
+ - AWSAdministratorAccess in the staging account
145
+ - The staging branch is ready to fast-forward
146
+ blast_radius: >
147
+ Destroys the staging database, container images, and DNS.
148
+ escalation: Stop and contact the platform owner if any stack delete fails twice.
149
+ tags: [aws, staging, destructive]
150
+ inputs:
151
+ - name: AWS_PROFILE
152
+ prompt: AWS profile with admin access
153
+ default: staging-admin
154
+ - name: DB_PASSWORD
155
+ prompt: Database password
156
+ secret: true
157
+ interpreters:
158
+ ruby: bin/rails runner -
159
+ ---
160
+ Everything below the front matter is the preamble and renders on the
161
+ landing page.
162
+ ```
163
+
164
+ `inputs` become a form when a run starts. Values are exported as
165
+ environment variables to every executed block, so blocks refer to them as
166
+ `$NAME` and stay copy-pasteable into a terminal. A blank field falls back
167
+ to the value given for the same name earlier in the session (never for a
168
+ secret), then the process environment, then `default`. A `secret` input is masked in
169
+ the form and never written to the run record.
170
+
171
+ `interpreters` maps a language to the command that runs a file of it. The
172
+ defaults are `bash`, `sh`, `zsh` and `ruby`; a Rails project would map
173
+ `ruby` to `bin/rails runner -`, and `sql: psql -X -v ON_ERROR_STOP=1 -f`
174
+ makes `sql run` blocks execute through `psql`.
175
+
176
+ ### Single-file runbooks
177
+
178
+ One markdown file works too: the front matter is the runbook's, every
179
+ `##` heading is a step, attributes go in an HTML comment after the
180
+ heading (`<!-- kind: verify, timeout: 30 -->`), and sections headed
181
+ **Verify** and **Rollback** stand in for the extra files. See
182
+ `examples/single_md_file_runbooks/db-maintenance.md`.
183
+
184
+ ### Step files
185
+
186
+ ```yaml
187
+ ---
188
+ title: Drain the ECS services
189
+ kind: automated # automated | manual | verify
190
+ destructive: false
191
+ timeout: 600 # seconds, for executable blocks in this step
192
+ cwd: . # working directory, relative to the runbook
193
+ ---
194
+ ```
195
+
196
+ The body is ordinary markdown: the instruction, the command, and the "how to
197
+ tell it worked" line that every good step carries. `kind` defaults to
198
+ `automated` when the body has an executable block, otherwise `manual`.
199
+
200
+ ## The fenced block convention
201
+
202
+ The info string's first word is the language. Any following words are
203
+ flags. A block with no flag is display only, whatever its language.
204
+
205
+ | Info string | Behaviour |
206
+ | --- | --- |
207
+ | ```` ```bash ```` | Display only. |
208
+ | ```` ```bash run ```` | Run button. Executed with the run's inputs in the environment; stdout, stderr and exit status recorded. |
209
+ | ```` ```bash destructive ```` | Run button behind a typed confirmation code issued by the server. Implies `run`. |
210
+ | ```` ```ruby run ```` | Same, executed with `ruby` (or the runbook's `interpreters.ruby`). |
211
+ | ```` ```bash background ```` | Start and Stop buttons. No timeout; output streams into the page; listed in the sidebar's Running panel; stopped when the run ends. |
212
+ | ```` ```bash terminal ```` | No Run button. "Run this in your own terminal, then confirm." An **I ran this** button records the confirmation, with an optional note. |
213
+ | ```` ```text expect ```` | Not executed. Compared with the output of the executable block above it; a mismatch is shown side by side with the differences marked. `{{any}}` matches anything within a line. |
214
+
215
+ An unknown flag, or `run` on a language that cannot execute, renders with a
216
+ visible warning instead of being ignored. `runsheets --check` lists them.
217
+
218
+ Each execution is a fresh process with the runbook directory as its working
219
+ directory. Nothing carries over between blocks except the environment.
220
+
221
+ ## The run record
222
+
223
+ ```text
224
+ ~/.local/share/runsheets/runs/
225
+ sessions/<session-id>/
226
+ session.json who, when, the notes, the runs, how it ended
227
+ session.log every action and its output, appended as it happens
228
+ <runbook-slug>/<session-id>/
229
+ run.json steps, blocks, timestamps, exit codes, acknowledgements
230
+ run.md the same as a readable transcript
231
+ blocks/
232
+ 020-stop-the-pipeline-1.1.cmd the exact code that ran
233
+ 020-stop-the-pipeline-1.1.out its stdout + stderr
234
+ ```
235
+
236
+ Records live outside the runbook so captured output never lands next to
237
+ the docs in a repository. A runbook's landing page lists its previous
238
+ runs; each points back to its session. A session left running by a killed
239
+ process is closed as `interrupted` the next time runsheets starts.
240
+
241
+ ## Security posture
242
+
243
+ - Binds to loopback by default. There is no multi-user story. `--bind` to
244
+ another address is possible, warns, and is a bad idea off a network you
245
+ control.
246
+ - Executes only blocks whose info string opts in. Everything else is inert.
247
+ - Every request needs this start's session key, which the browser gets by
248
+ opening the link runsheets prints. Another account on the same machine
249
+ can reach the port but sees nothing.
250
+ - Every state-changing request needs a per-process token that only a page
251
+ served by this process knows, and the `Host` header must be a loopback
252
+ name. Together these stop a page on another origin from driving the tool.
253
+ - Every page sets a Content-Security-Policy that lets only the page's own
254
+ script run, and the script acts only on blocks runsheets itself rendered.
255
+ Raw HTML in a runbook renders, but neither its script nor a copied Run
256
+ button does anything, so a runbook cannot drive the tool either.
257
+ - Anything a block leaves running (`cmd &`) is stopped when the block
258
+ finishes; a long-running process belongs in a `background` block.
259
+ - Destructive blocks show the blast radius and need a confirmation code the
260
+ server issues per block, checked server-side.
261
+ - Secret inputs reach the child process but never the run record. Captured
262
+ output is redacted before it is written; plain string replacement, so an
263
+ encoded secret is not caught.
264
+ - The tool writes nothing inside the runbook directory.
265
+ - Stopping the server ends the session: every run is closed and whatever it
266
+ left running is stopped. Secret values never reach the session log.
267
+
268
+ ## Design
269
+
270
+ The model (`Runbook`, `Library`, `Step`, `Block`, `Renderer`, `Executor`,
271
+ `Expectation`, `Search`, `RunRecord`, `Run`, `Session`, `SessionLog`) has
272
+ no dependency on Sinatra and is tested in isolation; `Web` and `Pages` are
273
+ tested with rack-test.
274
+
275
+ ## License
276
+
277
+ MIT. See [LICENSE.txt](LICENSE.txt).
data/bin/runsheets ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Make the script runnable straight from a checkout (bin/ on PATH, no
5
+ # `bundle exec`): put the sibling lib/ first on the load path. For an
6
+ # installed gem this resolves to the gem's own lib/, which is already there.
7
+ lib = File.expand_path("../lib", __dir__)
8
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
9
+
10
+ require "runsheets"
11
+
12
+ exit Runsheets::CLI.run(ARGV)
@@ -0,0 +1,40 @@
1
+ # About these examples
2
+
3
+ This file has no front matter, so it is a plain document, not a runbook.
4
+ It is not listed in the library tree; runbooks link to it.
5
+
6
+ Start runsheets on this directory to choose among the examples:
7
+
8
+ ```bash
9
+ runsheets --open examples
10
+ ```
11
+
12
+ The examples are grouped by shape, and each group is a folder in the
13
+ library tree:
14
+
15
+ - `directory_based_runbooks/`: runbook directories, a `runbook.md` beside
16
+ a `steps/` folder holding one file per step.
17
+ - `single_md_file_runbooks/`: single-file runbooks, every step in one
18
+ markdown file, together with the plain documents they link to.
19
+ `runbooks_with_scripts/` inside it holds a runbook whose blocks call
20
+ asgard tasks from the `.loki` files beside it.
21
+
22
+ | Example | Shape | Safe to run |
23
+ | --- | --- | --- |
24
+ | [Hello, runsheets](directory_based_runbooks/hello/runbook.md) | runbook directory | yes |
25
+ | [Disk space triage](single_md_file_runbooks/disk-space-triage.md) | single file | yes, it only reads |
26
+ | [Monthly PostgreSQL maintenance](single_md_file_runbooks/db-maintenance.md) | single file | needs a database |
27
+ | [Staging infrastructure teardown](directory_based_runbooks/staging-teardown/runbook.md) | runbook directory | no, read it |
28
+ | [Runbook tasks from local .loki files](single_md_file_runbooks/runbooks_with_scripts/asgard-tasks.md) | single file with `.loki` task files | yes, it only reads; needs asgard |
29
+
30
+ ## Plain documents
31
+
32
+ Besides this file, two plain documents back the disk space triage runbook:
33
+ [the disk usage glossary](single_md_file_runbooks/disk-usage-glossary.md) beside it, and
34
+ [the cleanup policy](single_md_file_runbooks/policies/cleanup-policy.md) in a folder of its own.
35
+
36
+ A runbook can link to a plain document anywhere under the directory
37
+ runsheets was started on (here, `examples/`), with an ordinary relative
38
+ link. Started on a single runbook instead (`runsheets examples/directory_based_runbooks/hello`),
39
+ that runbook's own directory is the limit, so a link climbing out of it,
40
+ like the one `directory_based_runbooks/hello/runbook.md` makes to this file, does not resolve.
@@ -0,0 +1,11 @@
1
+ ---
2
+ title: Rollback
3
+ ---
4
+
5
+ Nothing in this runbook needs rolling back. In a real runbook this file holds
6
+ the steps that undo a partial run, and it sits one click away in the sidebar
7
+ of every step so nobody has to scroll for it when something goes wrong.
8
+
9
+ ```bash
10
+ echo "nothing to undo"
11
+ ```
@@ -0,0 +1,40 @@
1
+ ---
2
+ title: Hello, runsheets
3
+ when_to_use: >
4
+ You want to see what an executable runbook looks like, or you need a
5
+ fixture that exercises every block kind without touching anything real.
6
+ prerequisites:
7
+ - A shell with bash and ruby on the PATH
8
+ - Nothing else; every command here is harmless
9
+ escalation: There is nobody to call. Read the step again.
10
+ tags: [example, safe]
11
+ inputs:
12
+ - name: NAME
13
+ prompt: Who should be greeted?
14
+ default: world
15
+ - name: SECRET_WORD
16
+ prompt: A word that must never appear in the run record
17
+ default: hunter2
18
+ secret: true
19
+ ---
20
+
21
+ # Hello, runsheets
22
+
23
+ This runbook exists to show the shape of a runbook directory and the fenced
24
+ block convention. Nothing in it changes your machine.
25
+
26
+ A runbook is a directory:
27
+
28
+ ```text
29
+ hello/
30
+ runbook.md this file: front matter and preamble
31
+ steps/*.md one file per step, ordered by filename
32
+ verify.md whole-procedure checks
33
+ rollback.md what to do when it goes wrong
34
+ ```
35
+
36
+ Start a run from the panel above, then walk the steps in the sidebar.
37
+
38
+ This runbook sits in a directory of examples. [About these
39
+ examples](../../about-the-examples.md) is a plain document two levels up,
40
+ linked like any other markdown file.
@@ -0,0 +1,33 @@
1
+ ---
2
+ title: Say hello
3
+ kind: automated
4
+ timeout: 30
5
+ ---
6
+
7
+ The inputs you gave when starting the run are exported as environment
8
+ variables, so a block can refer to them as `$NAME` and still be pasted into a
9
+ terminal unchanged.
10
+
11
+ ```bash run
12
+ echo "Hello, $NAME!"
13
+ echo "Running block $RUNSHEETS_BLOCK of step $RUNSHEETS_STEP in run $RUNSHEETS_RUN_ID"
14
+ ```
15
+
16
+ A `text expect` block after it shows what the output should look like.
17
+ Once the block has run, the page compares the two and hides the expect
18
+ block: a match just says so, and a mismatch shows both side by side with
19
+ the differences marked. The run id is different every time, so the
20
+ expected text has `{{any}}` in its place, which matches anything on that
21
+ line. It is a hint, not a verdict.
22
+
23
+ ```text expect
24
+ Hello, world!
25
+ Running block 010-say-hello-1 of step 010-say-hello in run {{any}}
26
+ ```
27
+
28
+ A block with no flag is just displayed. This one is the same command, but it
29
+ has no Run button:
30
+
31
+ ```bash
32
+ echo "Hello, $NAME!"
33
+ ```
@@ -0,0 +1,28 @@
1
+ ---
2
+ title: Inspect the Ruby that will run things
3
+ kind: automated
4
+ ---
5
+
6
+ Ruby blocks execute with `ruby` unless the runbook's front matter maps the
7
+ language to another command, such as `bin/rails runner -`.
8
+
9
+ ```ruby run
10
+ puts "Ruby #{RUBY_VERSION} on #{RUBY_PLATFORM}"
11
+ puts "cwd: #{Dir.pwd}"
12
+ puts "greeting for: #{ENV.fetch('NAME', '(unset)')}"
13
+ ```
14
+
15
+ Other languages stay display only until there is a safe way to route them.
16
+ This SQL is for a client you open yourself:
17
+
18
+ ```sql
19
+ SELECT count(*) FROM runs WHERE status = 'completed';
20
+ ```
21
+
22
+ Some things cannot be run from a page at all, because they are interactive.
23
+ Do them in your own terminal, then click **I ran this** so the run record
24
+ shows you did, with a note if you want one:
25
+
26
+ ```bash terminal
27
+ read -r -p "Press Enter when you have looked at the output above: "
28
+ ```
@@ -0,0 +1,11 @@
1
+ ---
2
+ title: Take a breath
3
+ kind: manual
4
+ ---
5
+
6
+ Not every step is a command. A manual step is an instruction; the operator
7
+ reads it, does it, and marks the step done, optionally with a note that goes
8
+ into the run record.
9
+
10
+ 1. Look at the sidebar. Steps that have run show a mark next to them.
11
+ 2. Decide whether you want to see a failure next, or skip it.
@@ -0,0 +1,21 @@
1
+ ---
2
+ title: Keep a clock running
3
+ kind: automated
4
+ ---
5
+
6
+ Some things have to stay up while you work through later steps: a tunnel, a
7
+ log tail, a port forward. A `background` block has Start and Stop buttons
8
+ instead of Run. Its output streams into the page while it runs, it has no
9
+ timeout, and it is listed in the sidebar's **Running** panel on every page
10
+ with a Stop button of its own.
11
+
12
+ ```bash background
13
+ while true; do
14
+ date "+%H:%M:%S still here"
15
+ sleep 1
16
+ done
17
+ ```
18
+
19
+ Leave it running and move to the next step, or stop it here. Anything still
20
+ running when the run is finished is stopped for you, and the execution is
21
+ recorded as `stopped` rather than as a failure.
@@ -0,0 +1,26 @@
1
+ ---
2
+ title: Exercise a failure
3
+ kind: automated
4
+ destructive: true
5
+ timeout: 5
6
+ ---
7
+
8
+ This step is marked destructive in its front matter even though it does no
9
+ harm, so you can see the confirmation flow. Both blocks are safe.
10
+
11
+ The first one exits non-zero on purpose. The page shows the exit status and
12
+ the run record keeps it:
13
+
14
+ ```bash destructive
15
+ echo "about to fail on purpose" >&2
16
+ exit 3
17
+ ```
18
+
19
+ The second one sleeps longer than the step's five second timeout, so the
20
+ process group is killed and the execution is recorded as timed out:
21
+
22
+ ```bash run
23
+ echo "sleeping"
24
+ sleep 30
25
+ echo "never printed"
26
+ ```
@@ -0,0 +1,18 @@
1
+ ---
2
+ title: Check the greeting
3
+ kind: verify
4
+ ---
5
+
6
+ A `verify` step is a read-only check. It runs in its place during the
7
+ procedure, and it also appears on the **Checks** page with every other
8
+ check, where **Run all** runs them one after another inside the run.
9
+
10
+ ```bash run
11
+ echo "Hello, $NAME!" | grep -q "Hello, $NAME!" && echo "greeting looks right"
12
+ ruby -e 'puts "ruby answers: #{6 * 7}"'
13
+ ```
14
+
15
+ ```text expect
16
+ greeting looks right
17
+ ruby answers: 42
18
+ ```
@@ -0,0 +1,18 @@
1
+ ---
2
+ title: Verify
3
+ kind: verify
4
+ ---
5
+
6
+ Whole-procedure checks. These should pass after every run.
7
+
8
+ ```bash run
9
+ test -n "$NAME" && echo "NAME is set to $NAME"
10
+ test -d "$RUNSHEETS_RUN_DIR/blocks" && echo "run directory exists: $RUNSHEETS_RUN_DIR"
11
+ ```
12
+
13
+ A secret input never reaches the run record, even when a block prints it.
14
+ The value is replaced before the output is written:
15
+
16
+ ```bash run
17
+ echo "the secret is $SECRET_WORD"
18
+ ```