Skip to content

Repository files navigation

terraform-linux-packages

Terraform for a dedicated Google Cloud project that serves Linux packages from a public GCS bucket behind HTTPS, with GitHub Actions write access via Workload Identity Federation.

GitHub Access Model

Only the repositories listed in github_repositories receive:

  • LIBOPS_PACKAGES_GCLOUD_OIDC_POOL
  • LIBOPS_PACKAGES_GCLOUD_PROJECT
  • LIBOPS_PACKAGES_GCLOUD_REGION
  • LIBOPS_PACKAGES_GSA
  • LIBOPS_PACKAGES_GCS_BUCKET
  • LIBOPS_PACKAGES_PACKAGE_REPO_URL
  • LIBOPS_PACKAGES_APTLY_GPG_KEY_ID
  • LIBOPS_PACKAGES_APTLY_GPG_PRIVATE_KEY_SECRET
  • LIBOPS_PACKAGES_APTLY_GPG_PASSPHRASE_SECRET

If github_actors is non-empty, the Workload Identity provider also restricts access to those actors. Every token must additionally contain a job_workflow_ref from approved_job_workflow_refs. The approved value names a reviewed LibOps reusable workflow and managed branch or release channel. Repository, optional actor, and managed workflow identity must all match; the credentialed job uses GitHub's runtime job.workflow_sha as the exact publication evidence.

libops/sitectl-isle is included in the default publisher allowlist for its v1 release. Removing any repository from the applied github_repositories value removes that repository's package-publisher WIF binding and managed Actions variables; it does not alter the repository itself.

Managing approved publisher workflows

approved_job_workflow_refs must cover both active publication paths:

  • libops/terraform-linux-packages/.github/workflows/reusable-goreleaser.yaml@refs/heads/main for the direct core release workflow
  • libops/.github/.github/workflows/sitectl-plugin-goreleaser.yaml@refs/heads/main for the shared plugin release workflow

Review and protect the managed source branches as privileged publication code. A normal LibOps workflow update needs no caller or WIF pin rotation: merge it, let callers consume main, and retain the resolved workflow commit from the hosted run. When introducing a new workflow path or managed release channel, add that exact ref to approved_job_workflow_refs, verify the plan changes only the intended WIF identity, apply it, and only then move callers to that channel. This variable remains required and has no Terraform default.

Usage

  1. Export credentials for both providers:
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/admin-creds.json
export GITHUB_TOKEN=ghp_xxx
  1. Review and copy the example variables:
cp terraform.tfvars.example terraform.tfvars
  1. Initialize and apply:
terraform init
terraform plan
terraform apply
  1. Add the Aptly GPG material out of band after Terraform creates the Secret Manager containers:
make create-aptly-gpg-key
make sync-aptly-gpg-key-id

make create-aptly-gpg-key prompts for the passphrase without echoing it and saves the revocation certificate under .out/gpg/.

  1. Delegate the reported DNS name servers for the package subdomain, for example packages.libops.io, from the parent DNS zone.

Publishing

This repo includes a local publishing path that uses the same validation and repository-building logic as GitHub Actions.

Prerequisites:

  • gh authenticated for the source GitHub repository
  • docker
  • gcloud authenticated for Secret Manager and GCS on the host

By default the local targets use the published tooling image ghcr.io/libops/terraform-linux-packages:main. To rebuild that image locally instead, run:

make package-tools-image-local

Example:

make package GITHUB_REPOSITORY=libops/sitectl PACKAGE_NAME=sitectl RELEASE_VERSION=v1.2.3

The publisher currently has no mandatory package exclusions. Only the core sitectl channel owner may use EXCLUDED_PACKAGE_NAMES to request comma- or whitespace-separated canonical lowercase package names for an intentional retirement or cleanup; plugin publishers cannot remove other packages:

make package \
  GITHUB_REPOSITORY=libops/sitectl \
  PACKAGE_NAME=sitectl \
  RELEASE_VERSION=v1.2.3 \
  EXCLUDED_PACKAGE_NAMES="sitectl-preview"

Every publication validates the current package name and all current .deb and .rpm artifacts before using Google Cloud. An excluded current package or artifact fails closed. Core sitectl and all sitectl-* plugins share the sitectl prefix, one signing key, and one APT source. After the publisher acquires the shared lock, it synchronizes the complete existing repository; a failed or partial sync stops publication before signing-key access or metadata upload. Published versions for core and every plugin are retained and included in replacement APT/RPM metadata so exact workload pins and rollback remain installable after a newer release. Republishing an existing filename with different bytes is rejected before signing-key access. Bucket object versioning provides an additional recovery layer for accidental replacement or deletion. The publisher removes only explicitly excluded historical artifacts from the staged repository, rebuilds and uploads replacement metadata, and only then deletes those excluded GCS objects. Keeping an intentional exclusion in every publication makes interrupted cleanup self-healing: later publications rediscover and retry any unreferenced object that was not deleted.

When invoked by the core channel owner, the reusable GoReleaser workflow accepts exclusions through its excluded-package-names input; plugin package names are rejected if they request any. GoReleaser runs in a job without OIDC permission. A dependent publication job checks out the exact commit that defines the called workflow, validates the exclusion policy, and builds a local package-tools image from that checkout before cloud authentication. Credentialed publication uses a direct environment-reading wrapper, verifies the unchanged local image ID, and never invokes GNU Make or pulls the mutable main image.

That target will:

  • download the .deb and .rpm release assets with gh
  • pull the published Linux tooling image if needed
  • fetch the GPG key material from Secret Manager inside the container
  • rebuild the Debian and RPM repository metadata inside the container
  • sync package assets to the package bucket prefix for that package
  • upload repository metadata with Cache-Control: no-store and invalidate the Cloud CDN cache for that package prefix

