dsh-remote 0.3.0: one-line macOS slave install
- deploy/install-mac.sh: per-user Node 22 (checksum-verified), DSH + pnpm in ~/.local, dsh-remote from the repo archive, pairing, LaunchAgent dsh web - deploy/pair.mjs: pair without a running DSH (detects login-proxy redirects) - install-local.mjs: --accept secrets list, --restart-on-sync - sync.restartOnSync: slave restarts itself after synced plugins change (waits for idle sessions, at most once per 10 min)
This commit is contained in:
1 parent
d1e134196d
commit
08202cad67
6 files changed
+243
-2
No files matched your search
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env node
|
||||
// Pair this machine with a dsh-remote master WITHOUT a running local DSH: exchange a
|
||||
// one-time pairing code for a device key and write the slave link state.
|
||||
// node deploy/pair.mjs --master http://192.168.0.111:3080 --code ABCD-... --name my-mac [--state <file>]
|
||||
// The state file defaults to $DSH_HOME/profiles/web/dsh-remote/state.json. Restart DSH after.
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const opt = (k, d) => { const i = args.indexOf(`--${k}`); return i >= 0 ? args[i + 1] : d }
|
||||
const master = String(opt('master', '')).replace(/\/+$/, '')
|
||||
const code = opt('code', '')
|
||||
const name = opt('name', os.hostname())
|
||||
const home = process.env.DSH_HOME || path.join(os.homedir(), '.dsh')
|
||||
const stateFile = opt('state', path.join(home, 'profiles', opt('profile', 'web'), 'dsh-remote', 'state.json'))
|
||||
if (!/^https?:\/\//.test(master) || !code) {
|
||||
console.error('usage: pair.mjs --master <http(s)://…> --code <pairing code> [--name <device name>]')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
const slug = (s) => String(s || '').toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40) || 'device'
|
||||
let state = {}
|
||||
try { state = JSON.parse(fs.readFileSync(stateFile, 'utf8')) } catch { state = {} }
|
||||
state.self ||= { id: slug(name), name, createdAt: Date.now() }
|
||||
state.self.name = name
|
||||
|
||||
const r = await fetch(`${master}/dsh-remote/enroll`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ code: code.trim().toUpperCase(), id: state.self.id, name, platform: `${process.platform}-${process.arch}` }),
|
||||
redirect: 'manual',
|
||||
})
|
||||
let j = {}
|
||||
try { j = await r.json() } catch { j = {} }
|
||||
if (r.status >= 300 && r.status < 400) {
|
||||
console.error(`pairing failed: ${master} redirected (${r.headers.get('location')?.slice(0, 80)}…). A login proxy (e.g. Authentik) is in front of /dsh-remote/enroll; use the LAN URL or exempt that path.`)
|
||||
process.exit(1)
|
||||
}
|
||||
if (!r.ok) { console.error(`pairing failed: ${j.error || `HTTP ${r.status}`}`); process.exit(1) }
|
||||
|
||||
state.master = { url: master, deviceId: j.deviceId, key: j.key, master: j.master, pairedAt: Date.now() }
|
||||
state.self.id = j.deviceId
|
||||
fs.mkdirSync(path.dirname(stateFile), { recursive: true, mode: 0o700 })
|
||||
const tmp = `${stateFile}.${process.pid}.tmp`
|
||||
fs.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 0o600 })
|
||||
fs.renameSync(tmp, stateFile)
|
||||
console.log(`paired as "${j.deviceId}" with master ${j.master?.name || master}`)
|
||||
Reference in new issue
Block a user