Commit Graph

691 Commits

Author SHA1 Message Date
sencho-token-app[bot] b494edaf7d chore(main): release 0.46.1 (#537)
Co-authored-by: sencho-token-app[bot] <275163604+sencho-token-app[bot]@users.noreply.github.com>
2026-04-12 20:30:59 -04:00
Anso 1702dabb7a fix(fleet): add auth middleware, input validation, and design system compliance (#536)
Add authMiddleware to all 13 fleet endpoints that were previously
accessible without authentication. Add NaN validation for parseInt
params, stackName validation on snapshot restore, and description
length cap on snapshot creation. Clean up updateTracker entries on
node deletion to prevent memory leaks.

Replace hardcoded colors with design system tokens, swap Select for
Combobox, replace overflow-y-auto with ScrollArea, fix card styling
(shadow-card-bevel, border tokens). Fix stale container data by
always refetching on stack expand with a loading guard against
concurrent requests.

Add operational logging for state-changing fleet operations and
diagnostic logging gated behind Developer Mode. Add 20 fleet tests
covering auth enforcement, input validation, tier gating, and
snapshot CRUD lifecycle.
2026-04-12 20:28:18 -04:00
sencho-token-app[bot] bbaee7f7f0 docs: refresh screenshots (#535)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-13 00:00:27 +00:00
sencho-token-app[bot] b76029e05d chore(main): release 0.46.0 (#534)
Co-authored-by: sencho-token-app[bot] <275163604+sencho-token-app[bot]@users.noreply.github.com>
2026-04-12 19:58:39 -04:00
Anso cd3d7b23be feat(app-store): add port conflict indicator to deploy sheet (#533)
Show a pulsating warning dot next to host ports that are already in
use by a running container. Hovering over the dot reveals which
Sencho-managed stack or external app occupies the port.

Adds GET /api/ports/in-use endpoint that returns a map of bound host
ports with ownership info, and a getPortsInUse method on
DockerController that reuses the existing container-to-stack
resolution logic.
2026-04-12 19:57:38 -04:00
sencho-token-app[bot] 7a2099b56e docs: refresh screenshots (#532)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-12 23:32:37 +00:00
sencho-token-app[bot] 2c2d76d241 chore(main): release 0.45.6 (#531)
Co-authored-by: sencho-token-app[bot] <275163604+sencho-token-app[bot]@users.noreply.github.com>
2026-04-12 19:31:04 -04:00
Anso 5f91e16417 fix(app-store): handle orphaned stack directories on template deploy (#530)
* fix(app-store): handle orphaned stack directories on template deploy

When a stack deployed via the App Store is later removed through Docker
Desktop or the CLI (instead of through Sencho), its directory remains on
disk without a compose file. The deploy endpoint previously rejected any
re-deploy with a 409 if the directory existed, even if empty.

Now the endpoint checks for a compose file before rejecting. If the
directory exists but contains no compose file, it is treated as an
orphaned remnant: cleaned up automatically and the deploy proceeds.

Also makes FileSystemService.hasComposeFile public so the deploy
endpoint can reuse it instead of duplicating the compose file check.

* docs(app-store): document orphaned stack directory cleanup behavior
2026-04-12 19:26:29 -04:00
sencho-token-app[bot] 36f2e8a7bc docs: refresh screenshots (#529)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-12 19:37:01 +00:00
sencho-token-app[bot] 04bac48c68 chore(main): release 0.45.5 (#528)
Co-authored-by: sencho-token-app[bot] <275163604+sencho-token-app[bot]@users.noreply.github.com>
2026-04-12 15:35:23 -04:00
Anso 4909c35e50 fix(resources): harden Resource Explorer with auth, validation, design, and UX fixes (#527)
- Sanitize error messages in all delete/prune/create/inspect endpoints
  to prevent Docker internals from leaking to the frontend
- Add CIDR, IPv4, and Docker resource ID input validation
- Add requirePaid gate to network topology endpoint
- Add invalidateNodeCaches after image/volume/network mutations
- Fix design system violations: card borders, destructive button variant,
  visible DialogDescription, overflow-auto replaced with ScrollArea,
  hardcoded Tailwind colors replaced with tokens
- Gate purge button behind isAdmin to prevent silent 403s
- Fix shared inspect loading state to be per-network-row
- Parse error response bodies for meaningful toast messages
- Add clipboard API fallback for non-HTTPS contexts
- Render Options section in network inspect sheet
- Add operational and diagnostic logging for resource operations
- Extend validation and DockerController test suites
- Update docs with Options field in network inspect
2026-04-12 15:31:35 -04:00
sencho-token-app[bot] a5cb316ca9 docs: refresh screenshots (#525)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-12 18:33:50 +00:00
sencho-token-app[bot] fd31b87598 chore(main): release 0.45.4 (#524)
Co-authored-by: sencho-token-app[bot] <275163604+sencho-token-app[bot]@users.noreply.github.com>
2026-04-12 14:32:14 -04:00
Anso d4882d32d9 fix(app-store): harden App Store with auth, validation, bug fixes, and design compliance (#523)
* fix(app-store): harden App Store with auth, validation, bug fixes, and design compliance

Add authMiddleware to GET /api/templates and POST /api/templates/deploy
endpoints. Add isValidStackName and isPathWithinBase checks to the deploy
endpoint. Replace fs.existsSync with async fsPromises.access. Extract
FileSystemService to local variable to avoid repeated getInstance calls.

Fix template mutation bug where PUID/PGID/TZ duplicated on re-open by
working on a copy instead of mutating state. Make env_file conditional
in generated compose YAML (only when env vars exist). Add port validation
(range 1-65535) with visual feedback and deploy blocking. Add env key
collision warning toast for custom variables.

Replace any types with proper LSIO API interfaces. Change catch types
from any to unknown with getErrorMessage. Add structured logging with
[Templates] prefix and diagnostic logging gated behind Developer Mode.

Align with design system: remove hardcoded bg-white and text-red-500,
use ScrollArea, fix destructive button variant, add tabular-nums and
strokeWidth 1.5, use cn() for conditional classes. Add empty-registry
state distinct from no-search-results.

Add 16 unit tests for TemplateService covering compose generation,
conditional env_file, env string generation, and cache clearing.
Update App Store docs with port validation and env collision details.

* refactor(app-store): remove unused interface exports

Remove export keyword from interfaces that are only used within their
own file: TemplateEnv, TemplateVolume, and TemplatesResponse in
TemplateService.ts; TemplateEnv and Template in AppStoreView.tsx.
No external consumers import these types.

* fix(app-store): remove unused fs default import

The fs.existsSync call was replaced with fsPromises.access in the
deploy endpoint, leaving the fs default import unused. Remove it
to fix the ESLint no-unused-vars error in CI.
2026-04-12 14:31:00 -04:00
sencho-token-app[bot] 8e91f91622 docs: refresh screenshots (#522)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-12 09:48:00 +00:00
sencho-token-app[bot] 9aadfcebed chore(main): release 0.45.3 (#521)
Co-authored-by: sencho-token-app[bot] <275163604+sencho-token-app[bot]@users.noreply.github.com>
2026-04-12 05:46:21 -04:00
Anso 2465f7607e fix(stacks): harden stack management with security, validation, and logging (#520)
* fix(stacks): harden stack management with security fixes, validation alignment, and logging

Validate WebSocket stack names with isValidStackName() to close a
path-traversal gap on the /api/stacks/:stackName/logs WS endpoint.
Align POST /api/stacks to use the canonical validator (allows underscores).
Replace error: any catch blocks with error: unknown + type narrowing.
Add cache invalidation to PUT /api/stacks/:stackName/env.
Rename DELETE param from :name to :stackName for consistency.

Add standard [Stacks] lifecycle logs and diagnostic [Stacks:debug] logs
gated behind the Developer Mode toggle (with 5s TTL cache).
Extract shared isDebugEnabled() and getErrorMessage() utilities.

Frontend: roll back optimistic status on API failure, guard unsaved
changes when switching stacks, pre-check duplicate names in App Store.

* docs(settings): update Developer Mode description to mention debug diagnostics
2026-04-12 05:43:15 -04:00
sencho-token-app[bot] 3ad1ab5c84 docs: refresh screenshots (#519)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-12 08:28:47 +00:00
sencho-token-app[bot] c02f8a5de0 chore(main): release 0.45.2 (#518)
Co-authored-by: sencho-token-app[bot] <275163604+sencho-token-app[bot]@users.noreply.github.com>
2026-04-12 04:27:01 -04:00
Anso 9db97107aa fix(dashboard): harden real-time dashboard with bug fixes and design compliance (#517)
* fix(dashboard): harden real-time dashboard with bug fixes and design compliance

- Fix host alert spam: add 5-minute cooldown for CPU/RAM/disk threshold
  alerts, preventing duplicate notifications every 30s during sustained
  breaches. Extract shared dispatchWithCooldown helper (also used by
  Docker janitor alerts).
- Fix memory metric inflation: subtract filesystem cache from stored
  memory_mb values, matching the existing calculateMemoryPercent logic.
- Fix crash detection reliability: replace fragile 'seconds ago' string
  matching with a tracked Set of alerted container IDs. Containers are
  only alerted once per crash event, with automatic cleanup when they
  start running again or after a 1-hour TTL.
- Fix health status bar: exited containers now trigger 'degraded' state
  independently of unread error notifications.
- Fix CPU chart Y-axis: auto-scale when aggregate container CPU exceeds
  100% instead of silently clipping at the hardcoded domain ceiling.
- Fix grammar: 'actives' to 'active' in container count label.
- Add shadow-card-bevel to all dashboard cards per design system.
- Update dashboard docs to reflect revised health status thresholds.

* test(dashboard): update monitor service tests for new alert signatures

- Add container Id fields to crash detection test fixtures
- Update host alert assertions to match dispatchWithCooldown 3-arg call
- Fix unhealthy container test to use State: 'unhealthy' instead of
  State: 'running' (running containers are now skipped in crash detect)
2026-04-12 04:25:41 -04:00
sencho-token-app[bot] c7f041957c docs: refresh screenshots (#516)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-12 07:22:13 +00:00
sencho-token-app[bot] 98a712657a chore(main): release 0.45.1 (#515)
Co-authored-by: sencho-token-app[bot] <275163604+sencho-token-app[bot]@users.noreply.github.com>
2026-04-12 03:20:27 -04:00
Anso 4950cd0bd0 fix(updates): scan all filesystem stacks for image updates (#514)
ImageUpdateService previously discovered stacks by iterating Docker
containers, which meant stacks without containers (e.g. after
docker compose down) were silently excluded from update checks.

Switch to a hybrid discovery approach: enumerate stacks from the
filesystem via FileSystemService.getStacks(), parse compose files
for image refs with .env variable resolution, then augment with
container-based image discovery for running stacks.

Also cleans up stale stack_update_status entries when stacks are
deleted or no longer exist on disk, and replaces the plain
update-available tooltip with an animated cursor follow pattern.
2026-04-12 03:17:37 -04:00
sencho-token-app[bot] f26ac16bc0 docs: refresh screenshots (#513)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-12 06:29:22 +00:00
sencho-token-app[bot] c18f5258e5 chore(main): release 0.45.0 (#511)
Co-authored-by: sencho-token-app[bot] <275163604+sencho-token-app[bot]@users.noreply.github.com>
2026-04-12 02:27:38 -04:00
sencho-token-app[bot] 0dc996264d docs: refresh screenshots (#512)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-12 06:25:55 +00:00
sencho-token-app[bot] 51b53b3088 chore(main): release 0.44.1 (#510)
Co-authored-by: sencho-token-app[bot] <275163604+sencho-token-app[bot]@users.noreply.github.com>
2026-04-12 02:24:11 -04:00
Anso 023e962a26 fix(fleet): forward host bind mounts to self-update helper container (#509)
The self-update helper container runs `docker compose up -d
--force-recreate` to recreate the main Sencho container. Previously it
only mounted the docker socket, the compose working directory, and the
data directory. If the user's docker-compose.yml references env_file,
configs, or secrets at paths outside the compose working directory
(e.g. /opt/docker/env/globals.env), the helper could not resolve them
and compose failed with "env file not found".

Now during initialize(), SelfUpdateService collects all host bind mounts
from the container inspect data (filtered to Type=bind). In
triggerUpdate(), these are forwarded to the helper as read-only mounts
at their original host paths (source:source:ro), skipping the socket,
data dir, and compose working dir which are already mounted explicitly.
This lets docker compose resolve any host-path reference the user has
configured, without needing to parse compose files for specific
directives.
2026-04-12 02:20:42 -04:00
sencho-token-app[bot] d62f6244b2 docs: refresh screenshots (#508)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-12 05:17:18 +00:00
sencho-token-app[bot] 6af1029894 chore(main): release 0.44.0 (#507)
Co-authored-by: sencho-token-app[bot] <275163604+sencho-token-app[bot]@users.noreply.github.com>
2026-04-12 01:15:36 -04:00
Anso 622c1f9262 feat: home dashboard and Settings Hub polish (#506)
* feat(dashboard): drop CPU column and relative timestamp from Stack Health and status bar

The Stack Health table's CPU column duplicated data already surfaced in
the top ResourceGauges and the CPU Usage historical chart. The health
status bar's 'just now' timestamp was cosmetic: no consumer relied on
lastUpdated state for polling, staleness detection, or conditional
rendering. Removing both tightens the dashboard and eliminates a dead
prop chain through useDashboardData.

* refactor: remove dead admin_email field from setup flow

The Setup form captured an admin email under 'Used for license recovery.
Never shared with third parties.' but the value was written to
global_settings and read nowhere: no license recovery, SMTP, or support
contact flow consumed it. Rather than building UI on top of the dead
field, delete the input, the payload key, and the backend persistence.
Any orphaned row from prior setups is harmless and the frontend ignores
unknown settings keys.

* feat(settings): use Radix ScrollArea with per-section scroll memory

Settings Hub used a native-scroll div that snapped to the top every time
the user switched subsections and exposed the default browser scrollbar.
Wrap the nav and content panes with the shadcn ScrollArea (Radix under
the hood, type='hover') and expose a viewportRef so the modal can stash
each section's scrollTop in a ref and restore it via useLayoutEffect on
switch. Style the thumb with translucent foreground tokens so it reads
as glass against popovers and dialogs. Replaces a hand-rolled scroll
hook and ad-hoc CSS utility.
2026-04-12 01:04:15 -04:00
Anso 4aa49f371d docs: clean and sanitize CHANGELOG history (#505)
Audit cleanup of CHANGELOG.md accumulated over 43 releases:

- Delete 23 orphaned `[Unreleased]` sections. Release-please does not
  consolidate manually-added Unreleased content into the next release, so
  each previous contributor's notes were left stranded between release
  blocks. The auto-generated release sections above each orphan already
  captured the commits.
- Sanitize internal implementation details that should not be in a
  public changelog: internal service/class/middleware names, library
  names, container file paths, internal endpoint paths, exact rate-limit
  thresholds, encryption primitives, and CVE/version-specific remediation
  details.
- Normalize tier naming throughout: legacy "Team Pro" / "Personal Pro" /
  "Sencho Pro" / "(Pro)" references rewritten to "Skipper" / "Admiral" /
  "Skipper and Admiral" per current product tiering.
- Deduplicate the v0.39.0 bulk dump and other sections where
  release-please swept the same commit subjects in multiple times.
- Rewrite the v0.1.0 section from a raw engineering log (with file
  paths, attack payloads, and internal module names) into user-facing
  release notes organised by Security / Added / Fixed / Changed /
  Removed.
- Update CONTRIBUTING.md to tell contributors not to edit CHANGELOG.md
  directly, matching the actual release-please flow. This is the root
  cause that created the 23 orphan Unreleased sections in the first
  place.

Verification:
- `## [Unreleased]` count: 0
- Legacy tier name count: 0
- Internal service/library name count: 0
- CVE IDs: 0
- File shrunk from 1,599 to 901 lines
2026-04-10 23:26:48 -04:00
Anso 16a6adfda9 docs(dockerfile): fix stale entrypoint comment (#504)
The comment next to the ENTRYPOINT directive still described the old
default ("fixes volume ownership as root then drops to sencho via
su-exec"), contradicting the updated explanatory block a few lines up.
Reword to match the new default: exec as root, drop to $SENCHO_USER
only when opted in.
2026-04-10 22:16:01 -04:00
sencho-token-app[bot] f3fcf924ad docs: refresh screenshots (#503)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-11 01:39:22 +00:00
sencho-token-app[bot] 3be5adcb4a chore(main): release 0.43.4 (#502)
Co-authored-by: sencho-token-app[bot] <275163604+sencho-token-app[bot]@users.noreply.github.com>
2026-04-10 21:37:44 -04:00
Anso 9eb945a6f0 fix: run as root by default to eliminate stack-folder permission failures (#501)
Every filesystem operation against user compose folders (save, create,
deploy, update, rollback, template install, fleet snapshot restore)
previously failed with EACCES whenever a stack container had chowned
its own bind mount to another UID, which is extremely common with
linuxserver/* images and anything that runs as root by default.

Running Sencho as root eliminates the entire class of permission bugs
at the source and matches the default posture of Portainer, Dockge,
Komodo, and Yacht. Mounting /var/run/docker.sock is already equivalent
to root-on-host, so the previous non-root hardening provided essentially
no additional isolation while breaking real features.

Changes:

- docker-entrypoint.sh: default path stays root, no GID dance, no
  privilege drop. Opt-out via SENCHO_USER=sencho restores the legacy
  behavior bit-for-bit (chown data dir, match Docker socket GID,
  su-exec to the user). Fails fast if SENCHO_USER names a nonexistent
  account. Kubernetes / OpenShift forced-non-root compat preserved via
  the existing id -u = 0 guard.
- FileSystemService: delete forceDeleteViaDocker (the ~40-line helper
  that shelled out to an alpine container to work around EACCES during
  deleteStack) and simplify deleteStack to a single fsPromises.rm call.
  Tests updated accordingly.
- Dockerfile: keep the sencho user+group pre-created so the opt-out
  path works out of the box; comments updated to document the new
  default.
- Docs: new "Container user" section in configuration.mdx documenting
  the root default and the SENCHO_USER opt-out; troubleshooting and
  self-hosting updated to match.
2026-04-10 21:35:31 -04:00
sencho-token-app[bot] f33c12fb36 docs: refresh screenshots (#500)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-11 00:44:03 +00:00
sencho-token-app[bot] 8c54d8d5cc chore(main): release 0.43.3 (#499)
Co-authored-by: sencho-token-app[bot] <275163604+sencho-token-app[bot]@users.noreply.github.com>
2026-04-10 20:42:18 -04:00
Anso ba9c4f4aa6 fix(compose): move atomic backup out of stack folder, silence stale stats 404s (#498)
The Skipper/Admiral atomic deploy/update path used to create
.sencho-backup/ inside the user's stack folder, which silently failed
with EACCES whenever a container had chowned the bind mount (swag,
tautulli, linuxserver/* images, etc). That broke auto-rollback and the
manual rollback endpoint for those stacks. Stack backups now live under
<DATA_DIR>/backups/<stackName>/ next to sencho.db, which is always
writable by the Sencho user.

While stress-testing the same scenario, MonitorService also flooded the
error log with "Error parsing stats for container ... 404 no such
container" because per-container stats polls (30s tick) raced with
docker compose recreating containers. The 404 case is now skipped
silently; non-404 stats failures still log at error level.
2026-04-10 20:06:17 -04:00
sencho-token-app[bot] 9a861f0a76 docs: refresh screenshots (#497)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-10 21:41:15 +00:00
sencho-token-app[bot] f09da9ed69 chore(main): release 0.43.2 (#496)
Co-authored-by: sencho-token-app[bot] <275163604+sencho-token-app[bot]@users.noreply.github.com>
2026-04-10 17:39:47 -04:00
Anso 4003e7c047 fix(fleet): capture local self-update helper errors (#495)
The helper container that runs `docker compose up --force-recreate` was
spawned with `docker run -d`, so the command returned immediately with
just the container ID. Any failure happening INSIDE the helper (bad
compose file, image mismatch, permission issue, socket problem) was
invisible: execFile's callback only fired for `docker run` command
errors, never for errors inside the detached helper. The UI fell back to
the generic 3-minute "Local update did not complete" heuristic with no
actionable information.

The helper now runs attached, so execFile's callback receives the
helper's exit code and stderr directly for any failure that happens
before the recreate kills this process. Additionally, the helper
persists exit code + stderr to `/app/data/.sencho-update-error` before
exiting, so the error survives the gateway's own death. On startup,
`SelfUpdateService.recoverPreviousError()` reads and deletes that file,
routing the real error through the existing `getLastError()` path so the
freshly booted gateway reports exactly why the previous attempt failed
instead of the generic timeout.
2026-04-10 17:39:01 -04:00
sencho-token-app[bot] fea8b98cb5 docs: refresh screenshots (#494)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-10 20:39:05 +00:00
sencho-token-app[bot] d909e1995a chore(main): release 0.43.1 (#493)
Co-authored-by: sencho-token-app[bot] <275163604+sencho-token-app[bot]@users.noreply.github.com>
2026-04-10 16:37:21 -04:00
Anso 12fe79fc85 fix(deps): migrate SSO OIDC code to openid-client v6 (#492)
* chore(deps): bump the all-npm-backend group across 1 directory with 4 updates

Bumps the all-npm-backend group with 4 updates in the /backend directory: [@aws-sdk/client-ecr](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-ecr), [openid-client](https://github.com/panva/openid-client), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest).


Updates `@aws-sdk/client-ecr` from 3.1026.0 to 3.1028.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-ecr/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1028.0/clients/client-ecr)

Updates `openid-client` from 5.7.1 to 6.8.2
- [Release notes](https://github.com/panva/openid-client/releases)
- [Changelog](https://github.com/panva/openid-client/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/openid-client/compare/v5.7.1...v6.8.2)

Updates `@types/node` from 25.5.2 to 25.6.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `vitest` from 4.1.3 to 4.1.4
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.4/packages/vitest)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-ecr"
  dependency-version: 3.1028.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-npm-backend
- dependency-name: openid-client
  dependency-version: 6.8.2
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-npm-backend
- dependency-name: "@types/node"
  dependency-version: 25.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: all-npm-backend
- dependency-name: vitest
  dependency-version: 4.1.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all-npm-backend
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(deps): migrate SSO OIDC code to openid-client v6

The v5 Issuer/Client/generators API was removed upstream. Rewrite the
OIDC auth flow in SSOService to use the v6 functional API:

* discovery()/Configuration replace Issuer.discover + new issuer.Client
* buildAuthorizationUrl replaces client.authorizationUrl
* authorizationCodeGrant replaces client.callback, and handles the
  state check internally so the manual CSRF guard is removed
* fetchUserInfo replaces client.userinfo and now receives claims.sub
  so v6 can reject userinfo/id_token subject mismatches

Also:
* Cache the discovered Configuration per provider in CacheService
  (TTL 5 min) so a single login flow does not pay the HTTPS discovery
  round trip twice. Invalidation wired into saveProviderConfig,
  deleteProviderConfig, and seedOidcFromEnv via a private helper.
* Fix the testOidcDiscovery hack that passed a "discovery-probe"
  placeholder when clientId was missing; validate clientId upfront
  instead and return a clear error.
* Log fetchUserInfo failures at warn level before falling back to
  id_token claims so a subject-mismatch rejection is not silently
  hidden.

Unblocks dependabot PR #470.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-10 16:28:26 -04:00
Anso af4b17cc37 ci: migrate DOCS_REPO_TOKEN to sencho-token-app installation token (#490)
Replace the long-lived DOCS_REPO_TOKEN personal access token with
short-lived installation tokens minted per-job from the sencho-token-app
GitHub App (owned by the AnsoCode org, installed on both Sencho and
sencho-docs).

Why this is better than the PAT:

- Scoped per-job to only the repo the job actually touches, not one
  token that works everywhere. update-screenshots and release-please
  mint against Sencho; sync-docs mints against sencho-docs.
- Scoped per-job to only the permission scopes the job actually needs
  (contents:write and sometimes pull-requests:write), not the broader
  scope a PAT carries.
- Auto-revoked at job end by the action's post-run step, so a leaked
  log line or artifact is useful for at most the remainder of that job.
- Not tied to any individual user account: no more risk of the token
  silently dying when the human who minted it leaves or rotates keys.
- Like a PAT but unlike GITHUB_TOKEN, installation tokens DO trigger
  downstream workflow runs, so the release-please tag push still
  cascades into docker-publish.yml exactly as before.

Jobs migrated:
- release-please.yml: single mint, passed to release-please-action.
- ci.yml update-screenshots: one mint reused by checkout, peter-evans
  create-pull-request, and the gh pr merge step.
- ci.yml sync-docs: one mint targeting sencho-docs, fed to the git
  clone URL as x-access-token.

DOCS_REPO_TOKEN can be deleted from repo secrets after the first full
release cycle under this migration validates release-please,
update-screenshots, and sync-docs all succeed end-to-end.

actions/create-github-app-token pinned to v3.0.0 at SHA
f8d387b68d61c58ab83c6c016672934102569859, consistent with the
SHA-pinning convention established in the supply-chain hardening PR.
2026-04-10 15:48:33 -04:00
Anso bed7269c27 ci: daily cache-bust the Alpine apk upgrade layer (#491)
The runtime stage's `apk upgrade --no-cache` layer was being reused from
the buildx gha/registry cache indefinitely, so newly-patched Alpine
packages (e.g. openssl CVE fixes) could sit behind a stale layer until
an unrelated Dockerfile change invalidated it by coincidence. This
surfaced as Trivy hard-failing on CVE-2026-28390 (libcrypto3/libssl3
DoS in CMS) on the github-app-token-migration PR, even though upstream
Alpine had already published 3.5.6-r0 with the fix.

Add an APK_CACHE_BUST build-arg that CI sets to the current UTC date
(YYYY-MM-DD). Each calendar day the arg value changes, which changes
the RUN layer's hash, which forces buildx to re-execute `apk upgrade`
and pick up whatever Alpine has published that day. All three build
invocations (PR docker-validate, release pre-publish scan, release
multi-arch push) pass the same value, so the scan and the published
artifact always share a layer and Trivy never scans stale bits.

Default value `unset` lets local developers `docker build` without
the arg; production CI always supplies the date.
2026-04-10 15:44:40 -04:00
Anso f2c13bf02d ci: cache Playwright browsers keyed on @playwright/test version (#489)
The Playwright browser download (Chrome for Testing + Chrome Headless
Shell + FFmpeg, ~170MB total) is the single largest cost in the
start-app composite action. Observed in a recent E2E run: 4m15s to
download cold vs ~10s on a warm cache. Across weekly cache evictions
and the ~30% of runs that hit a cold cache, that is multiple action
minutes per PR averaged over time, and several minutes of wall-time
regression on any individual cold run.

Split the existing `playwright install --with-deps chromium` step
into two halves:

1. `playwright install-deps chromium` runs unconditionally because
   apt packages live under /var and are not preserved by actions/cache.
   This is fast (~10s).

2. `playwright install chromium` runs only when the browser cache
   under ~/.cache/ms-playwright misses. The cache key is pinned to
   the @playwright/test version read from the root package.json, so
   a future bump invalidates the cache automatically and the old
   browsers are replaced cleanly.

Sharding (the other half of the original optimization plan) was
evaluated and rejected. With ~80s of per-job setup and only ~60s of
test time, 2-way sharding would roughly double action minutes while
saving only ~5s of wall time on warm runs. The math only pencils out
when test time significantly exceeds setup time, which is not the
case for Sencho today. Revisit if the suite grows past ~5 minutes
of pure test time.
2026-04-10 14:46:01 -04:00
Anso b27bc07f82 ci: reuse backend dist/ in E2E instead of rebuilding (#488)
The backend CI job already runs `tsc` against backend/src/ and only
marks the job green after build + tests + lint + audit all pass. The
E2E job then checked out the same source and ran the same tsc again
inside the start-app composite.

Upload backend/dist/ as a 1-day retention artifact at the end of the
backend job, download it in the E2E job before invoking the composite,
and gate the composite's "Build backend" step on a new
`skip-backend-build` input. The update-screenshots job (which also
uses the composite but has no upstream backend job) leaves the input
at its default of 'false' and continues to build from source.

Savings are modest (~10-15s per E2E run, on top of what setup-node's
~/.npm cache already buys us) but the real win is the contract:
the dist/ that E2E exercises is now byte-for-byte the same artifact
that the backend job verified, eliminating a whole class of "works
in backend job, breaks in E2E" drift.
2026-04-10 14:39:55 -04:00
Anso 61f1602ab8 ci: smoke-test the release image before publishing to Docker Hub (#487)
Starts the already-loaded localhost/sencho:release-scan image headless
on the runner and polls /api/health for up to 30 seconds. Catches
entrypoint regressions, native module ABI breakage, and first-boot
crashes on the exact artifact that the multi-arch push step below
will republish moments later.

Placed BEFORE the build-push step so a smoke failure does not move
the `latest`, semver, or minor tags on Docker Hub. The image reused
here is the same amd64 artifact Trivy scanned a step earlier, so
there is no extra build or pull on the critical path.

The container is started without --rm so that docker logs in the
trap can surface the stack trace when the container crashes during
boot; the trap force-removes it after capturing logs whether it
exited or is still running.

/api/health is public and returns before any DB, JWT, or Docker
socket work, so no env vars, volume mounts, or bind mounts are
required for the smoke test to be a valid signal that the process
booted and the HTTP server is accepting connections.
2026-04-10 14:28:57 -04:00