From e91624bb77c499c01f897353d8bf15ac6f6bef12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Sun, 13 Sep 2026 22:08:43 +0300 Subject: [PATCH 1/2] Add AMI manifest snapshot to testinfra (PR-side) Rides the session-scoped instance test_ami_nix.py already boots per PR, zero extra EC2/build cost. Uploaded as a build artifact. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/testinfra-ami-build.yml | 9 +++++++++ testinfra/manifest-snapshot.sh | 23 +++++++++++++++++++++++ testinfra/test_ami_nix.py | 11 +++++++++++ 3 files changed, 43 insertions(+) create mode 100755 testinfra/manifest-snapshot.sh diff --git a/.github/workflows/testinfra-ami-build.yml b/.github/workflows/testinfra-ami-build.yml index 7288358399..948875b344 100644 --- a/.github/workflows/testinfra-ami-build.yml +++ b/.github/workflows/testinfra-ami-build.yml @@ -126,11 +126,20 @@ jobs: env: AMI_ID: ${{ steps.build-ami.outputs.stage2_ami_id }} EXECUTION_ID: ${{ steps.build-ami.outputs.execution_id }} + MANIFEST_OUTPUT: ami-manifest-${{ matrix.postgres_version }}-${{ matrix.target.arch }}.txt run: | # TODO: use uv for pkg mgmt pip3 install boto3 'boto3-stubs[essential]' ec2instanceconnectcli pytest 'pytest-testinfra[paramiko]' requests pytest -vv -s testinfra/test_ami_nix.py + - name: Upload AMI manifest + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: ami-manifest-${{ matrix.postgres_version }}-${{ matrix.target.arch }} + path: ami-manifest-*.txt + overwrite: true + retention-days: 1 + - name: Cleanup resources on build cancellation if: ${{ cancelled() }} run: | diff --git a/testinfra/manifest-snapshot.sh b/testinfra/manifest-snapshot.sh new file mode 100755 index 0000000000..0f5a949f91 --- /dev/null +++ b/testinfra/manifest-snapshot.sh @@ -0,0 +1,23 @@ +#!/bin/sh +set -eu + +EXCLUDE='^/(proc|run|tmp|var/log|data)(/|$)' + +find / -xdev \( -type f -o -type l \) 2>/dev/null | grep -Ev "$EXCLUDE" | sort | while read -r f; do + if [ -L "$f" ]; then + printf '%s\tlink\t%s\n' "$f" "$(readlink "$f")" + else + printf '%s\t%s\t%s\n' "$f" "$(stat -c '%a:%U:%G' "$f" 2>/dev/null || echo '?')" "$(sha256sum "$f" 2>/dev/null | cut -d' ' -f1)" + fi +done + +echo '--- units ---' +systemctl list-unit-files --no-pager 2>/dev/null | sort +echo '--- users ---' +getent passwd | sort +echo '--- groups ---' +getent group | sort +echo '--- nft ---' +nft list ruleset 2>/dev/null || true +echo '--- sysctl ---' +sysctl -a 2>/dev/null | sort diff --git a/testinfra/test_ami_nix.py b/testinfra/test_ami_nix.py index cfd77554d6..ed0e31bf52 100644 --- a/testinfra/test_ami_nix.py +++ b/testinfra/test_ami_nix.py @@ -1407,3 +1407,14 @@ def test_apparmor_denies_access_to_sensitive_paths(host): f"to have succeeded.\nstdout: {result['stdout']}\nstderr: {result['stderr']}" ) print(f"Confirmed: access to {test_file} denied by AppArmor") + + +def test_manifest_snapshot(host): + ssh = host["ssh"] + script = os.path.join(os.path.dirname(__file__), "manifest-snapshot.sh") + upload_file_via_sftp(ssh, script, "/tmp/manifest-snapshot.sh") + run_ssh_command(ssh, "chmod +x /tmp/manifest-snapshot.sh") + result = run_ssh_command(ssh, "sudo /tmp/manifest-snapshot.sh") + assert result["succeeded"], result["stderr"] + with open(os.environ.get("MANIFEST_OUTPUT", "ami-manifest.txt"), "w") as f: + f.write(result["stdout"]) From 5990729303359dbb3732790c8b648ac0298165bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Sun, 13 Sep 2026 22:08:48 +0300 Subject: [PATCH 2/2] Add AMI manifest snapshot to ami-release-nix.yml (develop-side) Bare instance launch in us-east-1 from the just-built AMI (temp security group scoped to the runner's own IP, EC2 Instance Connect for the key), same pattern Packer already uses in this account. Caches the result to S3 as the develop baseline. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ami-release-nix.yml | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/ami-release-nix.yml b/.github/workflows/ami-release-nix.yml index 773aee018c..a6cf2de67a 100644 --- a/.github/workflows/ami-release-nix.yml +++ b/.github/workflows/ami-release-nix.yml @@ -105,6 +105,41 @@ jobs: postgres_version: ${{ matrix.postgres_version }} region: ${{ env.AWS_REGION }} + - name: Snapshot AMI manifest + if: matrix.target.arch == 'amd64' + env: + AMI_ID: ${{ steps.build-ami.outputs.stage2_ami_id }} + RUN_ID: ${{ github.run_id }} + run: | + ssh-keygen -t ed25519 -N '' -f /tmp/manifest-key -q + + VPC_ID=$(aws ec2 describe-vpcs --region "$AWS_REGION" --filters Name=is-default,Values=true --query 'Vpcs[0].VpcId' --output text) + SUBNET_ID=$(aws ec2 describe-subnets --region "$AWS_REGION" --filters Name=vpc-id,Values="$VPC_ID" --query 'Subnets[0].SubnetId' --output text) + SG_ID=$(aws ec2 create-security-group --region "$AWS_REGION" --group-name "manifest-snapshot-$RUN_ID" --description "temp sg for ami manifest snapshot" --vpc-id "$VPC_ID" --query GroupId --output text) + RUNNER_IP=$(curl -s https://checkip.amazonaws.com) + aws ec2 authorize-security-group-ingress --region "$AWS_REGION" --group-id "$SG_ID" --protocol tcp --port 22 --cidr "$RUNNER_IP/32" + + cleanup() { + [ -n "${INSTANCE_ID:-}" ] && aws ec2 terminate-instances --region "$AWS_REGION" --instance-ids "$INSTANCE_ID" && aws ec2 wait instance-terminated --region "$AWS_REGION" --instance-ids "$INSTANCE_ID" + aws ec2 delete-security-group --region "$AWS_REGION" --group-id "$SG_ID" + } + trap cleanup EXIT + + INSTANCE_ID=$(aws ec2 run-instances --region "$AWS_REGION" --image-id "$AMI_ID" --instance-type t3.small --subnet-id "$SUBNET_ID" --security-group-ids "$SG_ID" --associate-public-ip-address --count 1 --query 'Instances[0].InstanceId' --output text) + aws ec2 wait instance-running --region "$AWS_REGION" --instance-ids "$INSTANCE_ID" + IP=$(aws ec2 describe-instances --region "$AWS_REGION" --instance-ids "$INSTANCE_ID" --query 'Reservations[0].Instances[0].PublicIpAddress' --output text) + + for i in $(seq 1 30); do + aws ec2-instance-connect send-ssh-public-key --region "$AWS_REGION" --instance-id "$INSTANCE_ID" --instance-os-user ubuntu --ssh-public-key file:///tmp/manifest-key.pub + ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 -i /tmp/manifest-key "ubuntu@$IP" true && break + sleep 5 + done + + scp -o StrictHostKeyChecking=no -i /tmp/manifest-key testinfra/manifest-snapshot.sh "ubuntu@$IP:/tmp/manifest-snapshot.sh" + ssh -o StrictHostKeyChecking=no -i /tmp/manifest-key "ubuntu@$IP" "chmod +x /tmp/manifest-snapshot.sh && sudo /tmp/manifest-snapshot.sh" > ami-manifest.txt + + aws s3 cp ami-manifest.txt "s3://${{ secrets.ARTIFACTS_BUCKET }}/ami-manifests/${{ matrix.postgres_version }}/latest.txt" + - name: Setup post build env vars run: | POSTGRES_SUPABASE_VERSION=${{ steps.build-ami.outputs.postgres_release_version }}