feat: persist APN relay secret across restarts, add dev logging and server identification to pair UI

- Electron desktop: auto-start PushRelay with secret persisted in electron-store
- CLI serve (Tauri): persist relay secret to Global.Path.state/relay-secret (mode 0600)
- Pair endpoint now returns relayURL, serverID, relaySecretHash for debugging
- Desktop settings-pair component shows server name, relay URL, and secret hash above QR
- Add console.debug logging for pairing fetch lifecycle
- Export PushRelay from node.ts entry point for Electron consumption
This commit is contained in:
Ryan Vogel
2026-04-17 22:31:02 +00:00
parent 38d4d03ba8
commit 754951bbbd
7 changed files with 154 additions and 28 deletions
+19 -9
View File
@@ -1,5 +1,7 @@
import { spawnSync } from "node:child_process"
import { createHash, randomBytes } from "node:crypto"
import { writeFileSync } from "node:fs"
import path from "node:path"
import os from "node:os"
import { Server } from "../../server/server"
import { cmd } from "./cmd"
@@ -10,10 +12,24 @@ import { Project } from "../../project"
import { Installation } from "../../installation"
import { PushRelay } from "../../server/push-relay"
import { Log } from "../../util"
import { Global } from "../../global"
import * as QRCode from "qrcode"
const log = Log.create({ service: "serve" })
async function getOrCreatePersistedRelaySecret(): Promise<string> {
const filePath = path.join(Global.Path.state, "relay-secret")
try {
const existing = (await Bun.file(filePath).text()).trim()
if (existing.length > 0) return existing
} catch {
// file doesn't exist yet
}
const secret = randomBytes(18).toString("base64url")
writeFileSync(filePath, secret, { mode: 0o600 })
return secret
}
type PairPayload = {
serverID?: string
relayURL: string
@@ -225,7 +241,7 @@ export const ServeCommand = cmd({
]
const input = (args["relay-secret"] ?? process.env.OPENCODE_EXPERIMENTAL_PUSH_RELAY_SECRET ?? "").trim()
const relaySecret = input || randomBytes(18).toString("base64url")
const relaySecret = input || (await getOrCreatePersistedRelaySecret())
const connectQR = Boolean(args["connect-qr"])
if (connectQR) {
@@ -236,10 +252,7 @@ export const ServeCommand = cmd({
}
if (!input) {
console.log("experimental push relay secret generated")
console.log(
"set --relay-secret or OPENCODE_EXPERIMENTAL_PUSH_RELAY_SECRET to keep push registrations stable across server restarts",
)
log.info("using persisted relay secret", { hash: secretHash(relaySecret) })
}
console.log("printing connect qr without starting the server")
@@ -259,10 +272,7 @@ export const ServeCommand = cmd({
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
if (!input) {
console.log("experimental push relay secret generated")
console.log(
"set --relay-secret or OPENCODE_EXPERIMENTAL_PUSH_RELAY_SECRET to keep push registrations stable across server restarts",
)
log.info("using persisted relay secret", { hash: secretHash(relaySecret) })
}
if (relayURL && relaySecret) {
const host = server.hostname ?? opts.hostname