update deployer to fix some bugs I found
parent
d2f5ba3ca2
commit
3a1c468c06
|
@ -1,9 +1,10 @@
|
||||||
import { ssh, SSH } from './ssh.js'
|
import { ssh, SSH } from './ssh.js'
|
||||||
import { Expression, BuiltOutput } from './nix.js'
|
import { Expression } from './nix.js'
|
||||||
|
|
||||||
|
type Command = () => Promise<void>
|
||||||
class Cmd {
|
class Cmd {
|
||||||
registry: Record<string, () => Promise<void>> = {};
|
registry: Record<string, Command> = {};
|
||||||
register(obj: string, action: string, fn: () => Promise<void>) {
|
register(obj: string, action: string, fn: Command) {
|
||||||
this.registry[`${obj}.${action}`] = fn
|
this.registry[`${obj}.${action}`] = fn
|
||||||
}
|
}
|
||||||
registerAll(obj: string, intf: Object & { _commands?: string[] }) {
|
registerAll(obj: string, intf: Object & { _commands?: string[] }) {
|
||||||
|
@ -43,10 +44,11 @@ class Machine {
|
||||||
name: string;
|
name: string;
|
||||||
hasHome: boolean = false;
|
hasHome: boolean = false;
|
||||||
hostname?: string;
|
hostname?: string;
|
||||||
constructor(args: {name?: string, hasHome?: boolean, hostname?: string}) {
|
constructor({name = "", hasHome = false, hostname}: {name?: string, hasHome?: boolean, hostname?: string}) {
|
||||||
Object.assign(this, args)
|
this.name = name
|
||||||
// todo
|
this.hasHome = hasHome
|
||||||
this.name = ""
|
this.hostname = hostname
|
||||||
|
// name can be set later
|
||||||
}
|
}
|
||||||
isLocal() {
|
isLocal() {
|
||||||
return os.hostname() == this.name
|
return os.hostname() == this.name
|
||||||
|
@ -93,7 +95,7 @@ const machines = {
|
||||||
for (const [name, machine] of Object.entries(machines))
|
for (const [name, machine] of Object.entries(machines))
|
||||||
machine.name = name
|
machine.name = name
|
||||||
|
|
||||||
function cmd<R>(target: { _commands?: string[] }, propertyKey: string, descriptor: PropertyDescriptor): void {
|
function cmd(target: { _commands?: string[] }, propertyKey: string, _descriptor: PropertyDescriptor): void {
|
||||||
if (target._commands == undefined) target._commands = []
|
if (target._commands == undefined) target._commands = []
|
||||||
target._commands.push(propertyKey)
|
target._commands.push(propertyKey)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,30 @@
|
||||||
|
|
||||||
import 'zx/globals';
|
import 'zx/globals';
|
||||||
import { retry } from 'zx/experimental'
|
|
||||||
|
|
||||||
import { spawn, SpawnOptions, ChildProcess } from 'child_process'
|
import { spawn, ChildProcess } from 'child_process'
|
||||||
import { Socket } from 'node:net'
|
import { Socket } from 'node:net'
|
||||||
|
|
||||||
|
async function promiseWithTimeout<T>(timeoutMs: number, promise: Promise<T>, failureMessage?: string) {
|
||||||
|
let timeoutHandle: NodeJS.Timeout | null = null
|
||||||
|
const timeoutPromise = new Promise<never>((_resolve, reject) => {
|
||||||
|
timeoutHandle = setTimeout(() => reject(new Error(failureMessage)), timeoutMs)
|
||||||
|
})
|
||||||
|
const result = await Promise.race([promise, timeoutPromise])
|
||||||
|
if (timeoutHandle) clearTimeout(timeoutHandle)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const to_kill: ProcessPromise[] = []
|
||||||
|
|
||||||
|
// sad that I can't chain these
|
||||||
|
process.on("uncaughtException", err => {
|
||||||
|
console.error("uncaught exception", err)
|
||||||
|
console.log("killing", to_kill.length, "child processes")
|
||||||
|
for (const k of to_kill) if (k.child?.pid) process.kill(k.child.pid)
|
||||||
|
to_kill.length = 0
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
|
||||||
export function ssh(host: string): Promise<SSH>
|
export function ssh(host: string): Promise<SSH>
|
||||||
export function ssh<R>(host: string, cb: () => Promise<R>): Promise<R>
|
export function ssh<R>(host: string, cb: () => Promise<R>): Promise<R>
|
||||||
export async function ssh<R>(host: string, cb?: () => Promise<R>) {
|
export async function ssh<R>(host: string, cb?: () => Promise<R>) {
|
||||||
|
@ -14,10 +34,14 @@ export async function ssh<R>(host: string, cb?: () => Promise<R>) {
|
||||||
} catch (p) {
|
} catch (p) {
|
||||||
if (p instanceof ProcessOutput && p.exitCode == 255) {
|
if (p instanceof ProcessOutput && p.exitCode == 255) {
|
||||||
console.log("Spawning ssh master")
|
console.log("Spawning ssh master")
|
||||||
let x = $`ssh ${host} -M -N -o compression=no`
|
const x = $`ssh ${host} -M -N -o Compression=no -o PermitLocalCommand=yes -o LocalCommand="echo connected"`
|
||||||
await sleep(1000)
|
to_kill.push(x)
|
||||||
await retry(20, '1s', () => $`ssh ${host} -O check`.quiet())
|
await promiseWithTimeout(60000, new Promise<void>((resolve, _reject) => {
|
||||||
console.log("SSH connected")
|
x.stdout.on('data', (d: Buffer) => {
|
||||||
|
if (d.toString('utf8').trim() == "connected")
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}))
|
||||||
if (x.child) {
|
if (x.child) {
|
||||||
x.child.unref()
|
x.child.unref()
|
||||||
if (x.child.stderr instanceof Socket) x.child.stderr.unref()
|
if (x.child.stderr instanceof Socket) x.child.stderr.unref()
|
||||||
|
@ -42,6 +66,9 @@ export class SSH {
|
||||||
constructor(public host: string) { }
|
constructor(public host: string) { }
|
||||||
within<R>(cb: () => Promise<R>): Promise<R> {
|
within<R>(cb: () => Promise<R>): Promise<R> {
|
||||||
return within(async () => {
|
return within(async () => {
|
||||||
|
// todo: the default options.shell is set to local which.sync('bash')
|
||||||
|
// which doesn't neccesarily work on the remote
|
||||||
|
$.shell = "bash"
|
||||||
$.spawn = (command: string, options: any): any => {
|
$.spawn = (command: string, options: any): any => {
|
||||||
const stdio = ["pipe", options.stdio[1], options.stdio[2]]
|
const stdio = ["pipe", options.stdio[1], options.stdio[2]]
|
||||||
const proc: ChildProcess = spawn("ssh", [this.host, options.shell],
|
const proc: ChildProcess = spawn("ssh", [this.host, options.shell],
|
||||||
|
|
Loading…
Reference in New Issue