diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4fbf295 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM golang:1.25-alpine AS builder +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /eng-pad-server ./cmd/eng-pad-server + +FROM alpine:3.21 +RUN apk add --no-cache ca-certificates && \ + adduser -D -h /srv/eng-pad-server engpad +USER engpad +WORKDIR /srv/eng-pad-server +COPY --from=builder /eng-pad-server /usr/local/bin/eng-pad-server +EXPOSE 8443 9443 8080 +ENTRYPOINT ["eng-pad-server"] +CMD ["server", "-c", "/srv/eng-pad-server/eng-pad-server.toml"] diff --git a/PROGRESS.md b/PROGRESS.md index 28a4c85..32de998 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -5,17 +5,68 @@ See PROJECT_PLAN.md for the full step list. ## Completed -_(none yet)_ +### Phase 0: Project Setup (2026-03-24) +- Go module, Makefile, .golangci.yaml, .gitignore, example config + +### Phase 1: Database + Config (2026-03-24) +- TOML config loading with validation +- SQLite with WAL/FK/busy_timeout, schema migrations (7 tables + indexes) +- 4 tests: open+migrate, idempotent, foreign keys, cascade delete + +### Phase 2: Auth — Password (2026-03-24) +- Argon2id hashing/verification, bearer tokens (SHA-256 hashed storage) +- User creation and authentication +- 6 tests + +### Phase 3: CLI (2026-03-24) +- Cobra CLI: init, server, snapshot, status commands + +### Phase 4: gRPC Sync Service (2026-03-24) +- Proto definitions, generated Go code +- Auth interceptor (username/password from metadata) +- SyncNotebook (upsert in tx), DeleteNotebook, ListNotebooks +- Share link RPCs: CreateShareLink, RevokeShareLink, ListShareLinks +- gRPC server with TLS 1.3 + +### Phase 5: Rendering (2026-03-24) +- SVG: strokes → path elements with dashed/arrow support +- JPG: 300 DPI rasterization via Go image package +- PDF: minimal raw PDF generation (no external library) +- 6 tests + +### Phase 6: REST API (2026-03-24) +- chi router with TLS, auth middleware (bearer/cookie) +- Login endpoint, notebook/page endpoints, rendering endpoints +- Share link endpoints (no auth) + +### Phase 7: Share Links (2026-03-24) +- Token generation, validation, revocation, listing +- Expiry enforcement +- 4 tests, fixed expiry check bug + +### Phase 8: Web UI (2026-03-24) +- HTML templates: layout, login, notebook list, notebook view, page viewer +- Web server with embedded templates, session auth +- Share link views, server command wiring, graceful shutdown + +### Phase 9: FIDO2/U2F (2026-03-24) +- WebAuthn integration via go-webauthn/webauthn +- Credential CRUD, user lookup by credential ID + +### Phase 10: Deployment (2026-03-24) +- Dockerfile (multi-stage, non-root alpine) +- systemd units (service, backup oneshot, daily timer) +- Install script (user, dirs, config, units) ## In Progress -Phase 0: Project Setup +Phase 11: Android App Sync Integration (in eng-pad repo) ## Decisions - **Language**: Go (Metacircular standard) -- **Database**: SQLite via `modernc.org/sqlite` (pure Go, no CGo) -- **Auth**: Argon2id passwords + FIDO2/U2F via `go-webauthn/webauthn` +- **Database**: SQLite via modernc.org/sqlite (pure Go, no CGo) +- **Auth**: Argon2id passwords + FIDO2/U2F via go-webauthn/webauthn - **gRPC auth**: username/password in metadata per-request (no tokens) - **Web auth**: password → bearer token in session cookie - **Rendering**: SVG for web viewing, JPG/PDF for export diff --git a/deploy/scripts/install.sh b/deploy/scripts/install.sh new file mode 100755 index 0000000..c347da4 --- /dev/null +++ b/deploy/scripts/install.sh @@ -0,0 +1,34 @@ +#!/bin/sh +set -e + +SERVICE_USER=engpad +SERVICE_DIR=/srv/eng-pad-server +BIN_DIR=/usr/local/bin + +echo "Creating system user..." +id -u "$SERVICE_USER" >/dev/null 2>&1 || useradd -r -s /usr/sbin/nologin -d "$SERVICE_DIR" "$SERVICE_USER" + +echo "Creating directories..." +mkdir -p "$SERVICE_DIR"/{certs,backups} +chown -R "$SERVICE_USER:$SERVICE_USER" "$SERVICE_DIR" +chmod 700 "$SERVICE_DIR" + +echo "Installing binary..." +cp eng-pad-server "$BIN_DIR/" +chmod 755 "$BIN_DIR/eng-pad-server" + +echo "Installing config..." +if [ ! -f "$SERVICE_DIR/eng-pad-server.toml" ]; then + cp deploy/examples/eng-pad-server.toml "$SERVICE_DIR/" + chown "$SERVICE_USER:$SERVICE_USER" "$SERVICE_DIR/eng-pad-server.toml" + chmod 600 "$SERVICE_DIR/eng-pad-server.toml" + echo " Example config installed. Edit $SERVICE_DIR/eng-pad-server.toml before starting." +fi + +echo "Installing systemd units..." +cp deploy/systemd/eng-pad-server.service /etc/systemd/system/ +cp deploy/systemd/eng-pad-server-backup.service /etc/systemd/system/ +cp deploy/systemd/eng-pad-server-backup.timer /etc/systemd/system/ +systemctl daemon-reload + +echo "Done. Run 'eng-pad-server init -c $SERVICE_DIR/eng-pad-server.toml' to initialize." diff --git a/deploy/systemd/eng-pad-server-backup.service b/deploy/systemd/eng-pad-server-backup.service new file mode 100644 index 0000000..5f63859 --- /dev/null +++ b/deploy/systemd/eng-pad-server-backup.service @@ -0,0 +1,8 @@ +[Unit] +Description=Engineering Pad Server Backup + +[Service] +Type=oneshot +User=engpad +Group=engpad +ExecStart=/usr/local/bin/eng-pad-server snapshot -c /srv/eng-pad-server/eng-pad-server.toml diff --git a/deploy/systemd/eng-pad-server-backup.timer b/deploy/systemd/eng-pad-server-backup.timer new file mode 100644 index 0000000..423ead6 --- /dev/null +++ b/deploy/systemd/eng-pad-server-backup.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Daily Engineering Pad Server Backup + +[Timer] +OnCalendar=*-*-* 02:00:00 +RandomizedDelaySec=300 + +[Install] +WantedBy=timers.target diff --git a/deploy/systemd/eng-pad-server.service b/deploy/systemd/eng-pad-server.service new file mode 100644 index 0000000..50479d7 --- /dev/null +++ b/deploy/systemd/eng-pad-server.service @@ -0,0 +1,29 @@ +[Unit] +Description=Engineering Pad Server +After=network.target + +[Service] +Type=simple +User=engpad +Group=engpad +ExecStart=/usr/local/bin/eng-pad-server server -c /srv/eng-pad-server/eng-pad-server.toml +Restart=on-failure +RestartSec=5 + +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +PrivateDevices=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true +RestrictSUIDSGID=true +RestrictNamespaces=true +LockPersonality=true +MemoryDenyWriteExecute=true +RestrictRealtime=true +ReadWritePaths=/srv/eng-pad-server + +[Install] +WantedBy=multi-user.target