Commit Graph
1503 Commits
Author SHA1 Message Date
SnapOtter 2f39e38162 feat(tools): 2.0 phase 5 wave 2 - pdf depth (21 tools) (#220) 2026-06-13 10:18:55 +08:00
SnapOtter ae1337901d feat(tools)!: SnapOtter 2.0 phase 4 wave 1: 45 core tools across all modalities (#219) 2026-06-13 10:18:49 +08:00
SnapOtter d647d8ed19 feat(modality)!: SnapOtter 2.0 phase 3 modality framework: media/doc engines, pool routing, display modes (#218) 2026-06-13 10:18:39 +08:00
SnapOtter c451b939c7 feat(jobs)!: SnapOtter 2.0 phase 2 job spine: async queues, worker pools, object storage, admin dashboard (#217) 2026-06-13 10:17:13 +08:00
SnapOtterandGitHub 1c724d5d21 feat(db)!: SnapOtter 2.0 phase 1 foundation: postgres, migrator, compose stack (#216)
* feat(infra): add dev compose stack with postgres and redis

* fix(infra): comment dev env defaults until wired; harden dev compose restart and start_period

* chore(deps): add pg driver and testcontainers for postgres migration

* feat(db): translate schema to drizzle pg-core (timestamptz, boolean, pgEnum, jsonb)

Schema translation (apps/api/src/db/schema.ts):
- sqlite-core -> pg-core, all 10 tables preserved 1:1
- integer(mode:'timestamp') -> timestamp({ withTimezone: true })
- integer(mode:'boolean') -> boolean
- jobs.status text enum -> pgEnum('job_status') with same 4 values
- 7 columns changed from text to jsonb: jobs.inputFiles, jobs.settings,
  pipelines.steps, apiKeys.permissions, roles.permissions,
  auditLog.details, userFiles.toolChain
- settings.value stays text, jobs.error stays text, jobs.progress stays real

jsonb call-site sweep (removed JSON.stringify on writes, JSON.parse on reads):
- apps/api/src/routes/roles.ts: permissions read/write (3 sites)
- apps/api/src/routes/api-keys.ts: permissions write + read (2 sites)
- apps/api/src/routes/audit-log.ts: details read (1 site)
- apps/api/src/routes/pipeline.ts: steps write + read (2 sites)
- apps/api/src/routes/progress.ts: inputFiles write (2 sites)
- apps/api/src/routes/tool-factory.ts: toolChain read + write (2 sites)
- apps/api/src/routes/user-files.ts: toolChain read + write (4 sites)
- apps/api/src/permissions.ts: roles.permissions read (1 site)
- apps/api/src/lib/audit.ts: details write (1 site)
- apps/api/src/plugins/auth.ts: apiKeys.permissions read (1 site)

* refactor(db): type jsonb columns via $type and note raw CTE conversion requirements

* feat(db): archive sqlite migrations and generate postgres baseline

* chore(db): dockerignore legacy migrations, add archive breadcrumb, fix trailing newline

* feat(db): pg pool connection, advisory-locked boot migrations, DATABASE_URL config

* fix(db): friendly fatal on unreachable postgres, idempotent closeDb, lock-key convention note

* refactor(db): async drizzle calls in plugins, lib, permissions

* fix(api): analytics never throws, typed permission guard, single-query session invalidation

* refactor(db): async drizzle calls across all routes and bootstrap

Convert every route file and index.ts from sync SQLite drizzle
patterns to async node-postgres drizzle:

- .all() removed (bare await on select)
- .get() converted to destructured [row] = await ...
- .run() removed (bare await on insert/update/delete)
- .changes replaced with .rowCount (null-guarded) in progress.ts
- sqlite import removed from user-files.ts; raw CTEs converted to
  await db.execute(sql`...`) with postgres-dialect recursive CTEs
- ChainRow types updated: tool_chain is parsed jsonb (string[] | null),
  created_at is Date (timestamptz) with no * 1000 conversion
- All requirePermission() guard calls awaited (security: unawaited
  async guard returns truthy Promise, bypassing permission check)
- All hasEffectivePermission() and getPermissions() calls awaited
- All auditLog() calls awaited (preserves write-before-response order)
- trackEvent() and captureException() left un-awaited (fire-and-forget
  by design, guaranteed never-throw)
- ensureAnonymousUser(), startCleanupCron(), recoverStaleJobs() awaited
  in bootstrap sequence
- ensureInstanceId() and ensureDefaultSettings() made async

Files converted: 14 (index.ts + 12 route files + tools/index.ts)

* fix(db): await async checkStorageQuota in user-files upload/save routes

* fix(db): await checkStorageQuota in save-result route (missed second call site)

* feat(db): sqlite-to-postgres migrator with CLI and first-boot import

* fix(db): migrator error context, honest force semantics, boot-hook fatal, null-variance tests

* test: run suite against per-file postgres databases via testcontainers

- Add tests/global-setup.ts: spins up a Postgres testcontainer,
  creates a migrated template database once per vitest run.
- Rewrite tests/setup/per-fork-env.ts: each test file (forks pool)
  clones the template into its own database via CREATE DATABASE ...
  TEMPLATE, preserving the same per-file isolation granularity.
- Update vitest.config.ts: add globalSetup, pg alias, update comment.
- Fix tests/integration/test-server.ts: remove DB_PATH mkdir, async
  runMigrations, async db operations, remove SQLite WAL checkpoint.
- Fix 21 unit test db/index mocks: add pool and closeDb exports.
- Fix 8 unit test files: add async/await for now-async permission,
  audit, and analytics functions.
- Fix 18 integration test files: convert sync .run()/.all()/.get()
  to async drizzle patterns, add async to callbacks.
- Production change: apps/api/src/routes/teams.ts: cast COUNT(*)
  to ::int so Postgres returns a number instead of bigint string.

* fix(db): seed built-in roles, reject NUL bytes, cast COUNT, serialize job persists

- Seed built-in roles (admin, editor, user) at boot via ensureBuiltinRoles()
  with onConflictDoNothing, restoring data that legacy SQLite migration 0007
  provided via INSERT statements (the pg baseline is DDL-only).
- Reject NUL bytes in login credentials with 401 (postgres rejects \x00 in
  text columns; valid usernames never contain NUL, matching 1.x behavior).
- Cast COUNT(*)::int in user-files, audit-log, and roles listing queries so
  postgres returns a JS number instead of bigint-as-string.
- Serialize fire-and-forget job progress DB writes per jobId so the final
  "completed" status is never overwritten by a late-arriving "processing"
  write (race condition exposed by async postgres round-trips).

* test: fix teams race, seed roles in test server, poll for job status

- Add missing await to resetTeams() in teams PUT beforeEach (the async
  delete raced with the subsequent insert under postgres).
- Call ensureBuiltinRoles() in test server bootstrap so integration tests
  have the same built-in roles as production.
- Replace fixed 100ms flushPersist delay with a polling helper that waits
  for terminal job status, eliminating timing-dependent failures caused by
  postgres network round-trip latency.

* test: make heic temp-file cleanup assertion resilient to concurrent workers

Use a set-based diff instead of raw file count when checking that
decodeHeic cleans up temp files. Other concurrent test workers can
create heic-in-*/heic-out-* files in the shared tmpdir, inflating the
"after" count and causing spurious failures under full-suite load.

* fix(db): align builtin-role seed to post-0010 legacy state; test polish

* feat(docker): three-container compose (app, postgres, redis) with boot wait and migrations

* fix(docker): set TEST_DATABASE_URL so containerized tests skip testcontainers

* chore(docker): test compose project name, clearer 1.x upgrade comment, unref probe timer

* feat(enterprise): enforce D15 license boundary; move s3 storage into packages/enterprise

* fix(enterprise): restore lazy aws-sdk loading; community installs load no s3 code at boot

* fix(enterprise): boundary check catches dynamic imports; document getS3 concurrency

* feat(db)!: SnapOtter 2.0 phase 1 foundation: postgres, migrator, compose stack

BREAKING CHANGE: SQLite is no longer the runtime database. Deployments now
require Postgres (and Redis, used from phase 2). Existing installs migrate
with SQLITE_MIGRATE_PATH or 'pnpm --filter @snapotter/api migrate:sqlite'.

* fix(ci): postgres service + fresh e2e database per run; ignore unfixable torch CVE-2025-3000
2026-06-13 10:15:23 +08:00
SnapOtterandGitHub 4ec39c556f test: testing overhaul -- CI e2e gates, parallel suites, generated matrices, mutation testing (#215)
Closes the "e2e never runs in CI" hole. Adds per-PR e2e smoke gate,
nightly full-suite workflows, parallel vitest forks (per-fork DBs),
Playwright parallel/serial/visual projects against production builds,
metadata-generated test suites (drift guards, hostile inputs, format
matrix, pairwise settings, property-based fuzz), Stryker mutation
testing, Schemathesis API fuzz, coverage ratchet, and fixes for three
session-poisoning bugs that caused 200+ serial-bucket failures.

Bug fix included: favicon/split/bulk-rename could hang clients forever
when ZIP streaming failed after reply.hijack().
2026-06-10 22:01:13 +08:00
SnapOtter 3b8d529b44 fix(ci): revert rembg to 2.0.62 (2.0.75 requires numpy>=2.3)
rembg 2.0.75 pulls in numpy>=2.3.0 which conflicts with our pinned
numpy==1.26.4 and would break the entire AI dependency chain. The two
rembg CVEs (SSRF + path traversal) are in its server/CLI components
which we don't use; they're already in the pip-audit ignore list.
2026-06-10 21:21:39 +08:00
SnapOtter 8792080982 fix(deps): patch Dependabot security alerts
- Pillow 11.1.0 -> 12.2.0 (6 CVEs: OOB writes, decompression bomb, DoS)
- rembg 2.0.62 -> 2.0.75 (SSRF + path traversal in server component)
- @fastify/static ^8.1.0 -> ^9.1.3 (path traversal + route guard bypass)
- Remove redundant @fastify/static pnpm override
- Dismiss stale esbuild alert (already at 0.28.0)
- Dismiss file-type alert (16.5.4 is dev-only via @types/potrace)
2026-06-10 19:08:08 +08:00
SnapOtter 7c70c60b9e fix(ai): surface actionable fix for libGL.so.1 missing on headless installs
Proxmox LXC and other headless Linux installs lack libgl1, causing
all AI tools to show a misleading "install opencv-python-headless"
error even though the pip package is already installed. Detect the
libGL ImportError and suggest `apt-get install -y libgl1` instead.
2026-06-10 18:59:30 +08:00
SnapOtter 03600de4ef docs: correct API endpoints in llms.txt
The pipeline endpoints were documented as /api/v1/pipelines CRUD routes
that do not exist; the real routes live under /api/v1/pipeline/
(execute, save, list, delete, batch). The X-API-Key header was never
supported; API keys authenticate via Authorization: Bearer. Also adds
the openapi.yaml and llms-full.txt endpoints and the explicit batch
route.
2026-06-10 11:52:36 +08:00
SnapOtter 5cbfe61ea8 chore: remove superseded one-off scripts
generate-meme-thumbs.ts is superseded by build-templates.mjs which
generates thumbnails and the manifest in one pass. test-docker-fixes.sh
was a one-off Docker debugging script with no remaining references.
2026-06-10 10:48:01 +08:00
SnapOtter 78679c57f4 fix(test): update import paths for cleanup.test.ts after move to integration 2026-06-10 00:24:42 +08:00
SnapOtter 5b3b3c7632 fix(test): mock db/index.js in unit tests that import API modules
Five unit tests crashed because they imported modules that transitively
reached db/index.ts, which eagerly calls `new Database()` at module load
time. Added vi.mock for db/index.js matching the pattern used by 16
other passing API unit tests.

Moved cleanup.test.ts to tests/integration/ since it uses a real SQLite
database, runs migrations, and inserts rows.
2026-06-09 23:34:36 +08:00
SnapOtter 60e3ac2210 fix: resolve hardcoded /app paths and loosen mediapipe pin for native installs
Path resolution for the feature manifest and install script was hardcoded
to /app/..., which only works inside the Docker container. Native installs
(e.g. Proxmox at /opt/snapotter) hit "No such file or directory" errors.

Resolve both paths relative to the source file location via import.meta.url
so they work regardless of where the project is installed.

Also loosen mediapipe==0.10.21 to >=0.10.21 in requirements.txt and
requirements-gpu.txt to match the feature manifest. The exact pin has no
cp313 wheel, so it fails on Python 3.13 (Debian 13 default). mediapipe
0.10.35 ships py3-none universal wheels that resolve cleanly.

Reported-by: MickLesk (community-scripts/ProxmoxVE#14720)
2026-06-09 23:18:02 +08:00
SnapOtter edea82ba27 fix(ui): show repair UI when AI bundle models are broken (#214) 2026-06-09 23:09:34 +08:00
SnapOtter 8e15fe7a3f feat(i18n): add repair UI strings for broken AI bundles 2026-06-09 23:08:26 +08:00
SnapOtter 27a56c774b fix: allow reinstall of AI bundles with broken model files (#214) 2026-06-09 23:07:38 +08:00
SnapOtter 3196ba63d5 refactor: export verifyBundleModels with unit tests 2026-06-09 23:06:07 +08:00
SnapOtter 96764ec375 fix(test): align analytics-env test with ANALYTICS_ENABLED=true default
The test still expected ANALYTICS_ENABLED to default to false, but
commit a3cd33f3 changed the default to true for the consent flow.
2026-06-08 19:32:00 +08:00
SnapOtter 931e15a6bd fix(ui): constrain preview dropzone height in pipeline builder
The empty-state dropzone in the Automate preview panel was expanding
to fill the full 38% panel height, causing overlap and clipping of
the "Import from Library" button. Constrain to max 120px and tighten
spacing for a cleaner layout.
2026-06-08 18:41:55 +08:00
SnapOtter a3cd33f37f fix: restore ANALYTICS_ENABLED=true default for consent flow
The security hardening commit changed this to false, which prevents
the analytics consent page from appearing on first login. Users see
"disabled by administrator" instead of being asked to opt in/out.

With ANALYTICS_ENABLED=true, the SDK initializes but NO user data is
sent until the user explicitly accepts on the consent page. The consent
page is the privacy safeguard, not the server-side flag. Self-hosters
who want to fully disable analytics can still set ANALYTICS_ENABLED=false.
v1.17.2
2026-06-08 18:07:04 +08:00
SnapOtter 0631c41eb9 fix(docs): read version from root package.json for nav badge
The docs version badge was reading from apps/docs/package.json which
gets out of sync during manual releases. Read from the monorepo root
package.json instead so it always matches the released version.
2026-06-08 17:56:18 +08:00
SnapOtter 6e5e493612 feat: enterprise security artifacts (SBOM + hardening guide)
- Add Syft SBOM generation (CycloneDX + SPDX) to release workflow
- Add Trivy JSON vulnerability report as release artifact
- Add deployment hardening guide to docs site
- Add sidebar entry for security page
2026-06-08 17:51:57 +08:00
SnapOtter 0b90c62dae docs: add security page to docs sidebar 2026-06-08 17:48:48 +08:00
SnapOtter bfd6d25786 docs: add security and hardening guide
Container hardening, network isolation, Docker secrets, Kubernetes
deployment, backup strategy, and compliance artifacts reference.
2026-06-08 17:48:20 +08:00
SnapOtter ac39557b42 feat(ci): add Trivy JSON report as release artifact
Full vulnerability scan (all severities) uploaded to each GitHub
Release for enterprise customers to review before deployment.
2026-06-08 17:45:40 +08:00
SnapOtter 6a5e7f3389 feat(ci): add SBOM generation to release workflow
Syft generates CycloneDX and SPDX SBOMs from the amd64 production
image and uploads both to the GitHub Release.
2026-06-08 17:44:15 +08:00
SnapOtter d2fff1f87d fix(security): bump Go 1.25 and Pillow 12.2.0 for new Trivy CVEs
- Go 1.24 -> 1.25: fixes CVE-2026-25679 (HIGH: net/url IPv6 parsing)
- Pillow 12.1.1 -> 12.2.0: fixes CVE-2026-40192 (HIGH: decompression bomb DoS)
2026-06-08 17:32:34 +08:00
SnapOtter 61c587d024 docs: credit @electricmessiah and @florentineprinzessinzusachsen in release notes 2026-06-08 17:26:43 +08:00
SnapOtter 0c58db2417 fix(security): update Go 1.24 and Pillow 12.1.1 for Trivy CVEs
- Bump Go from 1.23 to 1.24 to fix CVE-2025-68121 (CRITICAL:
  crypto/tls certificate validation) and CVE-2025-61726 (HIGH:
  net/url memory exhaustion) in the caire binary.
- Bump Pillow from 11.1.0 to 12.1.1 to fix CVE-2026-25990 (HIGH:
  out-of-bounds write via crafted image).
2026-06-08 17:06:07 +08:00
SnapOtter 1846d1c7c9 fix(security): add pnpm bundled dependency CVEs to trivyignore
All 13 HIGH CVEs (glob, minimatch, picomatch, tar) are bundled
inside pnpm 9.x itself, not in our application dependencies.
pnpm overrides only affect our app's dependency tree, not pnpm's
internal modules. All require upgrading to pnpm 10.x.
2026-06-08 16:44:38 +08:00
SnapOtter 012e2136ee fix(security): resolve 13 HIGH Trivy CVEs in npm dependencies
- Override glob>=10.5.0 (CVE-2025-64756 command injection)
- Override minimatch>=9.0.6 (CVE-2026-26996/27903/27904 ReDoS)
- Override tar>=7.5.11 (CVE-2026-23745/23950/24842/26960/29786/31802
  path traversal and arbitrary file overwrite)
- picomatch>=4.0.4 already overridden (CVE-2026-33671 ReDoS)
- Add .trivyignore for pnpm 9.x CVEs (CVE-2025-69262/69263) that
  require a major version bump to pnpm 10.x
- Restore Trivy as a blocking gate with trivyignore support
- Restore scan dependency in manifest job
2026-06-08 16:32:52 +08:00
SnapOtter 2c0a04c195 fix(ci): make Trivy scan non-blocking for manifest creation
The Trivy scan finds HIGH CVEs in pnpm's own transitive dependencies
(glob, minimatch, tar, picomatch) which are build-time only and not
in the runtime image. These block manifest creation unnecessarily.
Scan results still upload to GitHub Security tab via SARIF.
2026-06-08 16:30:24 +08:00
SnapOtter 31e5f81a55 feat(docs): add v1.17.2 changelog and auto-update in release workflow
- Add v1.17.2 entry to docs/changelog.md
- Add "Update docs changelog" step to release workflow that
  auto-prepends .release-notes.md to the docs changelog on
  each release, then commits and pushes to trigger docs deploy
2026-06-08 16:21:29 +08:00
SnapOtter e0eb2e2075 fix(ci): add curl retry for libheif download and update release notes
- Add --retry 3 --retry-delay 5 to libheif curl in Dockerfile to
  handle transient GitHub CDN 504 errors on arm64 CI runners.
- Update release notes: use @ mentions, remove double-dash separators.
2026-06-08 16:17:28 +08:00
SnapOtter 37bc623dec fix(ci): exclude .git/.github from archive instead of deleting
Deleting .github breaks the Post Run cleanup for composite actions,
causing the entire job to fail. Use tar --exclude instead so the
working directory stays intact for GitHub Actions cleanup.
2026-06-08 16:11:34 +08:00
SnapOtter d43aa2f1b6 fix(ci): add --repo flag to gh release upload after .git removal
The archive step removes .git before creating the tarball, but
gh release upload needs git context to resolve the repo. Pass
--repo explicitly to avoid "not a git repository" errors.
2026-06-08 16:07:23 +08:00
SnapOtter 6dcbb9f122 docs: add release notes for v1.17.2 2026-06-08 15:55:55 +08:00
SnapOtter 9f26f0d733 test: increase GIF conversion timeout to 120s on CI
WebP->GIF conversion is slow on GitHub Actions runners and
intermittently exceeds the 30s default. Match the AVIF timeout.
2026-06-08 15:49:47 +08:00
SnapOtter 9e345bf37f fix: update APP_VERSION constant to 1.17.2 2026-06-08 15:19:24 +08:00
SnapOtter 24baf18746 test: update TypingCursor tests for new hero copy and 5s interval 2026-06-08 14:57:58 +08:00
SnapOtter 9931d6899c chore(release): 1.17.2 2026-06-08 14:45:44 +08:00
SnapOtter 1680344f9d revert(docs): restore original homepage layout
Reverts all docs homepage CSS and content changes back to the
state before the compact layout experiments.
2026-06-08 14:45:12 +08:00
SnapOtterandalbanobattistella c629fb3893 feat(i18n): update Italian translation with ~145 newly translated strings
Co-authored-by: albanobattistella <albanobattistella@users.noreply.github.com>

Closes #206
2026-06-08 14:44:16 +08:00
SnapOtter 1d7bc00d2d fix(docker): use CUDA 12.6 index for PaddlePaddle GPU and revert version
- fix(ocr): change paddlepaddle-gpu from --extra-index-url to --index-url
  for the CUDA 12.6 package index. With --extra-index-url, pip could
  resolve from PyPI (CUDA 11 build) instead of the cu126 index, causing
  "libcusolver.so.11: undefined symbol" errors on CUDA 12 containers.

- revert version to 1.17.1 (v1.17.2 release was deleted)
2026-06-08 14:36:59 +08:00
SnapOtter d33844d9c8 docs: shorten feature descriptions to two sentences max 2026-06-08 14:34:54 +08:00
SnapOtter 9a1d3d25f4 fix(docker): resolve 4 release-blocking issues from validation
- fix(rate-limit): treat RATE_LIMIT_PER_MIN=0 as unlimited (50k/min)
  instead of blocking all requests. @fastify/rate-limit interprets
  max:0 as "allow zero requests," breaking fresh container startups.

- fix(docker): add libgles2 for MediaPipe face detection tools.
  blur-faces, red-eye-removal, enhance-faces, and passport-photo
  failed with "libGLESv2.so.2 not found" on all headless containers.

- fix(docker/arm64): remove conflicting system libheif1 to avoid
  ABI symbol mismatch with our custom libheif 1.21.2 build.
  heif-convert failed with "undefined symbol: heif_get_plugin_directories."

- fix(docker/arm64): pre-install wheel+setuptools in base Python venv
  so basicsr can build from source on arm64 (no pre-built wheel).
  This unblocks upscale-enhance and photo-restoration bundles.
2026-06-08 14:07:55 +08:00
SnapOtter 7cb8d912e8 fix(docs): rewrite homepage layout with proper visual hierarchy
Rewrote all homepage CSS overrides from scratch:
- Brand name at 44px with gradient, subtitle at 34px (clear hierarchy)
- Feature cards at 14px/13px with 20px padding (readable, not cramped)
- 3-column feature grid with equal-height rows
- Removed VitePress 128px default gap before footer
- Trimmed Local AI feature description to balance card heights

Entire page fits in one viewport without scrolling.
2026-06-08 13:29:15 +08:00
SnapOtter acee5a6651 fix(landing): slow hero message rotation from 3s to 5s 2026-06-08 13:27:08 +08:00
SnapOtter 25e7c79982 fix(landing): move docker command into hero section
Relocate the terminal mockup from the separate HowItWorks section
into the hero so the install command is visible immediately without
scrolling. Remove the now-empty HowItWorks component from the page.
2026-06-08 13:23:53 +08:00