Add comprehensive documentation for releases and deployment

This commit is contained in:
Chris Wanstrath 2026-05-13 23:23:29 -07:00
parent 3baf943a53
commit e5e82f3085
8 changed files with 304 additions and 62 deletions

View File

@ -23,12 +23,36 @@ Personal web appliance that auto-discovers and runs multiple web apps on your ho
## Running
```bash
bun run dev # Hot reload (deletes pub/client/index.js first)
bun run start # Production
bun run check # Type check
bun run test # Tests
bun run dev # Hot reload (rebuilds client bundle on change)
bun run start # Production (generates templates, then runs server)
bun run check # Type check
bun run test # Tests
bun run build # Build client JS bundle (pub/client/index.js)
bun run release # Build release tarball for the Pi
```
## Building & Releasing
`bun run release` (runs `scripts/release.sh`) produces a self-contained tarball the Pi can install without building anything:
1. Client JS bundle → `pub/client/index.js`
2. Embedded templates → `src/lib/templates.data.ts` (generated from `templates/` by `scripts/embed-templates.ts`)
3. Pre-built bare git repos for bundled apps → `dist/repos/*.git` (built by `scripts/build-repos.sh`)
4. Cross-compiled CLI binary → `dist/toes` (linux-arm64, built by `scripts/build.ts`)
5. Everything staged and packed into `dist/toes-<version>.tar.gz`
The Pi installer (`install/install.sh`) downloads this tarball, runs `bun install` for the server and all bundled apps (in parallel), copies pre-built repos and CLI binary into place, and starts the systemd service. No git commands, no compilation on the Pi.
### Key scripts
- `scripts/build.sh` -- Builds client JS bundle only (used during dev)
- `scripts/build-repos.sh` -- Pre-builds bare git repos for bundled apps (excludes node_modules, snapshots, logs)
- `scripts/release.sh` -- Full release pipeline: client + templates + repos + CLI → tarball
- `scripts/build.ts` -- CLI binary compiler (current platform or `--all` for cross-compile)
- `scripts/embed-templates.ts` -- Generates `src/lib/templates.data.ts` from `templates/`
- `install/install.sh` -- Pi installer, downloads release tarball and sets everything up
- `scripts/remote-install.sh` -- Runs the installer on a remote Pi over SSH
## Project Structure
```
@ -235,6 +259,21 @@ function start(app: App): void {
}
```
## Install & Deployment
The install script (`install/install.sh`) is designed to run on a fresh Pi or as an updater:
1. Installs system packages (git, fish, avahi-utils, etc.) via apt
2. Installs Bun and grants `cap_net_bind_service`
3. Downloads and extracts the release tarball into `~/toes`
4. Runs `bun install` for the server
5. Copies bundled apps to `~/apps/` and runs `bun install` for each (in parallel)
6. Copies pre-built bare repos to `~/data/repos/` (for git-based versioning)
7. Installs the pre-built CLI binary to `/usr/local/bin/toes`
8. Sets up SSH access and the systemd service
The release tarball URL is configured as `RELEASE_URL` at the top of `install/install.sh`.
## Writing Apps and Tools
See `docs/GUIDE.md` for the guide to writing toes apps and tools.

View File

