diff --git a/apps/vms/unifi/30-virtualmachine.yaml b/apps/vms/unifi/30-virtualmachine.yaml index 40ad251..ac542d8 100644 --- a/apps/vms/unifi/30-virtualmachine.yaml +++ b/apps/vms/unifi/30-virtualmachine.yaml @@ -85,61 +85,10 @@ spec: name: unifi-rootdisk - name: cloudinit cloudInitNoCloud: - # UNATTENDED UOS INSTALL: install-uos resolves the LATEST UniFi OS Server linux-x64 - # installer from Ubiquiti's PUBLIC firmware API (fw-update.ui.com/api/firmware-latest, no - # auth) — so there is no per-release URL to bump (override with UOS_URL= to pin a - # build). cloud-init installs the prerequisites (podman + slirp4netns + uidmap + jq), - # downloads the installer (~840MB), and runs it NON-INTERACTIVELY (`yes |` — the installer - # reads its confirmations from stdin). The UI comes up on https://:11443 once installed. - userData: |- - #cloud-config - hostname: unifi - fqdn: unifi.home.keithjackson.dev - # Break-glass OS access on the default `ubuntu` user (public key — safe to commit) for - # ops/debugging. qemu-guest-agent (below) additionally enables `virtctl` guest access. - ssh_pwauth: false - ssh_authorized_keys: - - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCq3JM7CD3P+OExaD4sQARLDQUf5QqDRemAO0qWA3fG7O/6ncgX0d/xN95lJ5Ei8LewlzXF5cCVe43oJ5CJRjh41igWZ38XXgOQcbAbY0lZTJlJg/SAj9Xni++JY3Jws5Nslm8qCrqt1bDsP3O3Lnp4hmBVZ+1eJy3v9HlJ2/1mVOukIob/zVXQNdOpVx1+oubNqo+UYt3l1IxqF8x+2JlHt+POaTBMqbC3JDCaStkkHN20xItpydHZ2uSamXaTx2MH+rQjrABjuD6Xxbhxhx0hG2XrVDWyAuq91aZUpuDkybcuHnpQUSHeZJCMqDCd3Q8yd/dyifDZE8QcnY5hiVHJF8VzJT/r5+em+I4LESlE5WNfq80TWG42d9gw7Yi/8xT38iDm+0nArMd/laAPpH0JGvdIyMdWcVZgfWTgcT6EZ9cIK2J4N+ijrh/V+xwzApmYyMdVsu2UjMeKrJG4VZk43XKnpfJCLJ2LmwtPMKqrI+wyge9Bav2+t+taznKP141gL6YJW3gwArzj+oktK+/fbahtBAUnjh5emLb2rv0gbAHC9cNbovEIdA3A6FDME96zAlozU4AtoCTA7SA1AqIHuAOHkqGYFBV77eriO85K0J1qo0FwuByFINI6G/JtEnNa+CjJLvh0clJonw+6+iR2vB1hwfDmLLwrE7QRT7Ho9w== me@keithjackson.dev - package_update: true - package_upgrade: false # skip slow first-boot upgrade; base image is recent - packages: - - qemu-guest-agent # KubeVirt guest integration + virtctl guest access - - podman - - uidmap - - slirp4netns - - ca-certificates - - curl - - jq - - cloud-guest-utils - write_files: - # Install (or reinstall) UniFi OS Server. URL is public/no-auth, pinned per release; - # override at runtime with: sudo UOS_URL='https://fw-download.ubnt.com/...' install-uos - - path: /usr/local/sbin/install-uos - permissions: '0755' - content: | - #!/usr/bin/env bash - set -euo pipefail - # Resolve the latest UOS linux-x64 installer from Ubiquiti's public firmware API - # (no auth). Override by exporting UOS_URL= to pin a specific build. - if [ -z "${UOS_URL:-}" ]; then - UOS_URL="$(curl -fsSL https://fw-update.ui.com/api/firmware-latest | jq -r ' - ._embedded.firmware - | map(select(.product == "unifi-os-server" and .platform == "linux-x64")) - | sort_by(.version_major, .version_minor, .version_patch) - | last._links.data.href')" - fi - [ -n "${UOS_URL}" ] && [ "${UOS_URL}" != "null" ] || { echo "could not resolve UOS installer URL"; exit 1; } - tmp="$(mktemp /tmp/uos-installer.XXXXXX)" - echo "Downloading UOS installer: ${UOS_URL}" - curl -fL --retry 5 --retry-delay 10 -o "$tmp" "$UOS_URL" - chmod +x "$tmp" - echo "Running UOS installer non-interactively" - yes | "$tmp" - echo "UOS install finished; 'uosserver status' should report running (UI :11443)" - runcmd: - - [ systemctl, enable, --now, qemu-guest-agent ] - - [ systemctl, enable, --now, podman.socket ] - # Full unattended install; tee to the serial console AND /var/log/uos-install.log for - # visibility. Non-fatal so a failed install leaves the VM up for `sudo install-uos`. - - [ 'bash', '-lc', '/usr/local/sbin/install-uos 2>&1 | tee /var/log/uos-install.log | tee /dev/console; echo "install-uos exit=${PIPESTATUS[0]}" | tee -a /var/log/uos-install.log > /dev/console' ] - final_message: "cloud-init finished; UOS install log at /var/log/uos-install.log (UI :11443)" + # cloud-init is sourced from the `unifi-cloudinit` Secret (userDataSecretRef) rather than + # inline, because the full userData (SSH key + install script) exceeds KubeVirt's 2048-byte + # inline cloudInitNoCloud.userData limit. The Secret is generated from + # apps/vms/unifi/unifi-cloudinit.yaml by the kustomization's secretGenerator (stable name, + # no hash suffix). Content is non-sensitive (public key + public installer script). + userDataSecretRef: + name: unifi-cloudinit diff --git a/apps/vms/unifi/kustomization.yaml b/apps/vms/unifi/kustomization.yaml index d67b3be..65bb5f6 100644 --- a/apps/vms/unifi/kustomization.yaml +++ b/apps/vms/unifi/kustomization.yaml @@ -11,3 +11,15 @@ resources: - 30-virtualmachine.yaml - 40-networkpolicy.yaml - 50-service-l4.yaml +# The VM's cloud-init (SSH key + UOS install script) exceeds KubeVirt's 2048-byte inline userData +# limit, so it is delivered via a Secret (30-virtualmachine.yaml userDataSecretRef: unifi-cloudinit). +# secretGenerator wraps the readable unifi-cloudinit.yaml file as key `userdata`. disableNameSuffixHash +# keeps the name stable (the VM references it by fixed name — Kustomize can't rewrite a KubeVirt +# userDataSecretRef). Content is non-sensitive (public key + public installer), so NOT SOPS-encrypted. +secretGenerator: + - name: unifi-cloudinit + namespace: unifi + files: + - userdata=unifi-cloudinit.yaml +generatorOptions: + disableNameSuffixHash: true diff --git a/apps/vms/unifi/unifi-cloudinit.yaml b/apps/vms/unifi/unifi-cloudinit.yaml new file mode 100644 index 0000000..058669d --- /dev/null +++ b/apps/vms/unifi/unifi-cloudinit.yaml @@ -0,0 +1,59 @@ +#cloud-config +# UniFi OS Server VM cloud-init. Wrapped into a Secret (userDataSecretRef) by the kustomization's +# secretGenerator because it exceeds KubeVirt's 2048-byte inline cloudInitNoCloud.userData limit. +# NON-SENSITIVE (a public SSH key + a public installer script), so it is NOT SOPS-encrypted. +# +# install-uos resolves the LATEST UniFi OS Server linux-x64 installer from Ubiquiti's PUBLIC firmware +# API (fw-update.ui.com/api/firmware-latest, no auth) — no per-release URL to bump (override with +# UOS_URL=). cloud-init installs the prerequisites, downloads the installer (~840MB) and runs it +# NON-INTERACTIVELY (`yes |`). The UI comes up on https://:11443 once installed. +hostname: unifi +fqdn: unifi.home.keithjackson.dev +# Break-glass OS access on the default `ubuntu` user (public key — safe to commit) for ops/debugging. +# qemu-guest-agent (below) additionally enables `virtctl` guest access. +ssh_pwauth: false +ssh_authorized_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCq3JM7CD3P+OExaD4sQARLDQUf5QqDRemAO0qWA3fG7O/6ncgX0d/xN95lJ5Ei8LewlzXF5cCVe43oJ5CJRjh41igWZ38XXgOQcbAbY0lZTJlJg/SAj9Xni++JY3Jws5Nslm8qCrqt1bDsP3O3Lnp4hmBVZ+1eJy3v9HlJ2/1mVOukIob/zVXQNdOpVx1+oubNqo+UYt3l1IxqF8x+2JlHt+POaTBMqbC3JDCaStkkHN20xItpydHZ2uSamXaTx2MH+rQjrABjuD6Xxbhxhx0hG2XrVDWyAuq91aZUpuDkybcuHnpQUSHeZJCMqDCd3Q8yd/dyifDZE8QcnY5hiVHJF8VzJT/r5+em+I4LESlE5WNfq80TWG42d9gw7Yi/8xT38iDm+0nArMd/laAPpH0JGvdIyMdWcVZgfWTgcT6EZ9cIK2J4N+ijrh/V+xwzApmYyMdVsu2UjMeKrJG4VZk43XKnpfJCLJ2LmwtPMKqrI+wyge9Bav2+t+taznKP141gL6YJW3gwArzj+oktK+/fbahtBAUnjh5emLb2rv0gbAHC9cNbovEIdA3A6FDME96zAlozU4AtoCTA7SA1AqIHuAOHkqGYFBV77eriO85K0J1qo0FwuByFINI6G/JtEnNa+CjJLvh0clJonw+6+iR2vB1hwfDmLLwrE7QRT7Ho9w== me@keithjackson.dev +package_update: true +package_upgrade: false +packages: + - qemu-guest-agent + - podman + - uidmap + - slirp4netns + - ca-certificates + - curl + - jq + - cloud-guest-utils +write_files: + # Install (or reinstall) UniFi OS Server. URL is public/no-auth; override at runtime with: + # sudo UOS_URL='https://fw-download.ubnt.com/...' install-uos + - path: /usr/local/sbin/install-uos + permissions: '0755' + content: | + #!/usr/bin/env bash + set -euo pipefail + # Resolve the latest UOS linux-x64 installer from Ubiquiti's public firmware API (no auth). + # Override by exporting UOS_URL= to pin a specific build. + if [ -z "${UOS_URL:-}" ]; then + UOS_URL="$(curl -fsSL https://fw-update.ui.com/api/firmware-latest | jq -r ' + ._embedded.firmware + | map(select(.product == "unifi-os-server" and .platform == "linux-x64")) + | sort_by(.version_major, .version_minor, .version_patch) + | last._links.data.href')" + fi + [ -n "${UOS_URL}" ] && [ "${UOS_URL}" != "null" ] || { echo "could not resolve UOS installer URL"; exit 1; } + tmp="$(mktemp /tmp/uos-installer.XXXXXX)" + echo "Downloading UOS installer: ${UOS_URL}" + curl -fL --retry 5 --retry-delay 10 -o "$tmp" "$UOS_URL" + chmod +x "$tmp" + echo "Running UOS installer non-interactively" + yes | "$tmp" + echo "UOS install finished; 'uosserver status' should report running (UI :11443)" +runcmd: + - [ systemctl, enable, --now, qemu-guest-agent ] + - [ systemctl, enable, --now, podman.socket ] + # Full unattended install; tee to the serial console AND /var/log/uos-install.log for visibility. + # Non-fatal so a failed install leaves the VM up for `sudo install-uos`. + - [ 'bash', '-lc', '/usr/local/sbin/install-uos 2>&1 | tee /var/log/uos-install.log | tee /dev/console; echo "install-uos exit=${PIPESTATUS[0]}" | tee -a /var/log/uos-install.log > /dev/console' ] +final_message: "cloud-init finished; UOS install log at /var/log/uos-install.log (UI :11443)"