https://github.com/darious/snibox-next
Self-hosted snippets/notes/links library — Go + templ/HTMX + SQLite, deployed via docker.
https://github.com/darious/snibox-next
Last synced: 14 days ago
JSON representation
Self-hosted snippets/notes/links library — Go + templ/HTMX + SQLite, deployed via docker.
- Host: GitHub
- URL: https://github.com/darious/snibox-next
- Owner: darious
- License: mit
- Created: 2026-05-21T07:42:46.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-05-21T12:46:48.000Z (about 1 month ago)
- Last Synced: 2026-05-21T20:51:45.128Z (about 1 month ago)
- Language: Go
- Size: 121 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# snibox-next
Self-hosted, single-user snippets / notes / links library. Modern dark-mode
reimagining of [snibox/snibox](https://github.com/snibox/snibox). Single
static binary, single SQLite file, HTMX UI, deployed behind your existing
reverse proxy.
See [`SPEC.md`](SPEC.md) for the full design spec.
## Stack
- Go 1.22+ (`net/http` + chi)
- [`a-h/templ`](https://templ.guide) HTML components, HTMX 2.x for interactivity
- SQLite via `modernc.org/sqlite` (pure-Go, CGO-free — no toolchain headaches)
- `yuin/goldmark` markdown, `alecthomas/chroma` syntax highlighting (server-side, class mode)
- All assets embedded via `embed.FS`
## Auth (read this first)
**Auth is out of scope.** v1 trusts whoever can reach the port. The binary
refuses to bind to a non-loopback address unless you pass `--trust-network`
— see [SPEC §1.1](SPEC.md#11-auth-assumption-v1).
Deploy behind a reverse proxy that handles authentication:
nginx-proxy-manager, Authelia/Authentik, Caddy `basic_auth`, or a Tailscale-only
ingress. No login UI, no session table, no CSRF logic. If you need multi-user,
fork it — don't bolt it on.
## Quick start (local)
```sh
go install github.com/a-h/templ/cmd/templ@latest
templ generate
go run ./cmd/snibox --seed-demo
```
Opens on `http://127.0.0.1:8979`. Adds 20 demo items on first boot.
## Production deployment
The repo ships a `docker-compose.yml` tuned for the **q-docker** stack
pattern (port-published, on the external `web` network, WUD watch labels,
bind-mount under `/mnt/config/q-docker/service-data/`).
### 1. Pull the image
The release workflow publishes multi-arch (`amd64` + `arm64`) images to
GHCR on every push to `main` and on `v*` tags.
```
ghcr.io/darious/snibox-next:latest
ghcr.io/darious/snibox-next:v0.1.0
ghcr.io/darious/snibox-next:sha-
```
GHCR images are public; no `docker login` needed to pull.
### 2. Stack drop-in
Copy `docker-compose.yml` into `/mnt/config/q-docker/stacks/snibox-next/`
(Dockge will manage it). The compose file:
- Pulls `ghcr.io/darious/snibox-next:latest`
- Binds `/mnt/config/q-docker/service-data/snibox-next` → `/data`
- Publishes `:8979`
- Joins the external `web` network so nginx-proxy-manager can reach it
- Labels `wud.watch=true` so WUD notifies on new digests
- Sets `SNIBOX_TRUST_NETWORK=true` (the container binds `0.0.0.0`)
```sh
mkdir -p /mnt/config/q-docker/service-data/snibox-next
chown 10001:10001 /mnt/config/q-docker/service-data/snibox-next # container uid
cd /mnt/config/q-docker/stacks/snibox-next
docker compose up -d
```
The container runs as uid `10001` (non-root). If the bind-mount isn't
writable by that uid the app crashes with
`open db: unable to open database file (14)` — fix with the `chown` above.
### 3. nginx-proxy-manager host rule
| Field | Value |
|---|---|
| Domain | `snibox.lan` (or whatever) |
| Forward Hostname / IP | `snibox-next` (container name on `web` network) |
| Forward Port | `8979` |
| Block Common Exploits | on |
| Websockets Support | on |
| Access List | **set one** — that is the auth layer |
Add TLS via Let's Encrypt as you do for the other services.
### Bare-metal alternative (no Docker)
If you'd rather run the binary directly:
```sh
# build
CGO_ENABLED=0 go build -trimpath \
-ldflags="-s -w -X main.Version=$(git describe --tags --always)" \
-o /usr/local/bin/snibox ./cmd/snibox
# systemd unit (/etc/systemd/system/snibox.service)
[Unit]
Description=snibox-next
After=network-online.target
[Service]
Type=simple
User=snibox
WorkingDirectory=/var/lib/snibox
ExecStart=/usr/local/bin/snibox --addr 127.0.0.1:8979 --db /var/lib/snibox/snibox.db
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
```
## Flags / env
| Flag | Env | Default | Effect |
|------|-----|---------|--------|
| `--addr` | `SNIBOX_ADDR` | `127.0.0.1:8979` | Listen address. |
| `--db` | `SNIBOX_DB` | `./snibox.db` | SQLite database path. |
| `--seed-demo` | `SNIBOX_SEED_DEMO` | `false` | Import 20 demo items into an empty DB. Dev/preview only. |
| `--read-only` | `SNIBOX_READ_ONLY` | `false` | Block all writes (`POST`/`PUT`/`PATCH`/`DELETE`/`/import`). |
| `--trust-network` | `SNIBOX_TRUST_NETWORK` | `false` | Permit non-loopback bind. Set when behind Docker / a proxy. |
| `--version` | — | — | Print version and exit. |
## Backup & restore
The whole app is one SQLite file (`/data/snibox.db` in the container).
Three safe options:
### A. Online backup with `sqlite3`
Recommended — works while snibox is running.
```sh
docker exec snibox-next sh -c \
'apk add --no-cache sqlite >/dev/null 2>&1; \
sqlite3 /data/snibox.db ".backup /data/snibox-$(date +%F).bak"'
```
This uses SQLite's online backup API and is safe with WAL.
### B. `VACUUM INTO`
Compacts and atomically writes a fresh file:
```sh
docker exec snibox-next sh -c \
'sqlite3 /data/snibox.db "VACUUM INTO '"'"'/data/snibox-$(date +%F).db'"'"'"'
```
### C. JSON export
For a human-readable, version-controllable snapshot:
```sh
curl -s http://localhost:8979/export.json > snibox-$(date +%F).json
```
Restore via `POST /import?mode=replace` with the JSON body. Timestamps are
preserved per [SPEC §6.2](SPEC.md#62-import).
### Restoring from a `.bak` / `.db`
Stop the container, replace the file, start again:
```sh
docker compose down snibox-next
cp snibox-2026-05-21.bak /mnt/config/q-docker/service-data/snibox-next/snibox.db
docker compose up -d snibox-next
```
## Upgrading
If you're on `:latest`, WUD notifies you of new digests; pull + recreate:
```sh
docker compose pull snibox-next
docker compose up -d snibox-next
```
Migrations run automatically on boot from the embedded `migrations/`
directory; existing rows are untouched.
## Development
```sh
templ generate # rebuild .templ → .go
go test ./... -race -cover # full test suite
go build -o snibox ./cmd/snibox # single binary
```
Tests hit a real SQLite database (in-memory). No mocks.
- `internal/store` — repo unit tests, ~78% covered
- `internal/importer` — JSON import/export, ~89% covered
- `internal/handlers` — HTTP route tests through `httptest.Server`, ~60% covered
## Project layout
```
cmd/snibox/ entrypoint (flags, server boot, --version)
internal/
store/ repo + migrations + sqlite triggers
handlers/ chi routes + HTMX content-negotiation + middleware
views/ templ components (page + partials)
importer/ JSON import/export
markdown/ goldmark + chroma wrappers
id/ ULID helper
assets/ embed.FS for static + seed.json
docs/reference/ original prototype HTML/JSX/CSS (read-only reference)
```
## License
MIT — see [LICENSE](LICENSE).