No description
  • Go 68.3%
  • HTML 23.9%
  • HCL 7.8%
Find a file
Roberto Castellotti e6778331b9
Revise project warning and workflow description
Update README to reflect workflow changes and project status.
2026-02-18 17:35:12 +01:00
templates refactor: move ui templates to templates folder with shared base layout 2026-02-17 22:21:09 +01:00
.gitignore feat: migrate ui to oat and clean up repo docs 2026-02-17 22:14:32 +01:00
admin.go ops: production logging defaults and ubuntu runbook 2026-02-17 22:50:34 +01:00
api.go refactor: split api and web ui into separate files 2026-02-17 21:49:53 +01:00
api_test.go test: add api coverage and mise build/lint/test tasks 2026-02-17 22:03:22 +01:00
ARCHITECTURE.md docs: add project architecture overview 2026-02-17 22:51:22 +01:00
Caddyfile ops: production logging defaults and ubuntu runbook 2026-02-17 22:50:34 +01:00
go.mod feat: initial team dev log app 2026-02-17 21:46:57 +01:00
go.sum refactor: move admin CLI into dedicated file 2026-02-17 21:47:40 +01:00
improv.txt docs: add repository improvement backlog 2026-02-17 22:35:58 +01:00
main.go ops: production logging defaults and ubuntu runbook 2026-02-17 22:50:34 +01:00
mise.toml test: add api coverage and mise build/lint/test tasks 2026-02-17 22:03:22 +01:00
oat.min.css feat: migrate ui to oat and clean up repo docs 2026-02-17 22:14:32 +01:00
oat.min.js feat: migrate ui to oat and clean up repo docs 2026-02-17 22:14:32 +01:00
README.md Revise project warning and workflow description 2026-02-18 17:35:12 +01:00
terraform_ipv6_hcloud.tf infra: add terraform for ipv6-only machine with github host overrides 2026-02-17 22:53:36 +01:00
webui.go refactor: move ui templates to templates folder with shared base layout 2026-02-17 22:21:09 +01:00

Team Dev Log (Go + SQLite, no ORM)

Single-binary Go application for team development logs.

Warning

This project is vibecoded and currently requires a significant refactor. Do not use it for production or security-sensitive workloads.

Workflow Change

This changes the order from a standup-first process.

New flow:

  • During the day, each person posts short updates to the board.
  • Later, the team reviews the board entries together.

This keeps updates lightweight in real time and makes review asynchronous-first.

Features

  • API server on :9173
  • Web UI server on :9172
  • Query-only web view on :9172/entries-view
  • SQLite via database/sql + github.com/mattn/go-sqlite3 (no ORM)
  • Token auth with SHA-256 token hashes stored in DB
  • Admin CLI for user creation + token generation
  • Daily compaction at 5:00 PM local time with temporary write lock
  • Action logging to SQLite and stdout/file
  • Oat-based UI (oat.min.css / oat.min.js) served locally from the binary

Project Layout

  • main.go: process startup, server wiring, DB schema, compaction loop
  • api.go: API handlers/auth/middleware
  • admin.go: admin CLI commands and token generation
  • webui.go: embedded UI assets and UI handlers
  • templates/base.html: shared base layout template
  • templates/index.html: main UI template for /
  • templates/entries-view.html: query-only template for /entries-view
  • oat.min.css, oat.min.js: locally served Oat assets
  • api_test.go: API tests
  • mise.toml: tool + task config

Requirements

  • Go 1.22+
  • SQLite C toolchain support (CGO) for go-sqlite3

If using mise:

mise install

Build / Lint / Test

Via mise tasks:

mise run build
mise run lint
mise run test

Or directly:

go build .
go vet ./...
go test ./...

Run

Start servers:

./team-dev-log --db ./devlog.db --log -

You can also run explicitly with subcommand:

./team-dev-log serve --db ./devlog.db --log -

Notes:

  • --log - (default) writes logs to stdout.
  • --log /path/to/file.log writes logs to stdout + file.

Admin CLI

Top-level help:

./team-dev-log help

Admin help:

./team-dev-log admin --help
./team-dev-log admin create-user --help

Create user/token:

./team-dev-log admin create-user --username alice --db ./devlog.db --log -

Token format:

  • PUD + 9 uppercase slug chars (12 chars total)
  • raw token is shown once; DB stores only SHA-256 hash

Web UI

  • Main UI: http://localhost:9172/
  • Query-only view: http://localhost:9172/entries-view
    • Optional query params: day=YYYY-MM-DD, token=PUDXXXXXXXXX

