fix: PXE boot debugging — bisect root cause, syslog logging, serial console #3
@@ -1,34 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "lab-bastion",
|
"name": "lab",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "PXE bastion server for discover-first bare-metal provisioning",
|
"description": "PXE bastion server for discover-first bare-metal provisioning",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
|
||||||
"bastion": "./dist/cli/index.js"
|
|
||||||
},
|
|
||||||
"main": "./dist/server/main.js",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "pnpm -r run build",
|
||||||
"dev": "tsx src/cli/index.ts",
|
|
||||||
"start": "node dist/cli/index.js",
|
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:run": "vitest run",
|
"test:run": "vitest run",
|
||||||
"lint": "tsc --noEmit",
|
"typecheck": "tsc --build",
|
||||||
"clean": "rimraf dist"
|
"clean": "pnpm -r run clean && rimraf node_modules",
|
||||||
|
"lint": "eslint 'src/*/src/**/*.ts'"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0",
|
"node": ">=20.0.0",
|
||||||
"pnpm": ">=9.0.0"
|
"pnpm": ">=9.0.0"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.15.0",
|
"packageManager": "pnpm@9.15.0",
|
||||||
"dependencies": {
|
|
||||||
"@fastify/static": "^8.0.0",
|
|
||||||
"commander": "^13.0.0",
|
|
||||||
"execa": "^9.5.0",
|
|
||||||
"fastify": "^5.0.0",
|
|
||||||
"winston": "^3.17.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.10.0",
|
"@types/node": "^22.10.0",
|
||||||
"rimraf": "^6.0.0",
|
"rimraf": "^6.0.0",
|
||||||
|
|||||||
56
bastion/pnpm-lock.yaml
generated
56
bastion/pnpm-lock.yaml
generated
@@ -7,22 +7,6 @@ settings:
|
|||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
|
||||||
'@fastify/static':
|
|
||||||
specifier: ^8.0.0
|
|
||||||
version: 8.3.0
|
|
||||||
commander:
|
|
||||||
specifier: ^13.0.0
|
|
||||||
version: 13.1.0
|
|
||||||
execa:
|
|
||||||
specifier: ^9.5.0
|
|
||||||
version: 9.6.1
|
|
||||||
fastify:
|
|
||||||
specifier: ^5.0.0
|
|
||||||
version: 5.8.2
|
|
||||||
winston:
|
|
||||||
specifier: ^3.17.0
|
|
||||||
version: 3.19.0
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.10.0
|
specifier: ^22.10.0
|
||||||
@@ -40,6 +24,46 @@ importers:
|
|||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.2.4(@types/node@22.19.15)(tsx@4.21.0)
|
version: 3.2.4(@types/node@22.19.15)(tsx@4.21.0)
|
||||||
|
|
||||||
|
src/bastion:
|
||||||
|
dependencies:
|
||||||
|
'@fastify/static':
|
||||||
|
specifier: ^8.0.0
|
||||||
|
version: 8.3.0
|
||||||
|
'@lab/shared':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../shared
|
||||||
|
execa:
|
||||||
|
specifier: ^9.5.0
|
||||||
|
version: 9.6.1
|
||||||
|
fastify:
|
||||||
|
specifier: ^5.0.0
|
||||||
|
version: 5.8.2
|
||||||
|
winston:
|
||||||
|
specifier: ^3.17.0
|
||||||
|
version: 3.19.0
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.10.0
|
||||||
|
version: 22.19.15
|
||||||
|
|
||||||
|
src/cli:
|
||||||
|
dependencies:
|
||||||
|
'@lab/bastion':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../bastion
|
||||||
|
'@lab/shared':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../shared
|
||||||
|
commander:
|
||||||
|
specifier: ^13.0.0
|
||||||
|
version: 13.1.0
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.10.0
|
||||||
|
version: 22.19.15
|
||||||
|
|
||||||
|
src/shared: {}
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@colors/colors@1.6.0':
|
'@colors/colors@1.6.0':
|
||||||
|
|||||||
2
bastion/pnpm-workspace.yaml
Normal file
2
bastion/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
packages:
|
||||||
|
- "src/*"
|
||||||
31
bastion/src/bastion/package.json
Normal file
31
bastion/src/bastion/package.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "@lab/bastion",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "./dist/main.js",
|
||||||
|
"types": "./dist/main.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/main.js",
|
||||||
|
"types": "./dist/main.d.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc --build",
|
||||||
|
"clean": "rimraf dist",
|
||||||
|
"dev": "tsx src/main.ts",
|
||||||
|
"test": "vitest",
|
||||||
|
"test:run": "vitest run"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/static": "^8.0.0",
|
||||||
|
"@lab/shared": "workspace:*",
|
||||||
|
"execa": "^9.5.0",
|
||||||
|
"fastify": "^5.0.0",
|
||||||
|
"winston": "^3.17.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.10.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,31 +1,6 @@
|
|||||||
// Configuration from environment variables with sensible defaults.
|
// Configuration from environment variables with sensible defaults.
|
||||||
|
|
||||||
export interface BastionConfig {
|
import type { BastionConfig } from "@lab/shared";
|
||||||
fedoraVersion: string;
|
|
||||||
arch: string;
|
|
||||||
httpPort: number;
|
|
||||||
timezone: string;
|
|
||||||
locale: string;
|
|
||||||
bastionDir: string;
|
|
||||||
domain: string;
|
|
||||||
dhcpMode: "proxy" | "full";
|
|
||||||
dhcpRangeStart: string;
|
|
||||||
dhcpRangeEnd: string;
|
|
||||||
// Flags
|
|
||||||
skipDnsmasq?: boolean;
|
|
||||||
skipArtifacts?: boolean;
|
|
||||||
// Derived at runtime
|
|
||||||
iface: string;
|
|
||||||
serverIp: string;
|
|
||||||
network: string;
|
|
||||||
gateway: string;
|
|
||||||
sshKeys: string[];
|
|
||||||
adminUser: string;
|
|
||||||
fedoraMirror: string;
|
|
||||||
tftpDir: string;
|
|
||||||
httpDir: string;
|
|
||||||
stateFile: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadConfig(overrides: Partial<BastionConfig> = {}): BastionConfig {
|
export function loadConfig(overrides: Partial<BastionConfig> = {}): BastionConfig {
|
||||||
const fedoraVersion = overrides.fedoraVersion ?? process.env["FEDORA_VERSION"] ?? "43";
|
const fedoraVersion = overrides.fedoraVersion ?? process.env["FEDORA_VERSION"] ?? "43";
|
||||||
@@ -3,12 +3,13 @@
|
|||||||
|
|
||||||
import { mkdirSync, writeFileSync, existsSync, copyFileSync, symlinkSync } from "node:fs";
|
import { mkdirSync, writeFileSync, existsSync, copyFileSync, symlinkSync } from "node:fs";
|
||||||
import { execSync } from "node:child_process";
|
import { execSync } from "node:child_process";
|
||||||
import { loadConfig, type BastionConfig } from "./config.js";
|
import type { BastionConfig } from "@lab/shared";
|
||||||
|
import { loadConfig } from "./config.js";
|
||||||
import { populateNetworkConfig } from "./services/network.js";
|
import { populateNetworkConfig } from "./services/network.js";
|
||||||
import { createApp } from "./server.js";
|
import { createApp } from "./server.js";
|
||||||
import { startDnsmasq, stopDnsmasq, generateDnsmasqConf } from "./services/dnsmasq.js";
|
import { startDnsmasq, stopDnsmasq, generateDnsmasqConf } from "./services/dnsmasq.js";
|
||||||
import { generateDiscoverKickstart } from "./services/kickstart-generator.js";
|
import { generateDiscoverKickstart } from "./services/kickstart-generator.js";
|
||||||
import { renderBootIpxe } from "../templates/boot.ipxe.js";
|
import { renderBootIpxe } from "./templates/boot.ipxe.js";
|
||||||
import { logger } from "./services/logger.js";
|
import { logger } from "./services/logger.js";
|
||||||
|
|
||||||
function copyIfMissing(src: string, dest: string, label: string): void {
|
function copyIfMissing(src: string, dest: string, label: string): void {
|
||||||
@@ -179,8 +180,8 @@ function printBanner(config: BastionConfig): void {
|
|||||||
console.log(" \x1b[33mIt will be inventoried and rebooted automatically.\x1b[0m");
|
console.log(" \x1b[33mIt will be inventoried and rebooted automatically.\x1b[0m");
|
||||||
console.log("");
|
console.log("");
|
||||||
console.log(" Commands (from another terminal):");
|
console.log(" Commands (from another terminal):");
|
||||||
console.log(" \x1b[1mbastion list\x1b[0m -- show machines");
|
console.log(" \x1b[1mlab list\x1b[0m -- show machines");
|
||||||
console.log(" \x1b[1mbastion install <mac> <hostname>\x1b[0m -- queue install");
|
console.log(" \x1b[1mlab install <mac> <hostname>\x1b[0m -- queue install");
|
||||||
console.log("");
|
console.log("");
|
||||||
console.log(" Press \x1b[1mCtrl-C\x1b[0m to stop.");
|
console.log(" Press \x1b[1mCtrl-C\x1b[0m to stop.");
|
||||||
console.log("");
|
console.log("");
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
// /api/discover - receive hardware discovery reports from PXE-booted machines
|
// /api/discover - receive hardware discovery reports from PXE-booted machines
|
||||||
|
|
||||||
import type { FastifyInstance } from "fastify";
|
import type { FastifyInstance } from "fastify";
|
||||||
import type { StateManager, HardwareInfo, InstalledInfo } from "../services/state.js";
|
import type { HardwareInfo, InstalledInfo } from "@lab/shared";
|
||||||
|
import type { StateManager } from "../services/state.js";
|
||||||
import { logger } from "../services/logger.js";
|
import { logger } from "../services/logger.js";
|
||||||
|
|
||||||
export function registerApiRoutes(
|
export function registerApiRoutes(
|
||||||
@@ -5,13 +5,13 @@
|
|||||||
// - unknown -> discovery mode (collect hardware, POST to bastion)
|
// - unknown -> discovery mode (collect hardware, POST to bastion)
|
||||||
|
|
||||||
import type { FastifyInstance } from "fastify";
|
import type { FastifyInstance } from "fastify";
|
||||||
import type { BastionConfig } from "../config.js";
|
import type { BastionConfig } from "@lab/shared";
|
||||||
import type { StateManager } from "../services/state.js";
|
import type { StateManager } from "../services/state.js";
|
||||||
import {
|
import {
|
||||||
renderDiscoverIpxe,
|
renderDiscoverIpxe,
|
||||||
renderInstallIpxe,
|
renderInstallIpxe,
|
||||||
renderLocalBootIpxe,
|
renderLocalBootIpxe,
|
||||||
} from "../../templates/boot.ipxe.js";
|
} from "../templates/boot.ipxe.js";
|
||||||
import { logger } from "../services/logger.js";
|
import { logger } from "../services/logger.js";
|
||||||
|
|
||||||
export function registerDispatchRoutes(
|
export function registerDispatchRoutes(
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// Serves per-MAC install kickstart and the static discovery kickstart.
|
// Serves per-MAC install kickstart and the static discovery kickstart.
|
||||||
|
|
||||||
import type { FastifyInstance } from "fastify";
|
import type { FastifyInstance } from "fastify";
|
||||||
import type { BastionConfig } from "../config.js";
|
import type { BastionConfig } from "@lab/shared";
|
||||||
import type { StateManager } from "../services/state.js";
|
import type { StateManager } from "../services/state.js";
|
||||||
import { generateInstallKickstart, generateDiscoverKickstart } from "../services/kickstart-generator.js";
|
import { generateInstallKickstart, generateDiscoverKickstart } from "../services/kickstart-generator.js";
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
import Fastify from "fastify";
|
import Fastify from "fastify";
|
||||||
import fastifyStatic from "@fastify/static";
|
import fastifyStatic from "@fastify/static";
|
||||||
import { mkdirSync, existsSync } from "node:fs";
|
import { mkdirSync, existsSync } from "node:fs";
|
||||||
import type { BastionConfig } from "./config.js";
|
import type { BastionConfig } from "@lab/shared";
|
||||||
import { StateManager } from "./services/state.js";
|
import { StateManager } from "./services/state.js";
|
||||||
import { logger } from "./services/logger.js";
|
import { logger } from "./services/logger.js";
|
||||||
import { registerDispatchRoutes } from "./routes/dispatch.js";
|
import { registerDispatchRoutes } from "./routes/dispatch.js";
|
||||||
@@ -4,8 +4,8 @@ import { writeFileSync, mkdirSync } from "node:fs";
|
|||||||
import { dirname } from "node:path";
|
import { dirname } from "node:path";
|
||||||
import type { ResultPromise } from "execa";
|
import type { ResultPromise } from "execa";
|
||||||
import { execa } from "execa";
|
import { execa } from "execa";
|
||||||
import type { BastionConfig } from "../config.js";
|
import type { BastionConfig } from "@lab/shared";
|
||||||
import { renderDnsmasqConf } from "../../templates/dnsmasq.conf.js";
|
import { renderDnsmasqConf } from "../templates/dnsmasq.conf.js";
|
||||||
import { logger } from "./logger.js";
|
import { logger } from "./logger.js";
|
||||||
|
|
||||||
type DnsmasqProcess = ResultPromise<{ stdout: "pipe"; stderr: "pipe" }>;
|
type DnsmasqProcess = ResultPromise<{ stdout: "pipe"; stderr: "pipe" }>;
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
// Generate kickstart content for discovery and install modes.
|
// Generate kickstart content for discovery and install modes.
|
||||||
// Uses template literal functions -- no external template engine.
|
// Uses template literal functions -- no external template engine.
|
||||||
|
|
||||||
import type { BastionConfig } from "../config.js";
|
import type { BastionConfig } from "@lab/shared";
|
||||||
import { renderDiscoverKickstart } from "../../templates/discover.ks.js";
|
import { renderDiscoverKickstart } from "../templates/discover.ks.js";
|
||||||
import { renderInstallKickstart, type InstallKickstartParams } from "../../templates/install.ks.js";
|
import { renderInstallKickstart, type InstallKickstartParams } from "../templates/install.ks.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a discovery kickstart that collects hardware info and POSTs to bastion.
|
* Generate a discovery kickstart that collects hardware info and POSTs to bastion.
|
||||||
@@ -4,7 +4,7 @@ import { execSync } from "node:child_process";
|
|||||||
import { readFileSync, existsSync, mkdirSync } from "node:fs";
|
import { readFileSync, existsSync, mkdirSync } from "node:fs";
|
||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import type { BastionConfig } from "../config.js";
|
import type { BastionConfig } from "@lab/shared";
|
||||||
import { logger } from "./logger.js";
|
import { logger } from "./logger.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2,45 +2,10 @@
|
|||||||
|
|
||||||
import { readFileSync, writeFileSync, renameSync, mkdirSync } from "node:fs";
|
import { readFileSync, writeFileSync, renameSync, mkdirSync } from "node:fs";
|
||||||
import { dirname } from "node:path";
|
import { dirname } from "node:path";
|
||||||
|
import type { BastionState } from "@lab/shared";
|
||||||
|
|
||||||
export interface HardwareInfo {
|
// Re-export types for consumers that import from this module
|
||||||
mac: string;
|
export type { HardwareInfo, InstallConfig, InstalledInfo, BastionState } from "@lab/shared";
|
||||||
product: string;
|
|
||||||
board: string;
|
|
||||||
serial: string;
|
|
||||||
manufacturer: string;
|
|
||||||
cpu_model: string;
|
|
||||||
cpu_cores: number;
|
|
||||||
memory_gb: number;
|
|
||||||
arch: string;
|
|
||||||
disks: Array<{ name: string; size_gb: number; model: string }>;
|
|
||||||
nics: Array<{ name: string; mac: string; state: string }>;
|
|
||||||
first_seen: string;
|
|
||||||
last_seen: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InstallConfig {
|
|
||||||
hostname: string;
|
|
||||||
disk: string;
|
|
||||||
role: "worker" | "infra";
|
|
||||||
queued_at: string;
|
|
||||||
progress?: string;
|
|
||||||
progress_at?: string;
|
|
||||||
progress_detail?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InstalledInfo {
|
|
||||||
hostname: string;
|
|
||||||
role: string;
|
|
||||||
ip: string;
|
|
||||||
installed_at: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BastionState {
|
|
||||||
discovered: Record<string, HardwareInfo>;
|
|
||||||
install_queue: Record<string, InstallConfig>;
|
|
||||||
installed: Record<string, InstalledInfo>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EMPTY_STATE: BastionState = {
|
const EMPTY_STATE: BastionState = {
|
||||||
discovered: {},
|
discovered: {},
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// Supports proxy DHCP mode (alongside existing DHCP) and full DHCP mode.
|
// Supports proxy DHCP mode (alongside existing DHCP) and full DHCP mode.
|
||||||
// Handles UEFI HTTP Boot, iPXE chainloading, and PXE service directives.
|
// Handles UEFI HTTP Boot, iPXE chainloading, and PXE service directives.
|
||||||
|
|
||||||
import type { BastionConfig } from "../server/config.js";
|
import type { BastionConfig } from "@lab/shared";
|
||||||
|
|
||||||
export function renderDnsmasqConf(config: BastionConfig): string {
|
export function renderDnsmasqConf(config: BastionConfig): string {
|
||||||
const {
|
const {
|
||||||
12
bastion/src/bastion/tsconfig.json
Normal file
12
bastion/src/bastion/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist",
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"references": [
|
||||||
|
{ "path": "../shared" }
|
||||||
|
]
|
||||||
|
}
|
||||||
8
bastion/src/bastion/vitest.config.ts
Normal file
8
bastion/src/bastion/vitest.config.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { defineProject } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineProject({
|
||||||
|
test: {
|
||||||
|
name: 'bastion',
|
||||||
|
include: ['tests/**/*.test.ts'],
|
||||||
|
},
|
||||||
|
});
|
||||||
26
bastion/src/cli/package.json
Normal file
26
bastion/src/cli/package.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "@lab/cli",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"lab": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc --build",
|
||||||
|
"clean": "rimraf dist",
|
||||||
|
"dev": "tsx src/index.ts",
|
||||||
|
"test": "vitest",
|
||||||
|
"test:run": "vitest run"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@lab/bastion": "workspace:*",
|
||||||
|
"@lab/shared": "workspace:*",
|
||||||
|
"commander": "^13.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.10.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// Merged view of all known machines with hardware + install info.
|
// Merged view of all known machines with hardware + install info.
|
||||||
|
|
||||||
import type { Command } from "commander";
|
import type { Command } from "commander";
|
||||||
import type { BastionState } from "../../server/services/state.js";
|
import type { BastionState } from "@lab/shared";
|
||||||
|
|
||||||
const BOLD = "\x1b[1m";
|
const BOLD = "\x1b[1m";
|
||||||
const GREEN = "\x1b[0;32m";
|
const GREEN = "\x1b[0;32m";
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import { execSync } from "node:child_process";
|
import { execSync } from "node:child_process";
|
||||||
import type { Command } from "commander";
|
import type { Command } from "commander";
|
||||||
import type { BastionState } from "../../server/services/state.js";
|
import type { BastionState } from "@lab/shared";
|
||||||
|
|
||||||
export function registerReprovisionCommand(program: Command): void {
|
export function registerReprovisionCommand(program: Command): void {
|
||||||
program
|
program
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// Start the bastion server (HTTP + dnsmasq).
|
// Start the bastion server (HTTP + dnsmasq).
|
||||||
|
|
||||||
import type { Command } from "commander";
|
import type { Command } from "commander";
|
||||||
import { startBastion } from "../../server/main.js";
|
import { startBastion } from "@lab/bastion";
|
||||||
|
|
||||||
export function registerServeCommand(program: Command): void {
|
export function registerServeCommand(program: Command): void {
|
||||||
program
|
program
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
// Commands: serve, install, list, reprovision
|
// Commands: serve, install, list, reprovision
|
||||||
|
|
||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
|
import { APP_VERSION } from "@lab/shared";
|
||||||
import { registerServeCommand } from "./commands/serve.js";
|
import { registerServeCommand } from "./commands/serve.js";
|
||||||
import { registerInstallCommand } from "./commands/install.js";
|
import { registerInstallCommand } from "./commands/install.js";
|
||||||
import { registerListCommand } from "./commands/list.js";
|
import { registerListCommand } from "./commands/list.js";
|
||||||
@@ -11,9 +12,9 @@ import { registerReprovisionCommand } from "./commands/reprovision.js";
|
|||||||
const program = new Command();
|
const program = new Command();
|
||||||
|
|
||||||
program
|
program
|
||||||
.name("bastion")
|
.name("lab")
|
||||||
.description("Lab PXE Bastion -- discover-first bare-metal provisioning")
|
.description("Lab PXE Bastion -- discover-first bare-metal provisioning")
|
||||||
.version("0.1.0");
|
.version(APP_VERSION);
|
||||||
|
|
||||||
registerServeCommand(program);
|
registerServeCommand(program);
|
||||||
registerInstallCommand(program);
|
registerInstallCommand(program);
|
||||||
13
bastion/src/cli/tsconfig.json
Normal file
13
bastion/src/cli/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist",
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"references": [
|
||||||
|
{ "path": "../shared" },
|
||||||
|
{ "path": "../bastion" }
|
||||||
|
]
|
||||||
|
}
|
||||||
8
bastion/src/cli/vitest.config.ts
Normal file
8
bastion/src/cli/vitest.config.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { defineProject } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineProject({
|
||||||
|
test: {
|
||||||
|
name: 'cli',
|
||||||
|
include: ['tests/**/*.test.ts'],
|
||||||
|
},
|
||||||
|
});
|
||||||
20
bastion/src/shared/package.json
Normal file
20
bastion/src/shared/package.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "@lab/shared",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc --build",
|
||||||
|
"clean": "rimraf dist",
|
||||||
|
"test": "vitest",
|
||||||
|
"test:run": "vitest run"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
bastion/src/shared/src/constants/index.ts
Normal file
4
bastion/src/shared/src/constants/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Application-wide constants.
|
||||||
|
|
||||||
|
export const APP_NAME = "lab";
|
||||||
|
export const APP_VERSION = "0.1.0";
|
||||||
9
bastion/src/shared/src/index.ts
Normal file
9
bastion/src/shared/src/index.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export type {
|
||||||
|
HardwareInfo,
|
||||||
|
InstallConfig,
|
||||||
|
InstalledInfo,
|
||||||
|
BastionState,
|
||||||
|
BastionConfig,
|
||||||
|
} from "./types/index.js";
|
||||||
|
|
||||||
|
export { APP_NAME, APP_VERSION } from "./constants/index.js";
|
||||||
28
bastion/src/shared/src/types/config.ts
Normal file
28
bastion/src/shared/src/types/config.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// Configuration types for the bastion server.
|
||||||
|
|
||||||
|
export interface BastionConfig {
|
||||||
|
fedoraVersion: string;
|
||||||
|
arch: string;
|
||||||
|
httpPort: number;
|
||||||
|
timezone: string;
|
||||||
|
locale: string;
|
||||||
|
bastionDir: string;
|
||||||
|
domain: string;
|
||||||
|
dhcpMode: "proxy" | "full";
|
||||||
|
dhcpRangeStart: string;
|
||||||
|
dhcpRangeEnd: string;
|
||||||
|
// Flags
|
||||||
|
skipDnsmasq?: boolean | undefined;
|
||||||
|
skipArtifacts?: boolean | undefined;
|
||||||
|
// Derived at runtime
|
||||||
|
iface: string;
|
||||||
|
serverIp: string;
|
||||||
|
network: string;
|
||||||
|
gateway: string;
|
||||||
|
sshKeys: string[];
|
||||||
|
adminUser: string;
|
||||||
|
fedoraMirror: string;
|
||||||
|
tftpDir: string;
|
||||||
|
httpDir: string;
|
||||||
|
stateFile: string;
|
||||||
|
}
|
||||||
8
bastion/src/shared/src/types/index.ts
Normal file
8
bastion/src/shared/src/types/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export type {
|
||||||
|
HardwareInfo,
|
||||||
|
InstallConfig,
|
||||||
|
InstalledInfo,
|
||||||
|
BastionState,
|
||||||
|
} from "./state.js";
|
||||||
|
|
||||||
|
export type { BastionConfig } from "./config.js";
|
||||||
40
bastion/src/shared/src/types/state.ts
Normal file
40
bastion/src/shared/src/types/state.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// State types for discovered machines, install queue, and installed machines.
|
||||||
|
|
||||||
|
export interface HardwareInfo {
|
||||||
|
mac: string;
|
||||||
|
product: string;
|
||||||
|
board: string;
|
||||||
|
serial: string;
|
||||||
|
manufacturer: string;
|
||||||
|
cpu_model: string;
|
||||||
|
cpu_cores: number;
|
||||||
|
memory_gb: number;
|
||||||
|
arch: string;
|
||||||
|
disks: Array<{ name: string; size_gb: number; model: string }>;
|
||||||
|
nics: Array<{ name: string; mac: string; state: string }>;
|
||||||
|
first_seen: string;
|
||||||
|
last_seen: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InstallConfig {
|
||||||
|
hostname: string;
|
||||||
|
disk: string;
|
||||||
|
role: "worker" | "infra";
|
||||||
|
queued_at: string;
|
||||||
|
progress?: string;
|
||||||
|
progress_at?: string;
|
||||||
|
progress_detail?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InstalledInfo {
|
||||||
|
hostname: string;
|
||||||
|
role: string;
|
||||||
|
ip: string;
|
||||||
|
installed_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BastionState {
|
||||||
|
discovered: Record<string, HardwareInfo>;
|
||||||
|
install_queue: Record<string, InstallConfig>;
|
||||||
|
installed: Record<string, InstalledInfo>;
|
||||||
|
}
|
||||||
8
bastion/src/shared/tsconfig.json
Normal file
8
bastion/src/shared/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
8
bastion/src/shared/vitest.config.ts
Normal file
8
bastion/src/shared/vitest.config.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { defineProject } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineProject({
|
||||||
|
test: {
|
||||||
|
name: 'shared',
|
||||||
|
include: ['tests/**/*.test.ts'],
|
||||||
|
},
|
||||||
|
});
|
||||||
25
bastion/tsconfig.base.json
Normal file
25
bastion/tsconfig.base.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"composite": true,
|
||||||
|
"incremental": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"exactOptionalPropertyTypes": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +1,8 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"files": [],
|
||||||
"target": "ES2022",
|
"references": [
|
||||||
"module": "NodeNext",
|
{ "path": "src/shared" },
|
||||||
"moduleResolution": "NodeNext",
|
{ "path": "src/bastion" },
|
||||||
"lib": ["ES2022"],
|
{ "path": "src/cli" }
|
||||||
"strict": true,
|
]
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"declaration": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"incremental": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noUncheckedIndexedAccess": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"rootDir": "src",
|
|
||||||
"outDir": "dist",
|
|
||||||
"types": ["node"]
|
|
||||||
},
|
|
||||||
"include": ["src/**/*.ts"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
}
|
||||||
|
|||||||
15
bastion/vitest.config.ts
Normal file
15
bastion/vitest.config.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
coverage: {
|
||||||
|
provider: 'v8',
|
||||||
|
reporter: ['text', 'json', 'html'],
|
||||||
|
exclude: ['**/node_modules/**', '**/dist/**', '**/*.config.*'],
|
||||||
|
},
|
||||||
|
include: ['src/*/tests/**/*.test.ts'],
|
||||||
|
exclude: ['**/node_modules/**'],
|
||||||
|
testTimeout: 10000,
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user