prosolis ea3577a45e Items-list sparklines, retro CSS, pinned tooling, deploy docs
- Bulk-load recent price points per item and render a sparkline in
  the items list (new LoadRecentPriceHistory query avoids N+1).
- Add retro.css visual layer and refreshed login/items/layout styling.
- Swap the logo from webp to avif.
- Pin htmx/Chart.js/Tailwind/templ versions in the Makefile with
  vendor / tools / update-deps targets; README documents the
  dependency-bump flow and the hardened systemd deploy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:10:56 -07:00
2026-05-13 19:42:49 -07:00

Veola

Self-hosted Go web app that tracks items across e-commerce platforms and pushes deal alerts to a self-hosted ntfy instance. eBay marketplaces are polled through eBay's official Browse API; Amazon family, Yahoo Auctions JP, and Mercari JP go through the Apify scraping API.

Track. Watch. Notice.

Features

  • Watch arbitrary items across multiple marketplaces with per-item search queries, target prices, and poll intervals
  • eBay via the official eBay Browse API, with a per-day call quota tracked and enforced — polling halts before the limit and resets on eBay's Pacific-time clock
  • Apify active-listing, sold-listing, and price-comparison actors for the non-eBay marketplaces
  • 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)
  • make, curl, and a POSIX shell (for the Makefile)
  • A reachable ntfy instance (self-hosted or ntfy.sh)
  • An eBay developer keyset (App ID + Cert ID) — for eBay marketplaces
  • An Apify account + API key — for the non-eBay marketplaces

Build-time tools

All non-Go tool versions are pinned in the Makefile at the top:

TAILWIND_VERSION := v3.4.17
HTMX_VERSION     := 2.0.4
CHARTJS_VERSION  := 4.4.6
TEMPL_VERSION    := v0.3.1020

Install templ yourself at the pinned version; Tailwind is downloaded for you on first make css. htmx and Chart.js are already vendored in static/vendor/ at the pinned versions above and committed to the repo.

# Install the pinned templ CLI.
make tools

# (or, equivalently)
go install github.com/a-h/templ/cmd/templ@v0.3.1020

# Confirm it's on PATH (typically $(go env GOPATH)/bin).
templ --version

You do not need Node, npm, or Yarn. Tailwind ships as a self-contained binary — the Makefile fetches the standalone Tailwind CLI into ./bin/tailwindcss on first make css. The bin/ directory is gitignored.

By default the Makefile downloads the linux-x64 build. For other platforms, override TAILWIND_URL on the command line — pick the matching asset from the Tailwind releases page:

# macOS Apple Silicon
make css TAILWIND_URL=https://github.com/tailwindlabs/tailwindcss/releases/download/v3.4.17/tailwindcss-macos-arm64

# macOS Intel
make css TAILWIND_URL=https://github.com/tailwindlabs/tailwindcss/releases/download/v3.4.17/tailwindcss-macos-x64

# linux-arm64
make css TAILWIND_URL=https://github.com/tailwindlabs/tailwindcss/releases/download/v3.4.17/tailwindcss-linux-arm64

Or drop a binary at ./bin/tailwindcss yourself and the Makefile will use it.

Build

First time:

go install github.com/a-h/templ/cmd/templ@latest   # if not already
make build                                          # fetches Tailwind, runs templ, compiles

Subsequent builds:

make build

This runs templ generate, compiles Tailwind utilities into static/css/tailwind.css, and produces veola-bin. Makefile targets:

Target What it does
make generate Regenerate templ Go from .templ sources
make css Compile static/css/tailwind.css from static/css/input.css (fetches the Tailwind standalone CLI into bin/ on first run)
make build generate + css + go build -o veola-bin .
make run build, then run against config.toml
make test go test ./...
make clean Remove veola-bin

The binary is named veola-bin rather than veola because the module is also veolago build cannot write a binary with the same name as the module dir.

static/css/tailwind.css is committed, so a deploy box can go build -o veola-bin . without the Tailwind CLI or even make. Re-run make css whenever you change templates or static/css/input.css. The hand-written component layer in static/css/app.css is loaded separately and needs no rebuild.

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 key, eBay credentials, ntfy settings). Rotating it invalidates stored secrets — re-enter them through /settings after rotation.

Other notable config:

  • [ebay]client_id (App ID), client_secret (Cert ID), environment (production or sandbox), and daily_call_limit (Browse API calls per day; default 5000). All of client_id / client_secret / the limit can also be set or overridden at runtime via /settings.
  • server.secure_cookies — sets the Secure attribute on the session cookie. Defaults to true; keep it on for any HTTPS-reachable deployment, including behind a TLS-terminating proxy. Set false only for local plain-HTTP testing on a non-localhost address.

