Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | 12x 12x 12x 12x 12x 12x 12x 12x 6x 6x 6x 6x 6x 6x 6x 6x 2x 2x 6x 6x 6x 6x 6x 2x 2x 2x 2x 6x 6x 5x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 12x 1x 2x 2x 2x 2x 1x 1x 1x 1x | import { spawn, ChildProcess } from 'child_process';
import fs from 'fs';
import * as process from 'node:process';
import os from 'os';
import path from 'path';
import { Command } from 'commander';
// Ensure process.kill is configurable for testing by defining own property
try {
Object.defineProperty(process, 'kill', {
configurable: true,
writable: true,
value: process.kill,
});
} catch {
// ignore
}
/** Options for launching a Teleport application bot (tbot) */
export interface TeleportBotAgentOptions {
/** Name of the Teleport application */
appName: string;
/** Join token for the bot (required on first start, optional afterwards) */
joinToken?: string;
/** Working directory where 'data' and 'dest' folders will be created */
workDir: string;
/** TTL of short-lived machine certificates (e.g. '8h') */
certificateTtl?: string;
/** Interval at which short-lived certificates are renewed (must be < TTL, e.g. '1h') */
renewalInterval?: string;
}
/** Result of launching the Teleport bot */
export interface TeleportBotAgentResult {
/** PID of the launched tbot process */
pid: number;
/** Application name used */
appName: string;
/** Proxy server used */
proxyServer: string;
/** Path to generated certificate file */
certPath: string;
/** Path to generated key file */
keyPath: string;
/** Path to tbot log file */
logPath: string;
}
/**
* Launches a Teleport application bot (tbot) in background (detached),
* creating 'data' and 'dest' subdirectories under the provided workDir,
* and streaming stdout/stderr to console.
*/
export async function teleportBotAgent({
appName,
joinToken,
workDir,
certificateTtl = '8h',
renewalInterval = '1h',
}: TeleportBotAgentOptions): Promise<TeleportBotAgentResult> {
// Ensure workDir exists and add .gitignore to ignore contents
fs.mkdirSync(workDir, { recursive: true });
const gitignorePath = path.join(workDir, '.gitignore');
if (!fs.existsSync(gitignorePath)) {
fs.writeFileSync(gitignorePath, '*\n!.gitignore\n');
}
// Prepare data and dest subdirectories
const dataDir = path.join(workDir, 'data');
fs.mkdirSync(dataDir, { recursive: true });
// If stale lock file exists in dataDir, warn and remove it
const lockFile = path.join(dataDir, 'lock');
if (fs.existsSync(lockFile)) {
console.warn(`Warning: removing stale lock file at ${lockFile}`);
try { fs.unlinkSync(lockFile); } catch { /* ignore */ }
}
const destDir = path.join(workDir, 'dest');
fs.mkdirSync(destDir, { recursive: true });
const pidFile = path.join(workDir, 'tbot.pid');
const proxyServer = 'teleport.ftprod.fr:443';
// If a previous tbot run exists, kill its process and remove PID
if (fs.existsSync(pidFile)) {
try {
const oldPid = parseInt(fs.readFileSync(pidFile, 'utf8'), 10);
if (!isNaN(oldPid)) process.kill(oldPid);
} catch {
// ignore errors
}
try { fs.unlinkSync(pidFile); } catch { /* ignore */ }
}
// Build tbot start arguments
const args: string[] = [
'start',
'application',
`--storage=file://${dataDir}`,
`--destination=file://${destDir}`,
`--app=${appName}`,
];
if (joinToken) {
args.push(`--token=${joinToken}`);
}
// join-method is always required
args.push('--join-method=token', `--proxy-server=${proxyServer}`, '--specific-tls-extensions');
// Optional certificate TTL and renewal interval
if (certificateTtl) {
args.push(`--certificate-ttl=${certificateTtl}`);
}
if (renewalInterval) {
args.push(`--renewal-interval=${renewalInterval}`);
}
console.log(`Launching tbot command: tbot ${args.join(' ')}`);
const logPath = path.join(workDir, 'tbot.log');
const outFd = fs.openSync(logPath, 'a');
const errFd = fs.openSync(logPath, 'a');
const bot: ChildProcess = spawn('tbot', args, {
detached: true,
stdio: ['ignore', outFd, errFd],
});
fs.closeSync(outFd);
fs.closeSync(errFd);
bot.unref();
try { fs.writeFileSync(pidFile, `${bot.pid}`); } catch (_err) {}
const certPath = path.join(destDir, 'tls.crt');
const keyPath = path.join(destDir, 'tls.key');
return { pid: bot.pid ?? -1, appName, proxyServer, certPath, keyPath, logPath };
}
/** CLI registration for teleportBotAgent */
export const cli = {
command: 'start-app',
description: 'Lance un Teleport application bot en arrière-plan',
builder: (cmd: Command) =>
cmd
.requiredOption('--app <name>', 'Nom de l’application Teleport')
.option('--token <token>', 'Join token pour le bot')
.option('--workDir <path>', "Répertoire de travail pour 'data' et 'dest'")
.option('--certificate-ttl <ttl>', 'TTL of short-lived machine certificates (e.g. 8h)', '8h')
.option('--renewal-interval <interval>', 'Interval at which certificates are renewed (must be < TTL, e.g. 1h)', '1h'),
handler: async (opts: {
app: string;
token?: string;
workDir?: string;
certificateTtl?: string;
renewalInterval?: string;
}) => {
try {
const workDir = opts.workDir ?? os.homedir() + '/.ftprod-ai/tbot';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { teleportBotAgent: run } = require('./teleportBotAgent');
const res = await run({
appName: opts.app,
joinToken: opts.token,
workDir,
certificateTtl: opts.certificateTtl,
renewalInterval: opts.renewalInterval,
});
console.log('Bot lancé:', JSON.stringify(res));
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.error('Erreur lancement tbot:', msg);
process.exit(1);
}
},
};
|