No description
  • TypeScript 72.3%
  • Svelte 21.2%
  • Shell 3.6%
  • JavaScript 1.7%
  • CSS 0.7%
  • Other 0.5%
Find a file
2026-09-05 13:43:36 +02:00
scripts Prune stale host backups and verify the latest by default 2026-09-02 11:03:57 +02:00
src Convert displayed values to preferred units at two decimals 2026-09-05 13:43:36 +02:00
systemd/user Add weekly systemd backup timer with chained verification 2026-09-02 11:03:57 +02:00
tests Convert displayed values to preferred units at two decimals 2026-09-05 13:43:36 +02:00
.dockerignore Configure compose services from .env with an app image build 2026-09-02 09:19:11 +02:00
.env.example Add an encrypted scheduled database backup service 2026-09-02 10:56:30 +02:00
.gitignore Add an encrypted scheduled database backup service 2026-09-02 10:56:30 +02:00
.prettierignore Format codebase with prettier 2026-08-31 18:38:32 +02:00
.prettierrc Format codebase with prettier 2026-08-31 18:38:32 +02:00
AI-DISCLOSURE.md Yes, there is a human somewhere... 2026-08-31 19:20:34 +02:00
docker-compose.override.yml override i egen fil 2026-09-05 13:08:26 +02:00
docker-compose.yml Traefik-junk till override @ TomCruise 2026-09-02 13:11:33 +02:00
Dockerfile Retry npm ci on transient registry failures 2026-09-02 11:54:26 +02:00
jsconfig.json Set up SvelteKit app and local stack 2026-08-26 21:30:46 +02:00
package-lock.json Add prettier with Svelte plugin 2026-08-31 18:30:12 +02:00
package.json Bump package version to 0.1.0-beta 2026-09-02 11:38:31 +02:00
playwright.config.ts Default units separately for caliber, weight, and charge 2026-09-02 22:46:38 +02:00
postcss.config.js Set up SvelteKit app and local stack 2026-08-26 21:30:46 +02:00
README.md Default entry units through a metric or imperial preference 2026-09-02 19:10:35 +02:00
svelte.config.js Format migration and archive changes with prettier 2026-09-02 09:46:50 +02:00
tailwind.config.ts Format codebase with prettier 2026-08-31 18:38:32 +02:00
tsconfig.json Format codebase with prettier 2026-08-31 18:38:32 +02:00
vite.config.ts Set up SvelteKit app and local stack 2026-08-26 21:30:46 +02:00
vitest.config.ts Set up SvelteKit app and local stack 2026-08-26 21:30:46 +02:00

LoadLog

LoadLog is a lightweight, single-user handloading log for a local network or workstation. Bullets and loads are reusable templates; entries always link to a deduplicated template, matched across display units, so re-recording the same load never creates duplicate rows. Access requires signing in, and API requests without a valid session are rejected.

Setup

Requirements: Node 20+, npm, and Docker Compose.

cp .env.example .env
docker compose up -d
npm install
npm run dev

Open http://localhost:5173; the first page request initializes the schema. With the app open, optional sample content is added from a second terminal with npm run db:seed. Stop MariaDB with docker compose down; data remains in the named volume.

Set LOGIN_USER, LOGIN_PASSWORD, and SESSION_SECRET in .env before starting the app. The default development credentials are admin / change-me; replace them for any network-accessible deployment. Sessions are signed with SESSION_SECRET and expire after 30 days; the app refuses to start in production without a configured secret. Failed logins are logged and throttled per source address (LOGIN_MAX_FAILURES, default 5, with a LOGIN_LOCKOUT_MINUTES lockout, default 15), and non-GET requests from mismatched Origin headers are rejected as cross-site.

For server deployments, docker compose up -d --build reads the same .env file and refuses to start until SESSION_SECRET, LOGIN_PASSWORD, MARIADB_PASSWORD, and MARIADB_ROOT_PASSWORD are set. Production is detected through NODE_ENV or DEPLOYMENT (both default to production in compose); it then rejects the example credentials, requires a 32+ character SESSION_SECRET, and marks the session cookie secure unless COOKIE_SECURE=false. Both containers define healthchecks backed by GET /api/health, and the web service waits for a healthy database before starting.

Caliber supports mm and in; bullet weight and powder charge support g and gr. Every input carries a unit dropdown beside it, so each entry records exactly what was measured — displayed values never change retroactively. Preferences choose the default unit system (metric or imperial) for new entries. Preferences are persisted in MariaDB and the theme follows the system by default, with a local toggle for quick switching. Dates use ISO YYYY-MM-DD values. Counts can be blank or zero, and notes are plain text.

Load templates are managed from Manage templates beside the load selector. Editing a template affects future entries only; entries keep a snapshot of the load values recorded at the time. Deleting a template archives it from future selectors without deleting historical entries.

Bullets follow the same rules: recorded entries always display the bullet details captured at save time, and deleting a bullet with history archives it instead of destroying records. Bullets without entries are removed permanently, and DELETE /api/bullets/[id]?purge=1 force-deletes a bullet together with its recorded entries. Schema changes are applied through versioned migrations recorded in a schema_migrations table.

Backups

The compose stack runs a backup service that dumps the database on a schedule (BACKUP_INTERVAL_HOURS, default 24, first dump at startup), encrypts each dump with BACKUP_ENCRYPTION_KEY (AES-256, PBKDF2), writes it to ${BACKUP_DIR:-./backups} on the host filesystem, and prunes dumps older than BACKUP_RETENTION_DAYS (default 30). If POST_BACKUP_HOOK is set, it runs after every dump — use it to push the encrypted files off-host, e.g. rclone copy /backups remote:loadlog-backups.

Ad-hoc operations against a running stack (MARIADB_CONTAINER overrides the target container, defaults come from .env):

npm run db:backup                                    # encrypted dump into BACKUP_DIR
npm run db:restore -- backups/<file>.sql.gz.enc      # destructive: drops and recreates the tables
npm run db:verify -- backups/<file>.sql.gz.enc       # restores into a throwaway container and compares row counts

Run db:verify regularly — a backup that has not been restored is only a hope. Recovery point objective: one BACKUP_INTERVAL_HOURS window plus any gap in off-host syncing; lower the interval for tighter RPO. Recovery time objective: a few minutes for restore plus verification on the same host; off-host copies additionally cover host loss.

Scheduled off-host backups with systemd

For weekly encrypted backups into a mounted NAS directory, install the user units (adjust %h/loadlog if the checkout lives elsewhere):

sed -i 's|%h/loadlog|%h/path/to/LoadLog|g' systemd/user/*.service
mkdir -p ~/.config/systemd/user
cp systemd/user/loadlog-backup.{service,timer} systemd/user/loadlog-verify.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now loadlog-backup.timer
sudo loginctl enable-linger "$USER"

The timer runs Sundays at 03:00 (±30 minutes, catch-up enabled for powered-off machines) into ~/nas/backup/loadlog with the standard retention rules, and a successful backup immediately triggers loadlog-verify.service, which restores the newest dump into a throwaway container and compares row counts. Check status with systemctl --user list-timers loadlog-backup.timer and journalctl --user -u loadlog-backup.service.

Verification

npm run check
npm test
npm run test:e2e
npm run build

The Playwright smoke test expects the development server and a running MariaDB instance. npm run db:init is available for manually initializing the preferences table, though the app also initializes all tables automatically.