diff --git a/.github/workflows/packer-vyos.yml b/.github/workflows/packer-vyos.yml deleted file mode 100644 index 6b009f8..0000000 --- a/.github/workflows/packer-vyos.yml +++ /dev/null @@ -1,138 +0,0 @@ -# TODO: Migrate to vyos-build (https://github.com/vyos/vyos-build) for building -# VyOS images instead of Packer. The vyos-build approach provides better support -# for customization and doesn't require nested virtualization in CI. -# -# This workflow is currently disabled pending the migration. - -name: Build VyOS Image - -# Disabled: triggers commented out pending vyos-build migration -# on: -# push: -# branches: [master] -# paths: -# - 'infrastructure/network/vyos/packer/**' -# pull_request: -# paths: -# - 'infrastructure/network/vyos/packer/**' -# workflow_dispatch: - -on: - workflow_dispatch: - inputs: - note: - description: 'Workflow disabled - see TODO comment at top of file' - type: string - default: 'disabled' - -# concurrency: -# group: packer-vyos-${{ github.ref }} -# cancel-in-progress: false - -jobs: - disabled: - if: false - runs-on: ubuntu-latest - steps: - - run: echo "Workflow disabled pending vyos-build migration" - -# Historical reference - Packer-based build workflow: -# -# validate: -# runs-on: warp-ubuntu-latest-x64-8x -# steps: -# - uses: actions/checkout@v4 -# -# - uses: hashicorp/setup-packer@v3.1.0 -# with: -# version: '1.11.2' -# -# - name: Packer Init -# working-directory: infrastructure/network/vyos/packer -# run: packer init . -# -# - name: Packer Validate -# working-directory: infrastructure/network/vyos/packer -# run: | -# # Validate with dummy values for required vars without defaults -# # Note: vyos_iso_url/checksum come from source.auto.pkrvars.hcl (auto-loaded) -# packer validate \ -# -var "ssh_key_type=ssh-ed25519" \ -# -var "ssh_public_key=AAAA" \ -# . -# -# build: -# if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' -# runs-on: warp-ubuntu-latest-x64-8x -# needs: validate -# steps: -# - uses: actions/checkout@v4 -# -# - uses: actions/setup-go@v5 -# with: -# go-version: '1.23' -# cache-dependency-path: tools/labctl/go.sum -# -# - name: Build labctl -# run: | -# cd tools/labctl -# go build -o ../../labctl . -# -# - name: Install SOPS -# run: | -# curl -LO https://github.com/getsops/sops/releases/download/v3.9.2/sops-v3.9.2.linux.amd64 -# chmod +x sops-v3.9.2.linux.amd64 -# sudo mv sops-v3.9.2.linux.amd64 /usr/local/bin/sops -# -# - name: Write SOPS age key -# run: | -# echo "${{ secrets.SOPS_AGE_KEY }}" > /tmp/age-key.txt -# chmod 600 /tmp/age-key.txt -# -# - name: Extract SSH public key -# env: -# SOPS_AGE_KEY_FILE: /tmp/age-key.txt -# run: | -# sops --decrypt \ -# --extract '["ssh_public_key"]' images/packer-ssh.sops.yaml > /tmp/ssh_key.pub -# -# - name: Install QEMU -# run: | -# sudo apt-get update -# sudo apt-get install -y qemu-system-x86 qemu-utils -# -# - name: Enable KVM access -# run: | -# echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules -# sudo udevadm control --reload-rules -# sudo udevadm trigger --name-match=kvm -# sudo usermod -aG kvm $USER -# sudo chmod 666 /dev/kvm || true -# -# - uses: hashicorp/setup-packer@v3.1.0 -# with: -# version: '1.11.2' -# -# - name: Packer Init -# working-directory: infrastructure/network/vyos/packer -# run: packer init . -# -# - name: Packer Build -# working-directory: infrastructure/network/vyos/packer -# run: | -# # Extract key type and body from public key -# # Note: vyos_iso_url/checksum auto-loaded from source.auto.pkrvars.hcl -# SSH_KEY_TYPE=$(awk '{print $1}' /tmp/ssh_key.pub) -# SSH_KEY_BODY=$(awk '{print $2}' /tmp/ssh_key.pub) -# packer build \ -# -var "ssh_key_type=${SSH_KEY_TYPE}" \ -# -var "ssh_public_key=${SSH_KEY_BODY}" \ -# . -# -# - name: Upload to e2 -# run: | -# ./labctl images upload \ -# --credentials images/e2.sops.yaml \ -# --sops-age-key-file /tmp/age-key.txt \ -# --source infrastructure/network/vyos/packer/output/vyos-lab.raw \ -# --destination vyos/vyos-gateway.raw diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml new file mode 100644 index 0000000..c7242a5 --- /dev/null +++ b/.github/workflows/vyos-build.yml @@ -0,0 +1,164 @@ +name: Build VyOS Image + +on: + push: + branches: [master] + paths: + - 'infrastructure/network/vyos/vyos-build/**' + - 'infrastructure/network/vyos/configs/gateway.conf' + pull_request: + paths: + - 'infrastructure/network/vyos/vyos-build/**' + - 'infrastructure/network/vyos/configs/gateway.conf' + workflow_dispatch: + inputs: + upload: + description: 'Upload image to e2 storage' + type: boolean + default: true + +concurrency: + group: vyos-build-${{ github.ref }} + cancel-in-progress: false + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Validate flavor template + run: | + # Check that the template file exists and contains required placeholders + TEMPLATE="infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml" + + if [[ ! -f "${TEMPLATE}" ]]; then + echo "ERROR: Template file not found: ${TEMPLATE}" + exit 1 + fi + + if ! grep -q '%%SSH_KEY_TYPE%%' "${TEMPLATE}"; then + echo "ERROR: Template missing %%SSH_KEY_TYPE%% placeholder" + exit 1 + fi + + if ! grep -q '%%SSH_PUBLIC_KEY%%' "${TEMPLATE}"; then + echo "ERROR: Template missing %%SSH_PUBLIC_KEY%% placeholder" + exit 1 + fi + + echo "Template validation passed" + + - name: Check scripts are executable + run: | + for script in infrastructure/network/vyos/vyos-build/scripts/*.sh; do + if [[ ! -x "${script}" ]]; then + echo "ERROR: Script not executable: ${script}" + exit 1 + fi + echo "OK: ${script}" + done + + build: + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + runs-on: warp-ubuntu-latest-x64-8x + needs: validate + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache-dependency-path: tools/labctl/go.sum + + - name: Build labctl + run: | + cd tools/labctl + go build -o ../../labctl . + + - name: Install SOPS + run: | + curl -LO https://github.com/getsops/sops/releases/download/v3.9.2/sops-v3.9.2.linux.amd64 + chmod +x sops-v3.9.2.linux.amd64 + sudo mv sops-v3.9.2.linux.amd64 /usr/local/bin/sops + + - name: Write SOPS age key + run: | + echo "${{ secrets.SOPS_AGE_KEY }}" > /tmp/age-key.txt + chmod 600 /tmp/age-key.txt + + - name: Extract SSH public key + env: + SOPS_AGE_KEY_FILE: /tmp/age-key.txt + run: | + sops --decrypt \ + --extract '["ssh_public_key"]' images/packer-ssh.sops.yaml > /tmp/ssh_key.pub + echo "SSH key extracted" + + - name: Clone vyos-build + run: | + git clone -b current --single-branch --depth 1 \ + https://github.com/vyos/vyos-build.git /tmp/vyos-build + + - name: Generate build flavor + run: | + ./infrastructure/network/vyos/vyos-build/scripts/generate-flavor.sh \ + "$(cat /tmp/ssh_key.pub)" \ + /tmp/vyos-build/data/build-flavors/gateway.toml + + - name: Build VyOS image + run: | + # Generate version string + VERSION="lab-$(date +%Y%m%d%H%M%S)" + + docker run --rm --privileged \ + -v /tmp/vyos-build:/vyos \ + -v /dev:/dev \ + -e VYOS_BUILD_BY="ci@lab.gilman.io" \ + -w /vyos \ + vyos/vyos-build:current \ + bash -c "sudo ./build-vyos-image --architecture amd64 --build-by ci@lab.gilman.io --build-type release --version ${VERSION} gateway" + + # Find and move the output image (suppress permission denied errors) + echo "Looking for .raw image in build output..." + ls -la /tmp/vyos-build/ || true + + # The raw image is created in the vyos-build root directory + RAW_FILE=$(find /tmp/vyos-build -maxdepth 1 -name "*.raw" -type f 2>/dev/null | head -1) + + if [[ -z "${RAW_FILE}" ]]; then + echo "No .raw file in root, checking build directory..." + RAW_FILE=$(find /tmp/vyos-build/build -name "*.raw" -type f 2>/dev/null | head -1) + fi + + if [[ -z "${RAW_FILE}" || ! -f "${RAW_FILE}" ]]; then + echo "ERROR: Build failed - no raw image found" + echo "Contents of /tmp/vyos-build:" + ls -la /tmp/vyos-build/ || true + echo "Contents of /tmp/vyos-build/build:" + ls -la /tmp/vyos-build/build/ 2>/dev/null || true + exit 1 + fi + + echo "Found raw image: ${RAW_FILE}" + cp "${RAW_FILE}" /tmp/vyos-gateway.raw + echo "Build complete: /tmp/vyos-gateway.raw" + ls -lah /tmp/vyos-gateway.raw + + - name: Upload to e2 + if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.upload) + run: | + ./labctl images upload \ + --credentials images/e2.sops.yaml \ + --sops-age-key-file /tmp/age-key.txt \ + --source /tmp/vyos-gateway.raw \ + --destination vyos/vyos-gateway.raw + + - name: Upload build artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: vyos-gateway-image + path: /tmp/vyos-gateway.raw + retention-days: 7 + if-no-files-found: warn diff --git a/bootstrap/genesis/scripts/build-vyos-image.sh b/bootstrap/genesis/scripts/build-vyos-image.sh index ebd06fa..8a9624d 100755 --- a/bootstrap/genesis/scripts/build-vyos-image.sh +++ b/bootstrap/genesis/scripts/build-vyos-image.sh @@ -1,42 +1,37 @@ #!/usr/bin/env bash # Build VyOS Gateway Image -# Creates a raw disk image for the VP6630 gateway router +# Creates a raw disk image for the VP6630 gateway router using vyos-build # # Prerequisites: -# - Packer >= 1.9.0 -# - QEMU with KVM support -# - VyOS ISO (downloaded automatically or provided) +# - Docker +# - SSH public key # # Usage: # ./build-vyos-image.sh [options] # # Options: -# -i, --iso PATH Path to VyOS ISO (skips download) -# -o, --output DIR Output directory (default: output-vyos) +# -o, --output DIR Output directory (default: ./output-vyos) # -k, --ssh-key PATH SSH public key file (default: ~/.ssh/id_rsa.pub) +# -v, --version VER VyOS version string (default: timestamp) # -h, --help Show this help message # -# Network configuration is defined in: -# infrastructure/network/vyos/configs/gateway.conf +# Network configuration is embedded in the build flavor at: +# infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" -PACKER_DIR="${REPO_ROOT}/infrastructure/network/vyos/packer" +VYOS_BUILD_DIR="${REPO_ROOT}/infrastructure/network/vyos/vyos-build" # Defaults -VYOS_ISO="" -OUTPUT_DIR="${PACKER_DIR}/output-vyos" +OUTPUT_DIR="${SCRIPT_DIR}/output-vyos" SSH_KEY_FILE="${HOME}/.ssh/id_rsa.pub" - -# VyOS download settings -VYOS_VERSION="1.5-rolling-202412190007" -VYOS_URL="https://github.com/vyos/vyos-rolling-nightly-builds/releases/download/${VYOS_VERSION}/vyos-${VYOS_VERSION}-amd64.iso" -VYOS_CACHE_DIR="${HOME}/.cache/vyos" +VERSION="$(date +%Y%m%d%H%M%S)" +BUILD_BY="genesis@lab.gilman.io" usage() { - head -30 "$0" | grep -E '^#' | sed 's/^# \?//' + head -20 "$0" | grep -E '^#' | sed 's/^# \?//' exit 0 } @@ -52,17 +47,12 @@ error() { check_prerequisites() { log "Checking prerequisites..." - if ! command -v packer &>/dev/null; then - error "Packer not found. Install with: brew install packer" - fi - - if ! command -v qemu-system-x86_64 &>/dev/null; then - error "QEMU not found. Install with: brew install qemu" + if ! command -v docker &>/dev/null; then + error "Docker not found. Install Docker to continue." fi - # Check KVM availability (Linux only) - if [[ "$(uname)" == "Linux" ]] && [[ ! -r /dev/kvm ]]; then - error "KVM not available. Ensure virtualization is enabled and you have access to /dev/kvm" + if ! docker info &>/dev/null; then + error "Docker daemon not running or not accessible." fi # Check SSH key exists @@ -73,89 +63,85 @@ check_prerequisites() { log "Prerequisites satisfied" } -download_vyos_iso() { - if [[ -n "${VYOS_ISO}" ]]; then - if [[ ! -f "${VYOS_ISO}" ]]; then - error "Specified ISO not found: ${VYOS_ISO}" - fi - log "Using provided ISO: ${VYOS_ISO}" - return - fi - - mkdir -p "${VYOS_CACHE_DIR}" - VYOS_ISO="${VYOS_CACHE_DIR}/vyos-${VYOS_VERSION}-amd64.iso" - - if [[ -f "${VYOS_ISO}" ]]; then - log "Using cached ISO: ${VYOS_ISO}" - return - fi +generate_flavor() { + log "Generating build flavor with SSH credentials..." - log "Downloading VyOS ${VYOS_VERSION}..." - log "URL: ${VYOS_URL}" + # Extract SSH key components + SSH_KEY_TYPE=$(awk '{print $1}' "${SSH_KEY_FILE}") + SSH_KEY_BODY=$(awk '{print $2}' "${SSH_KEY_FILE}") - if ! curl -fSL -o "${VYOS_ISO}.tmp" "${VYOS_URL}"; then - rm -f "${VYOS_ISO}.tmp" - error "Failed to download VyOS ISO" + if [[ -z "${SSH_KEY_TYPE}" ]] || [[ -z "${SSH_KEY_BODY}" ]]; then + error "Invalid SSH public key format in ${SSH_KEY_FILE}" fi - mv "${VYOS_ISO}.tmp" "${VYOS_ISO}" - log "Downloaded: ${VYOS_ISO}" -} - -get_ssh_key_type() { - # Extract key type (first field: ssh-rsa, ssh-ed25519, etc.) - awk '{print $1}' "${SSH_KEY_FILE}" -} - -get_ssh_key_body() { - # Extract key body (second field: base64 encoded key) - awk '{print $2}' "${SSH_KEY_FILE}" -} - -run_packer_build() { - log "Starting Packer build..." - - cd "${PACKER_DIR}" + log " SSH Key Type: ${SSH_KEY_TYPE}" - # Initialize Packer plugins - log "Initializing Packer plugins..." - packer init . + # Create temp directory for build files + BUILD_TEMP=$(mktemp -d) + trap "rm -rf ${BUILD_TEMP}" EXIT - # Get SSH key type and body - SSH_KEY_TYPE=$(get_ssh_key_type) - SSH_KEY_BODY=$(get_ssh_key_body) + # Generate flavor from template + TEMPLATE_FILE="${VYOS_BUILD_DIR}/build-flavors/gateway.toml" + GENERATED_FLAVOR="${BUILD_TEMP}/gateway.toml" - # Calculate ISO checksum - log "Calculating ISO checksum..." - if command -v sha256sum &>/dev/null; then - ISO_CHECKSUM="sha256:$(sha256sum "${VYOS_ISO}" | awk '{print $1}')" - else - ISO_CHECKSUM="sha256:$(shasum -a 256 "${VYOS_ISO}" | awk '{print $1}')" + if [[ ! -f "${TEMPLATE_FILE}" ]]; then + error "Flavor template not found: ${TEMPLATE_FILE}" fi - # Determine accelerator based on platform - if [[ "$(uname)" == "Darwin" ]]; then - ACCELERATOR="hvf" - else - ACCELERATOR="kvm" - fi + sed -e "s|%%SSH_KEY_TYPE%%|${SSH_KEY_TYPE}|g" \ + -e "s|%%SSH_PUBLIC_KEY%%|${SSH_KEY_BODY}|g" \ + "${TEMPLATE_FILE}" > "${GENERATED_FLAVOR}" - log "Building VyOS image..." - log " ISO: ${VYOS_ISO}" + log "Generated flavor: ${GENERATED_FLAVOR}" +} + +run_vyos_build() { + log "Starting vyos-build..." + log " Version: ${VERSION}" + log " Build By: ${BUILD_BY}" log " Output: ${OUTPUT_DIR}" - log " SSH Key: ${SSH_KEY_FILE} (${SSH_KEY_TYPE})" - log " Accelerator: ${ACCELERATOR}" - - # Run Packer build - PACKER_LOG=1 packer build \ - -var "vyos_iso_url=file://${VYOS_ISO}" \ - -var "vyos_iso_checksum=${ISO_CHECKSUM}" \ - -var "output_directory=${OUTPUT_DIR}" \ - -var "ssh_key_type=${SSH_KEY_TYPE}" \ - -var "ssh_public_key=${SSH_KEY_BODY}" \ - . - - log "Packer build completed successfully!" + + mkdir -p "${OUTPUT_DIR}" + + # Pull the vyos-build container + log "Pulling vyos-build container..." + docker pull vyos/vyos-build:current + + # Run the build inside the container + # The container needs: + # - Privileged mode for raw disk image creation + # - /dev access for disk operations + # - Generated flavor file copied to build-flavors directory + log "Running VyOS image build..." + + docker run --rm --privileged \ + -v "${BUILD_TEMP}/gateway.toml:/vyos/data/build-flavors/gateway.toml:ro" \ + -v "${OUTPUT_DIR}:/output" \ + -v /dev:/dev \ + -e VYOS_BUILD_BY="${BUILD_BY}" \ + -e VYOS_VERSION="${VERSION}" \ + vyos/vyos-build:current \ + bash -c " + set -e + echo 'Building VyOS gateway image...' + cd /vyos + sudo ./build-vyos-image \ + --architecture amd64 \ + --build-by '${BUILD_BY}' \ + --build-type release \ + --version '${VERSION}' \ + gateway + + echo 'Copying output files...' + if [ -d /vyos/build ]; then + cp -v /vyos/build/*.raw /output/ 2>/dev/null || true + cp -v /vyos/build/*.qcow2 /output/ 2>/dev/null || true + fi + + echo 'Build complete!' + " + + log "vyos-build completed successfully!" } show_results() { @@ -164,27 +150,31 @@ show_results() { echo "VyOS Gateway Image Build Complete" echo "==============================================" echo "" - echo "Output image: ${OUTPUT_DIR}/vyos-lab.raw" + echo "Output directory: ${OUTPUT_DIR}" + if [[ -d "${OUTPUT_DIR}" ]]; then + echo "" + echo "Files:" + ls -lah "${OUTPUT_DIR}/" + fi echo "" echo "Next steps:" - echo " 1. Copy image to Tinkerbell NAS:" - echo " scp ${OUTPUT_DIR}/vyos-lab.raw nas:/volume1/images/vyos-lab.raw" + echo " 1. Upload image to e2 storage for Synology Cloud Sync:" + echo " labctl images upload ${OUTPUT_DIR}/vyos-*.raw" + echo "" + echo " 2. Or copy directly to NAS:" + echo " scp ${OUTPUT_DIR}/vyos-*.raw nas:/volume1/images/vyos/" echo "" - echo " 2. Or write directly to USB/SSD for manual install:" - echo " sudo dd if=${OUTPUT_DIR}/vyos-lab.raw of=/dev/sdX bs=4M status=progress" + echo " 3. Or write directly to USB/SSD for manual install:" + echo " sudo dd if=${OUTPUT_DIR}/vyos-*.raw of=/dev/sdX bs=4M status=progress" echo "" - echo "To update network configuration, edit:" - echo " infrastructure/network/vyos/configs/gateway.conf" + echo "Network configuration is embedded in the build flavor at:" + echo " infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml" echo "" } main() { while [[ $# -gt 0 ]]; do case $1 in - -i|--iso) - VYOS_ISO="$2" - shift 2 - ;; -o|--output) OUTPUT_DIR="$2" shift 2 @@ -193,6 +183,10 @@ main() { SSH_KEY_FILE="$2" shift 2 ;; + -v|--version) + VERSION="$2" + shift 2 + ;; -h|--help) usage ;; @@ -202,12 +196,12 @@ main() { esac done - log "VyOS Gateway Image Builder" + log "VyOS Gateway Image Builder (vyos-build)" log "Repository root: ${REPO_ROOT}" check_prerequisites - download_vyos_iso - run_packer_build + generate_flavor + run_vyos_build show_results } diff --git a/docs/architecture/05_building_blocks/02_tinkerbell_provisioning.md b/docs/architecture/05_building_blocks/02_tinkerbell_provisioning.md index c7ab8b3..9e955a5 100644 --- a/docs/architecture/05_building_blocks/02_tinkerbell_provisioning.md +++ b/docs/architecture/05_building_blocks/02_tinkerbell_provisioning.md @@ -9,7 +9,7 @@ Tinkerbell handles **Day Zero** operations — the initial bootstrap of physical | Target | What Tinkerbell Installs | Result | |:---|:---|:---| -| **VP6630** | VyOS (Packer-built image) | Lab router with VLANs and DHCP relay | +| **VP6630** | VyOS (vyos-build image) | Lab router with VLANs and DHCP relay | | **UM760** | Talos Linux | Node joins the Platform Cluster | | **MS-02 (x3)** | Harvester OS | Nodes join the Harvester HCI cluster | diff --git a/docs/architecture/06_runtime_view.md b/docs/architecture/06_runtime_view.md index 77ffdb3..d090591 100644 --- a/docs/architecture/06_runtime_view.md +++ b/docs/architecture/06_runtime_view.md @@ -10,7 +10,7 @@ The "Genesis" sequence bootstraps the entire infrastructure from bare metal to a ### Prerequisites - Physical hardware cabled and powered -- VyOS image built with Packer (baked-in configuration) +- VyOS image built with vyos-build (baked-in configuration) - Synology NAS available with Talos VM capability ### Sequence diff --git a/docs/architecture/07_deployment_view.md b/docs/architecture/07_deployment_view.md index a73b880..aceb089 100644 --- a/docs/architecture/07_deployment_view.md +++ b/docs/architecture/07_deployment_view.md @@ -79,7 +79,7 @@ This section describes the physical and virtual infrastructure topology — how | Node | Operating System | Deployment Method | |:---|:---|:---| -| **VP6630** | VyOS | Tinkerbell PXE (Packer-built image) | +| **VP6630** | VyOS | Tinkerbell PXE (vyos-build image) | | **MS-02 (x3)** | Harvester (Elemental OS) | Tinkerbell PXE | | **UM760** | Talos Linux | Tinkerbell PXE | | **Platform VMs (x2)** | Talos Linux | CAPI + Harvester | diff --git a/docs/architecture/09_design_decisions/007_image_pipeline_s3_intermediary.md b/docs/architecture/09_design_decisions/007_image_pipeline_s3_intermediary.md index 9dbcb74..e02397c 100644 --- a/docs/architecture/09_design_decisions/007_image_pipeline_s3_intermediary.md +++ b/docs/architecture/09_design_decisions/007_image_pipeline_s3_intermediary.md @@ -7,8 +7,8 @@ The lab requires machine images (Talos, VyOS, Harvester) to be available on the Synology NAS for PXE provisioning via Tinkerbell. Images come from two sources: -1. **HTTP downloads** — Pre-built images from vendors (Talos Factory, Rancher, VyOS) -2. **Packer builds** — Custom images built from ISO + configuration (VyOS gateway) +1. **HTTP downloads** — Pre-built images from vendors (Talos Factory, Rancher) +2. **vyos-build** — Custom VyOS images built via Docker with configuration baked in (VyOS gateway) We need a GitOps-friendly pipeline to: - Declaratively define required images in Git @@ -61,7 +61,7 @@ Store images as GitHub Release assets. Script on NAS polls for new releases. │ │ 2. Parse images/images.yaml │ │ │ │ 3. For each image: │ │ │ │ - HTTP: Download → Verify → Decompress │ │ -│ │ - Packer: Build → Collect artifact │ │ +│ │ - vyos-build: Build in container → Collect artifact │ │ │ │ 4. Upload to iDrive e2 │ │ │ └──────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────┬──────────────────────────────────────┘ diff --git a/docs/architecture/appendices/A_repository_structure.md b/docs/architecture/appendices/A_repository_structure.md index 60fb564..4b9b606 100644 --- a/docs/architecture/appendices/A_repository_structure.md +++ b/docs/architecture/appendices/A_repository_structure.md @@ -28,6 +28,7 @@ lab/ ├── .github/ │ └── workflows/ │ ├── crossplane-build.yml # Build Crossplane packages on tag +│ ├── vyos-build.yml # Build VyOS image (vyos-build) │ ├── vyos-validate.yml # PR validation for VyOS │ └── vyos-deploy.yml # Deploy VyOS on merge │ @@ -41,13 +42,11 @@ lab/ │ │ └── vyos/ │ │ ├── configs/ │ │ │ └── gateway.conf -│ │ ├── packer/ -│ │ │ ├── vyos.pkr.hcl # Packer template for VyOS image -│ │ │ ├── variables.pkr.hcl # Packer variables -│ │ │ ├── http/ -│ │ │ │ └── preseed.cfg # VyOS preseed configuration +│ │ ├── vyos-build/ +│ │ │ ├── build-flavors/ +│ │ │ │ └── gateway.toml # Build flavor with baked-in config │ │ │ └── scripts/ -│ │ │ └── provision.sh # Post-install provisioning script +│ │ │ └── generate-flavor.sh # Injects SSH credentials │ │ └── ansible/ │ │ ├── playbooks/ │ │ │ └── deploy.yml @@ -207,7 +206,7 @@ lab/ │ ├── genesis/ # Runbooks and scripts │ ├── README.md # Overview and prerequisites - │ ├── 01-build-vyos-image.md # Build VyOS image with Packer + │ ├── 01-build-vyos-image.md # Build VyOS image with vyos-build │ ├── 02-seed-cluster.md # Create Talos VM on NAS │ ├── 03-deploy-argocd.md # Manual Argo CD install │ ├── 04-apply-bootstrap.md # Apply bootstrap Application @@ -218,7 +217,7 @@ lab/ │ ├── 09-provision-harvester.md # Tinkerbell provisions MS-02s │ ├── 10-expand-platform.md # Add CP-2, CP-3 VMs │ └── scripts/ - │ ├── build-vyos-image.sh # Runs Packer to build VyOS image + │ ├── build-vyos-image.sh # Runs vyos-build to create VyOS image │ ├── generate-talos-config.sh # Runs talhelper │ ├── create-seed-vm.sh # Creates Talos VM on NAS │ └── install-argocd.sh # Helm install Argo CD @@ -564,18 +563,16 @@ infrastructure/ VyOS provides the lab's core networking: routing, firewall, DHCP, and VPN. -**Bootstrap Image (Packer):** +**Bootstrap Image (vyos-build):** - VyOS is provisioned via Tinkerbell during genesis bootstrap -- Packer builds a raw disk image with the initial configuration baked in -- Image includes: VLANs, DHCP relay, BGP peering config, and firewall rules +- The `vyos-build` toolchain builds a raw disk image with configuration baked in +- Image includes: VLANs, DHCP relay, BGP peering config, firewall rules, and SSH credentials - Built once during initial bootstrap; stored on NAS for Tinkerbell to serve -- Future configuration changes use the Ansible CI/CD pipeline (not Packer rebuild) +- Future configuration changes use the Ansible CI/CD pipeline (not image rebuild) -**Packer Build (`infrastructure/network/vyos/packer/`):** -- `vyos.pkr.hcl` - Packer template defining image build -- `variables.pkr.hcl` - Variables (VyOS version, output format, etc.) -- `http/preseed.cfg` - VyOS preseed for automated installation -- `scripts/provision.sh` - Applies initial config from `configs/gateway.conf` +**VyOS Build (`infrastructure/network/vyos/vyos-build/`):** +- `build-flavors/gateway.toml` - Build flavor defining config.boot content +- `scripts/generate-flavor.sh` - Injects SSH credentials from SOPS secrets **Ongoing Management:** - Configuration stored as declarative VyOS config file @@ -584,6 +581,7 @@ VyOS provides the lab's core networking: routing, firewall, DHCP, and VPN. - GitHub Action deploys config on merge to main **Workflow Files:** +- `.github/workflows/vyos-build.yml` - Builds VyOS image via vyos-build - `.github/workflows/vyos-validate.yml` - Validates VyOS config on PR - `.github/workflows/vyos-deploy.yml` - Deploys VyOS config on merge @@ -705,7 +703,7 @@ Step-by-step runbooks and scripts for bootstrapping the lab from scratch. **Runbooks (in order):** -1. `01-build-vyos-image.md` - Build VyOS image with Packer (bakes in initial config) +1. `01-build-vyos-image.md` - Build VyOS image with vyos-build (bakes in initial config) 2. `02-seed-cluster.md` - Create Talos VM on NAS 3. `03-deploy-argocd.md` - Install Argo CD manually via Helm 4. `04-apply-bootstrap.md` - Apply bootstrap Application pointing to `bootstrap/seed/` @@ -718,7 +716,7 @@ Step-by-step runbooks and scripts for bootstrapping the lab from scratch. **Scripts:** -- `build-vyos-image.sh` - Runs Packer to build VyOS raw disk image +- `build-vyos-image.sh` - Runs vyos-build to create VyOS raw disk image - `generate-talos-config.sh` - Runs talhelper to generate machine configs - `create-seed-vm.sh` - Creates Talos VM on NAS - `install-argocd.sh` - Installs Argo CD via Helm diff --git a/docs/architecture/appendices/B_bootstrap_procedure.md b/docs/architecture/appendices/B_bootstrap_procedure.md index 2891130..4267fa0 100644 --- a/docs/architecture/appendices/B_bootstrap_procedure.md +++ b/docs/architecture/appendices/B_bootstrap_procedure.md @@ -114,28 +114,28 @@ Phase 4: Full Platform (3-Node HA) **Purpose:** Create a bootable VyOS disk image with the initial lab configuration baked in. **Mechanism:** -- Run Packer against `infrastructure/network/vyos/packer/vyos.pkr.hcl` -- Packer downloads VyOS ISO, installs to virtual disk, applies initial config -- Configuration sourced from `infrastructure/network/vyos/configs/gateway.conf` -- Output: Raw disk image stored on NAS for Tinkerbell to serve +- Use the official `vyos-build` toolchain via Docker (`vyos/vyos-build:current`) +- Build flavor in `infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml` defines the configuration +- Configuration sourced from `infrastructure/network/vyos/configs/gateway.conf` (embedded in flavor) +- SSH credentials injected from SOPS secrets at build time +- Output: Raw disk image uploaded to iDrive e2, synced to NAS via Cloud Sync **Why Now:** - VyOS image must exist before Tinkerbell can serve it - Baking config into image avoids manual configuration during bootstrap -- Packer runs on admin workstation (not in cluster) +- Build runs in CI (GitHub Actions) or locally via Docker **Image Contents:** -- VyOS LTS release +- VyOS rolling release - Pre-configured VLANs (10, 20, 30, 40, 50, 60) - DHCP relay for VLANs 30 and 40 (points to Tinkerbell) - BGP peering configuration for service VIPs - Firewall rules for lab isolation - SSH keys for initial access -**Output:** +**Build Workflow:** ``` -infrastructure/network/vyos/packer/output/ -└── vyos-lab.raw # Raw disk image (~2GB) +.github/workflows/vyos-build.yml → vyos/vyos-build container → iDrive e2 → NAS Cloud Sync ``` ### Step 2: Generate Talos Configs @@ -746,7 +746,7 @@ spec: ┌─────────────────────────────────────────────────────────────────────────┐ │ PHASE 1: SEED (NAS) │ │ │ -│ Step 1: Build VyOS Image (Packer) │ +│ Step 1: Build VyOS Image (vyos-build) │ │ ↓ │ │ Step 2: Generate Talos Configs (talhelper) │ │ ↓ │ @@ -825,7 +825,7 @@ spec: | Phase | Step | Name | Duration | Purpose | |:------|:-----|:-----|:---------|:--------| -| 1 | 1 | Build VyOS Image | 10 min | Create VyOS disk image with Packer | +| 1 | 1 | Build VyOS Image | 10 min | Create VyOS disk image with vyos-build | | 1 | 2 | Generate Talos Configs | 2 min | Create machine configs for all platform nodes | | 1 | 3 | Create Seed Talos VM | 15 min | Bootstrap initial Kubernetes cluster on NAS | | 1 | 4 | Deploy Argo CD | 5 min | Install GitOps controller | @@ -875,7 +875,7 @@ Before beginning the bootstrap, ensure the following are in place: | 40 | 10.10.40.0/24 | Tenant clusters | DHCP (Tinkerbell) | | 60 | 10.10.60.0/24 | Storage replication | Static IPs | -**Note:** VyOS is provisioned via Tinkerbell during bootstrap (Step 7). The Packer-built image includes: +**Note:** VyOS is provisioned via Tinkerbell during bootstrap (Step 7). The vyos-build image includes: - VLANs configured and routing enabled - DHCP relay enabled for VLANs 30 and 40 (points to Tinkerbell) - DNS forwarding configured @@ -885,7 +885,7 @@ Before beginning the bootstrap, ensure the following are in place: | Tool | Version | Purpose | |:-----|:--------|:--------| -| Packer | v1.11.0+ | Build VyOS disk image | +| Docker | v24.0.0+ | Run vyos-build container for VyOS image | | talhelper | v3.0.0+ | Generate Talos machine configs | | SOPS | v3.9.0+ | Encrypt Talos secrets | | kubectl | v1.31.0+ | Kubernetes CLI | diff --git a/docs/design/image-pipeline.md b/docs/design/image-pipeline.md index 6bfabc2..8a71e55 100644 --- a/docs/design/image-pipeline.md +++ b/docs/design/image-pipeline.md @@ -5,13 +5,12 @@ * **Goal:** Create a GitOps-driven pipeline that manages source images (ISOs, raw, qcow2) and distributes them to the lab via NAS/NFS. * **Input:** Declarative YAML configuration defining image sources, validation rules, and optional file updates. * **Output:** Validated images in iDrive e2 (S3-compatible), synced to Synology NAS via Cloud Sync. -* **Key Constraint:** Downstream builds (Packer) are triggered via Git changes, not direct invocation. ## 2. Existing Context * **Language/Stack:** Go 1.23+, GitHub Actions, iDrive e2, Synology Cloud Sync, Mergify * **Relevant Files:** - * `infrastructure/network/vyos/packer/` - Existing Packer build (consumes source images) + * `infrastructure/network/vyos/vyos-build/` - VyOS image build using vyos-build toolchain * `docs/architecture/08_concepts/storage.md` - NFS storage architecture * **Style Guide:** * Configuration files use YAML @@ -40,19 +39,12 @@ spec: algorithm: sha256 expected: sha256:def456... # Post-decompression checksum - # Source image that triggers downstream build + # VyOS ISO for reference/manual builds - name: vyos-iso source: url: https://github.com/vyos/vyos-rolling-nightly-builds/releases/download/1.5-rolling-202412190007/vyos-1.5-rolling-202412190007-amd64.iso checksum: sha256:abc123... destination: vyos/vyos-1.5-rolling-202412190007.iso - updateFile: - path: infrastructure/network/vyos/packer/source.auto.pkrvars.hcl - replacements: - - pattern: 'vyos_iso_url\s*=\s*"[^"]*"' - value: 'vyos_iso_url = "{{ .Source.URL }}"' - - pattern: 'vyos_iso_checksum\s*=\s*"[^"]*"' - value: 'vyos_iso_checksum = "{{ .Source.Checksum }}"' # Harvester ISO (no transformation) - name: harvester-1.4.0 @@ -144,12 +136,12 @@ tools/ images/ ├── images.yaml # Image manifest ├── e2.sops.yaml # e2 credentials (SOPS encrypted) -├── packer-ssh.sops.yaml # Packer SSH keypair (SOPS encrypted) +├── packer-ssh.sops.yaml # SSH keypair for image builds (SOPS encrypted) └── .sops.yaml # SOPS config (age + PGP keys) .github/workflows/ ├── images-sync.yml # Source image pipeline -└── packer-vyos.yml # VyOS image build (triggered by file change) +└── vyos-build.yml # VyOS image build using vyos-build toolchain ``` ## 4. CLI Interface @@ -182,7 +174,7 @@ labctl images prune [flags] --dry-run Show what would be removed labctl images upload [flags] - Upload a local file to e2. Used by Packer workflows to upload built images. + Upload a local file to e2. Used by build workflows to upload built images. Computes SHA256 checksum and writes metadata JSON (same format as sync). --source PATH Path to local file to upload (required) @@ -242,9 +234,9 @@ echo "files_changed=true" >> "$GITHUB_OUTPUT" │ Derived Images │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ -│ 5. PACKER WORKFLOW (packer-vyos.yml) │ -│ └─> Triggered by changes to source.auto.pkrvars.hcl │ -│ ├─> packer init && packer build │ +│ 5. VYOS BUILD WORKFLOW (vyos-build.yml) │ +│ └─> Triggered by changes to vyos-build/ or configs/ │ +│ ├─> Run vyos-build in Docker container │ │ ├─> Upload built image to e2 │ │ └─> Cloud Sync pulls to NAS │ │ │ @@ -277,7 +269,7 @@ echo "files_changed=true" >> "$GITHUB_OUTPUT" } } -// For upload (local files, e.g., Packer output) +// For upload (local files, e.g., vyos-build output) { "name": "vyos-gateway", "checksum": "sha256:def456...", @@ -285,7 +277,7 @@ echo "files_changed=true" >> "$GITHUB_OUTPUT" "uploadedAt": "2024-12-20T12:00:00Z", "source": { "type": "local", - "path": "infrastructure/network/vyos/packer/output/vyos-lab.raw" + "path": "/tmp/vyos-gateway.raw" } } ``` @@ -299,7 +291,7 @@ lab-images/ │ │ └── talos-1.9.1-amd64.raw │ ├── vyos/ │ │ ├── vyos-1.5-rolling-202412190007.iso # Source ISO -│ │ └── vyos-gateway.raw # Built by Packer +│ │ └── vyos-gateway.raw # Built by vyos-build │ └── harvester/ │ └── harvester-1.4.0-amd64.iso └── metadata/ @@ -407,49 +399,54 @@ jobs: --sops-age-key-file /tmp/age-key.txt ``` -### 8.2 Packer Build (packer-vyos.yml) +### 8.2 VyOS Build (vyos-build.yml) ```yaml name: Build VyOS Image on: push: - branches: [main] + branches: [master] paths: - - 'infrastructure/network/vyos/packer/**' + - 'infrastructure/network/vyos/vyos-build/**' + - 'infrastructure/network/vyos/configs/gateway.conf' pull_request: paths: - - 'infrastructure/network/vyos/packer/**' + - 'infrastructure/network/vyos/vyos-build/**' + - 'infrastructure/network/vyos/configs/gateway.conf' workflow_dispatch: + inputs: + upload: + description: 'Upload image to e2 storage' + type: boolean + default: true concurrency: - group: packer-vyos-${{ github.ref }} + group: vyos-build-${{ github.ref }} cancel-in-progress: false jobs: - # Validate on PRs (fast, no build) validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: hashicorp/setup-packer@v3.1.0 - with: - version: '1.11.2' - - - name: Packer Init - run: packer init infrastructure/network/vyos/packer - - - name: Packer Validate + - name: Validate flavor template run: | - # Validate with dummy values for required vars without defaults - # Note: vyos_iso_url/checksum come from source.auto.pkrvars.hcl (auto-loaded) - packer validate \ - -var "ssh_key_type=ssh-ed25519" \ - -var "ssh_public_key=AAAA" \ - infrastructure/network/vyos/packer - - # Build only on merge to main + TEMPLATE="infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml" + if [[ ! -f "${TEMPLATE}" ]]; then + echo "ERROR: Template file not found" + exit 1 + fi + if ! grep -q '%%SSH_KEY_TYPE%%' "${TEMPLATE}"; then + echo "ERROR: Template missing %%SSH_KEY_TYPE%% placeholder" + exit 1 + fi + if ! grep -q '%%SSH_PUBLIC_KEY%%' "${TEMPLATE}"; then + echo "ERROR: Template missing %%SSH_PUBLIC_KEY%% placeholder" + exit 1 + fi + build: if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest @@ -476,40 +473,49 @@ jobs: chmod 600 /tmp/age-key.txt - name: Extract SSH public key + env: + SOPS_AGE_KEY_FILE: /tmp/age-key.txt run: | - sops --decrypt --age-key-file /tmp/age-key.txt \ + sops --decrypt \ --extract '["ssh_public_key"]' images/packer-ssh.sops.yaml > /tmp/ssh_key.pub - - uses: hashicorp/setup-packer@v3.1.0 - with: - version: '1.11.2' + - name: Clone vyos-build + run: | + git clone -b current --single-branch --depth 1 \ + https://github.com/vyos/vyos-build.git /tmp/vyos-build - - name: Packer Init - run: packer init infrastructure/network/vyos/packer + - name: Generate build flavor + run: | + ./infrastructure/network/vyos/vyos-build/scripts/generate-flavor.sh \ + "$(cat /tmp/ssh_key.pub)" \ + /tmp/vyos-build/data/build-flavors/gateway.toml - - name: Packer Build + - name: Build VyOS image run: | - # Extract key type and body from public key - # Note: vyos_iso_url/checksum auto-loaded from source.auto.pkrvars.hcl - SSH_KEY_TYPE=$(awk '{print $1}' /tmp/ssh_key.pub) - SSH_KEY_BODY=$(awk '{print $2}' /tmp/ssh_key.pub) - packer build \ - -var "ssh_key_type=${SSH_KEY_TYPE}" \ - -var "ssh_public_key=${SSH_KEY_BODY}" \ - infrastructure/network/vyos/packer + VERSION="lab-$(date +%Y%m%d%H%M%S)" + docker run --rm --privileged \ + -v /tmp/vyos-build:/vyos \ + -v /dev:/dev \ + -w /vyos \ + vyos/vyos-build:current \ + bash -c "sudo ./build-vyos-image --architecture amd64 --build-by ci@lab.gilman.io --build-type release --version ${VERSION} gateway" + + RAW_FILE=$(find /tmp/vyos-build -maxdepth 1 -name "*.raw" -type f 2>/dev/null | head -1) + cp "${RAW_FILE}" /tmp/vyos-gateway.raw - name: Upload to e2 + if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.upload) run: | ./labctl images upload \ --credentials images/e2.sops.yaml \ --sops-age-key-file /tmp/age-key.txt \ - --source infrastructure/network/vyos/packer/output/vyos-lab.raw \ + --source /tmp/vyos-gateway.raw \ --destination vyos/vyos-gateway.raw ``` -**Packer Variable Loading:** Packer automatically loads `*.auto.pkrvars.hcl` files from the -template directory. The `source.auto.pkrvars.hcl` file (updated by `labctl images sync`) provides -`vyos_iso_url` and `vyos_iso_checksum` without explicit `-var-file` flags. +**VyOS Build Process:** The workflow uses the official `vyos/vyos-build` Docker container +with build flavors. The `gateway.toml` flavor embeds the VyOS configuration directly into +the image, with SSH credentials injected via placeholder replacement. ### 8.3 Mergify Configuration (.mergify.yml) @@ -533,11 +539,6 @@ pull_request_rules: **Check Name Format:** `Workflow Name / Job Name` -**Why `Build VyOS Image / validate`?** The bot PR from `updateFile` modifies -`infrastructure/.../source.auto.pkrvars.hcl`, which triggers `packer-vyos.yml` -(not `images-sync.yml`). Using the Packer validate check ensures the PR is -tested before auto-merge. - ## 9. Security ### SOPS-Encrypted Credentials @@ -558,15 +559,15 @@ sops: ### SOPS-Encrypted SSH Keypair -Used by Packer builds for VM provisioning. The public key is baked into the image; the private key is stored for future use (e.g., post-build testing). +Used by VyOS builds for image provisioning. The public key is baked into the image; the private key is stored for future use (e.g., post-build testing). ```bash -# Generate keypair -ssh-keygen -t ed25519 -f packer-ssh -N "" -C "packer-ci" +# Generate keypair (filename kept as packer-ssh for compatibility) +ssh-keygen -t ed25519 -f packer-ssh -N "" -C "vyos-ci" # Create SOPS file cat > images/packer-ssh.sops.yaml << 'EOF' -ssh_public_key: "ssh-ed25519 AAAA... packer-ci" +ssh_public_key: "ssh-ed25519 AAAA... vyos-ci" ssh_private_key: | -----BEGIN OPENSSH PRIVATE KEY----- ... diff --git a/images/images.yaml b/images/images.yaml index 6322f65..474d15b 100644 --- a/images/images.yaml +++ b/images/images.yaml @@ -11,10 +11,3 @@ spec: url: https://github.com/vyos/vyos-nightly-build/releases/download/2025.12.20-0020-rolling/vyos-2025.12.20-0020-rolling-generic-amd64.iso checksum: sha256:7f9eb1d6d9aacbd8fb684bb384cf2251d987097993fe7dbead8653ffbde31d04 destination: vyos/vyos-2025.12.20-0020-rolling-generic-amd64.iso - updateFile: - path: infrastructure/network/vyos/packer/source.auto.pkrvars.hcl - replacements: - - pattern: 'vyos_iso_url\s*=\s*"[^"]*"' - value: 'vyos_iso_url = "{{ .Source.URL }}"' - - pattern: 'vyos_iso_checksum\s*=\s*"[^"]*"' - value: 'vyos_iso_checksum = "{{ .Source.Checksum }}"' diff --git a/infrastructure/network/vyos/configs/gateway.conf b/infrastructure/network/vyos/configs/gateway.conf index 3b228d2..d49e7e5 100644 --- a/infrastructure/network/vyos/configs/gateway.conf +++ b/infrastructure/network/vyos/configs/gateway.conf @@ -318,9 +318,9 @@ service { system { domain-name lab.gilman.io host-name gateway - /* SSH keys managed separately via Ansible or Packer + /* SSH keys managed separately via Ansible or vyos-build * Do not commit real keys to this file - * Packer: provision.sh accepts SSH_KEY parameter + * vyos-build: baked into image via build-flavors/gateway.toml * Ansible: deploy.yml -e ssh_public_key_file=~/.ssh/id_rsa.pub */ name-server 1.1.1.1 diff --git a/infrastructure/network/vyos/packer/scripts/provision.sh b/infrastructure/network/vyos/packer/scripts/provision.sh deleted file mode 100644 index beedf4b..0000000 --- a/infrastructure/network/vyos/packer/scripts/provision.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/vbash -# VyOS Provisioning Script -# Loads gateway.conf and configures SSH key -# -# Arguments: -# $1 - SSH key type (e.g., ssh-rsa, ssh-ed25519, ecdsa-sha2-nistp256) -# $2 - SSH public key (base64 encoded key body) - -set -e - -SSH_KEY_TYPE="$1" -SSH_KEY="$2" - -# Config file location (copied by Packer) -CONFIG_FILE="/tmp/gateway.conf" - -# Source VyOS environment -source /opt/vyatta/etc/functions/script-template - -echo "=== VyOS Lab Gateway Provisioning ===" - -# ============================================================================= -# Validate Required Arguments -# ============================================================================= -if [ -z "${SSH_KEY_TYPE}" ] || [ -z "${SSH_KEY}" ]; then - echo "ERROR: SSH key type and key are required" - echo "Usage: provision.sh " - echo "Example: provision.sh ssh-ed25519 AAAAC3Nz..." - exit 1 -fi - -if [ ! -f "${CONFIG_FILE}" ]; then - echo "ERROR: Configuration file not found: ${CONFIG_FILE}" - exit 1 -fi - -echo "SSH Key Type: ${SSH_KEY_TYPE}" - -# ============================================================================= -# Load Configuration -# ============================================================================= -echo "Loading configuration from gateway.conf..." - -configure - -# Load the base configuration file (source of truth) -load "${CONFIG_FILE}" - -# ============================================================================= -# Configure SSH Key -# ============================================================================= -echo "Configuring SSH authentication..." - -set system login user vyos authentication public-keys admin type "${SSH_KEY_TYPE}" -set system login user vyos authentication public-keys admin key "${SSH_KEY}" - -# ============================================================================= -# Commit and Save -# ============================================================================= -echo "Committing configuration..." -commit - -echo "Saving configuration..." -save - -exit - -echo "" -echo "=== VyOS Lab Gateway Provisioning Complete ===" diff --git a/infrastructure/network/vyos/packer/source.auto.pkrvars.hcl b/infrastructure/network/vyos/packer/source.auto.pkrvars.hcl deleted file mode 100644 index 3e17c03..0000000 --- a/infrastructure/network/vyos/packer/source.auto.pkrvars.hcl +++ /dev/null @@ -1,2 +0,0 @@ -vyos_iso_url = "https://github.com/vyos/vyos-nightly-build/releases/download/2025.12.20-0020-rolling/vyos-2025.12.20-0020-rolling-generic-amd64.iso" -vyos_iso_checksum = "sha256:7f9eb1d6d9aacbd8fb684bb384cf2251d987097993fe7dbead8653ffbde31d04" diff --git a/infrastructure/network/vyos/packer/variables.pkr.hcl b/infrastructure/network/vyos/packer/variables.pkr.hcl deleted file mode 100644 index ccd6299..0000000 --- a/infrastructure/network/vyos/packer/variables.pkr.hcl +++ /dev/null @@ -1,69 +0,0 @@ -# VyOS Packer Variables -# Infrastructure: VP6630 Gateway Router -# -# Network configuration (interfaces, IPs, VLANs) is defined in: -# ../configs/gateway.conf -# -# If interface names need to change, update gateway.conf directly. - -variable "vyos_iso_url" { - type = string - description = "URL to VyOS ISO image" - default = "https://github.com/vyos/vyos-rolling-nightly-builds/releases/download/1.5-rolling-202412190007/vyos-1.5-rolling-202412190007-amd64.iso" -} - -variable "vyos_iso_checksum" { - type = string - description = "SHA256 checksum of VyOS ISO (format: sha256:HASH)" - # No default - must be provided via build script or command line - # Build script calculates: sha256:$(sha256sum vyos.iso | awk '{print $1}') -} - -variable "output_directory" { - type = string - description = "Directory for output image" - default = "output" -} - -variable "disk_size" { - type = string - description = "Disk size for VyOS image" - default = "8G" -} - -variable "memory" { - type = number - description = "Memory for build VM (MB)" - default = 2048 -} - -variable "cpus" { - type = number - description = "CPUs for build VM" - default = 2 -} - -# SSH Configuration (required) -variable "ssh_key_type" { - type = string - description = "SSH key type (e.g., ssh-rsa, ssh-ed25519, ecdsa-sha2-nistp256)" - # No default - must be provided via build script - - validation { - condition = length(var.ssh_key_type) > 0 - error_message = "SSH key type is required." - } -} - -variable "ssh_public_key" { - type = string - description = "SSH public key body (base64 encoded) for vyos user" - sensitive = true - # No default - must be provided via build script or PKR_VAR_ssh_public_key - # Build script extracts from: ~/.ssh/id_rsa.pub (or --ssh-key flag) - - validation { - condition = length(var.ssh_public_key) > 0 - error_message = "SSH public key is required. Set PKR_VAR_ssh_public_key or use build script with --ssh-key flag." - } -} diff --git a/infrastructure/network/vyos/packer/vyos.pkr.hcl b/infrastructure/network/vyos/packer/vyos.pkr.hcl deleted file mode 100644 index f30b508..0000000 --- a/infrastructure/network/vyos/packer/vyos.pkr.hcl +++ /dev/null @@ -1,136 +0,0 @@ -# VyOS Gateway Image Build -# Builds a raw disk image with lab configuration baked in -# Target: VP6630 (Minisforum) - Lab Gateway Router - -packer { - required_plugins { - qemu = { - version = ">= 1.1.0" - source = "github.com/hashicorp/qemu" - } - } -} - -source "qemu" "vyos" { - iso_url = var.vyos_iso_url - iso_checksum = var.vyos_iso_checksum - output_directory = var.output_directory - shutdown_command = "sudo poweroff" - disk_size = var.disk_size - format = "raw" - accelerator = "kvm" - memory = var.memory - cpus = var.cpus - net_device = "virtio-net" - disk_interface = "virtio" - - # VyOS boot configuration - boot_wait = "5s" - boot_command = [ - # Wait for live system to boot - "", - # Login as vyos user (default password: vyos) - "vyos", - "vyos", - # Run automated installation - "install image", - # Confirm disk selection - "", - # Confirm partition deletion - "Yes", - # Accept default root partition size - "", - # Image name - "", - # Copy running config - "", - # Set password for vyos user - "vyos", - "vyos", - # Installation completes - "", - # Reboot into installed system - "reboot", - # Login to installed system - "vyos", - "vyos", - # Enable SSH for provisioner - "configure", - "set service ssh port 22", - "set system login user vyos authentication plaintext-password vyos", - "commit", - "save", - "exit" - ] - - # SSH connection for provisioner - ssh_username = "vyos" - ssh_password = "vyos" - ssh_timeout = "30m" - ssh_port = 22 - - # VM configuration - vm_name = "vyos-lab" - headless = true - - # QEMU settings - qemuargs = [ - ["-m", "${var.memory}"], - ["-smp", "${var.cpus}"] - ] -} - -build { - name = "vyos-lab-gateway" - sources = ["source.qemu.vyos"] - - # Copy gateway configuration - provisioner "file" { - source = "../configs/gateway.conf" - destination = "/tmp/gateway.conf" - } - - # Copy provisioning script - provisioner "file" { - source = "scripts/provision.sh" - destination = "/tmp/provision.sh" - } - - # Run provisioning script (SSH key is required) - provisioner "shell" { - inline = [ - "chmod +x /tmp/provision.sh", - "sudo /tmp/provision.sh '${var.ssh_key_type}' '${var.ssh_public_key}'" - ] - } - - # Final cleanup - provisioner "shell" { - inline = [ - # Remove SSH password auth (key-only after provisioning) - "source /opt/vyatta/etc/functions/script-template", - "configure", - "delete system login user vyos authentication plaintext-password", - "commit", - "save", - "exit", - # Clean up temp files - "rm -f /tmp/gateway.conf /tmp/provision.sh", - # Clear command history - "history -c" - ] - } - - # Rename output file to ensure .raw extension - post-processor "shell-local" { - inline = [ - "cd ${var.output_directory}", - "if [ -f 'vyos-lab' ] && [ ! -f 'vyos-lab.raw' ]; then mv vyos-lab vyos-lab.raw; fi", - "echo 'VyOS image built successfully!'", - "echo 'Output: ${var.output_directory}/vyos-lab.raw'", - "echo ''", - "echo 'To use with Tinkerbell, copy to NAS:'", - "echo ' scp ${var.output_directory}/vyos-lab.raw nas:/volume1/images/vyos-lab.raw'" - ] - } -} diff --git a/infrastructure/network/vyos/vyos-build/README.md b/infrastructure/network/vyos/vyos-build/README.md new file mode 100644 index 0000000..36478b0 --- /dev/null +++ b/infrastructure/network/vyos/vyos-build/README.md @@ -0,0 +1,74 @@ +# VyOS Gateway Image Build + +This directory contains the configuration and scripts for building custom VyOS gateway images using the official `vyos-build` toolchain. + +## Overview + +This approach: + +1. Uses the official `vyos/vyos-build` Docker container +2. Bakes the gateway configuration directly into the image via build flavors +3. Produces a raw disk image suitable for Tinkerbell/NAS deployment +4. Injects SSH credentials from SOPS secrets at build time + +## Directory Structure + +``` +vyos-build/ +├── build-flavors/ +│ └── gateway.toml # Build flavor template with config.boot +├── scripts/ +│ └── generate-flavor.sh # Injects SSH credentials into flavor +└── README.md +``` + +## Build Process + +The GitHub Actions workflow (`.github/workflows/vyos-build.yml`) handles the full build: + +1. Decrypts SSH public key from `images/packer-ssh.sops.yaml` +2. Generates the final flavor TOML with credentials injected +3. Clones `vyos-build` repository +4. Runs the build in the `vyos/vyos-build:current` container +5. Uploads the resulting image to iDrive e2 + +### Local Build (for testing) + +```bash +# 1. Clone vyos-build +git clone -b current --single-branch https://github.com/vyos/vyos-build.git /tmp/vyos-build + +# 2. Generate flavor with SSH key +./scripts/generate-flavor.sh "ssh-ed25519 AAAA..." /tmp/vyos-build/data/build-flavors/gateway.toml + +# 3. Run build in container +docker run --rm -it --privileged \ + -v /tmp/vyos-build:/vyos \ + -v /dev:/dev \ + vyos/vyos-build:current bash + +# Inside container: +cd /vyos +sudo ./build-vyos-image --architecture amd64 --build-by "local@test" gateway + +# Output: /vyos/build/vyos-*.raw +``` + +## Configuration + +The `gateway.toml` flavor file contains: + +- **`image_format = "raw"`**: Output format for Tinkerbell deployment +- **`disk_size = 8`**: 8GB disk image +- **`default_config`**: Full VyOS configuration embedded in the image + +The configuration matches `infrastructure/network/vyos/configs/gateway.conf` with SSH credentials added via placeholders: +- `%%SSH_KEY_TYPE%%` - SSH key type (e.g., `ssh-ed25519`) +- `%%SSH_PUBLIC_KEY%%` - SSH public key body + +## Relationship to Other Files + +| File | Purpose | +|------|---------| +| `configs/gateway.conf` | Source of truth for VyOS config (Ansible applies updates) | +| `vyos-build/build-flavors/gateway.toml` | Build-time config with SSH credentials | diff --git a/infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml b/infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml new file mode 100644 index 0000000..f4dbdd3 --- /dev/null +++ b/infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml @@ -0,0 +1,345 @@ +# VyOS Gateway Build Flavor +# Produces a raw disk image with lab configuration baked in +# Target: VP6630 (Minisforum) - Lab Gateway Router +# +# Usage: +# This is a template file - use generate-flavor.sh to create the final TOML +# with SSH credentials injected from SOPS secrets. + +# Output format: raw disk image for Tinkerbell/NAS deployment +image_format = "raw" + +# Image settings +disk_size = 8 # GB + +# Include QEMU guest agent for VM environments +packages = ["qemu-guest-agent"] + +# Boot settings - serial console for headless operation +[boot_settings] +console_type = "serial" +serial_console = "ttyS0,115200n8" + +# Default configuration - will be merged with SSH key at build time +# This serves as the base config.boot content +# +# IMPORTANT: The SSH key placeholder %%SSH_KEY_TYPE%% and %%SSH_PUBLIC_KEY%% +# will be replaced by the build script with actual values from SOPS secrets. +# +# The configuration below is based on infrastructure/network/vyos/configs/gateway.conf +# converted to VyOS config.boot format (nested curly braces) +default_config = ''' +firewall { + group { + network-group HOME_NETWORK { + network 192.168.0.0/24 + } + network-group LAB_NETWORKS { + network 10.10.0.0/16 + } + network-group RFC1918 { + network 10.0.0.0/8 + network 172.16.0.0/12 + network 192.168.0.0/16 + } + } + interface eth4 { + in { + name WAN_TO_LAB + } + local { + name LOCAL + } + out { + name LAB_TO_WAN + } + } + ipv4 { + name LAB_TO_WAN { + default-action accept + rule 10 { + action accept + description "Allow established/related" + state { + established + related + } + } + rule 20 { + action drop + description "Block new connections to home network" + destination { + group { + network-group HOME_NETWORK + } + } + state { + new + } + } + } + name LOCAL { + default-action drop + rule 10 { + action accept + state { + established + related + } + } + rule 20 { + action accept + description "Allow ICMP" + protocol icmp + } + rule 30 { + action accept + description "Allow SSH from lab" + destination { + port 22 + } + protocol tcp + source { + group { + network-group LAB_NETWORKS + } + } + } + rule 31 { + action accept + description "Allow SSH from home" + destination { + port 22 + } + protocol tcp + source { + group { + network-group HOME_NETWORK + } + } + } + rule 40 { + action accept + description "Allow DNS from lab" + destination { + port 53 + } + protocol udp + source { + group { + network-group LAB_NETWORKS + } + } + } + rule 50 { + action accept + description "Allow DHCP from lab" + destination { + port 67 + } + protocol udp + source { + group { + network-group LAB_NETWORKS + } + } + } + rule 60 { + action accept + description "Allow BGP from lab" + destination { + port 179 + } + protocol tcp + source { + group { + network-group LAB_NETWORKS + } + } + } + } + name WAN_TO_LAB { + default-action drop + rule 10 { + action accept + description "Allow established/related" + state { + established + related + } + } + rule 20 { + action accept + description "Allow from home network" + source { + group { + network-group HOME_NETWORK + } + } + } + } + } +} +interfaces { + ethernet eth4 { + address 192.168.0.2/24 + description "WAN - Transit to Home (CCR2004)" + } + ethernet eth5 { + description "TRUNK - Lab Switch (CRS)" + vif 10 { + address 10.10.10.1/24 + description "LAB_MGMT - Infrastructure Management" + } + vif 20 { + address 10.10.20.1/24 + description "LAB_PROV - Provisioning (PXE)" + } + vif 30 { + address 10.10.30.1/24 + description "LAB_PLATFORM - Platform Cluster" + } + vif 40 { + address 10.10.40.1/24 + description "LAB_CLUSTER - Tenant Clusters" + } + vif 50 { + address 10.10.50.1/24 + description "LAB_SERVICE - Service VIPs (BGP)" + } + vif 60 { + address 10.10.60.1/24 + description "LAB_STORAGE - Storage Replication" + } + } +} +nat { + source { + rule 100 { + outbound-interface { + name eth4 + } + source { + address 10.10.0.0/16 + } + translation { + address masquerade + } + } + } +} +protocols { + bgp { + address-family { + ipv4-unicast { + network 10.10.50.0/24 { + } + } + } + neighbor 10.10.30.10 { + address-family { + ipv4-unicast { + } + } + description "platform-cp-1 (UM760)" + remote-as 64513 + shutdown + } + neighbor 10.10.30.11 { + address-family { + ipv4-unicast { + } + } + description "platform-cp-2" + remote-as 64513 + shutdown + } + neighbor 10.10.30.12 { + address-family { + ipv4-unicast { + } + } + description "platform-cp-3" + remote-as 64513 + shutdown + } + parameters { + bestpath { + as-path { + multipath-relax + } + } + router-id 10.10.50.1 + } + system-as 64512 + } + static { + route 0.0.0.0/0 { + next-hop 192.168.0.1 { + } + } + } +} +service { + dhcp-relay { + interface eth5.30 + interface eth5.40 + relay-options { + relay-agents-packets discard + } + server 10.10.20.10 + } + dhcp-server { + shared-network-name LAB_MGMT { + subnet 10.10.10.0/24 { + lease 86400 + option { + default-router 10.10.10.1 + name-server 10.10.10.1 + } + range 0 { + start 10.10.10.200 + stop 10.10.10.250 + } + subnet-id 10 + } + } + } + dns { + forwarding { + allow-from 10.10.0.0/16 + listen-address 10.10.10.1 + listen-address 10.10.20.1 + listen-address 10.10.30.1 + listen-address 10.10.40.1 + listen-address 10.10.50.1 + system + } + } + ssh { + disable-password-authentication + port 22 + } +} +system { + domain-name lab.gilman.io + host-name gateway + login { + user vyos { + authentication { + public-keys admin { + key "%%SSH_PUBLIC_KEY%%" + type %%SSH_KEY_TYPE%% + } + } + } + } + name-server 1.1.1.1 + name-server 8.8.8.8 + ntp { + server time.cloudflare.com { + } + } + time-zone America/Los_Angeles +} +''' diff --git a/infrastructure/network/vyos/vyos-build/scripts/generate-flavor.sh b/infrastructure/network/vyos/vyos-build/scripts/generate-flavor.sh new file mode 100755 index 0000000..398429c --- /dev/null +++ b/infrastructure/network/vyos/vyos-build/scripts/generate-flavor.sh @@ -0,0 +1,68 @@ +#!/bin/bash +# Generate VyOS build flavor with SSH credentials from SOPS secrets +# +# Usage: +# ./generate-flavor.sh +# +# Arguments: +# ssh_public_key - Full SSH public key (e.g., "ssh-ed25519 AAAAC3Nz... comment") +# output_file - Path to write the generated flavor TOML +# +# Example: +# ./generate-flavor.sh "$(sops -d --extract '["ssh_public_key"]' images/packer-ssh.sops.yaml)" gateway-final.toml + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TEMPLATE_FILE="${SCRIPT_DIR}/../build-flavors/gateway.toml" + +usage() { + echo "Usage: $0 " + echo "" + echo "Arguments:" + echo " ssh_public_key - Full SSH public key string" + echo " output_file - Path for generated flavor TOML" + exit 1 +} + +if [[ $# -ne 2 ]]; then + usage +fi + +SSH_PUBLIC_KEY="$1" +OUTPUT_FILE="$2" + +# Validate inputs +if [[ -z "${SSH_PUBLIC_KEY}" ]]; then + echo "ERROR: SSH public key is required" + exit 1 +fi + +if [[ ! -f "${TEMPLATE_FILE}" ]]; then + echo "ERROR: Template file not found: ${TEMPLATE_FILE}" + exit 1 +fi + +# Parse SSH public key: "type key comment" -> extract type and key +SSH_KEY_TYPE=$(echo "${SSH_PUBLIC_KEY}" | awk '{print $1}') +SSH_KEY_BODY=$(echo "${SSH_PUBLIC_KEY}" | awk '{print $2}') + +if [[ -z "${SSH_KEY_TYPE}" ]] || [[ -z "${SSH_KEY_BODY}" ]]; then + echo "ERROR: Could not parse SSH public key" + echo "Expected format: 'type key [comment]'" + echo "Got: '${SSH_PUBLIC_KEY}'" + exit 1 +fi + +echo "=== Generating VyOS Build Flavor ===" +echo "SSH Key Type: ${SSH_KEY_TYPE}" +echo "SSH Key Length: ${#SSH_KEY_BODY} characters" +echo "Output: ${OUTPUT_FILE}" + +# Generate the final flavor by replacing placeholders +sed \ + -e "s|%%SSH_KEY_TYPE%%|${SSH_KEY_TYPE}|g" \ + -e "s|%%SSH_PUBLIC_KEY%%|${SSH_KEY_BODY}|g" \ + "${TEMPLATE_FILE}" > "${OUTPUT_FILE}" + +echo "=== Flavor generated successfully ===" diff --git a/tools/labctl/cmd/images/upload.go b/tools/labctl/cmd/images/upload.go index 16a9808..481bf97 100644 --- a/tools/labctl/cmd/images/upload.go +++ b/tools/labctl/cmd/images/upload.go @@ -21,7 +21,7 @@ var uploadCmd = &cobra.Command{ Short: "Upload a local file to e2", Long: `Upload a local file to e2 storage. -The upload command is used by Packer workflows to upload built images. +The upload command is used by build workflows to upload built images. It computes the SHA256 checksum and writes metadata JSON in the same format as the sync command.`, RunE: runUpload, diff --git a/tools/labctl/internal/config/manifest_test.go b/tools/labctl/internal/config/manifest_test.go index 247226d..aa1fbcb 100644 --- a/tools/labctl/internal/config/manifest_test.go +++ b/tools/labctl/internal/config/manifest_test.go @@ -63,7 +63,7 @@ spec: checksum: sha256:abc123 destination: vyos/vyos-1.5.iso updateFile: - path: infrastructure/network/vyos/packer/source.auto.pkrvars.hcl + path: infrastructure/example/vars.hcl replacements: - pattern: 'vyos_iso_url\s*=\s*"[^"]*"' value: 'vyos_iso_url = "{{ .Source.URL }}"' diff --git a/tools/labctl/internal/store/s3_test.go b/tools/labctl/internal/store/s3_test.go index 23070bf..c101b07 100644 --- a/tools/labctl/internal/store/s3_test.go +++ b/tools/labctl/internal/store/s3_test.go @@ -526,7 +526,7 @@ func TestImageMetadata_JSON(t *testing.T) { UploadedAt: time.Date(2024, 12, 20, 12, 0, 0, 0, time.UTC), Source: SourceMetadata{ Type: "local", - Path: "infrastructure/network/vyos/packer/output/vyos-lab.raw", + Path: "/tmp/vyos-gateway.raw", }, } diff --git a/tools/labctl/internal/updater/file_test.go b/tools/labctl/internal/updater/file_test.go index 5913eea..ad8d8ef 100644 --- a/tools/labctl/internal/updater/file_test.go +++ b/tools/labctl/internal/updater/file_test.go @@ -116,7 +116,7 @@ vyos_iso_checksum = "sha256:newchecksum"`, wantModified: false, }, { - name: "HCL packer vars format", + name: "HCL vars format", replacements: []Replacement{ {Pattern: `vyos_iso_url\s*=\s*"[^"]*"`, Value: `vyos_iso_url = "{{ .Source.URL }}"`}, },