import { promisify } from "util" ; import { join } from "path" ; import { exec } from "child_process" ; import { mkdir as mkdir0 , readFile as readFile0 , writeFile as writeFile0 , } from "fs" ; import { getLogger } from "@cocalc/project/logger" ; import { data } from "@cocalc/backend/data" ; import basePath from "@cocalc/backend/base-path" ; import getSpec from "./spec" ; import getPort from "get-port" ; import { project_id } from "@cocalc/project/data" ; import { INFO } from "@cocalc/project/info-json" ; const mkdir = promisify ( mkdir0 ) ; const readFile = promisify ( readFile0 ) ; const writeFile = promisify ( writeFile0 ) ; const winston = getLogger ( "named-servers:control" ) ; export async function start ( name ) { winston . debug ( `start ${ name }` ) ; const s = await status ( name ) ; if ( s . status == "running" ) { winston . debug ( `${ name } is already running` ) ; return s . port ; } const port = await getPort ( { port : preferredPort ( name ) } ) ; let ip = INFO . location . host ?? "127.0.0.1" ; if ( ip == "localhost" ) { ip = "127.0.0.1" ; } const base = join ( basePath , `/${ project_id }/port/${ name }` ) ; const cmd = await getCommand ( name , ip , port , base ) ; winston . debug ( `will start ${ name } by running "${ cmd }"` ) ; const child = exec ( cmd , { cwd : process . env . HOME } ) ; const p = await paths ( name ) ; await writeFile ( p . port , `${ port }` ) ; await writeFile ( p . pid , `${ child . pid }` ) ; await writeFile ( p . command , `$!/bin/sh\n${ cmd }\n` ) ; return port ; } async function getCommand ( name , ip , port , base ) { const { stdout , stderr } = await paths ( name ) ; const spec = getSpec ( name ) ; const cmd = spec ( ip , port , base ) ; return `${ cmd } 1>${ stdout } 2>${ stderr }` ; } export async function status ( name ) { const { pid , port } = await paths ( name ) ; try { const pidValue = parseInt ( ( await readFile ( pid ) ) . toString ( ) ) ; process . kill ( pidValue , 0 ) ; const portValue = parseInt ( ( await readFile ( port ) ) . toString ( ) ) ; if ( ! Number . isInteger ( portValue ) ) { throw Error ( "invalid port" ) ; } return { status : "running" , port : portValue } ; } catch ( _err ) { return { status : "stopped" } ; } } async function paths ( name ) { const path = join ( data , "named_servers" , name ) ; try { await mkdir ( path , { recursive : true } ) ; } catch ( _err ) { } return { pid : join ( path , "pid" ) , stderr : join ( path , "stderr" ) , stdout : join ( path , "stdout" ) , port : join ( path , "port" ) , command : join ( path , "command.sh" ) , } ; } function preferredPort ( name ) { const p = process . env [ `COCALC_${ name . toUpperCase ( ) }_PORT` ] ; if ( p == null ) return p ; return parseInt ( p ) ; }