Files
dsh-remote/deploy/install-local.mjs
T
lyuksovannyy 08202cad67 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)
2026-10-11 12:00:08 +00:00

64 lines
2.9 KiB
JavaScript

#!/usr/bin/env node
// Register a dsh-remote checkout as a `link:` bundle in the current user's DSH web profile
// (no pnpm/network needed) and optionally set its config.
//
// node deploy/install-local.mjs [--role slave|master|auto] [--name "My PC"] [--profile web]
//
// Effects (idempotent):
// <profile>/package.json dependencies["@local/dsh-remote"] = "link:<this dir>"
// dsh.profile.bundles += "@local/dsh-remote"
// <profile>/node_modules/@local/dsh-remote -> <this dir>
// <profile>/cordis.patch.yml appends a `- id: dsh-remote` config block if missing
// Restart `dsh web` afterwards (host code is loaded at boot).
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const args = process.argv.slice(2)
const opt = (k, d) => { const i = args.indexOf(`--${k}`); return i >= 0 ? args[i + 1] : d }
const pluginDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const home = process.env.DSH_HOME || path.join(os.homedir(), '.dsh')
const prof = path.join(home, 'profiles', opt('profile', 'web'))
const NAME = '@local/dsh-remote'
const pkgFile = path.join(prof, 'package.json')
const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf8'))
pkg.dependencies ||= {}
pkg.dependencies[NAME] = `link:${pluginDir}`
const bundles = (((pkg.dsh ||= {}).profile ||= {}).bundles ||= [])
if (!bundles.includes(NAME)) bundles.push(NAME)
fs.writeFileSync(pkgFile, JSON.stringify(pkg, null, 2) + '\n')
const linkDir = path.join(prof, 'node_modules', '@local')
fs.mkdirSync(linkDir, { recursive: true })
const link = path.join(linkDir, 'dsh-remote')
try { fs.rmSync(link, { recursive: true, force: true }) } catch { /* ignore */ }
fs.symlinkSync(pluginDir, link)
const patchFile = path.join(prof, 'cordis.patch.yml')
let patch = ''
try { patch = fs.readFileSync(patchFile, 'utf8') } catch { patch = '' }
if (!/^- id: dsh-remote\b/m.test(patch)) {
const role = opt('role', 'auto')
const name = opt('name', '')
const accept = String(opt('accept', '')).split(',').map((s) => s.trim()).filter((s) => /^[A-Z][A-Z0-9_]*$/.test(s))
const block = [
'# dsh-remote (installed by deploy/install-local.mjs)',
'- id: dsh-remote',
' config:',
` role: ${role}`,
` deviceName: ${JSON.stringify(name)}`,
' sync:',
' auto: true',
` restartOnSync: ${args.includes('--restart-on-sync') ? 'true' : 'false'}`,
' allow: []',
' deny: []',
accept.length ? ` secrets:\n${accept.map((s) => ` - ${s}`).join('\n')}` : ' secrets: []',
'',
].join('\n')
// An empty/comment-only patch file fails DSH boot, so only append real entries.
fs.writeFileSync(patchFile, (patch.trim() && !/^\s*\[\]\s*$/m.test(patch.trim()) ? patch.replace(/\n*$/, '\n') : '') + block)
}
console.log(`dsh-remote linked into ${prof}; restart dsh web to load it.`)