34 Commits

Author SHA1 Message Date
saltbo 5eac720dd4 fix(downloader): report aria2 per-peer progress; perf(ci): scope server COPY
Two follow-ups:

- aria2 peers now report progress (0..1) derived from the peer's piece bitfield
  (seeder => 1.0), matching qBittorrent. Previously the Peers list showed '—'
  for aria2 because the field was never populated.
- The server builder copied the whole repo via 'COPY . .', so a cmd/-only
  (downloader) change busted the layer and forced a full vite/tsup rebuild. Copy
  only the build inputs + final-stage sources (src, server, shared, public,
  index.html, vite.config.ts, tsconfig.json, migrations, scripts) so
  downloader-only pushes reuse the cached server image. Verified the builder
  stage still builds and produces dist/dist-server/migrations/entrypoint.
2026-06-19 16:19:50 -04:00
saltbo 348976205b perf(ci): cross-compile the Go CLI and build dev images amd64-only
The docker-dev build took ~14 min, dominated by a ~12-min server-image build —
its arm64 half runs under QEMU emulation (native better-sqlite3/sharp).

- Dev images (:dev, :dev-cli) now build linux/amd64 only, skipping arm64 QEMU
  entirely. Releases stay multi-arch. (The primary dev consumer runs amd64.)
- The Go CLI builder now runs on $BUILDPLATFORM and cross-compiles via
  GOOS/GOARCH (CGO off), so the release arm64 CLI builds natively instead of
  under QEMU — arm64 'go build' verified at ~5s vs emulated minutes. Adds
  go build/mod cache mounts for fast incremental rebuilds.
