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
80 changes: 80 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Bug report
description: Onebox did something other than what it said it would do.
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Onebox holds a production application on someone else's server, so a
report that lets a maintainer reproduce the failure is worth far more
than a description of it. Please include the plan or command output
that shows the discrepancy.

If this is a security vulnerability, do not file it here — follow
[SECURITY.md](https://github.com/labstack/onebox/blob/main/SECURITY.md)
instead.

- type: textarea
id: what-happened
attributes:
label: What happened
description: What did Onebox do, and what did you expect it to do instead?
validations:
required: true

- type: textarea
id: reproduce
attributes:
label: Steps to reproduce
description: >
The exact commands you ran, in order. Include the relevant part of your
configuration if the failure depends on it.
placeholder: |
1. ob plan
2. ob deploy
3. ...
validations:
required: true

- type: textarea
id: output
attributes:
label: Command output
description: >
Paste the output that shows the problem. `ob --output json <command>`
is often the most useful form. Redact hostnames, secrets, and registry
credentials before pasting.
render: shell
validations:
required: false

- type: textarea
id: version
attributes:
label: Version
description: Output of `ob version`.
render: shell
validations:
required: true

- type: input
id: target-host
attributes:
label: Target host
description: >
Linux distribution and version on the server Onebox is managing, plus
the container runtime version if you know it.
placeholder: Ubuntu 24.04, Docker 27.3
validations:
required: false

- type: textarea
id: doctor
attributes:
label: ob doctor output
description: >
Optional, but it resolves a large share of environment-related reports
on the first round trip.
render: shell
validations:
required: false
11 changes: 11 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Documentation
url: https://onebox.run
about: Installation, the first deploy, configuration reference, and command output.
- name: Questions and ideas
url: https://github.com/labstack/onebox/discussions
about: Ask how something works, or float an idea before it becomes a proposal.
- name: Security vulnerabilities
url: https://github.com/labstack/onebox/blob/main/SECURITY.md
about: Report privately. Please do not open a public issue.
53 changes: 53 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Feature request
description: Propose a change to what Onebox does.
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Onebox has an explicit, narrow scope — one application, one active
production host — and a documented safety envelope. A change that
widens either is a product decision rather than a code review question,
which is why it is worth settling here before any code exists.

[`docs/product.md`](https://github.com/labstack/onebox/blob/main/docs/product.md)
records the direction, and
[`/status/capabilities`](https://onebox.run/status/capabilities) records
what the binary does today versus what the schema merely accepts.

- type: textarea
id: problem
attributes:
label: The problem
description: >
What are you trying to do, and what makes it hard or impossible today?
Describe the situation rather than the solution you have in mind.
validations:
required: true

- type: textarea
id: proposal
attributes:
label: Proposed change
description: What should Onebox do instead? Sketch the command or configuration if you have one.
validations:
required: true

- type: textarea
id: workaround
attributes:
label: Current workaround
description: How are you handling this now, and why is that unsatisfactory?
validations:
required: false

- type: textarea
id: scope
attributes:
label: Scope and safety
description: >
Does this widen the one-application, one-host scope, or change what
Onebox will do to a running production system without asking? If so,
say what the new boundary should be.
validations:
required: false
30 changes: 30 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
## What this changes

<!-- What does this pull request do, and why? Link the issue it settles. -->

Closes #

## Why this is correct

<!--
Onebox's value is that its output can be trusted without reading its source, so
a change that is merely plausible is not enough. Explain how you know this
behaves the way the description says: the test that fails without it, the
output you compared, or the invariant it preserves.
-->

## Effect on the safety envelope

<!--
Does this change what Onebox will do to a running production system, or move
something between "the schema accepts it" and "the binary does it"? If so, say
so here and note the corresponding /status/capabilities update. Write "None" if
neither applies.
-->

## Checklist

- [ ] `just check` passes locally.
- [ ] Tests cover the new behaviour, including the failure paths.
- [ ] Generated documentation is current (`just check` verifies this).
- [ ] I have accepted the [CLA](https://github.com/labstack/onebox/blob/main/CLA.md), or will when the bot asks on my first pull request.
9 changes: 9 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ Then commit the result. `just check` fails if the committed pages do not match
what the binary produces, and CI fails on any uncommitted change after the
gate runs.

The social preview card is generated too, but on a looser contract.
`site/public/social-card.png` comes from `docs/media/social-card.typ` by way of
`just social-card`, which needs IBM Plex Mono on a font path — set
`ONEBOX_FONT_PATH` to a directory holding it. CI has no such directory, so the
gate cannot re-render the card to compare it; what `site-build` does assert is
that the committed PNG exists and still matches the dimensions the site's head
advertises. If you edit the `.typ`, run the recipe and commit the PNG beside it,
because nothing else will notice that the two have drifted apart.

## Tests

New behavior needs a test that fails without the change. Onebox's test suite is
Expand Down
56 changes: 56 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,62 @@ site-build: docs-generate-check
# Validate that every generated reference page matches the binary.
docs-check: docs-generate-check

# Render the social preview card into site/public.
#
# The card is the repository's GitHub social preview and the site's og:image, so
# it is generated rather than hand-drawn: edit docs/media/social-card.typ and run
# this. It is deliberately not part of `check`. Rendering needs IBM Plex Mono on
# a font path, which CI does not have and this repository does not vendor — the
# site loads the face through @fontsource for the browser, and carrying a copy in
# the tree to draw one image is a poor trade. `site-build` asserts the committed
# PNG is present and correctly sized, which is the part a gate can honestly do.
#
# Typst warns on an unknown font family and still exits 0. A missing face would
# therefore not fail the render; it would quietly ship a card set in a fallback
# face that no longer looks like the product. This reads the warning back and
# fails on it.
#
# The version is pinned because typst's layout changes between releases: another
# one re-renders every glyph position and produces a large diff that is not a
# real change.
social-card:
#!/bin/bash
set -euo pipefail
want_typst="0.15.1"
if ! command -v typst >/dev/null; then
echo "social-card: typst ${want_typst} is required — see https://github.com/typst/typst" >&2; exit 1
fi
have_typst=$(typst --version | awk '{print $2}')
if [ "$have_typst" != "$want_typst" ]; then
echo "social-card: typst ${have_typst} is installed, but the committed card was rendered with ${want_typst}." >&2
echo "social-card: another version re-lays out every glyph, so the diff would not be a real change." >&2
exit 1
fi
font_path="${ONEBOX_FONT_PATH:-}"
if [ -z "$font_path" ]; then
echo "social-card: set ONEBOX_FONT_PATH to a directory holding IBM Plex Mono (Regular and SemiBold)." >&2
echo "social-card: the OFL originals are at https://github.com/google/fonts/tree/main/ofl/ibmplexmono" >&2
exit 1
fi
# Rendered aside and moved into place only once it is known good, so a run
# that fell back to a substitute face cannot leave that card in the tree.
# A directory, not `mktemp -t <name>`: that reserves a name without the .png
# suffix typst needs, so appending one both leaves the reserved file behind
# and writes to a path nothing reserved.
staged_dir=$(mktemp -d)
trap 'rm -rf "$staged_dir"' EXIT
staged="$staged_dir/social-card.png"
render=$(typst compile --root . --font-path "$font_path" --ppi 96 --format png \
docs/media/social-card.typ "$staged" 2>&1)
if [ -n "$render" ]; then echo "$render"; fi
if echo "$render" | grep -qi "unknown font family"; then
echo "social-card: typst could not find a font it was asked for, so the card above is set in a fallback face." >&2
echo "social-card: point ONEBOX_FONT_PATH at IBM Plex Mono and render again." >&2
exit 1
fi
mv "$staged" site/public/social-card.png
echo "rendered site/public/social-card.png"

# Create and publish the next vYYYY.M.REVISION tag from releasable main.
release:
bash scripts/release.sh
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,22 @@ the full rules.
[Shipped vs proposed](https://onebox.run/status/capabilities) is the complete
account of what the binary executes today and what remains direction.

## How it compares

Onebox occupies a narrow spot: one application on one Linux server, with a
review gate in front of every change. Neighbouring tools solve overlapping
problems differently, and the difference is usually the boundary rather than
the feature list.

| If you use | Where Onebox differs |
| --- | --- |
| **Docker Compose** and a few shell scripts | Compose stays the runtime — Onebox generates it, and `ob eject` hands it back permanently. What you gain is the release layer around it: health-gated rolling replacement, recorded predecessors, rollback, and backups. |
| **Kamal** | Both deploy containers over SSH with no agent on the host. Kamal spans multiple hosts and applies when you run it; Onebox is deliberately single-host and puts a digest-bound plan and an explicit approval between you and production. |
| **Dokku**, **CapRover**, **Coolify** | Those run a control plane on the server and lead with git-push or a dashboard. Onebox has no dashboard and nothing resident: a CLI over SSH, a file in your repository, and generated Compose you can read. |
| **Ansible** | Ansible configures hosts in general; you still model application releases yourself. Onebox models only the release — plan, approve, deploy, roll back — and expects you to administer the Linux host underneath it. |
| **Kubernetes** or **k3s** | A cluster reconciles desired state continuously and survives a lost node. Onebox does neither, and says so: no failover, no scheduler. It buys the operational habits — a diff before apply, health gates, recorded releases — without the cluster. |
| **Terraform** | The plan-then-apply ceremony is borrowed on purpose. The subject is different: an application release on one box, not an infrastructure graph across providers. |

## Built for people and agents

The CLI is the interface for both. Every finite machine result uses one
Expand Down
52 changes: 52 additions & 0 deletions docs/media/social-card.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// The GitHub social preview card, which the documentation site also serves as
// its og:image at /social-card.png.
//
// Render it with `just social-card`, which pins the typst version, requires
// IBM Plex Mono on a font path, and fails when the face is missing. Running
// typst by hand does not: it warns about an unknown font family, exits 0, and
// writes a card set in whatever it found instead.
//
// The page is 960pt x 480pt, which is exactly 1280 x 640 pixels at 96 ppi: the
// size GitHub expects for a social preview, and large enough that a link unfurl
// does not resample it. `site-build` asserts the committed PNG still matches
// those numbers.
//
// The mark is read from the site's favicon rather than copied here, so a
// revised mark reaches the card the next time it is rendered.
#set page(width: 960pt, height: 480pt, margin: (x: 60pt, top: 44pt, bottom: 34pt), fill: rgb("#0d100d"))
#set text(font: "IBM Plex Mono", fill: rgb("#e2ece0"))

#place(top + left, dx: -60pt, dy: -44pt, rect(width: 960pt, height: 5pt, fill: rgb("#4f9a3c")))

#grid(
columns: (72pt, 1fr),
column-gutter: 22pt,
align: horizon,
image("../../site/public/favicon.svg", width: 68pt),
text(size: 56pt, weight: 600, "Onebox"),
)

#v(34pt)
#text(size: 34pt, weight: 600, fill: rgb("#8fd67a"))[
Plan-before-apply deploys. \
Zero downtime. One box.
]

#v(18pt)
#text(size: 20pt, fill: rgb("#87957f"))[
Production operations for one application \
intentionally running on one Linux server.
]

// Pushed down by flexible space rather than placed at the bottom out of flow.
// Placed, it drew at a fixed offset however tall the text above had grown, so
// one extra headline line put the divider through the body copy and typst still
// exited 0. In flow, that overflow spills onto a second page instead, and a
// two-page render fails the PNG export outright.
#v(1fr)
#line(length: 100%, stroke: 0.75pt + rgb("#22291f"))
#v(12pt)
#grid(columns: (1fr, auto),
text(size: 19pt, fill: rgb("#87957f"))[#text(fill: rgb("#4f9a3c"))[\$ ob plan] #h(18pt) sealed diff, then deploy],
text(size: 19pt, fill: rgb("#87957f"))[onebox.run],
)
2 changes: 1 addition & 1 deletion site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build && node scripts/check-tables.mjs",
"build": "astro check && astro build && node scripts/check-tables.mjs && node scripts/check-social-card.mjs",
"preview": "astro preview",
"astro": "astro"
},
Expand Down
Binary file added site/public/social-card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading