Commit Graph

17 Commits

Author SHA1 Message Date
xarmian 7c9f29b2f3 fix(ci): Go toolchain 1.26.10 (GO-2026-6809) and markdown-it 14.3.3 (TASK-4007) (#2267)
* fix(ci): Go toolchain 1.26.10 for GO-2026-6809, markdown-it 14.3.3 for npm audit (TASK-4007)

Main CI is red on both gates.
- govulncheck: GO-2026-6809 (crypto/x509) is fixed in go1.26.10. The
  go.mod toolchain line and the Dockerfile's golang image move to
  1.26.10. The workflow comments that name the toolchain value follow
  it, and go.mod's comment records the raise.
- Nix: its artifact is built with nixos-26.05's go, which predates
  1.26.10, so GO-2026-6809 joins nix/accepted-advisories.txt the way the
  1.26.9 block did, with the same removal condition.
- npm audit: markdown-it up to 14.3.2 has two high quadratic-time DoS
  advisories (GHSA-vfgh-jcmw-g37q, GHSA-gfr2-rv44-r23c). The overrides
  pin goes ^14.3.0 -> ^14.3.3; the lock moves markdown-it 14.3.2 ->
  14.3.3 and nothing else.

Claude-Session: https://claude.ai/code/session_01JRXHP9ZVfEN3YEuzmucxP3

* ci(deps): allow markdown-it 14.3.3 under the 7-day age check, a security fix (TASK-4007)

Dependency age (advisory) flagged only markdown-it@14.3.3, published
2026-10-16: the fix for GHSA-vfgh-jcmw-g37q and GHSA-gfr2-rv44-r23c.
pad-merge-pr requires every check at the head to be SUCCESS, so the
advisory red blocked the merge. A security fix cannot wait out the
window. The allow line is the file's own remedy for this case; its
removal after the version ages past 7 days is a filed follow-up.

Claude-Session: https://claude.ai/code/session_01JRXHP9ZVfEN3YEuzmucxP3
2026-10-21 00:07:12 -04:00
xarmian 41456fc783 build: go1.26.9 and x/net v0.60.0 for twelve new standard-library advisories (#1922)
* build: go1.26.9 and golang.org/x/net v0.60.0 for twelve new standard-library advisories; the Dockerfile builder agrees

* build(nix): vendorHash for golang.org/x/net v0.60.0

* build(nix): accept the 12 go1.26.9 stdlib advisories until nixpkgs ships it

* build(nix): the cleared stdlib heading no longer says none
2026-10-08 20:31:22 -04:00
xarmian fa2755e183 feat(materialize): headless op-log materializer bundle, goja runner and worker (TASK-2198 U2) (#1678)
* feat(materialize): headless op-log materializer bundle, goja runner and worker (TASK-2198 U2)

Builds the editor's own JS (Yjs -> y-tiptap -> Tiptap schema -> tiptap-markdown
-> the tab's flush pipeline) into one IIFE, embeds it, and runs it in goja. Not
wired into the server yet (U3 supervisor, U4 write path).

- web/src/lib/collab/materializer/: bundle entry (extension list mirrors
  Editor.svelte's collab branch via the U1 shared modules), op-log replay
  mirroring wsProvider's MESSAGE_SYNC arm, linkedom DOM shim and polyfills
  from the spike, and a vendored patch of prosemirror-markdown
  MarkdownSerializerState.atBlank (quadratic under goja; equivalent).
- web/scripts/build-materializer.mjs (esbuild) -> web/build-materializer/,
  run by `npm run build`; embedded as pad.MaterializerJS.
- internal/materialize: Runner (Go-native atob/btoa/TextEncoder/TextDecoder/
  getRandomValues, schema-version check, ctx interrupt with a clean VM after),
  and RunWorker's length-prefixed JSON protocol behind the hidden
  `pad __materialize-worker` command.
- Parity: vitest drives two real Editor.svelte mounts and compares
  materialize() with the tab's flush on 50 seeded cases; the same cases are
  committed as testdata/corpus.json and replayed in goja (TestCorpusParity).
- CI Go jobs, release tests, Docker and Nix produce the bundle; linkedom and
  esbuild are new devDependencies.

Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC

* test(materialize): pin the atBlank patch's equivalence under V8 too (TASK-2198 U2, codex r1)

Codex r1 claimed upstream's /(^|\n)$/ is true for a trailing CR in JS. It is
not (checked in V8: false for "\r" and "a\u2028", as the patch returns),
but the only equivalence test ran under goja and the patch header cited a
V8-side test that did not exist. This adds it, with the line-terminator edge
cases, and it fails when the patch checks CR instead of LF.

Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC

* build(nix): vendorHash for goja (TASK-2198 U2)

Taken from the Nix job's recompute on c7fa1931 (specified sha256-iEqv…,
got sha256-P1bQ…), so main does not depend on the heal job after merge.

Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC
2026-09-30 02:50:10 -04:00
dependabot[bot] 019c335a87 chore(docker)(deps): bump golang in the docker-minor-and-patch group (#1188)
Bumps the docker-minor-and-patch group with 1 update: golang.


Updates `golang` from 1.26-alpine to 1.27-alpine

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.27-alpine
  dependency-type: direct:production
  dependency-group: docker-minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-24 11:09:33 -04:00
dependabot[bot] e8e1822bfc chore(docker)(deps): bump node from 24-alpine to 26-alpine (#497)
Bumps node from 24-alpine to 26-alpine.

---
updated-dependencies:
- dependency-name: node
  dependency-version: 26-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 01:36:16 -04:00
dependabot[bot] d26d649cb5 chore(docker)(deps): bump alpine (#728)
Bumps the docker-minor-and-patch group with 1 update in the / directory: alpine.


Updates `alpine` from 3.23 to 3.24

---
updated-dependencies:
- dependency-name: alpine
  dependency-version: '3.24'
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docker-minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 01:36:11 -04:00
xarmian e380b4e660 chore(deps): bump Node 22 → 24 (Dockerfile + CI workflows) (TASK-1235) (#443)
Bumps all four Node version pins from "22" to "24" together so CI ↔
production stay aligned:
  • Dockerfile (production image): node:22-alpine → node:24-alpine
  • .github/workflows/ci.yml — Web job + E2E job
  • .github/workflows/release.yml — release pipeline

Going to LTS-bound 24 instead of dependabot's proposed 25-alpine,
which hits EOL on 2026-06-01 (~3 weeks from this commit). Going to
24 instead of waiting for 26-LTS (Oct 2026) because 5 months is too
long to sit on the deferred-bumps backlog and 24 is already a year
into LTS-tested production use. Worst case follow-up is one more
trivial Dockerfile bump in October.

Verified:
  • Local `docker build --target web-builder` on node:24-alpine
    (npm ci + npm run build) — clean, 24s end-to-end
  • `make check` — golangci-lint + go test + npm run build +
    svelte-check, 0 errors

Closes dependabot/docker/node-25-alpine (PR #209) — closing rather
than rebasing because we're going to 24, not 25.
2026-05-08 07:32:43 -04:00
dependabot[bot] 1c86165b77 chore(docker)(deps): bump alpine (#248)
Bumps the docker-minor-and-patch group with 1 update in the / directory: alpine.


Updates `alpine` from 3.21 to 3.23

---
updated-dependencies:
- dependency-name: alpine
  dependency-version: '3.23'
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docker-minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-07 23:45:40 -04:00
xarmian 92a4931f44 feat(docker): PUID/PGID entrypoint shim for Unraid + LinuxServer-style hosts (TASK-1168) (#425)
Tiny /bin/sh entrypoint shim that, if invoked as root, reads PUID/PGID
env vars (defaulting to 99/100 — Unraid's nobody:users), remaps the
in-image pad user, chowns /data, and execs the binary via su-exec.
If invoked as non-root (caller passed --user), it just execs directly
— caller knows what they want.

Solves the classic Unraid appdata-ownership-mismatch first-run failure
where the in-image pad user (uid 1000) couldn't write to a host volume
owned by nobody:users (uid 99, gid 100). Reusable on Synology / QNAP /
TrueNAS where the host's appdata user is similarly non-1000.

Behavior changes:
- Container starts as root (USER directive removed). Entrypoint drops
  privileges via su-exec before exec'ing pad — standard PUID/PGID
  pattern. Healthcheck adapts: root → su-exec to pad; non-root →
  direct wget.
- chown -R is always-run (warn-and-continue on per-file failures). A
  shallow stat-only check would silently break pad on a restored
  backup with mixed-ownership inner files.
- Healthcheck start-period bumped 10s → 60s to absorb slow chown -R
  on large attachment stores.
- Compose default 1000/1000 for backward compat with existing deploys
  whose volumes were created under the previous USER pad image.
- Raw `docker run` defaults to 99/100 (Unraid convention).

Validation rejects PUID=0 / PGID=0 (would defeat the unprivileged-user
invariant), empty values, and non-numeric values with clear errors.

Goes through 11 rounds of codex pre-implementation design review,
catching:
- gid bug where groupmod alone leaves /etc/passwd's primary-gid stale
- compose $-interpolation gotcha (needs $$( ) not $())
- getent missing from default alpine BusyBox
- shell ${VAR:-} silently masking explicit empty values
- healthcheck running as root after USER drop
- su-exec failing for --user non-root pass-through

Part of PLAN-1166 (Pad on Unraid — Community Apps launch). Unblocks
TASK-1169 (XML template authoring).
2026-05-06 08:40:33 -04:00
xarmian 7bb9076ac5 fix(dockerfile): pass version metadata via build args instead of broken in-container substitution (TASK-1080) (#385)
The previous build line had:

    -X main.commit=$(git rev-parse --short HEAD 2>/dev/null) \
    -X main.buildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)

The `git rev-parse` ran INSIDE the build container, but .dockerignore
intentionally excludes `.git/` so the working directory has no git
metadata; the `2>/dev/null` swallowed the resulting error and the
substitution silently produced an empty string. Net result: every
Docker build of pad shipped a binary with `commit=""`, which
fullVersion() collapses to just `version` ("dev") with no commit
metadata at all. Caught during Claude Desktop dogfooding when
`pad_meta` returned `pad_version: "dev"` and bug reports had no way
to identify which build they were hitting.

Two ways to fix: add .git/ to the build context (rejected — operator
explicitly does not want .git in the image build), or pre-compute on
the host and pass via --build-arg (this PR). The wrapper that does
the host-side computation lands in pad-cloud separately.

The `date` substitution worked because alpine has `date` in the
builder image, but it had a worse problem: a fresh `date` value on
every build invalidates layer caching for this RUN. Moving to ARG
lets the caller decide cache semantics — production wrappers will
pass a real timestamp; dev rebuilds can omit BUILD_TIME entirely
to keep the cache warm.

Defaults are deliberately ugly-but-honest so a `docker build .`
without args produces "dev (unknown)" rather than hiding the
misconfiguration. Production builds with all three args produce
e.g. "0.1.0-rc.5 (40f636e 2026-05-03T00:15:00Z)".

Sanity-checked all four input combinations (defaults, all-set,
version+commit only, commit-empty) against the existing
fullVersion() logic — output shapes are clean for each.
2026-05-02 20:29:02 -04:00
xarmian 157ca4e88f chore: bump Go toolchain to 1.26 (TASK-763) (#247)
* chore: bump Go toolchain to 1.26 (TASK-763)

Bump Go from 1.25 to 1.26 across all toolchain pins:

- go.mod — go 1.25.0 → go 1.26.0
- Dockerfile — golang:1.25-alpine → golang:1.26-alpine
- .github/workflows/ci.yml — three setup-go steps (Go, Go-Postgres, E2E jobs)
- .github/workflows/release.yml — release pipeline

No `toolchain` directive: the repo is pre-launch with no external
contributors yet, so we set the floor where we want it (hard requirement).

Verified locally before commit:
- golangci-lint v2.11.4 builds and runs under Go 1.26.2 (pinned in CI)
- golang:1.26-alpine and 1.26.2-alpine images present on Docker Hub
- go build ./... clean
- go vet ./... clean
- go test ./... all pass

Parent: PLAN-644 (OSS Repo Hygiene and Launch Polish).

* chore: gofmt -w under Go 1.26 (TASK-763)

Apply Go 1.26's gofmt to the codebase. ~41 files reformatted, all
struct-tag whitespace realignment — no semantic changes. Verified:

- gofmt -l ./cmd ./internal returns empty after
- go build ./... still clean
- go test ./... still passes (run before commit)

Bundling the gofmt diff with the toolchain bump in the same PR because
the formatting drift is a direct consequence of moving from 1.25 to
1.26; splitting them creates a mandatory two-PR ordering for no value.

Parent: PLAN-644.

* docs: bump documented Go floor to 1.26 (TASK-763)

Match go.mod's hard 1.26.0 requirement in the source-build instructions.
Caught by Codex review round 1 on PR #247.

- README.md:158 — "Go 1.25+" → "Go 1.26+"
- CONTRIBUTING.md:9 — "Go 1.25+" → "Go 1.26+"
2026-04-25 11:35:19 -04:00
xarmian be9a96ba21 chore: run containers as non-root + harden K8s securityContext (TASK-680) (#219)
First thing external reviewers flag on a public repo: "why does this
image run as root?". Fixes both Dockerfiles and the K8s deployment.

Dockerfiles (Dockerfile + Dockerfile.goreleaser):
- Add a non-login uid:1000 "pad" user via adduser
- chown /data so the app can write its SQLite DB as the unprivileged user
- Declare USER pad before the ENTRYPOINT

deploy/k8s/deployment.yaml:
- Pod-level securityContext: runAsNonRoot, runAsUser/Group 1000, fsGroup
  1000 (so the emptyDir volume is group-writable), seccompProfile
  RuntimeDefault
- Container-level securityContext: allowPrivilegeEscalation false,
  readOnlyRootFilesystem true, drop ALL capabilities

Verified:
- docker build succeeds; `docker inspect ... Config.User` = "pad"
- Container running as uid 1000 serves /api/v1/health successfully
- Container runs with --read-only rootfs + writable /data volume with no
  runtime errors (server only writes to /data, never /tmp)
- deploy/k8s/deployment.yaml parses via yq; securityContext block
  structurally correct

Parent: PLAN-644.
2026-04-22 20:23:14 -04:00
xarmian 2b413f5d95 chore: add HEALTHCHECK to both Dockerfiles (TASK-681) (#218)
Before: neither Dockerfile declared HEALTHCHECK. docker-compose.yml
added it at compose level, so "docker run ghcr.io/xarmian/pad:latest"
had no health signal for Docker/Kubernetes/Swarm.

Adds HEALTHCHECK to Dockerfile and Dockerfile.goreleaser probing
GET /api/v1/health (served by internal/server/server.go:353). Uses
wget from busybox (already present in alpine:3.21) so no extra
apk install is needed. Interval 30s / timeout 5s / start-period 10s
/ retries 3 — conservative defaults safe for low-traffic single-user
instances.

Verified the built image reports the expected HEALTHCHECK via
`docker inspect` and that the probe succeeds against a live container.

Parent: PLAN-644.
2026-04-22 20:13:53 -04:00
xarmian a0b8abd4da fix: Dockerfile CMD needs 'server start' (server is a subcommand group) 2026-04-13 01:03:12 +00:00
xarmian 8095885cf2 fix: Dockerfile CMD should be 'server' not 'serve' 2026-04-13 01:03:12 +00:00
xarmian 0972347cf0 Fix svelte warnings and add build version info (#26)
* Fix all 17 svelte-check warnings across 7 components

- Add tabindex to toolbar role elements (BoardView, Editor)
- Replace nested buttons with div[role=button] in ListView group headers
- Add role="none" to click-to-close backdrop overlays (Editor, slug page)
- Fix label→span for non-input field labels (CreateWorkspaceModal)
- Add keyboard handlers to interactive divs (CreateWorkspaceModal drop zone, slug page modal)
- Remove unused .slash-backdrop CSS (Editor) and input[type=text] selector (CreateWorkspaceModal)
- Fix state_referenced_locally in RawMarkdownEditor ($state init)
- Add svelte-ignore for conditional tabindex false positive (ToastContainer)

* feat: add build version, commit hash, and timestamp to CLI and web UI

Inject version info via ldflags during build (Makefile, GoReleaser,
Dockerfile). Expose version/commit/build_time in the health API
endpoint and display it in the sidebar footer. Dev builds show
"dev (abc1234 ...)", releases show "v1.2.3 (abc1234 ...)".
2026-03-30 11:54:45 -04:00
xarmian a2e5a6622d Add Docker support and PAD_DATA_DIR config
- Multi-stage Dockerfile for building from source
- Dockerfile.goreleaser for GoReleaser-built release images
- GoReleaser config: publish multi-arch Docker images to ghcr.io
- Add PAD_DATA_DIR environment variable for configuring data directory
- Update README with Docker install option
- Add .dockerignore
2026-03-28 04:34:17 +00:00