Tailwind is now compiled from static/css/input.css into a committed static/css/tailwind.css by the standalone CLI, fetched on demand into bin/ (gitignored) so no Node toolchain is required. layout.templ loads the local stylesheet instead of cdn.tailwindcss.com. Adds a Makefile with generate/css/build/run/test/clean targets. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Veola
Self-hosted Go web app that tracks items across e-commerce platforms (eBay, Amazon family, Yahoo Auctions JP, Mercari JP) via the Apify scraping API and pushes deal alerts to a self-hosted ntfy instance.
Track. Watch. Notice.
Features
- Watch arbitrary items across multiple marketplaces with per-item search queries, target prices, and poll intervals
- Active-listing, sold-listing, and price-comparison actors per item
- Price-history chart and best-price badge once enough history accumulates
- Deal alerts pushed to ntfy when current price falls at or below target
- Single-binary deploy, SQLite storage, no CGO
See veola-spec.md for the full specification.
Requirements
- Go 1.22+ (developed against 1.25)
- An Apify account + API key
- A reachable ntfy instance (self-hosted or ntfy.sh)
Build
go build -o veola-bin .
The binary is named veola-bin rather than veola because the module is also veola — go build cannot write a binary with the same name as the module dir.
If you change any .templ files, regenerate first:
~/go/bin/templ generate
Configure
Copy the example and edit:
cp config.toml.example config.toml
Both session_secret and encryption_key must be at least 32 bytes and different from each other. Generate with:
openssl rand -hex 32
encryption_key encrypts secrets at rest in SQLite (Apify keys, ntfy settings). Rotating it invalidates stored secrets — re-enter them through /settings after rotation.
Run
./veola-bin -config config.toml
First-run flow:
- Visit
http://localhost:8080/. With no users, you are redirected to/setup. - Create the admin account.
- Log in at
/login. - Add items at
/items/new. Optionally fill in your Apify key and ntfy URL via/settingsif you didn't put them inconfig.toml.
The scheduler starts with the server and polls each active item on its configured interval. The bottom-of-hour global poll runs every scheduler.global_poll_interval_minutes.
Layout
main.go entry point: config, db open, scheduler, http server
internal/
config/ TOML config loading and validation
crypto/ AES-GCM encryption for secrets at rest
db/ SQLite schema, migrations, store
models/ domain types
apify/ Apify API client
ntfy/ ntfy push client
auth/ session + CSRF
scheduler/ poll loop, alert/dedup/badge logic
handlers/ HTTP handlers
templates/ templ components
static/ CSS, vendored htmx
Test
go test ./...
Unit tests cover crypto round-trip, db round-trip and dedup, and scheduler alert/badge logic. No handler-level tests yet.
Operate
- The SQLite file lives at
server.db_path(default./veola.db). Back this up — it holds your watched items, history, encrypted secrets, and user accounts. - The process responds to
SIGINT/SIGTERMwith a graceful HTTP shutdown (30s timeout) followed by scheduler stop. - Logs go to stdout as structured
log/slogrecords.
Aesthetic
Sega Genesis blue. Not dark mode, not light mode — blue mode. See the visual design section of veola-spec.md for the palette.