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.
This commit is contained in:
@@ -7,6 +7,7 @@ const { createCaddyContext } = require('./caddy');
|
||||
const { createDnsContext } = require('./dns');
|
||||
const { createSessionContext } = require('./session');
|
||||
const NotificationManager = require('../managers/notification-manager');
|
||||
const tailscaleManager = require('../managers/tailscale-manager');
|
||||
|
||||
/**
|
||||
* Assemble the full application context
|
||||
@@ -95,13 +96,10 @@ function assembleContext({
|
||||
config: siteConfig
|
||||
});
|
||||
|
||||
// Tailscale context (inline for now - could be extracted)
|
||||
const tailscale = {
|
||||
// These will be populated by server.js for now
|
||||
// TODO: Extract tailscale module
|
||||
};
|
||||
|
||||
// Assemble flat context (temporary - routes still expect this)
|
||||
// Note: tailscale interface detection lives in src/utilities/network-detector.js
|
||||
// (DC-031). The empty `tailscale` stub previously wired here was dead code
|
||||
// — verified zero readers via grep across src/.
|
||||
const ctx = {
|
||||
// Namespaced contexts
|
||||
docker,
|
||||
@@ -109,7 +107,21 @@ function assembleContext({
|
||||
dns,
|
||||
session,
|
||||
notification,
|
||||
tailscale,
|
||||
// Tailscale manager — wraps `tailscale status --json` with 5min cache.
|
||||
// Replaces the long-standing null stub at src/app.js:189. See
|
||||
// src/managers/tailscale-manager.js for full API surface.
|
||||
tailscale: {
|
||||
getStatus: tailscaleManager.getStatus,
|
||||
getLocalIP: tailscaleManager.getLocalIP,
|
||||
getSummary: tailscaleManager.getSummary,
|
||||
getDevices: tailscaleManager.getDevices,
|
||||
isTailscaleIP: tailscaleManager.isTailscaleIP,
|
||||
invalidateCache: tailscaleManager.invalidateCache,
|
||||
getAccessToken: tailscaleManager.getAccessToken,
|
||||
startSyncTimer: tailscaleManager.startSyncTimer,
|
||||
stopSyncTimer: tailscaleManager.stopSyncTimer,
|
||||
syncAPI: tailscaleManager.syncAPI,
|
||||
},
|
||||
|
||||
// App and config
|
||||
app,
|
||||
|
||||
Reference in New Issue
Block a user