1import { createHmac } from 'crypto' 24export const harvDashboardHostnamePrefix = (cluster_name: string, dashboardSecret: string) => { 25 const hmac = createHmac('sha256', dashboardSecret) 26 hmac.update(cluster_name) 27 return `harvester-${hmac.digest('hex').slice(0, 8)}` 30export const printHarvDashboardLogin = ({cluster_name, vipHost, lanHost, extHost}: { 31 cluster_name: string, vipHost?: string, lanHost?: string, extHost?: string 33 const secName = `harv-dashboard-${cluster_name}` 35 console.log(chalkGreen(`Harvester Dashboard: https://${vipHost}`)) 36 } else if (lanHost && extHost) { 37 console.log(`Harvester Dashboard: https://${lanHost} (lan) or https://${extHost} (ext)`) 39 console.log(`Login: admin`) 43export const plsDlKubeConfig = (hostname: string) => { 45 console.log(`1) Goto https://${hostname}/dashboard/harvester/c/local/support`) 49const sshExecQuiet = async (sshHost: string, cmd: string, stepName: string) => { 51 cmd: `ssh ${sshHost} '${cmd}'`, 52 isQuiet: true, noOutCmd: true, 53 }).catch(err => { err.message = `${stepName}: ${err.message}`; throw err }) 57const rancherApiUrl = (clusterVip: string) => `https://${clusterVip}` 59export const k8sHarvDashboardPw = async ({nodeHostname}: {nodeHostname?: string} = {}) => { 61 const {cluster_name, clusterVip} = ctx 62 nodeHostname ||= ctx.sshNodeHostname || ctx.nodeHostname! 64 const secName = `harv-dashboard-${cluster_name}` 66 const sshHost = `rancher@${nodeHostname}` 67 const rancherApi = rancherApiUrl(clusterVip) 68 const rancherPodCmd = `sudo -i kubectl -n cattle-system get pods -l app=rancher --no-headers | head -1 | awk "{ print \\$1 }"` 69 const bootstrap = await sshExecQuiet(sshHost, 70 `sudo -i kubectl -n cattle-system exec $( ${rancherPodCmd} ) -c rancher -- reset-password 2>/dev/null | tail -1`, 72 const token = await sshExecQuiet(sshHost, 73 `curl -sk ${rancherApi}/v3-public/localProviders/local?action=login -H "Content-Type: application/json" -d "{\\"username\\":\\"admin\\",\\"password\\":\\"${bootstrap}\\"}" | jq -r .token`, 75 await sshExecQuiet(sshHost, 76 `curl -sk ${rancherApi}/v3/users?action=changepassword -H "Authorization: Bearer ${token}" -H "Content-Type: application/json" -d "{\\"currentPassword\\":\\"${bootstrap}\\",\\"newPassword\\":\\"${stablePw}\\"}" > /dev/null`, 78 await sshExecQuiet(sshHost, 79 `sudo -i kubectl patch settings.management.cattle.io first-login -p "{\\"value\\":\\"false\\"}" --type=merge`, 81 printHarvDashboardLogin({cluster_name, vipHost: clusterVip}) 83export { k8sHarvDashboardPw as resetHarvesterDashboardPw } 85export const dlKubeConfig = async ({nodeHostname}: {nodeHostname?: string} = {}) => { 87 const {cluster_name, clusterVip} = ctx 88 nodeHostname ||= ctx.sshNodeHostname || ctx.nodeHostname! 94 cmd: `kubectl --kubeconfig=${kcAbsPath} get nodes --request-timeout=5s`, 95 isQuiet: true, noOutCmd: true, timeoutAfterSec: 10, 98 console.log(chalkGreen(`✓ ${kcRelPath} valid and connectable`)) 101 console.log(chalkYellow(`${kcRelPath} exists but not connectable, re-downloading...`)) 104 const secName = `harv-dashboard-${cluster_name}` 106 const sshHost = `rancher@${nodeHostname}` 107 const vipOrHostname = clusterVip || nodeHostname 108 const rancherApi = rancherApiUrl(clusterVip!) 109 const token = await sshExecQuiet(sshHost, 110 `curl -sk ${rancherApi}/v3-public/localProviders/local?action=login -H "Content-Type: application/json" -d "{\\"username\\":\\"admin\\",\\"password\\":\\"${dashboardPw}\\"}" | jq -r .token`, 111 'rancher-login').catch(() => '') 112 const rawConfig = token ? await sshExecQuiet(sshHost, 113 `curl -sk ${rancherApi}/v1/management.cattle.io.clusters/local?action=generateKubeconfig -H "Authorization: Bearer ${token}" -X POST | jq -r .config`, 114 'generate-kubeconfig').catch(() => '') : '' 115 if (!rawConfig || rawConfig === 'null') { 116 console.log(chalkYellow(`failed to generate kubeconfig via API, run k8sHarvDashboardPw first`)) 117 plsDlKubeConfig(vipOrHostname) 120 const kubeConfigStr = replaceAll(rawConfig, 'https://localhost/', `https://${vipOrHostname}/`) 121 await fs1Promises.writeFile(tsAbsPath(kcAbsPath), kubeConfigStr) 122 console.log(chalkGreen(`downloaded kubeconfig to ${kcRelPath}`)) 123 console.log(`( fyi if this doesn't work - you can also manually download kubeconfig from https://${vipOrHostname}/dashboard/harvester/c/local/support )`) 126export const k8sUpdateKubeconfig = async () => { 128 const {cluster_name, clusterVip, klusterVipHostname, klustCertsH} = ctx 133 const vipUrl = `https://${clusterVip}/` 134 if (!kcContent.includes(vipUrl)) { 135 console.log(chalkGreen(`✓ ${kcRelPath} already uses hostname`)) 138 const lanSuffix = klusterVipHostname?.replace('*.', '') 139 const extWcSubdomain = Object.values(klustCertsH || {}).flat().map(d => d.replace('*.', '')).find(d => d !== lanSuffix) 142 const dashboardPrefix = harvDashboardHostnamePrefix(cluster_name, dashboardSec) 143 const extHost = `${dashboardPrefix}.${extWcSubdomain}` 145 console.log(chalkYellow(`${extHost} not resolving yet, skipping kubeconfig update`)) 148 const hostnameUrl = `https://${extHost}/` 150 updatedKc = updatedKc.replace(/\n\s+certificate-authority-data:\s+"[^"]+"/g, '') 151 const tmpPath = kcAbsPath + '.tmp' 152 await fs1Promises.writeFile(tsAbsPath(tmpPath as any), updatedKc) 154 cmd: `kubectl --kubeconfig=${tmpPath} get nodes --request-timeout=5s`, 155 isQuiet: true, noOutCmd: true, timeoutAfterSec: 10, 159 console.log(chalkYellow(`kubeconfig with ${extHost} not connectable, keeping VIP`)) 163 console.log(chalkGreen(`updated ${kcRelPath}: ${clusterVip} → ${extHost}`)) 166export const k8sSetupCli = async () => { 167 const importMetaUrl = (importMetaUrlCtx.getStore() as {importMetaUrl?: string} | undefined)?.importMetaUrl 172 const cliMjsAbsPath = callerDir + '/cli.mjs' 177 const isPt0 = callerPtPath.startsWith('pt0/') 178 const depthFromPt0 = isPt0 ? callerPtPath.slice(4).split('/').length : 0 179 const pt0RelPath = isPt0 180 ? '../'.repeat(depthFromPt0).slice(0, -1) 181 : callerPtPath.replace(/[^/]+/g, '..') + '/pt0' 183`import { eptKubeCli } from '${pt0RelPath}/deployF/k8sF/cliF/epKubeCliF.mts' 184import { klusterCtx } from '${pt0RelPath}/deployF/k8sF/ctxF/klusterCtxF.mts' 186import { getProcArgv } from '${pt0RelPath}/serverF/isDirectlyRunF.mts' 188klusterCtx.enterWith(klusterCfg) 189await eptKubeCli(getProcArgv(2)) 191 console.log(`created ${cliMjsRelPath}`) 194 const binRelPath = `pt0/path_bin/${cluster_name}` 195 const binAbsPath = ptDir + '/' + binRelPath 199ptnode ${cliMjsRelPath} ${cluster_name} "$@" 201 await liveSpawnThrow({cmd: `chmod +x ${binAbsPath}`, isQuiet: true, noOutCmd: true}) 202 console.log(`created ${binRelPath}`)