You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
136 lines
5.6 KiB
136 lines
5.6 KiB
import {Directive, Inject, Injectable, OptionDefinition, uuid4} from '@extollo/lib'
|
|
import { IpRange } from "../../models/IpRange.model";
|
|
import { Setting } from "../../models/Setting.model";
|
|
import { Provisioner } from "../../services/Provisioner.service";
|
|
import { IpAddress, isIpAddress, isSubnet, Subnet } from "../../types";
|
|
import * as crypto from 'crypto'
|
|
|
|
@Injectable()
|
|
export class StandupDirective extends Directive {
|
|
@Inject()
|
|
protected readonly provisioner!: Provisioner
|
|
|
|
getKeywords(): string | string[] {
|
|
return 'standup'
|
|
}
|
|
|
|
getDescription(): string {
|
|
return 'perform initial configuration and setup for the control server'
|
|
}
|
|
|
|
getOptions(): OptionDefinition[] {
|
|
return [
|
|
'--pve-master-node {name} | the name of the master node in the PVE cluster',
|
|
'--pve-api-host {host} | the hostname or IP of a PVE cluster node',
|
|
// '--pve-token-id {id} | the fully-qualified ID of a PVE API token',
|
|
// '--pve-token-secret {secret} | secret for the PVE API token',
|
|
'--pve-root-password {password} | password for the root@pam PVE user',
|
|
'--ip-pool-start {start} | the starting address in the P5X IP pool',
|
|
'--ip-pool-end {end} | the final address in the P5X IP pool',
|
|
'--ip-pool-subnet {subnet} | the numeric subnet of the P5X IP pool (e.g. 24)',
|
|
'--ip-pool-gateway {gateway} | the address of the gateway for the P5X IP pool',
|
|
'--storage-pool {name} | the name of the PVE shared storage pool',
|
|
'--dns-domain {name} | the DNS domain name used for K8s nodes',
|
|
'--node-network-bridge {name} | the name of the PVE network bridge to which nodes will join',
|
|
'--node-cpu-cores {cores} | the number of cores to provision each K8s node with',
|
|
'--node-ram-mib {ram} | the amount of ram (in MiB) to provision each K8s node with',
|
|
]
|
|
}
|
|
|
|
public async handle(argv: string[]): Promise<void> {
|
|
// Configure the settings:
|
|
await Setting.set('pveMasterNode', `${this.getOptionOrFail('pve-master-node')}`)
|
|
await Setting.set('pveApiHost', `${this.getOptionOrFail('pve-api-host')}`)
|
|
// await Setting.set('pveTokenId', `${this.getOptionOrFail('pve-token-id')}`)
|
|
// await Setting.set('pveTokenSecret', `${this.getOptionOrFail('pve-token-secret')}`)
|
|
await Setting.set('pveRootPassword', `${this.getOptionOrFail('pve-root-password')}`)
|
|
await Setting.set('pveStoragePool', `${this.getOptionOrFail('storage-pool')}`)
|
|
await Setting.set('dnsDomain', `${this.getOptionOrFail('dns-domain')}`)
|
|
await Setting.set('nodeNetworkBridge', `${this.getOptionOrFail('node-network-bridge')}`)
|
|
await Setting.set('nodeCpus', this.getIntOptionOrFail('node-cpu-cores'))
|
|
await Setting.set('nodeRamMib', this.getIntOptionOrFail('node-ram-mib'))
|
|
await Setting.set('rootPassword', uuid4())
|
|
|
|
const [sshPublicKey, sshPrivateKey] = await this.getSSHKeyPair()
|
|
await Setting.set('sshPublicKey', sshPublicKey)
|
|
await Setting.set('sshPrivateKey', sshPrivateKey)
|
|
|
|
// Store the IP address pool:
|
|
const ipRange = this.container().makeNew<IpRange>(IpRange)
|
|
ipRange.name = 'Default address pool'
|
|
ipRange.startIp = this.getIpOptionOrFail('ip-pool-start')
|
|
ipRange.endIp = this.getIpOptionOrFail('ip-pool-end')
|
|
ipRange.subnet = this.getSubnetOptionOrFail('ip-pool-subnet')
|
|
ipRange.gatewayIp = this.getIpOptionOrFail('ip-pool-gateway')
|
|
ipRange.save()
|
|
|
|
// Provision the master node:
|
|
const masterNode = await this.provisioner.provisionMasterNode()
|
|
}
|
|
|
|
protected getIntOptionOrFail(option: string): number {
|
|
const val = parseInt(this.option(option, NaN), 10)
|
|
if ( isNaN(val) ) {
|
|
this.error(`Missing or malformed required integer option: --${option}`)
|
|
process.exit(1)
|
|
}
|
|
return val
|
|
}
|
|
|
|
protected getSubnetOptionOrFail(option: string): Subnet {
|
|
const val = parseInt(this.option(option, NaN), 10)
|
|
if ( !isSubnet(val) ) {
|
|
this.error(`Missing or malformed required numeric subnet: --${option}`)
|
|
process.exit(1)
|
|
}
|
|
return val
|
|
}
|
|
|
|
protected getIpOptionOrFail(option: string): IpAddress {
|
|
const val = `${this.option(option, '')}`
|
|
if ( !isIpAddress(val) ) {
|
|
this.error(`Missing or malformed required IP address: --${option}`)
|
|
process.exit(1)
|
|
}
|
|
return val
|
|
}
|
|
|
|
protected getOptionOrFail(option: string): unknown {
|
|
const val = this.option(option, undefined)
|
|
if ( typeof val === 'undefined' ) {
|
|
this.error(`Missing required option: --${option}`)
|
|
process.exit(1)
|
|
}
|
|
return val
|
|
}
|
|
|
|
protected async getSSHKeyPair(): Promise<[string, string]> {
|
|
return new Promise<[string, string]>((res, rej) => {
|
|
crypto.generateKeyPair(
|
|
'rsa',
|
|
{
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'spki',
|
|
format: 'pem',
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs8',
|
|
format: 'pem',
|
|
cipher: 'aes-256-cbc',
|
|
passphrase: '',
|
|
},
|
|
},
|
|
(err, pubkey, privkey) => {
|
|
if ( err ) {
|
|
return rej(err)
|
|
}
|
|
|
|
res([pubkey, privkey])
|
|
},
|
|
)
|
|
})
|
|
}
|
|
|
|
}
|