95 Commits

Author SHA1 Message Date
Anso ed553f1f19 feat: change default listen port from 3000 to 1852 (#756)
Updates the backend listen port, Vite dev proxy target, Docker EXPOSE,
compose port mapping, .env.example default, GitHub Actions smoke-test
default, healthcheck URLs, and every doc/example reference. Test fixtures
that include example URLs were updated for consistency, though their
assertions are port-agnostic.

The rate-limit value of 3000 in middleware/rateLimiters.ts and the
3000 entry in WEB_UI_PORTS (which detects user containers like Grafana)
are intentionally untouched.
2026-04-24 22:23:31 -04:00
Anso ce2fb1a5a0 ci: auto-scaffold release blog post every 5th tag (#638)
Adds a workflow that fires on every v* tag push, computes the window
since the latest release post's anchor version in the sencho-website
repo, and (when the window reaches 5) opens a draft PR in
sencho-website with a fully pre-filled scaffold: grouped
ChangelogSection blocks parsed from CHANGELOG.md, version field set to
the new anchor, and a checklist body covering intro, screenshot, docs
link, and reading time. No auto-publish; the human writes the
narrative.

Uses the same sencho-token-app GitHub App as release-please, with its
installation extended to include sencho-website.
2026-04-16 16:16:04 -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 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
Anso 3f9aeb229b ci: cut action-minute waste on bot PRs and non-docs pushes (#486)
Skip the backend, frontend, docker-validate, and e2e jobs on the
chore/refresh-screenshots auto-PR opened by the update-screenshots job.
Screenshot PRs only touch docs/images/ and cannot affect build, lint,
or test surfaces, so running the full PR CI suite on them burns ~6
action minutes per release for zero signal. This mirrors the existing
release-please--branches--main--components--sencho skip.

Also short-circuit sync-docs: when a push to main does not touch any
file under docs/, skip the clone, rsync, and commit steps. rsync
--delete would be a no-op anyway, but spinning up the runner and
cloning sencho-docs still burns ~30s per non-docs merge, which adds
up across multiple merges per day. Requires fetch-depth: 2 so the
detect step can diff HEAD~1 HEAD.

Finally, correct the scan-build comment in docker-publish.yml: the
push-build's amd64 leg reuses layers from the buildkit daemon's
in-memory cache (same job, same daemon), not from the shared registry
buildcache. The cache-from pull is a cold-start fallback for the
first-ever run.
2026-04-10 14:19:15 -04:00
Anso fdb476a84a ci: hard-fail PR and release scans on unacknowledged HIGH/CRITICAL CVEs (#484)
* ci: hard-fail PR and release scans on unacknowledged HIGH/CRITICAL CVEs

Make Trivy a real gate instead of an advisory signal:

- PR CI (docker-validate) no longer uses `continue-on-error: true` on the
  Trivy step, so any HIGH/CRITICAL finding not in `.trivyignore` fails the PR.
- The release pipeline (docker-publish.yml) now builds an amd64-only scan
  image into the local daemon before the multi-arch push-build, re-runs
  Trivy against that exact artifact, and only proceeds to the push if the
  scan passes. This closes the gap where a CVE landed between PR merge and
  release-time rebuild.
- New `.trivyignore` at repo root is the single source of truth for
  acknowledged CVEs across both workflows; it starts empty so the first CI
  run surfaces the full list, which we then populate with justifications.

* ci: populate .trivyignore with initial HIGH/CRITICAL acknowledgements

First CI run on the hard-fail Trivy policy surfaced 7 unacknowledged
findings. Each has been reviewed and justified inline:

- 6 CVEs in the statically-linked Go modules inside docker-compose v5.1.1
  (github.com/docker/docker, buildkit, otel/sdk x2, grpc). These are
  transitively bundled and cannot be bumped without an upstream Compose
  rebuild. The grpc CVE is already explicitly acknowledged in the
  Dockerfile rationale block at Dockerfile:108-111.
- 1 CVE in picomatch 4.0.3 bundled inside the npm CLI that ships with
  node:22-alpine. npm is only invoked at build time against our own
  package.json, so the ReDoS vector is not reachable.

Every entry has a revisit trigger (next Compose release or next Alpine
node bump).
2026-04-10 14:01:02 -04:00
Anso 3b12f2678f ci: gate docker-publish on production environment (#483)
Pin the push_to_registry job to a GitHub `production` environment so the
DOCKERHUB_USERNAME and DOCKERHUB_TOKEN credentials can live there
instead of as repo-wide secrets. Any future workflow that tries to push
to Docker Hub without declaring this environment will fail to resolve
the credentials, which is exactly the blast-radius reduction we want.

As a side benefit, adding a required reviewer to the `production`
environment in repo settings now turns every release into a manual
approval gate with zero workflow changes. The secrets were already
moved into the environment ahead of this commit, so the next release
run will pick them up seamlessly.
2026-04-10 13:51:05 -04:00
Anso e5e58ed932 ci: capture backend and frontend logs for E2E failures (#481)
When an E2E test flakes in CI, the backend and frontend stdout is gone
the moment the step exits. That makes a failed run extremely hard to
triage: you get the Playwright report but nothing from the services
underneath it. Redirect both dev servers to files under the workspace's
ci-logs/ directory and include that directory in the failure-case
artifact upload, so the report and the service logs travel together.

Log files live under $GITHUB_WORKSPACE rather than /tmp because
actions/upload-artifact@v4+ computes a common parent directory across
all path entries. Mixing repo-relative paths with /tmp paths makes '/'
the common root, which v4+ then strips from the artifact, silently
dropping the absolute entries. Keeping everything under the workspace
avoids that footgun.

`stdbuf -oL -eL` forces line-buffered output on both services. Node
stdout is block-buffered when redirected to a regular file, so a crash
or abort can lose the final few hundred lines - exactly the output you
need when debugging a CI failure.

Also remove the duplicated `E2E_USERNAME: admin` / `E2E_PASSWORD:
password123` env blocks from the e2e and update-screenshots jobs.
Those values were already the fallback defaults in e2e/helpers.ts
(`process.env.E2E_USERNAME ?? 'admin'`), so the env blocks were doing
nothing on the default path and created two sources of truth for the
same literals. e2e/helpers.ts remains the canonical source; callers
that want to override the creds still set E2E_USERNAME / E2E_PASSWORD
as job-level env.
2026-04-10 13:47:07 -04:00
Anso 2a2efb847e feat(release): sign and attest published docker images (#480)
Every published Sencho Docker image is now signed with Sigstore cosign
using GitHub Actions keyless OIDC, and ships with an embedded SBOM plus
SLSA provenance attestation attached as OCI referrers on the image.
Users can verify the signature and inspect the attestations themselves
before pulling an image into production.

  cosign verify saelix/sencho:<tag> \
    --certificate-identity-regexp "https://github.com/AnsoCode/Sencho/.*" \
    --certificate-oidc-issuer https://token.actions.githubusercontent.com

The release pipeline now also publishes a new moving minor tag
(saelix/sencho:X.Y, e.g. 0.42) alongside the existing latest and
immutable X.Y.Z tags, so operators who want the latest patch on a given
minor line can pin without chasing every release. Since every 0.x minor
is potentially breaking, the {{major}}-only tag is deferred until 1.0.

cosign is installed via the sigstore/cosign-installer action pinned to
a full commit SHA. The signing step batches every tag produced by
metadata-action into a single cosign invocation pinned to the built
digest, so all tags resolve to the same manifest and we save N-1
Rekor/Fulcio round trips. The job declares id-token: write so the
ambient OIDC token can be minted.

A new docs page (docs/reference/verifying-images.mdx) walks users
through installing cosign, verifying an image, and inspecting the SBOM
and provenance with docker buildx imagetools. A note calls out that
signatures start shipping with v0.43.0, so older images failing
verification is expected behavior and not tampering.
2026-04-10 13:46:27 -04:00
Anso c1ce13758f ci: harden supply chain (SHA pin actions, least-privilege permissions) (#475)
Pin three third-party actions to full commit SHAs with version comments
so dependabot can update them in place without exposing the pipeline to
upstream tag tampering:

  - aquasecurity/trivy-action@v0.35.0
  - peter-evans/create-pull-request@v8.1.1
  - googleapis/release-please-action@v4.4.0

Add a workflow-level permissions: contents: read default to ci.yml, with
update-screenshots keeping its explicit contents: write + pull-requests:
write override. Add the same default to docker-publish.yml (single job).

Expand .dockerignore to explicitly exclude .env*, *.key, *.pem, .github/,
docs/, website/, e2e/, tests/, test-results/, and playwright-report/ as
defense in depth against accidental build-context leaks. None of these
paths are read by any COPY step in Dockerfile.
2026-04-10 13:41:49 -04:00
Anso 8d3cae78e1 ci: optimize workflows to cut redundant runs (#465)
* ci: optimize workflows to cut redundant runs and add timeout protection

Skip validation jobs (build, test, lint, Docker, E2E) on push-to-main
since code was already validated on the PR. Skip CI entirely for
release-please PRs (version bump only). Add timeout-minutes to all jobs
to prevent hung runners from burning 360 min (GitHub default). Add npm
caching to the start-app composite action. Add concurrency control to
docker-publish. Group Dependabot updates to batch PRs per ecosystem.

Estimated savings: ~10 min per release cycle, ~30-40 min/day.

* chore: add tsbuildinfo to gitignore
2026-04-09 20:48:26 -04:00
Anso c0b53e469a ci: fix screenshot PR auto-merge on private repos (#264)
gh pr merge --auto requires branch protection with required status
checks. This repo has no branch protection, so GitHub rejects --auto
with "clean status" error. Remove --auto to merge immediately.
2026-03-29 21:52:56 -04:00
Anso 31e1795af0 feat(scheduled-ops): add scheduled operations for Team Pro users (#231)
Adds the ability to schedule recurring Docker operations (stack restarts,
fleet snapshots, system prunes) via cron expressions with full execution
history logging. Includes Run Now for on-demand execution.
2026-03-28 23:56:37 -04:00
Anso 32a7d53b2b feat: RBAC, atomic deployments, fleet backups, and licensing (Pro) (#185)
* feat: add RBAC viewer accounts, atomic deployments, and fleet-wide backups (Pro)

Introduces three Pro-tier features:

- RBAC: Multi-user system with admin/viewer roles, user management UI,
  automatic migration from single-admin credentials, viewer restrictions
  across the entire UI (read-only editor, hidden action buttons)

- Atomic Deployments: Pre-deploy file backup to .sencho-backup/, automatic
  rollback on health probe failure, manual rollback button, health probes
  added to stack updates, webhook-triggered deploys use atomic rollback

- Fleet-Wide Backups: Point-in-time snapshots of compose files across all
  nodes (local + remote), stored centrally in SQLite, per-stack restore
  with optional redeploy, graceful handling of offline nodes

* fix(settings): use correct ProGate prop name in UsersSection

* fix(settings): remove unused isPro prop from UsersSection

* fix(auth): fetch user info after login and setup so isAdmin is set correctly

* feat(pricing): revise pricing strategy and enforce variant-based seat limits

Raise Personal Pro from $49/yr to $69/yr with 3 viewer seats (up from 1).
Add $15/mo billing option for Team Pro. Mark lifetime pricing as a
90-day early-adopter offer. Store Lemon Squeezy variant_name on
activation/validation and enforce seat limits server-side per variant.

* feat(licensing): add Lemon Squeezy checkout, webhook, and billing portal integration

Server-side checkout URL generation (POST /api/checkout) with admin email
pre-fill and instance_id custom data. HMAC-SHA256 verified webhook endpoint
(POST /api/webhooks/lemonsqueezy) handling order, subscription, and payment
lifecycle events for automatic license activation. Customer billing portal
link stored from webhook events and exposed via GET /api/billing/portal.
In-app checkout buttons in Settings with manual license key fallback.

* fix(licensing): exempt Lemon Squeezy webhook from auth middleware

The catch-all auth middleware on /api/* was blocking the public webhook
endpoint. Added /webhooks/lemonsqueezy to the exemption list alongside
/auth/* and /webhooks/:id/trigger.

* feat(pricing): update pricing to final live rates

Personal Pro: $7.99/month, $69.99/year, $249 lifetime.
Team Pro: $49.99/month, $499.99/year, $1,499 lifetime.
Added personal_monthly checkout variant across backend, frontend, and website.

* refactor(licensing): remove server-side checkout/webhook for self-hosted model

Sencho is self-hosted — each user runs their own instance, so there is
no central server to receive webhooks or hold the store API key. Replaced
in-app checkout buttons with a "View Pricing" redirect to sencho.io and
kept manual license key activation as the primary flow.

- Delete LemonSqueezyService (checkout, webhook, HMAC verification)
- Remove POST /api/checkout, GET /api/billing/portal, POST /api/webhooks/lemonsqueezy
- Remove raw body parser and auth exemption for webhook route
- Remove all LEMONSQUEEZY_* env vars from .env.example
- Replace checkout buttons in SettingsModal with single "View Pricing" button
- Simplify LicenseContext checkout to open sencho.io pricing page
- Update licensing docs to reflect website-based purchase flow

* chore: normalize em-dashes to hyphens across codebase (linter)

* chore: remove accidentally tracked directories from index
2026-03-26 21:58:24 -04:00
dependabot[bot] 3b76a5268e chore(deps): bump actions/checkout from 4 to 6 (#160)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-25 19:44:51 -04:00
dependabot[bot] 8686660e06 chore(deps): bump actions/setup-node from 4 to 6 (#159)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-25 19:44:49 -04:00
dependabot[bot] c3a4d79b6c chore(deps): bump docker/metadata-action from 5 to 6 (#158)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-25 19:44:27 -04:00
dependabot[bot] 7703430a24 chore(deps): bump docker/setup-buildx-action from 3 to 4 (#157)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-25 19:44:25 -04:00
dependabot[bot] 459c2c9297 chore(deps): bump docker/login-action from 3 to 4 (#102)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-25 02:57:16 +00:00
dependabot[bot] 37c7ab485c chore(deps): bump actions/upload-artifact from 4 to 7 (#103)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-25 02:57:14 +00:00
dependabot[bot] 886f5bdd8c chore(deps): bump docker/setup-qemu-action from 3 to 4 (#104)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-25 02:56:49 +00:00
dependabot[bot] 4f8218d9bd chore(deps): bump docker/build-push-action from 6 to 7 (#106)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-25 02:56:30 +00:00
dependabot[bot] 97a772131c chore(deps): bump peter-evans/create-pull-request from 6 to 8 (#105)
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 6 to 8.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v6...v8)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-25 02:54:34 +00:00
Anso 9f9de482ce chore: GitHub workflow revamp — GitHub Flow, community files, CI updates (#101)
* chore: add comprehensive .gitignore

* ci: update CI workflow for GitHub Flow

- Change triggers from develop to main (PRs to main + pushes to main)
- Add concurrency controls to cancel stale runs
- Update docker/build-push-action to v6
- Add descriptive job names for branch protection status checks
- Update screenshot refresh and docs sync to trigger on main pushes

* ci: update docker-publish for GitHub Flow

- Remove develop branch trigger (no more dev tag)
- Keep v* tag trigger for releases
- Update docker/build-push-action to v6

* docs: add community and governance files

- CONTRIBUTING.md with dev setup and PR guidelines
- SECURITY.md with vulnerability reporting policy
- CODE_OF_CONDUCT.md (Contributor Covenant v2.1 reference)
- PR template with conventional commits checklist
- Issue templates for bug reports and feature requests
- CODEOWNERS defaulting to @AnsoCode
- Dependabot config for npm (root, backend, frontend) and GitHub Actions

* docs: add README with badges, quick start, and contributing section

* chore: add LICENSE placeholder and open license decision issue (#100)

* docs: update CLAUDE.md for GitHub Flow branching model

- Replace develop-based Git Flow with GitHub Flow (main only)
- All branches now created off main, PRs target main
- Simplify release checklist (no develop-to-main merge step)
- Update testing strategy to reference Vitest and Playwright
- Fix docs.json reference (was mint.json)

* chore: track CLAUDE.md in version control

Remove CLAUDE.md from .gitignore so project workflow instructions
are versioned alongside the code they govern.

* docs: add MANUAL_STEPS.md for GitHub settings that require UI configuration
2026-03-24 22:32:44 -04:00
SaelixCode 79fde6e2bd fix(editor): bundle Monaco locally to fix stuck Loading state and CSP block
- Add monaco-editor to frontend deps and configure @monaco-editor/react
  to use the local bundle via loader.config({ monaco }) instead of
  fetching from cdn.jsdelivr.net (blocked by CSP scriptSrc: self)
- Wire editorWorker via Vite ?worker syntax — blob: URLs already
  permitted by existing workerSrc CSP directive, no CSP changes needed
- fix(ci): use DOCS_REPO_TOKEN in release-please so tag creation
  cascades to docker-publish.yml (GITHUB_TOKEN cannot trigger workflows)
- fix(ci): auto-merge screenshots PR via gh pr merge --auto --squash
2026-03-24 19:43:55 -04:00
SaelixCode c294def7cc feat(ci): add release-please automated versioning workflow 2026-03-24 19:01:09 -04:00
SaelixCode 6c911fd67f fix(ci): trigger docker-publish on v* tag push instead of GitHub Release event 2026-03-24 18:48:21 -04:00
Anso 278f7f18d9 fix(ci): add linux/arm64 Docker build support with QEMU optimization (#76)
- Add docker/setup-qemu-action@v3 to docker-publish.yml (was missing,
  causing multi-platform builds to hang indefinitely)
- Add platforms: linux/amd64,linux/arm64 to build-push-action step
- Optimize Dockerfile with --platform=$BUILDPLATFORM on builder stages
  so TypeScript compilation runs at native amd64 speed; only the lean
  npm ci --omit=dev step runs under QEMU in the final stage, compiling
  the three native modules (bcrypt, better-sqlite3, node-pty) for the
  correct target architecture — reduces arm64 build time from 6+ hours
  to ~15-30 minutes
2026-03-24 07:50:33 -04:00
Anso 12467c3fc1 fix: add linux/arm64 platform to Docker build for ARM server support 2026-03-23 16:58:25 -04:00
SaelixCode 8902f6fb4c fix(ci): exclude .git from rsync --delete in sync-docs
rsync --delete removes everything in the destination that is absent from
the source. sencho/docs/ has no .git directory, so rsync was deleting
sencho-docs/.git/ on every run. The commit step then had no git repo to
work with, giving 'fatal: not in a git directory'.

Fix: add --exclude='.git' to the rsync invocation.
2026-03-23 08:02:42 -04:00
SaelixCode b24863db66 fix(ci): fix update-screenshots token and sync-docs empty-repo handling
update-screenshots:
- Add pull-requests: write to job permissions (required by
  peter-evans/create-pull-request to open a PR)
- Switch checkout and create-pull-request from GITHUB_TOKEN to
  DOCS_REPO_TOKEN (classic PAT with repo scope); GITHUB_TOKEN is
  blocked from creating PRs against protected branches

sync-docs:
- Replace actions/checkout + init fallback with a single bash step that
  runs git clone and falls back to git init on failure; actions/checkout
  on an empty repo creates a .git in a broken state (no HEAD/branch),
  which the previous [ ! -d .git ] guard never caught, leaving the
  commit step with no valid working tree
- Drop --global safe.directory config (no longer needed once we own
  the git setup ourselves)
2026-03-23 00:28:47 -04:00
SaelixCode 9505132aaf fix(ci): fix sync-docs empty-repo crash and update-screenshots protected-branch push
update-screenshots: replace direct git push to develop (blocked by branch
protection) with peter-evans/create-pull-request@v6, which opens or
updates a chore/refresh-screenshots PR instead.

sync-docs: add continue-on-error on the sencho-docs checkout so an empty
repo doesn't abort the job. A new init step runs git init -b main and
re-adds the remote when .git is missing. Final push uses
git push origin HEAD:main to work on both first-run (empty repo) and
subsequent runs (existing branch).
2026-03-23 00:09:31 -04:00
SaelixCode 353fd253e9 fix(ci): add load: true to buildx so Trivy can find the built image 2026-03-22 22:07:32 -04:00
SaelixCode f5c5eda30a fix(ci): use double-quoted string for if condition to fix YAML parse error 2026-03-22 22:04:49 -04:00
SaelixCode dc79683b01 fix(ci): YAML syntax error in if condition and safe.directory for sync-docs 2026-03-22 22:00:57 -04:00
SaelixCode ed8b8e33b6 feat: add update-screenshots CI job and screenshot capture spec 2026-03-22 21:28:20 -04:00
SaelixCode 7d1b996bb7 fix: trigger docs sync on develop instead of main 2026-03-22 21:17:45 -04:00
SaelixCode 9496b14f72 feat: add automated docs pipeline and scaffold /docs folder
- Add sync-docs CI job: runs on push to main, copies /docs into sencho-docs repo via DOCS_REPO_TOKEN
- Scaffold /docs with mint.json, getting-started/introduction.mdx, getting-started/quickstart.mdx, features/overview.mdx
- Update CHANGELOG
2026-03-22 21:08:16 -04:00
SaelixCode fbd6b8933d ci: restructure pipeline into parallel jobs with E2E, Docker validation, and auditing
- Split serial build-and-test job into parallel backend, frontend, docker-validate, and e2e jobs
- backend job: build → test → lint (new ESLint) → npm audit --audit-level=high
- frontend job: build → lint → npm audit --audit-level=high
- docker-validate job: builds image on every PR without pushing; Trivy scans for CRITICAL/HIGH CVEs (informational)
- e2e job: runs full Playwright suite against live dev servers after backend+frontend pass
- Add backend/eslint.config.mjs and lint script to backend/package.json
2026-03-22 00:48:49 -04:00
SaelixCode ce50db0fde security: pre-release hardening, automated testing, and production readiness
SECURITY (critical fixes):
- Add authMiddleware to /api/system/console-token (was publicly accessible)
- Validate api_url on node create/update to prevent SSRF (rejects localhost/loopback)
- Add rate limiting (5 req/15 min/IP) to /api/auth/login and /api/auth/setup
- Fix path traversal in env_file resolution — absolute/escaping paths rejected
- Add stack name validation to GET routes (was only on PUT/POST)
- Add helmet security headers middleware
- Restrict CORS to FRONTEND_URL in production

PRODUCTION READINESS:
- Add GET /api/health public endpoint + HEALTHCHECK in Dockerfile
- Add SIGTERM/SIGINT graceful shutdown handler (drains connections, closes DB)
- Run container as non-root sencho user in Dockerfile

QUALITY:
- Fix 4 silent empty catch{} blocks in EditorLayout (now show toast.error)
- Connect ErrorBoundary to root App in main.tsx
- Replace WebSocket.Server with named WebSocketServer import (ESM compat)

TESTING (new automated test suite):
- Install Vitest; 38 backend tests across 4 suites covering validation utilities,
  health endpoint, auth middleware, login flows, SSRF protection, and path traversal
- Extract isValidStackName/isValidRemoteUrl/isPathWithinBase to utils/validation.ts
- Playwright E2E scaffolding: auth, stacks, nodes specs + shared login helper
- CI: run Vitest + ESLint on every PR
2026-03-21 21:59:44 -04:00
SaelixCode 243802eaa7 ci: fix npm cache dependency path for monorepo 2026-03-04 16:09:30 -05:00
SaelixCode 35b444d528 chore: add workflow_dispatch to docker publish to trigger UI registration 2026-03-04 16:06:06 -05:00
SaelixCode 3889d4914e chore: initialize git flow, ci pipeline, and changelog 2026-03-04 13:06:29 -05:00