2026-06-19 15:56:00 -04:00
Jasper Van b959e5e6bf fix(downloader): supervise managed engines and make the CLI go-installable (#461)
Three related changes hardening the remote downloader, bundled because the
module-path rename touches every file — splitting would leave a mid-history
commit that doesn't build.

Engine supervision: a managed engine subprocess (aria2c / qbittorrent-nox)
that exits unexpectedly now crashes the worker. watchEngineProcess waits on
the child, logs the exit at error level, and cancels the run context so
`downloader up` returns non-zero; the container (restart: unless-stopped)
then restarts the whole stack. Previously the exit error was discarded and
the worker kept heartbeating as healthy while every task failed against the
dead RPC. A deliberate shutdown kill is told apart from a crash via a
stopping flag.

aria2 GID recovery: waitAria2 re-discovers the live GID via findTask when
aria2 reports 'GID ... is not found' mid-download (e.g. after an aria2
restart) instead of failing the task. Adds isAria2GIDNotFound, which matches
tellStatus's message format that isAria2DownloadNotFound missed.

CLI install: move the entrypoint to the module root (cmd/main.go) and rename
the module github.com/saltbo/zpan/cmd -> github.com/saltbo/zpan so
`go install .` from cmd/ yields a zpan binary. Internal imports shorten to
github.com/saltbo/zpan/internal/...; the Dockerfile builds the module root.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:56:07 -04:00
saltbo 53ca110218 feat(about): split runtime into runtime engine + deployment platform
Flatten the instance `runtime` object into two fields: `runtime` (the JS engine,
node | workerd) and `platform` (the deployment host). The About page shows each
as its own row with friendly labels (e.g. "workerd" + "Cloudflare Workers",
"Node.js" + "Docker").

- Detect the platform from the entry file (entry === target): each serverless
  entry declares it; entry-node sniffs Cloud Run (K_SERVICE) / Docker
  (ZPAN_RUNTIME, set in the Dockerfile) / bare node. Cloudflare is detected from
  the D1 binding.
- Decouple the cloud payload: zpan-cloud-sdk fixes runtime { provider, target },
  so CloudInstanceInfo keeps that shape and buildCloudInstanceInfo maps to it;
  buildInstanceInfo serves the richer flat shape to the About API.
- Migrate PostHog instance telemetry to the runtime/platform shape and merge the
  duplicate runtimeInfo in licensing-admin into the shared one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 14:56:29 -04:00
saltbo e700fd4977 feat(about): add changelog drawer, latest-version check, and commit hash
Maintain a CHANGELOG.md (Keep a Changelog format) at the repo root and surface
it on the admin About page:

- The page now shows the running build's short commit hash next to the version,
  linked to the GitHub commit. Commit is injected at build time via a new
  resolveAppCommit() (ZPAN_APP_COMMIT -> WORKERS_CI_COMMIT_SHA -> git rev-parse),
  wired through vite/tsup defines, the node entry, Docker, and CI.
- A new admin-only GET /api/system/changelog endpoint fetches CHANGELOG.md from
  master on GitHub, caches it, parses the latest released version, and reports
  whether an update is available (semver compare against the running version).
- The About page renders a "latest version" row with an update-available badge
  and a side drawer that displays the changelog markdown.

Tests cover the semver compare, changelog parse/fetch caching, the API wrapper,
and the route (admin-gated, parsed payload).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:38:29 -04:00
saltbo 0063557d09 fix(docker): repair image startup and guard it in CI
The bundled app-version.mjs self-exec block (`import.meta.url ===
file://${process.argv[1]}`) fired at runtime once tsup inlined it into
entry-node.js, since the container runs `node dist-server/entry-node.js`.
It shelled out to `git describe`, which is absent in the image, crashing
the server on boot and leaving the compose stack unhealthy. Drop the
unused CLI entry; resolveAppVersion is still build-time inlined.

Also stop the CLI build from overwriting the `latest` tag: metadata-action
defaults to latest=auto, so cli-meta re-added a bare `latest` that, running
after the server build, clobbered it with the downloader image. Pin
latest=false on cli-meta.

Add a docker-smoke CI job that runs `docker compose up --wait` so an image
that fails to boot turns CI red, and give the GeoIP download --retry to cut
flake on that external fetch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 10:51:13 -04:00
Jasper Van af43701daf fix(version): set app version global in node entry (#418)
* fix(version): resolve app version at runtime in node entry

E2E runs the Node server via tsx, which bypasses the tsup build-time
define, leaving __ZPAN_APP_VERSION__ unset so getAppVersion throws and
/api/licensing/pair returns 500. Resolve the version at runtime via
resolveAppVersion (git describe) when the global is unset; in the built
output the define inlines the constant, so the branch is never reached
and git is not invoked in production. Keeps the git-describe version as
the single source of truth across all paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(version): inject app version into docker build

The Docker build excludes .git from its context (.dockerignore), so the
build-time git describe in build:node would throw and break the image
build. Let resolveAppVersion read ZPAN_APP_VERSION, set it from an
APP_VERSION build arg in the builder stage, and pass the release tag from
the release workflow. git describe stays the default everywhere else, so
the version is unified across CF Workers, tsx, and Docker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:29:47 -04:00
saltbo a1785f67b8 fix(docker): make downloader data volume writable 2026-06-07 00:14:17 -04:00
saltbo 5f83099c7c fix(docker): configure remote downloader runtime 2026-06-06 20:44:08 -04:00
saltbo 7e7eeb4ca7 fix(downloads): expose torrent listen port in docker 2026-06-06 20:26:03 -04:00
saltbo c1fa89e8e7 fix(downloads): align docker geoip path 2026-06-06 09:53:23 -04:00
saltbo bc3ef9f512 fix(downloads): use standard geoip data paths 2026-06-06 09:52:07 -04:00
saltbo 12604529b1 feat(downloads): report peer geo regions 2026-06-06 09:44:36 -04:00
saltbo ebe0e426bf fix(downloader): stabilize docker runtime validation 2026-06-06 02:29:35 -04:00
saltbo f31278b434 refactor(cli): move zpan command module to cmd 2026-06-06 01:49:41 -04:00
saltbo 9486360389 feat(cli): promote downloader to zpan command 2026-06-06 01:38:16 -04:00
saltbo c19cbbfcac feat(downloader): auto bootstrap device login 2026-06-06 01:26:28 -04:00
saltbo 30bc6e1a12 build: migrate project to pnpm 2026-06-01 10:44:33 -04:00
saltbo a575e055fe fix(docker): resolve 404 on port 8222 and simplify build
Docker build ran vite with the cloudflare plugin, which put SPA output
under dist/client/, while entry-node.ts serves from ./dist — causing
404 on every request in the container.

- Add build:node (vite --mode node) so the SPA lands in dist/, and
  fold build:server into it as a single command
- Move better-sqlite3 to dependencies (Node runtime needs it; CF
  Workers build tree-shakes it out anyway)
- Collapse Dockerfile from 4 stages to 2: one npm ci with a BuildKit
  cache mount, then npm prune --omit=dev in place. Drops the
  duplicate install and the cross-stage better-sqlite3 copy hack.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 23:19:46 -04:00
saltbo 0137f009d4 feat(docker): add deploy variants and auto-generate auth secret
- Add --external better-sqlite3 to tsup build to fix ESM runtime error
- Add docker-entrypoint.sh to auto-generate BETTER_AUTH_SECRET if not set
- Persist generated secret to /data/.auth_secret across restarts
- Move image-based compose files to deploy/ directory
- Add deploy/docker-compose.rustfs.yml for ZPan + RustFS setup
- Keep build-from-source docker-compose.yml at project root

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 10:41:24 -04:00
saltbo 70d60c686f fix(docker): use separate prod deps stage with copied native binaries
npm prune --omit=dev hits peer dependency conflicts. Instead, install
prod deps with --ignore-scripts and copy the pre-built better-sqlite3
native binary from the full deps stage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:09:37 -04:00
saltbo 9ddcbba0b4 fix(docker): install build tools for better-sqlite3 native compilation
node:24-slim lacks python3/make/g++ needed by node-gyp to compile
better-sqlite3. Install them in deps stage, then use npm prune
to get prod-only node_modules with pre-built native binaries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:04:30 -04:00
saltbo e7c732cb75 fix(docker): skip prepare script in prod deps stage
The deps-prod stage runs npm ci --omit=dev which excludes husky,
but the prepare script tries to run it. Use --ignore-scripts since
prod deps don't need git hooks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:00:56 -04:00
saltbo cdb17f52db chore(docker): slim runtime image, include migrations
Split the build into a dedicated deps-prod stage (`npm ci --omit=dev`)
and copy only those node_modules into the runtime layer. Previously
the runtime image inherited the full builder node_modules including
biome, vitest, playwright, wrangler, drizzle-kit, tsup, and tsx —
hundreds of MB of devDependencies shipped to production for no reason.

Also COPY the migrations/ directory so the Node platform's programmatic
drizzle migrator can find SQL files at boot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 11:06:41 -04:00
saltbo 7c77731e96 chore: update CI for npm, upgrade Node to 24 with volta pin
- Replace pnpm with npm in GitHub Actions workflow
- Upgrade Node.js from 20 to 24 in CI, Dockerfile, and engines
- Pin Node 24.14.1 via volta

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 00:31:08 -04:00
saltbo 310e4950b3 refactor: flatten monorepo to single-package CF Pages Functions structure
Replace pnpm workspace monorepo (packages/server, packages/web, packages/shared)
with a flat single-package layout following Hono's pages-stack pattern. Switch from
pnpm to npm and from Workers+Assets to CF Pages Functions deployment model.

- Move source: packages/server/src/ → server/, packages/web/src/ → src/, packages/shared/src/ → shared/
- Add functions/api/[[route]].ts as CF Pages Functions entry (replaces entry-cloudflare.ts)
- Update 22 import paths: server uses relative, web uses @shared/@server aliases
- Merge three package.json into one, switch to npm
- Update wrangler.toml: remove main/assets (Pages auto-detects functions/ dir)
- Add per-directory tsconfig.json for VS Code type resolution
- Simplify Dockerfile for flat layout
- Fix react-pdf CSS import path (dist/esm/ → dist/)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 00:27:38 -04:00
Jasper Van efec832c6f feat: Dockerfile + docker-compose for Node.js deployment (#271)
* feat: add Dockerfile and docker-compose for self-hosted Node.js deployment

Multi-stage build using node:20-slim. Builder runs pnpm build:pages and
the server bundle; runtime image carries the bundled entry, native deps
(better-sqlite3) and the static frontend. A symlink keeps entry-node.ts's
relative `../../dist` static root resolving correctly when running from
packages/server/dist. Compose mounts a named volume at /data so the
SQLite database persists across restarts.

Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f

* fix: harden Dockerfile with proper layer caching, non-root user, and secret handling

Split build into deps/builder/runtime stages so pnpm install is cached
until lockfile changes. Run the Node process as a non-root `zpan` user.
Replace hardcoded BETTER_AUTH_SECRET with a required env var that fails
loudly when missing.

Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f

---------

Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
2026-04-09 17:22:18 -04:00
saltbo b817cab2c1 feat: ZPan v2.0 roadmap, architecture, and repo reset
Remove the Go v1 codebase (preserved in the v1 branch) and establish
the foundation for v2 — a complete rewrite in TypeScript (server) and
Rust (CLI/desktop). This commit includes the product roadmap (v2.0–v2.9),
technical architecture document, and updated project documentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 20:57:15 -04:00
saltbo d2549f7342 chore: update the Dockerfile for the goreleaser 1.20
Signed-off-by: saltbo <saltbo@foxmail.com>
2023-09-16 23:11:07 +08:00
saltbo 1e5f7543d0 chore: update the release.yml of gh action 2021-02-19 02:45:04 +08:00
saltbo 83bd87e03e refactor: repalce the statik by the go1.16 embedfs 2021-02-19 02:12:25 +08:00
saltbo 00ff6b6f83 fix: error for the ut 2021-02-11 00:20:07 +08:00
saltbo 20d526e57b docs: update the README.md 2020-09-09 02:12:50 +08:00
saltbo c58eac5256 chore: add the Dockerfile 2020-09-06 13:05:19 +08:00