30 Commits

Author SHA1 Message Date
Adam Shiervani 91be2f75c0 feat(releases): map recovery artifact filename per SKU (#59)
The system recovery endpoint hard-coded `update.img`, which is the
eMMC/RKDevTool image. The SDMMC variant ships as `update_sd.img.zip`
(a balenaEtcher-flashable archive), so requesting recovery for the
`jetkvm-v2-sdmmc` SKU returned the wrong artifact (or 404'd once SKU
folders were enforced). Drive the filename from a small per-SKU map
and reject unmapped SKUs with 400 so a typo can't silently fall back
to the wrong image.
2026-04-28 23:31:10 +02:00
Adam Shiervani 68ce64adad feat(sync-releases): show artifact details, verify GPG, allow custom rollout (#58)
* feat(sync-releases): show artifact details, verify GPG, allow custom rollout

The previous production prompt was a single y/N with no artifact context, no
way to override the hardcoded 10% rollout, and no signature verification. An
operator confirming a release this way had to trust that the right files were
in S3 and that the .sig was issued by the OTA root key — neither was visible.

Changes:
* Print full artifact summary before the prompt: URL, sha256, compatibleSkus,
  and signature status for each artifact.
* Verify each .sig file with gpg --status-fd=1 and check the primary key
  fingerprint against OTA_ROOT_KEY_FPR (mirrored from rv1106-system's
  release_r2.sh). Reports valid / wrong-root / invalid / missing-pubkey /
  gpg-unavailable / absent, with a loud WARNING line for wrong-root and
  invalid signatures so the operator cannot miss them.
* Print the latest already-synced release of the same type before the prompt
  so the operator can confirm this is the next expected version.
* Add an interactive rollout-percentage prompt with 10% default, validated to
  0-100, replacing the hardcoded 10.
* Add an `a`/`abort` answer alongside y/N so operators can stop a multi-
  release sync mid-run when they spot something wrong, instead of having to
  N through every remaining version.
* Print the DB target and bucket as the first line of main() so a wrong
  .env.production selection is obvious before any prompts fire.
* Print a final run summary with counters: created / skipped-by-user /
  already-synced / no-artifacts, plus an "aborted at <type> <version>" line
  when the run was cut short.
* Add `npm run sync-releases:production` script and ignore .env.production.

Verification path runs only when NODE_ENV=production, so non-prod runs and
the test suite never spawn gpg or download artifacts. All 52 existing tests
still pass; tsc build is clean.

* refactor: hoist objectKeyFromArtifactUrl into helpers

Both src/releases.ts and scripts/sync-releases.ts had their own copy of the
same URL-to-S3-key conversion. Moved into src/helpers.ts and imported from
both call sites so a future change (e.g. CDN path prefix handling) only needs
to land once.

* fix(sync-releases): only treat ERRSIG rc=9 as missing pubkey

GnuPG's ERRSIG line carries an `rc` reason code. rc=9 is the only one that
means "we don't have the signer's key" — rc=4 (unsupported algorithm) and
other codes are real verification failures. The previous implementation
collapsed every ERRSIG into noPubkey, which would have falsely told the
operator the OTA root key was missing when the actual problem was e.g. an
unsupported pubkey algorithm.

Now parses the rc field and surfaces non-9 ERRSIG codes as `invalid` with a
human reason (rc=4 → "unsupported algorithm", others → "gpg error code N").
2026-04-28 11:23:30 +02:00
Adam Shiervani eebf332bc0 fix: keep default release when latest has no compatible SKU artifact
After the getDefaultRelease SKU-compat fix, the default path was
graceful but the rollout-upgrade path stayed strict: if a device was in
the rollout bucket and the latest release lacked a compatible artifact
for the requested SKU, dbReleaseToMetadata still threw and 404'd the
whole request — even though responseJson already held a valid default.

Short-circuit the upgrade when the latest release has no compatible
artifact and update the regression test to assert the default is kept
instead of asserting the throw.
2026-04-27 20:01:56 +02:00
Adam Shiervani 68b10004b5 test: cover rollout-bucket fallback for SKU-incompat upgrades
Adds a regression test for the rollout-aware path: when the in-progress
release has no compatible artifact for the requested SKU and the device
is outside the rollout window, the response must keep the default
release instead of throwing.

Also extends compare-releases.sh with a second device ID picked to land
in a low rollout bucket (bucket 9 vs the existing bucket 81), so the
script now exercises both eligible and ineligible rollout paths.
2026-04-27 19:44:52 +02:00
Adam Shiervani b9a1298fdb test: accept both SKU-incompat 404 messages in compare-releases
The release API used to emit "Version X predates SKU support..." for
every SKU-incompat 404. Upcoming changes route this through
getDefaultRelease and emit "No default <type> release available for
SKU..." instead. Both 404s mean the same thing to the device, so accept
either wording in the diff tolerator.
2026-04-27 19:21:48 +02:00
Adam Shiervani b24a057591 Add SKU-aware OTA release artifacts (#56)
* feat: add SKU-aware OTA release artifacts

Persist OTA artifact URL/hash data separately from rollout state so stable release responses can choose artifacts by compatible SKU while release rollout remains version/type based.

* fix: select compatible OTA releases by SKU

Ensure stable release selection only considers releases with artifacts compatible with the requested SKU, and tighten tests around the DB-backed OTA contract.

* fix: match production OTA release responses

Only expose stable signature URLs that actually exist and preserve production's version-first SKU error behavior.

* fix: restrict legacy OTA artifacts and make sync create-only

Pre-SKU artifacts (no skus/ folder) are jetkvm-v2 only. Marking them
compatible with jetkvm-v2-sdmmc would brick devices that received
firmware predating their hardware. Future SKUs must opt in via an
explicit skus/<sku>/ upload.

sync-releases now skips releases already in the DB instead of upserting
them. This prevents routine sync runs from rewriting Release.url/hash
or appending duplicate ReleaseArtifact rows if R2_CDN_URL ever changes.
Backfills and repairs are left to one-off scripts.

* refactor: drop forceUpdate query parameter from /releases

The flag is no longer sent by any client. Routine update checks now
always go through the rollout-aware default-and-latest path, which is
what forceUpdate effectively short-circuited to. Removes one query
parameter, one branch in the handler, and the corresponding axis from
the compare-releases sweep.

* fix: skip incompatible defaults and parallelize stable DB lookups

getDefaultRelease previously picked the newest 100%-rolled-out release
without checking SKU compatibility. If that release lacked a compatible
artifact, the request 404'd downstream even though older 100%-rolled-out
releases had valid binaries for the SKU. It now filters to releases that
actually ship a compatible artifact before selecting the latest, falling
back to a 404 only when no compatible default exists.

The four DB lookups in the stable rollout-aware path are independent; run
them concurrently so background-check latency drops from ~4 round trips
to ~1.
2026-04-27 19:18:54 +02:00
Adam Shiervani edf9b177c1 feat: serve optional GPG signature URLs for OTA releases (#54)
* feat: serve optional GPG signature URLs for OTA releases

Resolve .sig file existence from S3 at response time and include
appSigUrl/systemSigUrl in the release payload when present. Works
across all code paths (prerelease, forceUpdate, rollout) and supports
backfilling signatures for older releases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix releases sig URL cache typing

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:39:21 +01:00
Nikita Sliusarev 8596c72b29 feat: implement user account allowlist (#51) 2026-02-02 12:23:01 +01:00
Nikita Sliusarev dd963739ea feat: use user-provided env for compose container creation (#52) 2026-02-02 12:22:41 +01:00
Adam Shiervani 46d56cb978 Improve DX (#50)
* feat: add seed script for development with test users, devices, and release data

* feat: add development Docker Compose configuration and update README for local setup
2026-01-30 14:07:36 +01:00
Adam Shiervani 888fd63410 chore: update Node.js engine version in package.json to 22.x 2026-01-28 11:08:54 +01:00
Adam Shiervani 1958830b27 OTA for specific SKUs (#49) 2026-01-28 10:53:23 +01:00
Adam Shiervani 3e6acb65c3 Add integration tests for releases (#48) 2026-01-27 12:24:22 +01:00
Adam Shiervani 86978b2fcc feat: return device version when listing devices (#44) 2025-11-12 17:50:24 +01:00
Aveline a73f688ce8 feat: allow to specify maxSatisfying for /releases api (#42) 2025-10-29 17:24:47 +01:00
Techno Tim 3f13242d64 feat(ci): GitHub action for pull requests (#24) 2025-10-14 09:11:55 +02:00
Noah Halstead 17d01bb7eb Upgrade to Express v5, Dockerfile, and Health Checks (#29)
Co-authored-by: Aveline <g@xswan.net>
Co-authored-by: Adam Shiervani <adam.shiervani@gmail.com>
Co-authored-by: Marc Brooks <IDisposable@gmail.com>
2025-10-13 15:29:40 +02:00
Adam Shiervani 2d20ce00e2 chore: update dev script with watch and debug options (#21)
* chore: update dev script with watch and debug options

* chore: add debug option to development script in package.json

---------

Co-authored-by: Marc Brooks <IDisposable@gmail.com>
2025-09-24 17:28:26 +02:00
Adam Shiervani 354da9a420 feat: add force update option for manual update checks (#18) 2025-09-24 12:04:21 +02:00
Adam Shiervani ad118205ce chore: update .gitignore and enhance WebSocket connection handling (#35)
* Add .env.development to .gitignore
* Improve handling of existing WebSocket connections by waiting for closure before terminating
challenge-base
2025-04-09 17:10:45 +02:00
Adam Shiervani efce3ebe7b Fix/more logging (#34)
* A tiny bit clearer logging

* Enhance WebSocket close event logging to include closure code and reason
2025-04-09 17:00:44 +02:00
Adam Shiervani 710c4d73b4 A tiny bit clearer logging (#33) 2025-04-09 12:48:16 +02:00
Adam Shiervani dc5aed27e8 Add more logging (#32)
* Add more logging

* Refactor logging in WebRTC signaling to remove "WS" prefix for consistency
2025-04-09 11:33:27 +02:00
Adam Shiervani 27755874af feat: implement Tricke ICE WebRTC signaling with dedicated WebSocket (#31)
* feat: implement Tricke ICE WebRTC signaling with dedicated WebSocket handling

* Update src/webrtc.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-08 23:25:07 +02:00
Adam Shiervani d7aa9f99f5 refactor: improve WebSocket handling in CreateSession function (#30) 2025-04-03 19:29:11 +02:00
Adam Shiervani e9191d7972 Merge pull request #16 from jetkvm/fix/increase-timeout
Increase WebSocket response timeout from 5 to 15 seconds
2025-02-11 21:10:02 +01:00
Adam Shiervani 8e29026017 chore: increase WebSocket response timeout from 5 to 15 seconds 2025-02-11 20:08:46 +01:00
Siyuan Miao 722e70f599 feat: include device ip and ICE server list in device handshake payload 2025-02-11 16:08:43 +01:00
Siyuan Miao 954303afa5 feat: allow to override CORS origins using environment variable 2025-02-11 13:45:46 +01:00
thinkafterbefore ae4bc804c2 Release 202412292129 2024-12-29 21:29:59 +01:00