On macOS, this avoids needing native installs of aptly or createrepo_c.

To print or sync the signing key ID from Secret Manager:

make print-aptly-gpg-key-id
make sync-aptly-gpg-key-id

Notes

  • The managed SSL certificate will stay in provisioning until the delegated package subdomain resolves to the created load balancer IP.
  • The bucket is public so apt and other package managers can fetch package metadata and artifacts without authentication.
  • This stack grants the GitHub service account bucket-level roles/storage.objectAdmin for syncing package repositories and a custom role with compute.urlMaps.get and compute.urlMaps.invalidateCache for Cloud CDN invalidation.
  • Cloud CDN uses origin cache headers. Package assets are uploaded as long-lived immutable objects, while mutable repository metadata is uploaded as no-store.
  • Terraform creates the Secret Manager secret containers, but it does not write the private key or passphrase into Terraform state.

libops setup

Below are the commands ran to get packages.libops.io setup using this repo

publish the latest version for our utils

terraform init
terraform apply
make create-aptly-gpg-key GCLOUD_PROJECT=libops-linux-packages
make sync-aptly-gpg-key-id
terraform apply
make package \
  GITHUB_REPOSITORY=libops/sitectl \
  PACKAGE_NAME=sitectl \
  RELEASE_VERSION=v0.10.1
make package \
  GITHUB_REPOSITORY=libops/sitectl-drupal \
  PACKAGE_NAME=sitectl-drupal \
  RELEASE_VERSION=v0.0.4

Requirements

Name Version
terraform >= 1.2.4
github 6.12.1
google 7.35.0

Providers

Name Version
github.libops 6.12.1
google 7.35.0

Modules

No modules.

Resources

Name Type
github_actions_variable.aptly_gpg_key_id resource
github_actions_variable.aptly_gpg_passphrase_secret resource
github_actions_variable.aptly_gpg_private_key_secret resource
github_actions_variable.bucket resource
github_actions_variable.gsa resource
github_actions_variable.oidc resource
github_actions_variable.package_url resource
github_actions_variable.project resource
github_actions_variable.region resource
google_compute_backend_bucket.packages resource
google_compute_global_address.packages resource
google_compute_global_forwarding_rule.packages_https resource
google_compute_managed_ssl_certificate.packages resource
google_compute_target_https_proxy.packages resource
google_compute_url_map.packages resource
google_dns_managed_zone.packages resource
google_dns_record_set.packages_a resource
google_iam_workload_identity_pool.pool resource
google_iam_workload_identity_pool_provider.github resource
google_project.project resource
google_project_iam_custom_role.cdn_cache_invalidator resource
google_project_iam_member.github_cdn_cache_invalidator resource
google_project_service.service resource
google_secret_manager_secret.aptly_passphrase resource
google_secret_manager_secret.aptly_private_key resource
google_secret_manager_secret_iam_member.github_aptly_passphrase_accessor resource
google_secret_manager_secret_iam_member.github_aptly_private_key_accessor resource
google_service_account.github resource
google_service_account_iam_member.oidc_user resource
google_storage_bucket.packages resource
google_storage_bucket_iam_member.github_writer resource
google_storage_bucket_iam_member.public_read resource
github_repository.repo data source

Inputs

Name Description Type Default Required
approved_job_workflow_refs Managed LibOps reusable-workflow refs allowed to publish packages. Runtime publication resolves and records the exact workflow commit. set(string) n/a yes
aptly_gpg_key_id GPG key ID Aptly uses to sign the published repository. string "" no
aptly_gpg_passphrase_secret_id Secret Manager secret ID that stores the Aptly GPG key passphrase. string "aptly-gpg-passphrase" no
aptly_gpg_private_key_secret_id Secret Manager secret ID that stores the armored Aptly private key. string "aptly-gpg-private-key" no
billing_account Google Cloud billing account ID. string n/a yes
bucket_location Bucket location. string "US" no
bucket_name Name of the public package bucket. string "libops-linux-packages" no
dns_zone_dns_name DNS suffix managed by the zone, with trailing dot. string "packages.libops.io." no
dns_zone_name Cloud DNS managed zone name. string "packages-libops-io" no
github_actors Optional GitHub actors allowed to use the provider. Leave empty to allow any actor from the approved repositories. set(string) [] no
github_owner GitHub organization that owns the repositories allowed to publish packages. string "libops" no
github_repositories Full GitHub repository names allowed to impersonate the publishing service account. set(string)
[
"libops/sitectl",
"libops/sitectl-app-tmpl",
"libops/sitectl-archivesspace",
"libops/sitectl-drupal",
"libops/sitectl-isle",
"libops/sitectl-libops",
"libops/sitectl-ojs",
"libops/sitectl-omeka-classic",
"libops/sitectl-omeka-s",
"libops/sitectl-wp"
]
no
org_id Google Cloud organization ID. string n/a yes
package_domain Fully qualified domain name that will serve the package repository. string "packages.libops.io" no
project_id Google Cloud project ID. string "libops-linux-packages" no
project_name Google Cloud project display name. string "libops-linux-packages" no
region Default Google Cloud region. string "us-east5" no

Outputs

Name Description
aptly_gpg_passphrase_secret_id Secret Manager secret ID for the Aptly key passphrase.
aptly_gpg_private_key_secret_id Secret Manager secret ID for the armored Aptly private key.
bucket_name Package bucket name.
dns_name_servers Name servers assigned to the managed zone. Delegate the zone at your registrar or parent DNS.
github_service_account_email Service account email used by GitHub Actions.
package_url Public HTTPS package repository URL.
project_id Google Cloud project ID.
workload_identity_provider Full Workload Identity Provider resource name for GitHub Actions auth.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages