- Go 68.3%
- HTML 23.9%
- HCL 7.8%
|
|
||
|---|---|---|
| templates | ||
| .gitignore | ||
| admin.go | ||
| api.go | ||
| api_test.go | ||
| ARCHITECTURE.md | ||
| Caddyfile | ||
| go.mod | ||
| go.sum | ||
| improv.txt | ||
| main.go | ||
| mise.toml | ||
| oat.min.css | ||
| oat.min.js | ||
| README.md | ||
| terraform_ipv6_hcloud.tf | ||
| webui.go | ||
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 loopapi.go: API handlers/auth/middlewareadmin.go: admin CLI commands and token generationwebui.go: embedded UI assets and UI handlerstemplates/base.html: shared base layout templatetemplates/index.html: main UI template for/templates/entries-view.html: query-only template for/entries-viewoat.min.css,oat.min.js: locally served Oat assetsapi_test.go: API testsmise.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.logwrites 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
- Optional query params:
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 ContentAccess-Control-Allow-Origin: *Access-Control-Allow-Headers: Content-Type, Authorization, X-Auth-TokenAccess-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):
- New writes are temporarily locked (
POST /api/entriesreturns423 Locked). - Day's
normalentries are merged into onedaily_compactentry. - Original day
normalentries are deleted. - 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-journaldretain and rotate logs. - Use
action_logstable 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 won’t 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-logis writable bydevlog.
- Ensure
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