@ -1,42 +1,69 @@
# 🐾 Toes
Toes is a personal web appliance you run on your home network.
Personal web appliance you run on your home network.
Plug it in, turn it on, and forget about the cloud.
## setup
## Development
Toes runs on a Raspberry Pi. You'll need:
```bash
bun run dev # Hot reload (rebuilds client bundle on change)
bun run start # Production mode
bun run check # Type check
bun run test # Tests
bun run build # Build client JS bundle
bun run release # Build a release tarball for the Pi
```
- A Raspberry Pi 5 running the latest Raspberry Pi OS
- A `toes` user with passwordless sudo
### Releasing
SSH into your Pi as the `toes` user and run:
`bun run release` builds everything the Pi needs into a single tarball:
1. Client JS bundle (`pub/client/index.js`)
2. Embedded templates (`src/lib/templates.data.ts`)
3. Pre-built bare git repos for bundled apps (`dist/repos/`)
4. Cross-compiled CLI binary for linux-arm64 (`dist/toes`)
Output: `dist/toes-<version>.tar.gz`
The Pi does zero building — it untars, runs `bun install`, and starts. Upload the tarball to wherever `RELEASE_URL` in `install/install.sh` points (currently `https://toes.dev/release/latest.tar.gz`).
### Scripts
| Script | What it does |
|--------|-------------|
| `scripts/build.sh` | Builds the client JS bundle into `pub/client/index.js` |
| `scripts/build-repos.sh` | Pre-builds bare git repos for bundled apps in `dist/repos/` |
| `scripts/release.sh` | Full release: client + templates + repos + CLI → tarball |
| `scripts/build.ts` | Builds the CLI binary (current platform or cross-compile) |
| `scripts/embed-templates.ts` | Generates `src/lib/templates.data.ts` from `templates/` |
| `scripts/setup-ssh.sh` | Configures SSH access for the `cli` user on the Pi |
| `scripts/remote-install.sh` | Runs the installer on a remote Pi over SSH |
## Setup
Toes runs on a Raspberry Pi 5 with a `toes` user and passwordless sudo.
```bash
curl -fsSL https://toes.dev/install | bash
```
This will:
The installer downloads the release tarball, installs bun and system packages, runs `bun install` for the server and all bundled apps (in parallel), copies the pre-built CLI and git repos into place, and starts the systemd service.
1. Install system dependencies (git, fish shell, networking tools)
2. Install Bun and grant it network binding capabilities
3. Clone and build the toes server
4. Set up bundled apps and tools (clock, code, cron, env, stats)
5. Install and enable a systemd service for auto-start
Dashboard: `http://<hostname>.local`
Once complete, visit `http://<hostname>.local` on your local network.
## Features
## features
- Hosts Bun/Hype webapps (SSR and SPA)
- `git push` Heroku-style deploys
- Web dashboard with real-time status, logs, and tools
- `toes` CLI (local install or SSH)
- Per-app environment variables, cron jobs, metrics
- Public sharing via tunnels
- Effortlessly hosts bun/hype webapps - both SSR and SPA.
- `git push`, Heroku-style deploys
- https://toes.local web UI for managing your projects.
- `toes` CLI for managing your projects.
## SSH CLI
## ssh cli
You can manage your toes server from any machine on your network over SSH — no install required.
Manage your server from any machine on the network — no install required.
```bash
ssh cli@toes.local # interactive shell with tab completion
@ -44,24 +71,12 @@ ssh cli@toes.local list # run a single command
ssh cli@toes.local logs fog # stream logs for an app
```
The `cli` user's login shell is the `toes` binary itself. No password is needed. With no arguments, you get an interactive REPL. With arguments, it runs the command and exits.
## CLI Configuration
## cli configuration
by default, the CLI connects to `localhost:3000` in dev and `toes.local:80` in production.
By default, the CLI connects to `localhost:3000` in dev and `toes.local:80` in production.
```bash
toes config # show current host
TOES_URL=http://192.168.1.50:3000 toes list # connect to IP
TOES_URL=http://mypi.local toes list # connect to hostname
```
## fun stuff
- textOS (TODO, more?)
- Claude that knows about all your toes APIS and your projects.
- non-webapps
## february goal
- [ ] Corey and Chris are running Toes servers on their home networks, hosting personal projects and games.

81
docs/WEBHOOKS.md Normal file
View File