The main UI stores token in browser localStorage under devlog_token.

API

Full curl-first API usage.

Setup shell variables

export API="http://127.0.0.1:9173"
export TOKEN="PUDXXXXXXXXX"
export TODAY="$(date +%F)"

Auth header options

Use either:

-H "Authorization: Bearer $TOKEN"

or:

-H "X-Auth-Token: $TOKEN"

Health check

curl -i "$API/api/health"

Expected: 200 and {"status":"ok"}

Current authenticated user

curl -i \
  -H "Authorization: Bearer $TOKEN" \
  "$API/api/me"

Expected: 200 and:

{"id":1,"username":"alice"}

Unauthorized example:

curl -i "$API/api/me"

Expected: 401 and {"error":"unauthorized"}

Create entry

curl -i -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content":"implemented API docs and tests"}' \
  "$API/api/entries"

Expected: 201 and:

{"id":123,"status":"created"}

Create entry using X-Auth-Token:

curl -i -X POST \
  -H "X-Auth-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content":"using alternate auth header"}' \
  "$API/api/entries"

Create entry error cases:

Invalid JSON:

curl -i -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content":' \
  "$API/api/entries"

Expected: 400 {"error":"invalid json"}

Missing content:

curl -i -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content":"   "}' \
  "$API/api/entries"

Expected: 400 {"error":"content is required"}

Compaction write lock window:

curl -i -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content":"may fail if compaction lock is active"}' \
  "$API/api/entries"

Possible during lock: 423 {"error":"writes are temporarily locked for daily compaction"}

List entries (default day, default limit)

curl -i \
  -H "Authorization: Bearer $TOKEN" \
  "$API/api/entries"

List entries for specific day and limit

curl -i \
  -H "Authorization: Bearer $TOKEN" \
  "$API/api/entries?day=$TODAY&limit=100"

Expected 200 body shape:

{
  "day":"2026-02-17",
  "entries":[
    {
      "id":123,
      "user":"alice",
      "entry_type":"normal",
      "content":"implemented API docs and tests",
      "created_at":"2026-02-17T20:43:12Z"
    }
  ]
}

List entries error cases:

Invalid day format:

curl -i \
  -H "Authorization: Bearer $TOKEN" \
  "$API/api/entries?day=17-02-2026"

Expected: 400 {"error":"day must be YYYY-MM-DD"}

No/invalid token:

curl -i "$API/api/entries"

Expected: 401 {"error":"unauthorized"}

CORS preflight

curl -i -X OPTIONS \
  -H "Origin: http://localhost:9172" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type, Authorization" \
  "$API/api/entries"

Expected:

  • 204 No Content
  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Headers: Content-Type, Authorization, X-Auth-Token
  • Access-Control-Allow-Methods: GET, POST, OPTIONS

Endpoint summary

  • GET /api/health (no auth)
  • GET /api/me (auth required)
  • POST /api/entries (auth required)
  • GET /api/entries?day=YYYY-MM-DD&limit=1..1000 (auth required)

Daily 5 PM Compaction

At local 17:00 (or first scheduler tick after 17:00):

  1. New writes are temporarily locked (POST /api/entries returns 423 Locked).
  2. Day's normal entries are merged into one daily_compact entry.
  3. Original day normal entries are deleted.
  4. Run is recorded in compactions (once per day).

Logging

Each action is persisted in action_logs and also emitted through the process logger. Recommended production mode is --log - so logs go to stdout/journald. Optional file logging remains available with --log /path/to/file.log.

Logged actors/actions include:

  • API user actions (create_entry, list_entries, whoami)
  • Admin CLI actions (create_user)
  • System compaction events

Database Schema

Auto-created on startup:

  • users(id, username, token_hash, created_at)
  • entries(id, user_id, entry_type, content, created_at)
  • action_logs(id, actor_type, actor_username, action, metadata, created_at)
  • compactions(day, ran_at)

Production Operations (Ubuntu)

This section assumes Ubuntu 22.04/24.04 and root/sudo access.

1) Install runtime dependencies

sudo apt update
sudo apt install -y caddy sqlite3 curl

2) Create service user and directories

sudo useradd --system --home /opt/team-dev-log --shell /usr/sbin/nologin devlog || true
sudo mkdir -p /opt/team-dev-log /var/lib/team-dev-log
sudo chown -R devlog:devlog /opt/team-dev-log /var/lib/team-dev-log

3) Deploy binary

Copy the binary built for target architecture:

