Files
dsh-remote/deploy/install-local.mjs
T
lyuksovannyy 8872e73e76 dsh-remote 0.1.0: master/slave remote control for DSH
- authenticated WebSocket link (dependency-free RFC6455 + stream mux), pairing
  codes, per-device keys (hashed), revoke/remove
- /r/<device>/ proxy of the full DSH UI (HTTP, Remote WebSocket, SSE) with
  per-device cookie minting; slave->slave relay through the master
- Devices sidebar panel (all devices' sessions, new session on device),
  Remote devices settings page, finished-session toasts
- plugin + config sync master->slave: registry bundles via pluginManager,
  link: plugins as tarballs, cordis.patch.yml into a managed bundle layer
  (!!js preserved), allow/deny, opt-in per-secret credential sync
- deploy helpers + e2e probes, unit tests
2026-10-11 08:23:19 +00:00

62 lines
2.6 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 block = [
'# dsh-remote (installed by deploy/install-local.mjs)',
'- id: dsh-remote',
' config:',
` role: ${role}`,
` deviceName: ${JSON.stringify(name)}`,
' sync:',
' auto: true',
' allow: []',
' deny: []',
' 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.`)