@ -0,0 +1,81 @@
# Rev Webhooks for Toes
Deploy Toes apps by saving to rev.host — no manual deploy step, no rsync scripts.
## How It Works
```
rev save "fix combat" → rev.host → relay (sneaker.toes.space) → toes.local pulls + deploys
```
toes.local can't receive inbound connections (home NAT), so it maintains an outbound connection to a relay — same tunnel infrastructure used by `toes share`.
## Setup Flow
1. In the Toes dashboard (or `toes` CLI via SSH), enable rev webhooks for an app
2. Toes connects to the relay and gets a stable webhook URL
3. Add that URL in rev.host project settings as a webhook endpoint
4. Done — `rev save` and `rev merge` now trigger deploys
## What Toes Does on Webhook
1. Receives event from relay (repo, ref, timestamp)
2. Pulls latest from rev.host (needs a rev auth token stored in Toes env)
3. Runs `scripts.predeploy` if defined in package.json (type-check, build, etc.)
4. Runs `bun install`
5. Restarts the app
## CLI
```bash
# Enable/disable rev webhooks
toes webhook enable [name] # Shows the relay URL to paste into rev.host
toes webhook disable [name]
# Manual trigger (pull latest and deploy now)
toes deploy [name]
# Check webhook status
toes webhook status [name]
```
## Settings UI
App settings page gets a "Rev Webhooks" section:
- Toggle to enable/disable
- Displays the relay URL (copy button)
- Field for rev.host auth token
- Last deploy timestamp + status
- "Deploy Now" button (manual trigger)
## Auth
Toes needs read access to pull from rev.host. Store a rev API token per-app (or globally):
```bash
toes env set -g REV_TOKEN rt_abc123
# or per-app
toes env set my-app REV_TOKEN rt_abc123
```
## Predeploy Scripts
Project-specific build steps go in package.json:
```json
{
"scripts": {
"toes": "bun run --watch index.tsx",
"predeploy": "bunx tsc --noEmit && bun build client/main.tsx --outdir dist --minify"
}
}
```
Toes runs `predeploy` after pulling but before restarting. If it exits non-zero, the deploy is aborted and the previous version stays running.
## Open Questions
- Should the relay URL be per-app or per-Toes-instance? (Per-instance with app routing via path seems simpler: `https://sneaker.toes.space/hooks/<instance-id>/<app-name>`)
- Webhook secret/signature verification — rev.host should sign payloads so the relay can't be spoofed
- Should `toes deploy` work without webhooks enabled? (Just pull from rev.host on demand — useful as a migration path from deploy.sh)
- Rollback: `toes rollback [name]` to revert to previous rev version?

View File

@ -8,7 +8,7 @@ set -euo pipefail
# Installs or updates toes on a Raspberry Pi.
# Must be run as the 'toes' user with passwordless sudo.
ARCHIVE="https://git.nose.space/defunkt/toes/archive/main.tar.gz"
RELEASE_URL="https://toes.dev/release/latest.tar.gz"
DEST=~/toes
APPS_DIR=~/apps
DATA_DIR=~/data
@ -63,23 +63,19 @@ sudo setcap 'cap_net_bind_service=+ep' "$BUN"
info "Downloading toes"
mkdir -p "$DEST"
curl -fsSL "$ARCHIVE" | tar xz --strip-components=1 -C "$DEST"
curl -fsSL "$RELEASE_URL" | tar xz --strip-components=1 -C "$DEST"
# ── Directories ──────────────────────────────────────────
mkdir -p "$APPS_DIR" "$DATA_DIR" "$DATA_DIR/toes"
# ── Dependencies & build ─────────────────────────────────
# ── Dependencies ─────────────────────────────────────────
cd "$DEST"
info "Installing dependencies"
quiet bun install
info "Building"
rm -rf "$DEST/dist"
quiet bun run build
# ── Bundled apps ─────────────────────────────────────────
REPOS_DIR="$DATA_DIR/repos"
@ -94,19 +90,6 @@ for app_dir in "$DEST"/apps/*/; do
(
cp -a "$app_dir" "$APPS_DIR/$app"
quiet bun install --frozen-lockfile --cwd "$APPS_DIR/$app" || quiet bun install --cwd "$APPS_DIR/$app"
# Seed bare repo for git-based versioning
bare="$REPOS_DIR/$app.git"
quiet git -C "$APPS_DIR/$app" init -b main
quiet git -C "$APPS_DIR/$app" add -A
quiet git -C "$APPS_DIR/$app" -c user.name=toes -c user.email=toes@localhost commit -m "install"
if [ -d "$bare" ]; then
quiet git -C "$APPS_DIR/$app" push --force "$bare" main
else
quiet git clone --bare "$APPS_DIR/$app" "$bare"
quiet git -C "$bare" config http.receivepack true
fi
rm -rf "$APPS_DIR/$app/.git"
) &
pids+=("$!")
done
@ -115,15 +98,16 @@ for pid in "${pids[@]}"; do
wait "$pid" || fail "A bundled app failed to install."
done
# Copy pre-built bare repos for git-based versioning
cp -a "$DEST"/dist/repos/*.git "$REPOS_DIR/"
# ── CLI + SSH ────────────────────────────────────────────
info "Setting up SSH access"
sudo bash "$DEST/scripts/setup-ssh.sh"
info "Installing CLI"
sudo rm -f /usr/local/bin/toes
bun run cli:build
sudo cp dist/toes /usr/local/bin/toes
sudo install -m 755 "$DEST/dist/toes" /usr/local/bin/toes
# ── Systemd ──────────────────────────────────────────────

View File

@ -17,6 +17,7 @@
"scripts": {
"check": "bun run templates && bunx tsc --noEmit",
"build": "./scripts/build.sh",
"release": "./scripts/release.sh",
"cli:build": "bun run scripts/build.ts",
"cli:build:all": "bun run scripts/build.ts --all",
"cli:install": "bun cli:build && sudo cp dist/toes /usr/local/bin",

36
scripts/build-repos.sh Executable file
View File

@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Pre-builds bare git repos for bundled apps so the install script
# doesn't need to run any git commands.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="$SCRIPT_DIR/.."
APPS_DIR="$ROOT/apps"
OUT_DIR="$ROOT/dist/repos"
rm -rf "$OUT_DIR"
mkdir -p "$OUT_DIR"
for app_dir in "$APPS_DIR"/*/; do
app=$(basename "$app_dir")
[ -f "$app_dir/package.json" ] || continue
tmp=$(mktemp -d)
tar -C "$app_dir" \
--exclude='node_modules' \
--exclude='logs' \
--exclude='current' \
--exclude='[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]' \
-cf - . | tar -C "$tmp" -xf -
git -C "$tmp" init -b main -q
git -C "$tmp" add -A
git -C "$tmp" -c user.name=toes -c user.email=toes@localhost commit -q -m "install"
git clone --bare -q "$tmp" "$OUT_DIR/$app.git"
git -C "$OUT_DIR/$app.git" config http.receivepack true
rm -rf "$tmp"
echo " $app"
done
echo ">> Bare repos built in dist/repos/"

