diff --git a/.github/workflows/images-sync.yml b/.github/workflows/images-sync.yml new file mode 100644 index 0000000..bc0d5b2 --- /dev/null +++ b/.github/workflows/images-sync.yml @@ -0,0 +1,97 @@ +name: Sync Images + +on: + push: + branches: [master] + paths: + - 'images/**' + pull_request: + paths: + - 'images/**' + workflow_dispatch: + inputs: + force: + description: 'Force re-upload all images' + type: boolean + default: false + prune: + description: 'Run prune after sync' + type: boolean + default: false + +concurrency: + group: images-sync-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: write + pull-requests: write + +jobs: + sync: + runs-on: ubuntu-latest + 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 + if: github.event_name != 'pull_request' + 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 + if: github.event_name != 'pull_request' + run: | + echo "${{ secrets.SOPS_AGE_KEY }}" > /tmp/age-key.txt + chmod 600 /tmp/age-key.txt + + # PR: validate manifest only (no credentials needed) + - name: Validate Manifest (PR) + if: github.event_name == 'pull_request' + run: ./labctl images validate + + # Push/dispatch: full sync with credentials + - name: Sync Images + if: github.event_name != 'pull_request' + id: sync + run: | + FLAGS="" + if [ "${{ inputs.force }}" == "true" ]; then FLAGS="--force"; fi + + ./labctl images sync \ + --credentials images/e2.sops.yaml \ + --sops-age-key-file /tmp/age-key.txt \ + $FLAGS + + - name: Create PR if files changed + if: github.event_name == 'push' && steps.sync.outputs.files_changed == 'true' + uses: peter-evans/create-pull-request@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: 'chore: update source image references' + title: 'chore: update source image references' + body: | + Automated update of source image references. + + Updated by `labctl images sync`. + branch: automated/image-updates + labels: automated + delete-branch: true + + - name: Prune Orphaned Images + if: github.event_name != 'pull_request' && inputs.prune == true + run: | + ./labctl images prune \ + --credentials images/e2.sops.yaml \ + --sops-age-key-file /tmp/age-key.txt diff --git a/.github/workflows/packer-vyos.yml b/.github/workflows/packer-vyos.yml new file mode 100644 index 0000000..6b009f8 --- /dev/null +++ b/.github/workflows/packer-vyos.yml @@ -0,0 +1,138 @@ +# 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/.mergify.yml b/.mergify.yml new file mode 100644 index 0000000..dd71300 --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,17 @@ +# TODO: Once vyos-build workflow is implemented, add check-success condition +# to verify the VyOS build before auto-merging image update PRs. + +pull_request_rules: + - name: Auto-merge automated image updates + conditions: + - author=github-actions[bot] + - label=automated + - base=master + - "#approved-reviews-by>=0" + actions: + merge: + method: squash + commit_message_template: | + {{ title }} + + {{ body }} diff --git a/images/e2.sops.yaml b/images/e2.sops.yaml index 2ea9c6a..8dd1eb8 100644 --- a/images/e2.sops.yaml +++ b/images/e2.sops.yaml @@ -1,31 +1,31 @@ -access_key: ENC[AES256_GCM,data:2umSrpnNWg1Z/F4THPwnqA529qA=,iv:s+IusD2M/GkDILSmIeU9O6d9BcOCwFKv2ioMAWWnQxk=,tag:udWIa/WYwyYIGpiRUo0m8w==,type:str] -secret_key: ENC[AES256_GCM,data:Lwjs2u64QbX/DEkipUiq8XzP+xP27k7CnSRm2gs64EjWROuF6JSMvQ==,iv:cnO7A7UxCwKclt401BtUE/3nY1uvNhnClu0Upo+xcSg=,tag:bDvbTC0hXcnGNmZPrXLJIQ==,type:str] -endpoint: s3.us-west-1.idrivee2.com -bucket: provisioning +access_key: ENC[AES256_GCM,data:O33rmWp1jLBpsMm7/YqjHLJyaV0=,iv:fWoChZ+t5g8Eie/2Azp0sK28oB8Z0NXaZPlGXUoL0ME=,tag:3dXoEZWO2xstUHLntgN7Xw==,type:str] +secret_key: ENC[AES256_GCM,data:VPtl+wfbMCgj9lC7AvN+d/ifdE+EurAnVrmsCq8T74RlEP1QWAry1A==,iv:3hlYNxvvhY4bhW8thMtT00fDZsPUd0LGugSLs1sRKnM=,tag:kR+akUTyODwFHqaWXFPjCg==,type:str] +endpoint: ENC[AES256_GCM,data:kvtqTVSjychwy9PY7U9IL8bD6d+hcDZg6sE7rEr0zUGZ,iv:vuf01bAH7XvlJwoGSv4BAOfgJDvbx2NO5Yk40Wqj8yo=,tag:V3pNe2+nFLGfe0FTE04bjg==,type:str] +bucket: ENC[AES256_GCM,data:syQNZMQci1VPV00D,iv:M2srbHnS8X72zYIrWQMVywH08uDiIdFlA8NqngCOUrU=,tag:Vi1RcWzc563YW0nO0zs+cw==,type:str] sops: age: - recipient: age1d9n4x345xfahp0wmjak4gjzawpjvmcxmyf5knct7yy84mznq2sdqp0dy2d enc: | -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhTHNoLytHYUpBYmxYaWlt - WTV2S2lEUXZqMnFTcWFzUG91c3JTWXRVM2dBClVrKzF5eHo2N1F3L256T3VqLzNj - b2tBM0lvM2ZiOUw0d21yM2FJN2hCMjAKLS0tIDh5c0phUEZPQi9PSE4zb3oyZ25Q - bjRYeEtEUVd1MVBzUmV3eVVPT2FJYTgKH95crCdvD+NnA6MtlZfLfyooY7HBDlQn - zO5i3kjcxby2cuGTdpL3P3qq9DCCJ32dXajVPq7rFElqxDQmRv/uWQ== + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWRHNkK2lock5NallTWWxh + VEJ6NHAzQ28xQTU2TDErTWttVjlHYzFVYWhNCkE1WWo2ZnhwL3ZjQTk4azh2RDdl + dDVBS2VLQy9zQTY1L0hSTk1qbzlSSmsKLS0tIHZKOEdVdHNnQy9jdTUwb3dpMU1M + a09BeDcwd2VPa2FyRjNHdlV2TFozeWMKw5RtkQxeXqP0K8tzAAtKsNhLSse0FEDZ + +imP4X0F1dmllr19MZUpTh1TU505usmY1YpIUMo4xos/9svc5x1qWw== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-20T06:07:03Z" - mac: ENC[AES256_GCM,data:S3J3+HCyfnn2dbPgCDS2tFbSGKvxcDLJl1xSd/6lh5qgwaOFGH8r8rJBt3iC4JrBIquu7A4xIFzumFpe/5cfRyyc/CgOjlbUl5ylrNyZrwjAIJTSsgd3jKfvn6p8UyDWrlNaNrLuQn3BxObKYV0qL//v8ZXKMKKM9QHjIUgjgww=,iv:g68vsNFRhcb1039EWTTd31zG+j4ld0FrKWIDnfYE9vY=,tag:1X4Y7watmWqy7t93W5pbUA==,type:str] + lastmodified: "2025-12-20T06:33:06Z" + mac: ENC[AES256_GCM,data:j0Rh4NvVILtGhck94NLsyvHgHupF4SPEznVV8p/4uqlAgS05iURkAblJKnUIK0Ch3MDShSF6kGGqDva88a9Af02wAiy4fBqzzLM8RIF0nYzXdLn0RK2SeFDkB5uC6Ae0jJ36HtjetprDyQTVXTVpoojq7fJWiNMz7FwopkWmcvE=,iv:ydNz6E0nIXk+FhclVZWcjoHRVeJA/zlnwCvrWIKLOIc=,tag:J8he0z5Ard0eFJHeOWYYOg==,type:str] pgp: - - created_at: "2025-12-20T06:06:04Z" + - created_at: "2025-12-20T06:33:06Z" enc: |- -----BEGIN PGP MESSAGE----- - hF4DhYpGbIWgl5wSAQdA7WWkTJsZwJlD0iuCowi3CijlWrFhwPoh65VfT09Rimgw - osO38nxkM5tMWsbTsGp781OepdGbnepoJv8HCXbaVFMJZNOTo1lGviSUz+9bSArP - 0lwBWrR4wsSr5TGk8qe7AJ3w8gabG13KJs4JXXVXdwXO00znq4FvSU8Sw5vD0Vmp - iK3Uq832Mij03gaEQPfMqXVmXMnrxdT56LS3qUqwzMa7srMrSWeB2R7D1l+lwQ== - =+0NA + hF4DhYpGbIWgl5wSAQdAYxP5cB5Uum2gpgLdjK/LD98NwDSfvKqqpOQQt8QkWQMw + nM9Czjci1spTfb9qIguwOJfdWtRYXwPFzmyxj2niRE+JXqGAXmhgKVZ1h69FRIct + 0l4B3mt/VoWoLkJD52BiV32hcMwtlkK2D+g9tL2eNrBEZL01cuHs4CIy7nief1n3 + kzzn+hrtCGVuwaO1joc+3wx0qkajVJ2X6RBfuzxVQBW40bFgAAdZl6t6dnJUATeX + =+q8v -----END PGP MESSAGE----- fp: 3965F16E293466CFE77D47F38C15553EEB22DB2A - encrypted_regex: ^(access_key|secret_key)$ + unencrypted_suffix: _unencrypted version: 3.11.0