sudo install -m 0755 ./devlog-linux-amd64 /opt/team-dev-log/devlog
sudo chown devlog:devlog /opt/team-dev-log/devlog

4) Configure systemd service

Create /etc/systemd/system/team-dev-log.service:

[Unit]
Description=Team Dev Log
After=network.target
Wants=network-online.target

[Service]
Type=simple
User=devlog
Group=devlog
WorkingDirectory=/opt/team-dev-log
Environment=TZ=UTC
ExecStart=/opt/team-dev-log/devlog --db /var/lib/team-dev-log/devlog.db --log -
Restart=always
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/var/lib/team-dev-log

[Install]
WantedBy=multi-user.target

Apply and start:

sudo systemctl daemon-reload
sudo systemctl enable --now team-dev-log
sudo systemctl status team-dev-log

5) Create initial API user

sudo -u devlog /opt/team-dev-log/devlog admin create-user \
  --username alice \
  --db /var/lib/team-dev-log/devlog.db \
  --log -

6) Configure Caddy reverse proxy

Use the repository Caddyfile as a base and set your domain.

Example /etc/caddy/Caddyfile:

devlog.example.com {
	import devlog_common
}

(devlog_common) {
	encode zstd gzip

	handle /api/* {
		reverse_proxy 127.0.0.1:9173
	}

	handle {
		reverse_proxy 127.0.0.1:9172
	}

	log {
		output stdout
		format console
	}
}

Validate and reload:

sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo systemctl status caddy

7) Firewall baseline

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

8) Logging strategy in production

  • Run app with --log - so logs go to stdout.
  • Let systemd-journald retain and rotate logs.
  • Use action_logs table for product/audit event history.
  • Keep Caddy access logs separate from app logs.

Useful commands:

# App logs (live)
sudo journalctl -u team-dev-log -f

# Last 200 app log lines
sudo journalctl -u team-dev-log -n 200 --no-pager

# Caddy logs
sudo journalctl -u caddy -f

9) Backups (SQLite)

Use SQLite online backup mode:

sudo -u devlog sqlite3 /var/lib/team-dev-log/devlog.db \
  ".backup '/var/lib/team-dev-log/devlog-$(date +%F).db'"

Copy backups off-host (S3, rsync, etc.) on a schedule.

10) Upgrades / rollback

Upgrade:

sudo install -m 0755 ./devlog-linux-amd64 /opt/team-dev-log/devlog
sudo systemctl restart team-dev-log
sudo systemctl status team-dev-log

Rollback:

sudo install -m 0755 /opt/team-dev-log/devlog.previous /opt/team-dev-log/devlog
sudo systemctl restart team-dev-log

11) Health checks and verification

curl -i http://127.0.0.1:9173/api/health
curl -I https://devlog.example.com/
curl -i https://devlog.example.com/api/health

12) Common troubleshooting

  • Service wont start:
    • sudo journalctl -u team-dev-log -n 200 --no-pager
  • Caddy config issues:
    • sudo caddy validate --config /etc/caddy/Caddyfile
  • Database permission errors:
    • Ensure /var/lib/team-dev-log is writable by devlog.

Zig Cross-Compile (CGO SQLite)

Because go-sqlite3 uses CGO, use Zig as C toolchain.

Linux amd64:

CC="zig cc -target x86_64-linux-musl" \
CXX="zig c++ -target x86_64-linux-musl" \
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \
go build -ldflags "-s -w" -o devlog-linux-amd64 .

Linux arm64:

CC="zig cc -target aarch64-linux-musl" \
CXX="zig c++ -target aarch64-linux-musl" \
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 \
go build -ldflags "-s -w" -o devlog-linux-arm64 .

Security Notes

  • Raw tokens are never stored.
  • Token hashes are SHA-256.
  • Put API/UI behind HTTPS reverse proxy for internet exposure.
  • Restrict exposed ports with firewall/security-group rules.

TODO

  • integrations with slack etcetera
  • polish the oat based ui, use the tabs component the way the oat docs does, to switch betweeb webui and api, with curl and sample response
  • add webhook support for external systems (GitHub, GitLab, Jira)
  • add role-based access control (admin/member/viewer)
  • add token rotation and token revoke commands in admin CLI
  • add pagination + cursor-based listing for /api/entries
  • add search/filter endpoints (by user, keyword, entry type)
  • add export endpoints (JSON/CSV/Markdown daily summary)
  • add email and chat notifications for daily compaction summary
  • add OpenAPI spec + generated API client examples
  • add optional SSO/OIDC authentication mode