Run

./veola-bin -config config.toml

CLI flags:

Flag Default Notes
-config <path> config.toml Path to the TOML config file
-debug off Verbose log/slog at LevelDebug. Logs raw external API payloads (eBay / ZenMarket / etc.) — useful when diagnosing parse failures. Not for production.

First-run flow:

  1. Visit http://localhost:8080/. With no users, you are redirected to /setup.
  2. Create the admin account. The first user is always an admin.
  3. Log in at /login.
  4. Add items at /items/new. Optionally fill in your eBay/Apify credentials and ntfy URL via /settings if you didn't put them in config.toml. The Settings page also shows the running eBay API call count for the day.

Account registration is admin-only — there is no public signup. Once at least one user exists, /setup returns 404. New users are created from the Settings page by an admin.

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
  ebay/              eBay Browse API client (OAuth2 + item search)
  ntfy/              ntfy push client
  auth/              session + CSRF
  scheduler/         poll loop, alert/dedup/badge logic
  handlers/          HTTP handlers
templates/           templ components
static/              compiled Tailwind + app.css, vendored htmx/Chart.js, JS
tailwind.config.js   Tailwind content globs
Makefile             build targets

Test

go test ./...

Unit tests cover crypto round-trip, db round-trip and dedup, scheduler alert/badge logic, and eBay marketplace/filter mapping. No handler-level tests yet.

Keeping dependencies current

Veola pulls from four sources, all version-pinned for reproducibility.

# Bump Go module deps to their newest compatible versions, then prints
# upstream-release URLs for the four pinned tools so you can spot bumps.
make update-deps

# After bumping any of TAILWIND_VERSION / HTMX_VERSION / CHARTJS_VERSION
# in the Makefile, refetch the vendored assets at the new pins:
make vendor

# After bumping TEMPL_VERSION:
make tools

# Then rebuild and run the test suite:
make build && make test

The pinned tool versions live at the top of the Makefile. The vendored JS at static/vendor/htmx.min.js and static/vendor/chart.umd.min.js is committed and updated only by make vendor. Tailwind v3.4.17 is intentionally pinned — v4 is a breaking release that drops the tailwind.config.js format Veola uses.

For automated tracking, point Dependabot or Renovate at the repo. Both can watch go.mod natively; Renovate's custom-regex managers can also track the *_VERSION lines in the Makefile.

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.
  • config.toml and veola.db (plus its -wal/-shm) hold secrets and live session tokens — keep them chmod 600 and owned by the service user.
  • The process responds to SIGINT / SIGTERM with a graceful HTTP shutdown (30s timeout) followed by scheduler stop.
  • Logs go to stdout as structured log/slog records.

Deploying publicly

Veola speaks plain HTTP and is meant to sit behind a TLS-terminating reverse proxy (e.g. Traefik, Caddy, nginx).

  • Keep server.secure_cookies = true (the default).
  • Terminate TLS at the proxy and set HSTS there — Veola does not emit HSTS itself.
  • Veola sets Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, and Referrer-Policy on every response, and trusts X-Forwarded-For for client IPs — configure the proxy to strip client-supplied X-Forwarded-* headers so they cannot be spoofed.
  • GET /healthz returns 200 ok with no auth — wire it up to your proxy/uptime probe.

A hardened systemd unit template lives at deploy/veola.service. It assumes:

  • Binary at /usr/local/bin/veola-bin
  • Config at /etc/veola/config.toml
  • A veola system user with /var/lib/veola as its working / data directory (the only writable path under ReadWritePaths)

Install sketch:

sudo useradd --system --home /var/lib/veola --shell /usr/sbin/nologin veola
sudo install -d -o veola -g veola -m 0750 /var/lib/veola /etc/veola
sudo install -m 0755 veola-bin /usr/local/bin/veola-bin
sudo install -m 0640 -o veola -g veola config.toml /etc/veola/config.toml
sudo install -m 0644 deploy/veola.service /etc/systemd/system/veola.service
sudo systemctl daemon-reload
sudo systemctl enable --now veola

Aesthetic

Sega Genesis blue. Not dark mode, not light mode — blue mode. See the visual design section of veola-spec.md for the palette.

Description
Self-hosted Go web app that tracks items across e-commerce platforms (eBay, Amazon family, Yahoo Auctions JP, Mercari JP) via the [Apify](https://apify.com) scraping API and pushes deal alerts to a self-hosted [ntfy](https://ntfy.sh) instance.
Readme 573 KiB
Languages
Go 67%
templ 21.4%
CSS 7.3%
JavaScript 3%
Makefile 1.3%