fuzzyworld

fuzzyworld-tools / files / README.md

README.md

Blame: the commit that last changed each line · file · history

623239b7 2026-10-26 Pavel Safronov1# fuzzyworld-tools
2
bb7e1f57 2026-11-06 Pavel Safronov3Tooling for Pavel's self-hosted code and apps on DreamHost. It replaces GitLab (code, CI, Pages) and WordPress
4(the blog) with static files plus a few helper machines.
623239b7 2026-10-26 Pavel Safronov5
bb7e1f57 2026-11-06 Pavel Safronov6**The one rule: DreamHost only serves files.** There is no app server. Pages are generated when you push, and
7Apache just sends them. Work that needs a real machine (CI, screenshots) runs on a *runner* (a home TrueNAS box, or
8the laptop when it is idle), which pulls jobs from DreamHost over ssh. No runner is a normal state: jobs wait.
623239b7 2026-10-26 Pavel Safronov9
bb7e1f57 2026-11-06 Pavel Safronov10The roadmap and backlog are in [PLAN.md](PLAN.md).
11
12## What it provides
13
14| Site | What it is | Details |
15|---|---|---|
16| **code.fuzzyworld.net** | Browse pages and read-only `git clone` for public repos: every unmerged branch, syntax highlighting, history, blame, CI results, bug-report issues, AI-usage charts | [docs/code-site.md](docs/code-site.md) |
17| **apps.fuzzyworld.net** | Deployed web apps, one per path (`/bubbles/`, `/slide/`, ...), published with `fw deploy`, with CI screenshots | [docs/apps.md](docs/apps.md) |
18| **writing.fuzzyworld.net** | The writing blog: a static site built from Markdown by `fw blog`, with comments by email | [docs/blog.md](docs/blog.md) |
19| **VS Code in the browser** | Any public repo, read-only, at `apps.fuzzyworld.net/vscode/?repo=<name>` | [docs/operations.md](docs/operations.md#vs-code-in-the-browser) |
20| **CI runners** | Run each push's pipeline (`.fw-ci.yml`) in Docker, like a GitLab runner | [docs/ci.md](docs/ci.md) |
21| **Admin panel** | `fw admin`: a local web page for bug reports, blog comments and deploy state | [docs/admin.md](docs/admin.md) |
1758548c 2026-10-27 Pavel Safronov22
23## Philosophy
24
25Things made with AI should be shared freely with the world. The code and apps here were made with AI, so they
26are MIT-licensed: use them, change them, share them.
27
28My conversations with the AI are different. My words and my way of working are mine. I publish a conversation
2754717a 2026-10-30 Pavel Safronov29only when I choose to.
1758548c 2026-10-27 Pavel Safronov30
bb7e1f57 2026-11-06 Pavel Safronov31The same text is on [/why-i-left-gitlab/](https://code.fuzzyworld.net/why-i-left-gitlab/).
623239b7 2026-10-26 Pavel Safronov32
e9fdc5db 2026-11-01 Pavel Safronov33## Hosting tiers
34
bb7e1f57 2026-11-06 Pavel Safronov35Each feature needs a certain kind of host. A tier includes the ones above it, so you can pick what your hosting allows.
e9fdc5db 2026-11-01 Pavel Safronov36
37| Tier | The host must offer | Features |
bb7e1f57 2026-11-06 Pavel Safronov38|---|---|---|
e9fdc5db 2026-11-01 Pavel Safronov39| **T0, files only** (ftp only) | serving static files | the built blog, Atom feeds, the landing page, the generated code and CI pages, VS Code in the browser |
bb7e1f57 2026-11-06 Pavel Safronov40| **T1, files + jobs** (ftp + python) | ssh, cron, git hooks, a mailbox reachable by IMAP (or on the same host); nothing runs when a page is requested | page generation on push, the CI queue and runner API, cleanup jobs, comments and bug reports by email, the `fw` commands |
41| **T2, files + an endpoint** (ftp + php/cgi) | a script that answers web requests | the comment form (`post.php`): written, **not enabled, not installed** |
e9fdc5db 2026-11-01 Pavel Safronov42| **T3, always-on process** | a long-running server | nothing; out of scope |
43
bb7e1f57 2026-11-06 Pavel Safronov44T2 is new attack surface (code that runs on a web request, as the same user as everything else on the account),
45so comments are accepted by email only. To use the form you would host `server/comments/post.php` on its own
46subdomain and set `comments_form` in the blog config.
e9fdc5db 2026-11-01 Pavel Safronov47
bb7e1f57 2026-11-06 Pavel Safronov48## Quick start
f9ca0752 2026-10-27 Pavel Safronov49
bb7e1f57 2026-11-06 Pavel Safronov50First create the repo on DreamHost (private unless `--public`):
51
52```bash
53fw new my-game --public --description "One line."
54```
55
56Then make it an app (details and every option: [docs/apps.md](docs/apps.md)):
f9ca0752 2026-10-27 Pavel Safronov57
58```jsonc
bb7e1f57 2026-11-06 Pavel Safronov59// package.json of the app repo
f9ca0752 2026-10-27 Pavel Safronov60"scripts": { "deploy": "fw deploy" },
bb7e1f57 2026-11-06 Pavel Safronov61"fuzzyworld": { "app": "my-game", "title": "My Game", "description": "One line." },
45b1fe38 2026-10-27 Pavel Safronov62"devDependencies": { "fuzzyworld-tools": "git+https://code.fuzzyworld.net/fuzzyworld-tools.git#semver:^1.0.0" }
f9ca0752 2026-10-27 Pavel Safronov63```
64
bb7e1f57 2026-11-06 Pavel Safronov65```bash
66git push dh:git/my-game.git # `dh` is the SSH alias in ~/.ssh/config
67npm run deploy # clone main, install, check, build, upload, print the URL
f9ca0752 2026-10-27 Pavel Safronov68```
69
bb7e1f57 2026-11-06 Pavel Safronov70Use `fw` outside an app repo (for example on a second laptop):
f9ca0752 2026-10-27 Pavel Safronov71
bb7e1f57 2026-11-06 Pavel Safronov72```bash
73npm install -g "git+https://code.fuzzyworld.net/fuzzyworld-tools.git#semver:^1.0.0"
74```
f9ca0752 2026-10-27 Pavel Safronov75
623239b7 2026-10-26 Pavel Safronov76## Commands
77
bb7e1f57 2026-11-06 Pavel Safronov78`bin/fw` runs some commands on the laptop (`deploy`, `repoint`, `usage`, `admin`, `blog`) and forwards the rest to
79DreamHost (`ssh dh ~/bin/fw ...`).
80
81### Repos
623239b7 2026-10-26 Pavel Safronov82
83| Command | What it does |
84|---|---|
85| `fw list` | every repo, its visibility and last commit |
86| `fw new <name> [--public] [--description TEXT]` | create `~/git/<name>.git` on DreamHost; **private unless `--public`** |
bb7e1f57 2026-11-06 Pavel Safronov87| `fw public <name>` / `fw private <name>` | publish the source on code.fuzzyworld.net right away / unpublish it (pages, clone URL and index entry are removed) |
623239b7 2026-10-26 Pavel Safronov88| `fw describe <name> TEXT` | set the description shown on the index and summary page |
89| `fw publish <name>` | refresh one repo's public copy and pages (the push hook does this) |
bb7e1f57 2026-11-06 Pavel Safronov90| `fw publish-all` | refresh every repo, printing `[n/total] name (seconds)`, then the index and the CI dashboard |
91| `fw repoint [DIR] [--https] [--dry-run]` | for each repo in DIR whose `origin` is on gitlab.com: rename it `gitlab`, add `origin` on DreamHost (or the read-only https URL with `--https`, public repos only), fetch, retarget branch tracking. Changes only remotes, and checks that uncommitted work, HEAD and stashes are unchanged |
623239b7 2026-10-26 Pavel Safronov92
bb7e1f57 2026-11-06 Pavel Safronov93### Apps and deploys
623239b7 2026-10-26 Pavel Safronov94
bb7e1f57 2026-11-06 Pavel Safronov95| Command | What it does |
623239b7 2026-10-26 Pavel Safronov96|---|---|
bb7e1f57 2026-11-06 Pavel Safronov97| `fw deploy [--ref B] [--as NAME] [--here] [--dry-run]` | build and publish this repo's app ([docs/apps.md](docs/apps.md)) |
98| `fw deploys [--json]` | what is deployed where: the deploy ledger and each deployed app against its repo's default branch. **Known gap: `bin/fw` does not forward it yet (see [Known gaps](#known-gaps)); until then run `ssh dh '~/bin/fw deploys'`.** |
1758548c 2026-10-27 Pavel Safronov99
bb7e1f57 2026-11-06 Pavel Safronov100### CI
55e5fc3e 2026-11-04 Pavel Safronov101
bb7e1f57 2026-11-06 Pavel Safronov102| Command | What it does |
55e5fc3e 2026-11-04 Pavel Safronov103|---|---|
bb7e1f57 2026-11-06 Pavel Safronov104| `fw ci <name> [branch]` | queue a CI run of a branch's latest commit (default: the default branch) |
105| `fw ci-list` | queued and running CI jobs |
106| `fw ci-watch` | follow CI in the terminal (private repos too): running jobs with each job's state, the queue, the last 5 results; ends when nothing is queued or running |
55e5fc3e 2026-11-04 Pavel Safronov107
bb7e1f57 2026-11-06 Pavel Safronov108### Bug reports and comments
55e5fc3e 2026-11-04 Pavel Safronov109
bb7e1f57 2026-11-06 Pavel Safronov110| Command | What it does |
111|---|---|
112| `fw issues <repo> public\|reviewed\|private` | set who sees the repo's bug-report pages (default private) and republish them |
113| `fw issues list\|show\|record\|approve\|hide\|close\|reopen\|publish\|publish-all\|ingest\|imap-check` | moderate bug reports ([docs/issues.md](docs/issues.md)) |
114| `fw comments list\|show\|approve\|reject\|remove\|untrust\|publish\|export\|ingest\|imap-check` | moderate blog comments ([docs/blog.md](docs/blog.md#comments-by-email)) |
55e5fc3e 2026-11-04 Pavel Safronov115
bb7e1f57 2026-11-06 Pavel Safronov116### Laptop-side tools
55e5fc3e 2026-11-04 Pavel Safronov117
bb7e1f57 2026-11-06 Pavel Safronov118| Command | What it does |
119|---|---|
120| `fw admin [--port N] [--no-open] [--code-dir DIR]` | the local admin web page ([docs/admin.md](docs/admin.md)) |
121| `fw blog list\|build\|preview\|deploy <config>` | the static blog ([docs/blog.md](docs/blog.md)) |
122| `fw blog watch <config> [--once] [--dry-run]`, `fw blog watch install\|uninstall <config>` | mirror the blog source into git and auto-deploy safe edits (one machine only) |
123| `fw blog import-wp <config> --wp URL`, `fw blog check <config>` | one-time WordPress import / compare the built site with the WordPress text |
124| `fw usage [--all\|--now\|--trailers [--session ID]]` | Claude Code token use per session; `--trailers` prints commit-message trailers ([docs/usage.md](docs/usage.md)) |
55e5fc3e 2026-11-04 Pavel Safronov125
bb7e1f57 2026-11-06 Pavel Safronov126`npm run` scripts in this repo: `dev` (preview the pages locally), `deploy` and `deploy-status` (deploy this repo's own
127server by default; `-- all` or a target name adds the runner and VS Code web; [docs/operations.md](docs/operations.md#install-and-update)), `list-repos`
128(`fw list`) and `prepare-usage-for-pr` (`fw usage --trailers`).
55e5fc3e 2026-11-04 Pavel Safronov129
bb7e1f57 2026-11-06 Pavel Safronov130## Visibility
85702b14 2026-11-04 Pavel Safronov131
bb7e1f57 2026-11-06 Pavel Safronov132Each bare repo stores its own setting: `git config fuzzyworld.visibility public|private`.
133**Anything other than exactly `public` is private**, including a missing value, so a repo is never published by
134accident. A private repo has no pages, no clone URL and no index entry, and pushing to it publishes nothing.
55e5fc3e 2026-11-04 Pavel Safronov135
bb7e1f57 2026-11-06 Pavel Safronov136Other settings in the repo config:
85702b14 2026-11-04 Pavel Safronov137
bb7e1f57 2026-11-06 Pavel Safronov138| Key | Meaning |
139|---|---|
140| `fuzzyworld.issues` | `public`, `reviewed`, or anything else = private: who sees bug-report pages ([docs/issues.md](docs/issues.md)) |
141| `fuzzyworld.description` | the one-line description |
142| `fuzzyworld.ci` | `off`: pushes queue no CI jobs |
143| `fuzzyworld.gitlab-id`, `fuzzyworld.gitlab-path` | for repos migrated from GitLab |
85702b14 2026-11-04 Pavel Safronov144
bb7e1f57 2026-11-06 Pavel Safronov145## Repository layout
85702b14 2026-11-04 Pavel Safronov146
bb7e1f57 2026-11-06 Pavel Safronov147| Folder | Contents |
148|---|---|
149| `bin/` | `fw` (laptop side) and `deploy-tools` |
150| `lib/` | laptop-side programs: `deploy.mjs`, `admin.py`, `usage.py`, `wp_import.py`, `repoint.sh` |
151| `server/` | everything installed on DreamHost: `fw`, `gen.py` (page generator), `issues.py`, `comments.py`, `blog.py`, `blog_watch.py`, `deploys.py`, `runner-api`, hooks, jobs, site templates |
152| `runner/` | the CI runner (`fw-runner.py`), its installers and the screenshot script |
153| `vite/` | the vite plugin for apps (`fuzzyworld-tools/vite`) |
154| `vscode-web/` | VS Code in the browser: installer and the `fw-fs` extension |
155| `tests/` | the test suite |
85702b14 2026-11-04 Pavel Safronov156
bb7e1f57 2026-11-06 Pavel Safronov157Where things live on DreamHost: [docs/operations.md](docs/operations.md#layout-on-dreamhost).
85702b14 2026-11-04 Pavel Safronov158
bb7e1f57 2026-11-06 Pavel Safronov159## Security in one paragraph
0e603cf7 2026-10-31 Pavel Safronov160
bb7e1f57 2026-11-06 Pavel Safronov161The site never executes anything from a repo: pages are pre-generated HTML with all repo content escaped, only
162image files are copied raw, scripts are denied by `.htaccess`, and the code site sends
163`Content-Security-Policy: script-src 'none'`. The full list is in [docs/code-site.md](docs/code-site.md#security).
0e603cf7 2026-10-31 Pavel Safronov164
bb7e1f57 2026-11-06 Pavel Safronov165## Documentation
0e603cf7 2026-10-31 Pavel Safronov166
bb7e1f57 2026-11-06 Pavel Safronov167| File | Covers |
168|---|---|
169| [docs/code-site.md](docs/code-site.md) | page URLs, branches, licences, local preview, security |
170| [docs/apps.md](docs/apps.md) | `package.json` keys, `fw deploy`, the vite plugin, the apps index, screenshots |
171| [docs/ci.md](docs/ci.md) | queue, runners, `.fw-ci.yml`, secrets, watching CI |
172| [docs/issues.md](docs/issues.md) | bug reports by email: modes, privacy rules, run records, replay links |
173| [docs/admin.md](docs/admin.md) | `fw admin`: security model and pages |
174| [docs/blog.md](docs/blog.md) | `fw blog`, comments by email, the watcher, the WordPress import |
175| [docs/usage.md](docs/usage.md) | Claude Code usage trailers, cost estimates, usage charts |
176| [docs/operations.md](docs/operations.md) | DreamHost layout, scheduled jobs, installing and updating, VS Code in the browser, requirements |
177| [PLAN.md](PLAN.md) | roadmap, decisions, backlog |
178
179## Tests
623239b7 2026-10-26 Pavel Safronov180
181```bash
bb7e1f57 2026-11-06 Pavel Safronov182python3 -m unittest discover tests # the whole suite: pages, blog, comments, issues, runner, admin, deploys
183node --test tests/deploy.test.mjs # fw deploy's config, rsync filters and replay-template checks
623239b7 2026-10-26 Pavel Safronov184```
185
bb7e1f57 2026-11-06 Pavel Safronov186## Known gaps
1758548c 2026-10-27 Pavel Safronov187
bb7e1f57 2026-11-06 Pavel Safronov188- `fw deploys` is documented above but `bin/fw` on the laptop rejects it ("unknown command"). `fw admin` is not affected.
189- The npm package does not ship `server/` (`files` in `package.json`), but `fw blog` (including `import-wp` and `check`) needs it, and `fw usage --trailers` reads `server/prices.json` (without it the trailers lose the `@over100k` split). So these work fully only from a checkout of this repo.
f9ca0752 2026-10-27 Pavel Safronov190
bb7e1f57 2026-11-06 Pavel Safronov191Other problems found are tracked in the global queue, not here.