Files
Alexander Heldt d61e88fa29 Light of day
2026-06-21 17:41:56 +00:00

2.5 KiB

puppy-tracker

A tiny offline-first PWA for tracking your puppy's sleep, meals, pees, and poos. The browser is the primary client; a small Go server provides a shared source-of-truth and sync between devices.

How sync works

  • Each event has a UUID and an updatedAt timestamp.
  • Mutations (add / edit / delete) happen against localStorage first, so the app keeps working when offline. Deletes are recorded as tombstones so they can propagate.
  • On app load, on online, on every mutation (debounced), and every 60 s, the client POSTs its full event list to /api/events/sync. The server merges it with its own copy using last-write-wins on updatedAt and returns the merged set.
  • Service worker bypasses cache for /api/* so writes always hit the server when online; static assets are still cached for offline use.

A status pill in the header shows syncing… / synced 2m ago / pending / sync error / offline. Tap it to force-sync.

Layout

puppy-tracker/
├── flake.nix         # packages (server, static, default), devShell, nixosModule
├── module.nix        # systemd unit, StateDirectory, hardening
├── server/
│   ├── go.mod
│   └── main.go       # JSON-file store, LWW sync, static file serving
└── src/              # the web app
    ├── index.html
    ├── app.js
    ├── style.css
    ├── sw.js
    ├── manifest.json
    └── icon.svg

Run locally

nix run                       # http://localhost:8080, data in $XDG_DATA_HOME/puppy-tracker
PUPPY_ADDR=:9000 nix run      # custom port

# Hot-iterate (data in /tmp):
nix develop -c sh -c 'cd server && go run . -static ../src -data /tmp/puppy-events.json'

Use it on NixOS

In your system flake:

{
  inputs.puppy-tracker.url = "path:/path/to/puppy-tracker";

  outputs = { self, nixpkgs, puppy-tracker, ... }: {
    nixosConfigurations.my-host = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        puppy-tracker.nixosModules.default
        {
          services.puppy-tracker = {
            enable = true;
            port = 8080;
            openFirewall = true;
          };
        }
      ];
    };
  };
}

The server runs as a DynamicUser systemd unit. Data is stored at /var/lib/puppy-tracker/events.json via StateDirectory.

Notes

  • No auth. Intended for a home LAN. If exposing publicly, terminate TLS and authenticate with a reverse proxy in front (Caddy / nginx / Tailscale Funnel).