View File

@ -16,3 +16,4 @@ bun build src/client/index.tsx \
echo ">> Client bundle created at pub/client/index.js"
ls -lh pub/client/index.js

85
scripts/release.sh Executable file
View File

@ -0,0 +1,85 @@
#!/usr/bin/env bash
# Builds a release tarball with all artifacts pre-built.
# The Pi just untars, runs bun install, and starts.
#
# Usage: bun run release
# Output: dist/toes-<version>.tar.gz
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
VERSION=$(grep '"version"' package.json | head -1 | sed 's/.*"version": *"\(.*\)".*/\1/')
STAGING="$ROOT/dist/toes-$VERSION"
echo ">> Building toes $VERSION release"
# ── Clean ────────────────────────────────────────────────
rm -rf dist
mkdir -p dist
# ── Client bundle ────────────────────────────────────────
echo ">> Building client bundle"
rm -rf pub/client
mkdir -p pub/client
bun build src/client/index.tsx \
--outfile pub/client/index.js \
--target browser \
--minify
# ── Embedded templates ───────────────────────────────────
echo ">> Embedding templates"
bun run scripts/embed-templates.ts
# ── Bare repos ───────────────────────────────────────────
echo ">> Building bundled app repos"
bash scripts/build-repos.sh
# ── CLI binary (linux-arm64 for Pi) ──────────────────────
echo ">> Building CLI for linux-arm64"
bun build src/cli/index.ts \
--compile \
--target bun-linux-arm64 \
--minify \
--sourcemap=external \
--define="__GIT_SHA__=\"$VERSION\"" \
--outfile dist/toes-linux-arm64
# ── Stage release ────────────────────────────────────────
echo ">> Staging release"
mkdir -p "$STAGING"
# Source (excluding dev artifacts)
tar -C "$ROOT" \
--exclude='node_modules' \
--exclude='.git' \
--exclude='dist' \
--exclude='apps/*/node_modules' \
--exclude='apps/*/logs' \
--exclude='apps/*/current' \
--exclude='apps/*/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]' \
-cf - . | tar -C "$STAGING" -xf -
# Pre-built artifacts
cp -a pub/client "$STAGING/pub/client"
cp -a dist/repos "$STAGING/dist/repos"
mkdir -p "$STAGING/dist"
cp dist/toes-linux-arm64 "$STAGING/dist/toes"
# Generated templates (so the server doesn't need to generate them)
cp src/lib/templates.data.ts "$STAGING/src/lib/templates.data.ts"
# ── Tarball ──────────────────────────────────────────────
echo ">> Creating tarball"
tar -C dist -czf "dist/toes-$VERSION.tar.gz" "toes-$VERSION"
rm -rf "$STAGING"
SIZE=$(du -h "dist/toes-$VERSION.tar.gz" | cut -f1)
echo ">> dist/toes-$VERSION.tar.gz ($SIZE)"