English · 简体中文
Logging and updates — desktop
Why freeapi.log exists (#824)
The embedded server prints operational output to console — including the one-time password-reset code that POST /api/auth/forgot-password prints and nothing else ever shows. A server operator reads it from docker logs or the terminal. A Finder/Explorer-launched Electron app has no attached stdout, so that code was unreadable and the reset flow could never complete. Hence desktop/src/logger.ts tees console into <userData>/logs/freeapi.log (90aaa5b).
logger.ts — FileSink + rotation
LOG_NAME=freeapi.log,PREVIOUS_NAME=freeapi.log.1,MAX_LOG_BYTES=1 MB(desktop/src/logger.ts:18-21). Two files, 1 MB each — enough history for a reset or bad boot, small enough nobody's disk notices.createFileSink(dir, maxBytes=1M)— appends tofreeapi.log, rotates tofreeapi.log.1once it passesmaxBytes(rotate before the write that would cross the limit, never on empty file). Synchronousfs.writeSyncso a code printed a moment before the app quits is already on disk. Every filesystem call wrapped — logging never crashes the app.installFileLogger(dir=logsDir())— wrapsconsole.log/info/warn/error, prefixesISO timestamp [level], forwards to original console andsink.write. Idempotent (installedflag).process.on('exit', () => sink.close()). The only dashboard-logs feed on desktop is this tap (#993 coordination withserver/src/lib/server-logs.ts— the server's in-memory ring + DB persistedwarn/error).openLogsFolder()/openBackupsFolder()—fs.mkdirSync(..., { recursive: true })thenshell.openPath; create dir first so reveal works even before anything written. Tray → “Open Logs Folder” / “Open Backups Folder”.
// Ordering matters — install before any server console output
import { installFileLogger } from './logger.js';
installFileLogger(); // before createApp / server boot
Tray discoverability
The tray menu exposes both folders; openBackupsFolder resolves to <userData>/backups (where server/src/services/backups.ts dataDir() writes dumps). The dashboard's Backups panel shows paths relative to that directory.
Update delivery
Electron-builder publish
desktop/electron-builder.yml publish: { provider: github, owner: f3tools, repo: f3router } — artifacts (dmg, nsis, zip, AppImage, deb, tar.xz, rpm) upload on desktop-release.yml (*.rpm glob since d8fae97). latest.yml / latest-mac.yml generated per channel.
Build identity
scripts/bundle-server.mjs define: { 'process.env.F3ROUTER_COMMIT_SHA': JSON.stringify(GITHUB_SHA), 'process.env.F3ROUTER_INSTALL_METHOD': '"desktop"' } — 40-hex SHA stamped at build, mirrored into live process.env in server-host.ts so server/src/routes/update.ts resolves the running commit vs GET /api/update available. FREEAPI_VERSION from app.getVersion() (0.9.2).
In-dashboard checker
The dashboard polls GET /api/update (checker #635/#703) — shows banner when latest > running, with release notes. Desktop and Docker/Node share the same checker; desktop additionally offers auto-update via Squirrel.
macOS signing / notarization
Signed + notarized via electron-builder (#373/#1035): CSC_*, APPLE_ID secrets in desktop-release.yml. DMG re-stamp after ticket stapling via scripts/refresh-mac-update-metadata.mjs (the latest-mac.yml sha512 must match the stapled DMG, not the pre-staple one). Linux rpm beside deb since d8fae97.
Why unsigned builds cannot auto-update
Squirrel.Mac verifies the code signature before applying an update. An unsigned dmg (local npm run dist without certs) produces a valid app but no valid update feed — the auto-updater will fetch latest-mac.yml then refuse to apply. Manual download is required.
Related
- Desktop app — package manifest, build pipeline, runtime layout.
- Logs viewer — the polling API the desktop tap feeds.
- Observability — server store internals (ring + DB).