695794987b772692c6cafb17d34c7da08bf310d3
24
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5efacd11e8 |
fix(security): close rotateEncryptionKey crash window with in-process key rollback (DC-107) [glm-grade=A]
If the atomicWriteJSON of rotated credentials failed after rotateKey() had already persisted+cached the new key, the on-disk key could no longer decrypt the on-disk credentials.json (permanent loss on restart). Catch-path now restores the old key via new cryptoUtils.restoreKey() (canonical atomic-write, 0600, hex-validated) while still holding the proper-lockfile. Double-failure (rollback throws) is contained and the lock is still released. Hard-crash mid-rollback is covered by the existing .bak startup fallback. Judge: GLM-4.6 stand-in, round-1 grade A, 0 blocking, 1 LOW polish (folded). URN urn:ump:z2sz3x6abtcffpqyde2l2ssq34vy47t5h2gbmkr3wtmfxwkerinq Tests: 121 suites / 2785 green (6 consecutive runs pre-fold; suite re-run post-fold). |
||
|
|
eb2bab7a96 |
refactor(persistence): migrate credential-manager to canonical atomic-write util (DC-106) [glm-grade=B]
All three writeFileSync sites in the encrypted-credentials manager now delegate to src/utils/atomic-write.js atomicWriteJSON — rotateEncryptionKey save, _ensureFileExists bootstrap, and the _lockedUpdate commit under the proper-lockfile lock. A crash can no longer tear credentials.json mid-write: power loss through the old path could leave an empty/short file and silently drop every stored credential (DNS provider tokens etc). Lock-safety pre-study: proper-lockfile only stats its own sibling .lock dir, never the target file, so the rename swap cannot trip ECOMPROMISED. Test mock extended to the fd-level fs API (openSync/writeSync/ fsyncSync/closeSync/renameSync + fdMap/closedTmp state) so the canonical path is exercised end-to-end under the mock; write assertions moved from writeFileSync.mock.calls to destination-state reads. New DC-106 pins: wx+0600+fsync+rename discipline, plaintext-secret canary never on disk, fd-lifecycle order fsync->close->rename via invocationCallOrder, and atomic _ensureFileExists create at 0600. Eighth store migrated (DC-099..DC-105 preceded). Judge: GLM-5.3 cold read, round-1 B/ship (deleg_b3c038c2). URN urn:ump:vscequdet7wt5un7jhtl2nlg6cfkabsbazy5m2tjnyyguu5wstxa (readback verified: grade B, topic codex-judge-verdict). Sole finding is pre-existing and non-blocking: rotateEncryptionKey persists the new key before writing rotated creds (crash window) — queued as DC-107 follow-up. Full suite: 121 suites / 2783 tests green. |
||
|
|
80a82c4cae |
refactor(persistence): canonical atomic file writer + notifications.json crash-safety (DC-099) [glm-grade=B]
- src/utils/atomic-write.js: single shared tmp+fsync+rename writer (exclusive-create 0600, unique tmp names, cleanup-on-failure, best-effort parent-dir fsync after rename for swap durability) - notification-manager: both write paths (load-time canonicalization write-back + saveConfig) converted from plain writeFileSync — a crash mid-write can no longer truncate notifications.json - DC-097/098 test seams migrated to the atomic path; new suite pins syscall discipline (order, wx flags, tmp naming, error cleanup, dir-fsync swallow) - 121 suites / 2768 tests green - Judge: GLM-5.3 cold read grade B/ship (deleg_23f7abad); polish items folded: dir-fsync added, header copy-count corrected. Remaining: migrate invite/user/share-store private _atomicWriteJSON copies as they are touched (queued). URN: pending (recorded post-commit) |
||
|
|
3ccd00d1a1 |
fix(notifications): persist canonicalized config on load — legacy keys no longer stale on disk (DC-097) [glm-grade=B]
_loadConfig canonicalized legacy spellings in memory only (DC-092); the on-disk notifications.json kept email user/pass, camelCase event keys and string secure until the next explicit UI save — i.e. forever on installs that never open the settings page. - _persistCanonicalForm(): after the defaults merge, re-serialize and write back only when the bytes differ; idempotent on subsequent loads. - Best-effort: write failures (read-only mount, EACCES) warn and continue — the in-memory config is already correct; constructor never throws. - No secrets in new log lines; JSON.stringify(this.config) same as saveConfig. Judge notes (non-blocking, GLM-5.3 cold read): unknown top-level keys are now dropped from disk at boot (pre-existing merge-drop semantics, previously deferred to next UI save); write is non-atomic, matching saveConfig. Verdict: urn:ump:rchawiu427idev5mrettlxkyw2rcnwqpegiolqmzbc5ygc277u5q Tests: +5 (__tests__/notification-config-writeback-dc097.test.js) — canonical rewrite, idempotence, clean-file-untouched, EACCES no-throw, fresh-install no-write. Full suite 119 suites / 2740 tests green. |
||
|
|
97672f7e74 |
[glm-grade=A] fix(notifications): un-gate 7 dead emitters + repair legacy 4-arg send shape (DC-094)
Two silent-death defects in one class: 1. Seven emitters absent from DEFAULT events, so the send() gate (config.events[canonical] !== true) dropped them on every install: ssl-cert-expiry, dns-propagation, drift-detected, dependency-restart-complete/-failed, recipeRemoved, workflow. All now default ON; dependency-restart spellings fold onto one canonical toggle; recipeRemoved aliases to recipe-removed in both the manager and route alias maps. 2. Nine call sites used a legacy 4-arg send(event, title, message, type) against the 3-arg signature: the message string landed in the type slot (Discord embed color fell back to info-blue, history.type wrong) and providers received the TITLE as the body - deploy-failure notifications carried no error text at all. Fixed at source (9 sites) plus a type-guarded shim in send() for external legacy callers. Also: explicit data.title now flows to ntfy Title header, email subject, Discord embed title, and history; settings UI gains 9 event toggles (separate Backup Complete/Failed) with defaults-on semantics. Tests: +24 (new DC-094 suite: defaults, gate pass-through, alias folding send-time and load-time, stored-config inheritance, shim body/title/ color/subject/history, type-guard, 3-arg no-regression); 4 assertions in bundled-workflows-health-check updated from the old 4-arg mock contract to the canonical shape (same behavior asserted). Full suite 116 suites / 2706 tests green. Judge: GLM-5.3 cold read via delegate_task (deleg_8a7cedd0), grade A, zero blockers; 2 polish items (Backups toggle conflation, shim type-guard) folded into this commit. Verdict URN: urn:ump:uyipjjwdjqjy3alvceqxvlucrymd7hnsben5udpp2bqycoh3l2la |
||
|
|
7e4ee60dcf |
[glm-grade=B] fix(notifications): repair UI/API contract drift — SMTP auth, event gate, test button (DC-092)
Three user-facing notification features were silently dead from contract
drift between the settings UI and the backend:
1. SMTP auth never applied: the UI sent email.user/email.pass while the
manager read username/password. Route now normalizes aliases; legacy
config files canonicalize at load.
2. Event toggles were cosmetic: UI sent camelCase keys (containerDown),
the send() gate read kebab-case ('container-down'). EVENT_ALIASES now
folds every known spelling (manager gate, route store, legacy files);
UI sends and reads canonical keys.
3. Deploy/auto-restart notifications always dropped: deploy-success/
deploy-failed/auto-restart were missing from DEFAULT events, and
send('test') was itself gated -> the Test button was a no-op. Defaults
added; 'test' bypasses the gate.
Also: strict boolean contract (string 'false' for secure/enabled rejected
— previously coerced truthy, silently forcing TLS), SMTP port bounds,
non-destructive credential merge (blank password no longer clobbers the
stored one), GET /config returns port/secure/to/username + hasPassword
(password never returned), full form prefill + keep-hint placeholder.
Verified live pre-fix: send('test')/'deploymentSuccess'/'auto-restart'
all returned 'not enabled'. Post-fix: +20 tests (route + manager),
full suite 2641/2641 (112 suites).
Judge: GLM-5.3 cold read via delegate_task (deleg_ad765d63), grade B,
ship, zero blockers; judge independently ran the DC-092 suites (38/38).
Polish #1 (canonical event for provider titles) folded in. Remaining
emitters with the same gate-miss class (recipeRemoved, workflow,
ssl-cert-expiry, dns-propagation, drift-detected, dependency-restart-*)
noted for a follow-up tick.
|
||
|
|
5e60c27f2b | [grade=A] Harden server-managed license renewals | ||
|
|
089f5d2902 |
[glm-grade=A] fix(update-manager): compose-prefixed image names probe <project>/<service> not library/<project>-<service> (DC-082)
Pre-fix: dashcaddy-dashcaddy-api:latest was normalized to library/dashcaddy-dashcaddy-api before probing Docker Hub. The actual upstream namespace for a docker-compose prefixed image is <project>/<service> (slash, not hyphen). Docker Hub returned 401 on the wrong repo, and the error log emitted Docker Hub registry returned HTTP 401 after auth on every restart of every container. Fix: 1. _composeProjectToRepo splits dashcaddy-dashcaddy-api on the FIRST hyphen to recover dashcaddy/dashcaddy-api. Returns null for non-compose-prefixed names (official images like nginx/alpine, library/foo, namespace/foo already-slashed). 2. _isNotPublishedError detects the 401-after-auth pattern for compose-prefixed names only. Steady-state for locally-built images that aren't published. 3. getLatestImageDigest routes compose-prefixed names to the corrected namespace. Routes already-namespaced names directly. Falls back to library/ for the Official Image path. 4. Catch block: if the 401 is compose-prefixed-not-published, log info instead of error. Real auth failures on legitimate images still log as error. 17/17 tests pass in 1.27s. Full suite 2425/2425 (4 pre-existing billing/pdfkit failures unrelated to this change). GLM stand-in verdict URN: urn:ump:7rhk7keukv3zrx654gbckxaycnuwm4agduf37creqbsoauszopoa |
||
|
|
a4e4b24732 |
fix(update-manager): force IPv4 + per-request timeout + transient-only retry on registry digest probes (DC-078) [glm-grade=A]
The per-hour checkForUpdates() loop called Docker Hub / ghcr.io without family:4, without a hard request timeout, and without retry on transient network errors. On DNS2 (Technitium at 100.121.150.22 returns AAAA records even when IPv6 routing to public registries is intermittently broken), every container check surfaced AggregateError [ETIMEDOUT] in error.log with stack `at internalConnectMultiple (node:net:1114:18)`. The dual-stack DNS race consumed the default 30s connect timeout per unreachable IPv6 family before falling back to IPv4 — 30s+ per container per check cycle. Three reliability properties added via shared fetchWithReliability() helper: 1. family:4 — IPv4-only DNS lookup. Avoids the dual-stack race entirely. 2. Hard per-request timeout (10s) — caps total latency per attempt. 3. Retry on transient codes only (ETIMEDOUT/ENOTFOUND/ENETUNREACH/...) — HTTP 4xx/5xx are surfaced as real responses, not retried. The 401 → WWW-Authenticate → token → Bearer auth flow is now explicit in getDockerHubDigest (was previously a side effect of authenticateAndGetDigest, which has been removed — no remaining callers). Verified end-to-end against real Docker Hub: - linuxserver/plex:latest → real digest in 1349ms (was 30s+ AggregateError) - 5-container checkForUpdates() cycle: 3.6s total (was 150s+) - 86/86 update-manager tests pass; 2343/2343 full suite (4 pre-existing pdfkit module-resolution failures unrelated to this change) |
||
|
|
71d20ceef3 | [glm-grade=A] fix(auto-restart): await async servicesStateManager.read() so handleContainerDown actually fires (DC-060) | ||
|
|
e99413150e |
[glm-grade=B] fix: dead dashboard WS, corrupted error.log, auth-polling storm, re-auth freeze + CVE bumps
Adversarial audit 2026-08-16 (GLM-5.3 delegate, 2 rounds, 141 tool calls):
P0-1: Dashboard WebSocket (/api/v1/ws) dead on EVERY boot since DC-076.
server.js passed module exports (DependencyManager class, {AutoRestartManager}
namespace, SSLMonitor class) instead of createApp()'s live instances — first
.on() threw ERR_INVALID_ARG_TYPE, catch swallowed it. Fix: app.locals.ctx
exposed in src/app.js; server.js passes all 8 real EventEmitter instances.
P0-2: error.log corrupted since 2026-07-14. errorMiddleware called
logError(FILE, SIZE, path, err, meta) — 5 args into a 3-arg wrapper —
logging 'Error: 5242880' garbage every ~60s and DISCARDING the real error
object. Fix: correct 3-arg call + legacy-shape guard in logErrorWrapper +
~74 log.error sites swept to pass real error objects (AST-verified scope-
safe 71/71, 29/29 modules load clean).
P0-3: auth-polling storm (stranded grade=B commit never landed in prod):
401/403 behind TOTP gate hammered /api/v1/services/status + SSE reconnect
every 2-8s, with misleading direct-probe fallback marking services 'up'.
Fix landed + B-round MEDIUM follow-up: TOTP re-auth success now clears
_dcAuthLost, resumes SSE (new _sseResume clears the latch), and refreshes.
Also: eslintignore static-sites/ (33→0 errors); nodemailer 8→9.0.5 and
sharp 0.33→0.35.3 (3 high CVEs killed; jest green on new majors);
dockerode@5/uuid deferred (semver-major, Docker API surface).
Verification: 80/80 suites, 1837/1837 tests; ESLint 0 errors/743 warnings;
node --check all changed files; bundles rebuilt + SW cache bumped.
Judges: Codex quota-dead until Aug 19 (verified live) — GLM adversarial
delegate per operator directive 2026-08-07. Round 1: 98-call mechanical
verification (timed out pre-verdict). Round 2 (this grade): B, one MEDIUM
(re-auth freeze) — fixed in this commit as prescribed.
|
||
|
|
ae54927210 | Merge: 92 app templates + Authelia deployment on test server | ||
|
|
503de258b8 |
[grade=pending] QA sprint: commit 103 at-risk files from multi-agent sprint work
Committed by Hermes autonomous QA sprint 2026-08-13. These files were modified during the Aug 12 sprint but never committed. |
||
|
|
e8b9dd5b91 |
[grade=A] DC-060: replace 49 console.* calls in update-manager.js with structured logger
Replaced all 49 console.log/warn/error calls in src/managers/update-manager.js with log.info/log.warn/log.error from src/utils/logging. The unified logger provides structured JSON in prod, pretty output in dev, error.log rotation, log-level filtering, and test capture via stderr spy — none of which the raw console calls offered. Tagged every call as 'update' for consistent grep-ability across the dashboard. Mixed-content strings (containerName, schedule, imageName, error.message) were extracted into the meta payload object so they're queryable instead of inlined into the message field. 1539/1539 Jest tests pass. ESLint clean for the file (14 pre-existing warnings unchanged, zero new). Codex grade A. |
||
|
|
140ef8726b | [grade=B] refactor: remove stale duplicate license key generator | ||
|
|
273f6b8edb |
DC-052: license-tier enforcement (Free caps at 3, gates share on Pro)
First pricing enforcement. Per PRODUCT-SPEC-DECISIONS.md:
- Free = up to 3 users, no share features
- Pro = unlimited users + Tailscale-mediated share + public share links
- LIFETIME keys are creator-only (Sami runs --lifetime on his dev
machine; production API rejects LIFETIME codes at activate())
- Free has NO trial; Pro is a deliberate paid choice
Changes:
- src/managers/license-manager.js:
- isPro() shorthand (active + non-expired = true; LIFETIME counts)
- allowsLifetimeLicense() reads ALLOW_LIFETIME_LICENSE env var
- activate() rejects LIFETIME codes with a clear error unless the
env flag is set (so Stripe webhook can't accidentally issue one)
- src/security/user-store.js: countUsers() helper for the tier gate
- src/utilities/errors.js: PaymentRequiredError (HTTP 402, code DC-402)
- routes/auth/admin.js:
- _requireProIfUserLimitReached middleware on POST /admin/users
and POST /admin/invites (throws 402 at count >= 3 + Free)
- /invites/:token/accept also gated — burns the invite at cap so
it can't be replayed later
- routes/auth/index.js: bridge licenseManager + userStore onto
req.app.locals so the gate middleware can find them; pass
licenseManager into the provider registry for future use
Tests: 19 new (license-tier-enforcement.test.js) covering isPro(),
allowsLifetimeLicense(), LIFETIME accept/reject paths, countUsers(),
PaymentRequiredError shape, and end-to-end admin-route gating.
Full suite: 1317/1317 passing across 50 suites.
Defer to DC-053 (share routes), DC-054 (Stripe bridge), DC-055
(pricing page), DC-056 (ToS).
|
||
|
|
de3215f704 |
fix(update-manager): add ghcr.io registry support
Previously, /api/v1/updates/available silently skipped any image on a non-Docker-Hub registry (line 154 routing: 'ghcr.io/seerr-team/seerr' has 3 slash-delimited segments → 'Custom registry not yet supported'). Symptoms: 4 of 6 production containers (seerr, albyhub, phoenixd, velxio) all on ghcr.io. UpdateManager would log 'Custom registry not yet supported: ghcr.io/...' and return null. Updates invisible in the Updates modal, even when newer images existed. Fix: - Rewrite image parsing to detect the tag-vs-registry-host colon correctly (lastColon > lastSlash guard, handles ghcr.io:443/path). - Add getGhcrDigest() mirroring the DockerHub pattern, against ghcr.io's OCI distribution endpoint. Same bearer-token auth flow, the existing parseAuthHeader + authenticateAndGetDigest already handle the WWW-Authenticate format ghcr.io returns. - Multiple Accept headers for the response — Docker Hub used manifest.v2 only; GHCR serves manifest.list.v2 for multi-arch tags like ':latest', and the response is the multi-arch manifest itself with the platform-specific digest in the Child header chain. We use the digest from the 'docker-content-digest' response header, which the GHCR endpoint sets even for manifest lists. Verified: UpdateManager log now shows 'Found 6 updates available' on DNS2 (previously 0-2, all from non-Docker-Hub images). /api/v1/updates/available returns entries for seerr, albyhub, etc. Per-tile Update button (core.js:811) + Updates modal Update/Update All (features.js:1508 + L()) are already wired and now functional for all registries. |
||
|
|
f750d01ed0 |
DC-039: route all module file defaults through platformPaths.dataDir
Multiple modules derived file paths from __dirname, which is unstable in two
ways: (1) it moves whenever the file is reorganized under src/, and (2) it
points to the in-container source dir /app/src/<x> in production, which is
not bind-mounted, so writes would silently land in the image layer.
Affected modules (10 files): backup-manager, resource-monitor, update-manager,
docker-security, audit-logger, bundled-workflows, port-lock-manager, logging,
error-handler, license-keygen, plus crypto-utils and credential-manager which
already had multi-candidate resolvers but no centralised fallback.
Introduced platformPaths.dataDir (derived from SERVICES_FILE/CONFIG_FILE/
DNS_CREDENTIALS_FILE env vars when set, else path.dirname(servicesFile)) so
every module resolves the same canonical data directory. Each module now
fans the runtime files into the data dir while preserving per-file env-var
overrides for custom deployments.
Why a single resolver:
- one place to swap the default path scheme in v2.x without chasing
hardcoded __dirname joins
- a single source-of-truth for tests, backup tools, and the soon-to-be
added single-volume migration script
- prevents the class of DC-033 (self-updater 0.0.0) bugs where __dirname
drift in a subdirectory silently loses runtime state
Also fixed:
- audit-logger: AUDIT_LOG_FILE default was /app/src/security/audit-log.json
(writable in dev, image-layer in production). Now /app/data/audit-log.json
via platformPaths.dataDir, matching logging.js's same file. Same physical
path, no behavior change for callers that already set AUDIT_LOG_FILE.
- logging.js: LOG_DIR was __dirname (src/utils/) — error.log and
audit-log.json were being written into the source tree. Now
platformPaths.dataDir, matching every other persistent file.
- error-handler.js: ERROR_LOG_FILE hard-coded to __dirname/error.log
(src/utilities/error.log), redundant with logging.js's own default.
Now platformPaths.dataDir/error.log.
- host-registry / event-store / event-workers: simplified the
'platformPaths.dataDir || path.join(__dirname, ../../data)' pattern
to just platformPaths.dataDir (the legacy fallback is no longer
reachable — services.json lives at dataDir/services.json now).
- public-routes-drift.test.js: added 'routes/security.js' to the
direct-mount list so the /api/v1/security/events/ingest and
/api/v1/security/events/batch entries in PUBLIC_ROUTES are
recognized as mounted (was missing — fixed DC-044's drift-detection
test gap).
Tests: 1214/1214 pass (0 new failures, 1 new test for the corrected route
mount detection path). ESLint: 146 warnings + 4 errors — same baseline as
HEAD (no new warnings or errors introduced; one pre-existing require-await
on readline was removed as a drive-by in event-workers.js since the module
uses line-level fs reads, not readline). Container config files like
audit-log.json, container-stats.json, and workflow-history.json still
exist on the running container's image layer — Docker will pick up the
new defaults on the next recreate (the update path already moves
services.json+config.json+credentials.json via the data bind mount).
|
||
|
|
6fb4f9b169 |
DC-043: tailscale coordination API client + admin/settings routes
Companion to DC-042 (tailscale-manager). Adds the write-side of Tailscale
integration — REST client for api.tailscale.com that lets DashCaddy
manage its own tailnet (devices, pre-auth keys, users, ACL).
src/managers/tailscale-coord.js — new module:
* Ping, list/get/delete devices, create/list/delete pre-auth keys,
list users, get/update ACL
* 5min read cache, 60s device-list cache, 1hr ping cache
* Cache invalidation on writes
* Graceful {configured:false} when no token
* TailscaleCoordError class with code mapping (unauthorized, not_found,
rate_limited, server_error)
* fetchImpl injection point for tests; native https in production
* 45 unit tests covering all paths
src/context/index.js — new tailscaleCoord namespace:
* getClient() lazy-builds a fresh client each call (token re-read from
credentialManager so settings changes take effect without restart)
* loadMetadata/saveMetadata for tailscale-config.json
* setApiToken/hasApiToken wrappers around credentialManager
routes/tailscale-admin.js — new routes:
* GET /api/v1/tailscale/settings — config status, never the token
* PUT /api/v1/tailscale/settings — validate + store encrypted
* DELETE /api/v1/tailscale/settings — wipe token + metadata
* POST /api/v1/tailscale/settings/test — ping without saving
* GET /api/v1/tailscale/admin/devices — full device list
* DELETE /api/v1/tailscale/admin/devices/:id — revoke device
* GET /api/v1/tailscale/admin/users — tailnet users
* GET /api/v1/tailscale/admin/keys — pre-auth key metadata
* POST /api/v1/tailscale/admin/keys — create pre-auth key (returns secret ONCE)
* DELETE /api/v1/tailscale/admin/keys/:id — revoke pre-auth key
* 29 route integration tests with supertest
src/app.js — wired the new router into the /api/v1/tailscale mount.
BACKLOG.md — DC-042 marked done, DC-043 added with full design notes.
Note: deliberately no token auto-rotation — Tailscale API keys
don't auto-renew, and silently re-issuing admin credentials
would erode the audit-trail checkpoint that token expiry
provides.
Tests: 1167 -> 1212 (+45), all green. Lint clean.
|
||
|
|
d04238621f |
DC-042: implement real Tailscale manager — replace null stub
The previous getTailscaleStatus() in src/app.js was a hard-coded `return null` stub with a TODO saying it would be populated by context. The context had a tailscale.* namespace declared with null function stubs (routes/context.js:71), but nothing ever set them to real functions. routes/tailscale.js has been calling ctx.tailscale.getStatus() / getLocalIP() / isTailscaleIP() and getting undefined back, silently returning empty device lists. The tailscaleAuthMiddleware's allowedTailnet check (DC-121, device-not-in-tailnet 403) was dead code for the same reason. This commit replaces the stub with a real implementation: - New src/managers/tailscale-manager.js shells out to the host's `tailscale status --json` (cached 5 minutes), parses the result, and exposes getStatus / getLocalIP / getSummary / getDevices / isTailscaleIP / invalidateCache / getAccessToken (stub) / startSyncTimer / stopSyncTimer / syncAPI (stub). All failure modes (CLI missing, tailscaled down, malformed JSON, EACCES) are handled gracefully — return null with no cache poisoning. - src/context/index.js now wires the manager into ctx.tailscale.* so routes/tailscale.js and middleware.js's allowedTailnet gate get the real functions. - src/app.js:189 getTailscaleStatus() now delegates to the manager instead of returning null. - The duplicate isTailscaleIP() in src/app.js:179 (no malformed-input guards) is removed in favor of the canonical version in src/utilities/network-detector.js (DC-031) which the manager also uses. - start.sh now bind-mounts /usr/bin/tailscale (statically linked Go binary — works under Alpine libc) and /var/run/tailscale/ into the container, read-only. Lets the container invoke the CLI without needing its own tailscale install. - 41 new unit tests in __tests__/tailscale-manager.test.js cover: CLI success/missing/daemon-down/malformed-JSON paths, 5-min cache hit/miss, 1-hour installed-cache hit/miss, getLocalIP IPv4/IPv6/missing-choices, getSummary shape, getDevices shape with full + minimal peer fields, startSyncTimer/stopSyncTimer interval + idempotency, TAILSCALE_BIN env override. Total: 1138 tests pass (was 1097, +41 new), 0 new ESLint warnings. What this unlocks: - /api/v1/tailscale/status → real installed/connected/hostname/ip/ peerCount/onlinePeerCount summary instead of empty - /api/v1/tailscale/devices → real device list (was returning []) - /api/v1/tailscale/check-connection → works (uses real isTailscaleIP) - tailscaleAuthMiddleware allowedTailnet check (DC-121) is no longer dead code — a request from a Tailscale IP not in the allowed tailnet now actually gets 403 instead of being silently allowed. |
||
|
|
10f72af959 |
fix(update): proper require path fixes + license-keygen restore for v1.14.4 compatibility
v1.14.4 (commit
|
||
|
|
29f2c7999f |
DC-020: restore license-keygen.js + fix broken require paths (container crash-loop)
The refactor(desloppify) commit
|
||
|
|
283121edba |
Merge krystie-improvements into main
Resolves 24 conflicts between Hermes (DC-008/009/010 + response-helper
envelope standardization) and Krystie (DC-005 src/ refactor path fixes,
DC-006 TOTP integration, DC-007 new test suites, cloud backup
destinations).
Conflict resolutions:
- src/utils/logging.js: took ours (consumers depend on logError/
safeErrorMessage/createLogger exports)
- src/config/site.js: merged (her factored validateAndLogConfig +
applyConfigFields helpers)
- src/context/dns.js: took hers (admin/readonly role iteration for
write operations)
- src/utilities/backup-
manager.js: took hers (Dropbox/WebDAV/SFTP cloud feature)
- status/dist/*, status/
sw.js: took hers (minified bundles + newer SW cache)
Additional fix (post-merge regression):
- src/monitoring/health-checker.js: fixed DC-005 path miss —
'require(./platform-paths)' → 'require(../../platform-paths)'
Test status: 921/922 passing. One known failure in logging.test.js
(async file-handle timing) tracked as follow-up.
|
||
|
|
7bc2a207f3 |
DC-005: Fix all 138 broken test paths after src/ refactor
After the DC-005 module reorganization (41 files moved into src/ subdirs),
138 test suites failed because the refactor script's path-rewrite logic
missed three categories:
1. Files inside src/ doing 'require("./src/...")' — should be 'require("../...")'
2. Files in src/X/Y/ doing 'require("../../../src/...")' — should be 'require("../../...")'
3. Test files in __tests__/ with leftover 'require("../../../src/...")' paths
Root cause: the original refactor script ran before all files were moved,
so it computed relative paths against stale filesystem state.
Result:
- 30/30 test suites pass
- 879/879 tests pass (was: 18/30 suites, 614/687 tests)
Also fixed:
- routes/apps/restore.js: wrong responses import path
- routes/*/*.js: '../../src/utilities/X' → '../src/utilities/X' (depth 2 routes)
|