2.5 KiB
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
updatedAttimestamp. - Mutations (add / edit / delete) happen against
localStoragefirst, 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 onupdatedAtand 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).