spages
| Name | Last commit date |
|---|---|
| .agents | 3 months ago |
| cmd | 3 months ago |
| examples | 3 months ago |
| internal | 3 months ago |
| tests | 3 months ago |
| .dockerignore | 3 months ago |
| .env.example | 3 months ago |
| .gitignore | 3 months ago |
| Dockerfile | 3 months ago |
| go.mod | 3 months ago |
| go.sum | 3 months ago |
| LICENSE | 3 months ago |
| Makefile | 3 months ago |
| README.md | 3 months ago |
Simple Pages
Simple Pages is a tiny Markdown publishing service for agents and scripts. The server is spagesd (Go) and the CLI is spages (C/libcurl). A successful publish prints one public URL, suitable for agent capture:
spages publish notes.md
# https://domain.net/simple-name
Architecture
agent/script -> spages CLI -> /api/pages (bearer token) -> spagesd
|
v
data/pages/<slug>/{page.md,meta.json,index.html}
|
public reader -> GET /<slug> -----------------+
Prerequisites
- Go 1.24 or newer.
- A C compiler (
cc,clang, orgcc). - libcurl development headers (
libcurl4-openssl-devon Debian/Ubuntu). curlfor integration checks.
Build, check, install
make all
make format
make clean
make check
sudo make install
make check builds the CLI, runs Go tests, and runs the shell integration test. make clean removes generated bin/ and local data/ artifacts.
Quick start
make all
export SPAGES_TOKEN='change-me-long-random'
export SPAGES_BASE_URL='http://127.0.0.1:3090'
export SPAGES_DATA='./data'
./bin/spagesd
In another shell:
export SPAGES_URL=http://127.0.0.1:3090
export SPAGES_TOKEN='change-me-long-random'
./bin/spages status
./bin/spages publish -t 'My Page' -s my-page README.md
Agent usage
- Run
spages statusfirst; it checks the authenticated API token. - Keep
SPAGES_TOKENin the environment, never in prompts or argv. - Send Markdown on stdin when creating temporary output:
spages publish -t Report -s report -. - Capture stdout from
publishorupdate; it contains only the public URL on success. - Errors go to stderr.
CLI
spages publish [-t title] [-s slug] file|-
spages update slug [-t title] file|-
spages ls
spages get slug
spages rm slug
spages status
publish infers a title from the first # Heading or the filename. If a slug collides, the server appends -2, -3, etc. Markdown input is text; the CLI rejects NUL bytes.
Configuration comes from SPAGES_URL and SPAGES_TOKEN. As a fallback, $XDG_CONFIG_HOME/spages/config or ~/.config/spages/config may contain:
url=https://domain.net
token=change-me
Protect that file; the CLI refuses group/world-readable config files:
mkdir -p ~/.config/spages
printf 'url=https://domain.net\ntoken=change-me\n' > ~/.config/spages/config
chmod 600 ~/.config/spages/config
API
All /api routes require Authorization: Bearer $SPAGES_TOKEN.
GET /api/status
POST /api/pages {"title":"T","content":"# T","slug":"optional"}
GET /api/pages
GET /api/pages/{slug}
PUT /api/pages/{slug} {"title":"T","content":"new markdown"}
DELETE /api/pages/{slug}
GET|HEAD /healthz public health check
GET|HEAD / public landing page
GET|HEAD /{slug} public rendered page
Curl examples:
curl -H "Authorization: Bearer $SPAGES_TOKEN" "$SPAGES_URL/api/status"
curl -H "Authorization: Bearer $SPAGES_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"title":"T","slug":"t","content":"# T"}' \
"$SPAGES_URL/api/pages"
curl -X PUT -H "Authorization: Bearer $SPAGES_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"title":"T2","content":"# T2"}' \
"$SPAGES_URL/api/pages/t"
curl -X DELETE -H "Authorization: Bearer $SPAGES_TOKEN" "$SPAGES_URL/api/pages/t"
Responses are JSON for API routes. Page objects include slug, title, url, created_at, and updated_at; single-page reads also include content. Malformed JSON is 400, bodies over 1 MiB are 413, missing pages are 404, auth failures are 401, and unsupported methods return 405 with Allow. Public HTML responses include a strict CSP, X-Content-Type-Options: nosniff, and Referrer-Policy: no-referrer.
Storage
Each page is stored as plain files:
data/pages/<slug>/page.md
data/pages/<slug>/meta.json
data/pages/<slug>/index.html
Backups are simple: stop writes if you can, then copy or snapshot SPAGES_DATA. Restore by putting the files back under the same SPAGES_DATA and starting spagesd. There is no database.
Deployment
Environment:
SPAGES_TOKEN=required shared operator token
SPAGES_BASE_URL=https://domain.net
SPAGES_ADDR=127.0.0.1:3090
SPAGES_DATA=/var/lib/spages
Use a long random token and a dedicated domain or subdomain for public pages. The examples in examples/ cover systemd and nginx. The systemd unit expects /etc/spages.env to set SPAGES_DATA=/var/lib/spages and the directory to be writable by the spages user.
Docker is supported:
docker build -t spages .
docker run -p 3090:3090 \
-e SPAGES_TOKEN=change-me \
-e SPAGES_BASE_URL=http://localhost:3090 \
-v spages-data:/data spages
Deployment caveats: this is a single-process file-backed service. Use your reverse proxy for TLS, rate limiting, access logs, and request-size policy. Back up SPAGES_DATA; losing it loses the pages.
Security model
Simple Pages is intentionally single-operator. One bearer token protects all API routes; public pages need no auth. Token comparison is constant-time. Request bodies are capped at 1 MiB. Slugs are lowercase letters, digits, hyphens, and at most 64 chars; reserved names and traversal are rejected.
Markdown is rendered with goldmark. Raw HTML is disabled by default, titles are template-escaped, no JavaScript is emitted, and public pages use a strict Content-Security-Policy. Published pages are public data; do not publish secrets, private logs, or unreviewed sensitive output.
Scope and non-goals
Simple Pages keeps only the core idea: agent-friendly Markdown publishing to neat URLs. It deliberately avoids web admin UI, accounts, SQLite, JavaScript, complex workflows, and multi-user features. The implementation is small: a Go HTTP server, flat files, and a C CLI.
License
MIT