|
| 1 | +# Archiving EOL packages |
| 2 | + |
| 3 | +This document describes how to archive packages for end-of-life (EOL) distributions and prune EOL Ruby versions from the repositories. This is a routine maintenance task that frees CI disk space and keeps the repository lean. |
| 4 | + |
| 5 | +## Background |
| 6 | + |
| 7 | +The CI publish step downloads the full Aptly state archive (`state.tar.zst`) from Google Cloud Storage on every run. This archive grows with every distribution and Ruby version ever published. When distributions or Ruby versions reach EOL, their packages remain in the state archive indefinitely, consuming disk space on GitHub Actions runners. |
| 8 | + |
| 9 | +To address this, we maintain **archive repositories** alongside the main repositories: |
| 10 | + |
| 11 | +| Repository | Bucket | Domain | Purpose | |
| 12 | +|------------|--------|--------|---------| |
| 13 | +| APT (main) | `fsruby-server-edition-apt-repo` | `apt.fullstaqruby.org` | Current, supported packages | |
| 14 | +| APT (archive) | `fsruby-server-edition-apt-repo-archive` | `apt-archive.fullstaqruby.org` | Frozen packages for EOL distributions | |
| 15 | +| YUM (main) | `fsruby-server-edition-yum-repo` | `yum.fullstaqruby.org` | Current, supported packages | |
| 16 | +| YUM (archive) | `fsruby-server-edition-yum-repo-archive` | `yum-archive.fullstaqruby.org` | Frozen packages for EOL distributions | |
| 17 | + |
| 18 | +Archive repositories are static — CI never writes to them. They use the same versioned bucket structure as the main repos. Each migration creates a new version that merges newly-archived distros with the existing archive contents, so the archive grows incrementally over time. |
| 19 | + |
| 20 | +This pattern follows the precedent set by [PostgreSQL](https://apt-archive.postgresql.org/) (`apt-archive.postgresql.org`) and [HashiCorp](https://www.hashicorp.com/en/blog/announcing-the-linux-package-archive-site) (`archive.releases.hashicorp.com`). |
| 21 | + |
| 22 | +## Two types of cleanup |
| 23 | + |
| 24 | +There are two independent axes of cleanup, each with its own script: |
| 25 | + |
| 26 | +### 1. Distro archival — moving entire EOL distribution repos |
| 27 | + |
| 28 | +When a Linux distribution reaches EOL, we stop building packages for it and move its existing packages to the archive. Users on EOL distributions can still install packages by pointing at the archive repo. |
| 29 | + |
| 30 | +**Scripts:** |
| 31 | + * `internal-scripts/ci-cd/archive/migrate-apt-to-archive.rb` |
| 32 | + * `internal-scripts/ci-cd/archive/migrate-yum-to-archive.rb` |
| 33 | + |
| 34 | +### 2. Package pruning — removing EOL Ruby version packages |
| 35 | + |
| 36 | +When a Ruby version reaches EOL, we stop building it (by removing it from `config.yml`), but its packages persist inside every distro's repository. Pruning removes these stale packages from the still-supported distro repos to reduce state size. |
| 37 | + |
| 38 | +**Scripts:** |
| 39 | + * `internal-scripts/ci-cd/archive/prune-apt-packages.rb` |
| 40 | + * `internal-scripts/ci-cd/archive/prune-yum-packages.rb` |
| 41 | + |
| 42 | +## Removing an EOL distribution |
| 43 | + |
| 44 | +### Step 1: Remove from the build system |
| 45 | + |
| 46 | + 1. Edit `config.yml` and remove the distribution from the `distributions` list (or add it to an exclusion). |
| 47 | + 2. Delete the `environments/<distro>/` directory. |
| 48 | + 3. Regenerate CI/CD workflows: |
| 49 | + |
| 50 | + ~~~bash |
| 51 | + ./internal-scripts/generate-ci-cd-yaml.rb |
| 52 | + ~~~ |
| 53 | + |
| 54 | + 4. Commit and merge these changes. |
| 55 | + |
| 56 | +### Step 2: Migrate packages to the archive |
| 57 | + |
| 58 | +**Prerequisites:** |
| 59 | + * `gcloud` CLI authenticated with write access to the GCS buckets |
| 60 | + * `az` CLI authenticated with access to the `fsruby2infraowners` Key Vault (for the GPG signing key) |
| 61 | + * `aptly`, `zstd`, and `gpg` installed locally |
| 62 | + * Docker running (for `createrepo_c` in YUM migration) |
| 63 | + |
| 64 | +**Dry run first** to verify which distros will be archived: |
| 65 | + |
| 66 | +~~~bash |
| 67 | +PRODUCTION_REPO_BUCKET_NAME=fsruby-server-edition-apt-repo \ |
| 68 | +ARCHIVE_REPO_BUCKET_NAME=fsruby-server-edition-apt-repo-archive \ |
| 69 | +./internal-scripts/ci-cd/archive/migrate-apt-to-archive.rb --dry-run |
| 70 | +~~~ |
| 71 | + |
| 72 | +The script auto-detects EOL distros by comparing `aptly repo list` output against the distributions defined in `config.yml`. You can also specify distros explicitly: |
| 73 | + |
| 74 | +~~~bash |
| 75 | +./internal-scripts/ci-cd/archive/migrate-apt-to-archive.rb --dry-run --distros centos-8,debian-9 |
| 76 | +~~~ |
| 77 | + |
| 78 | +**Execute the migration** (removes `--dry-run`): |
| 79 | + |
| 80 | +~~~bash |
| 81 | +PRODUCTION_REPO_BUCKET_NAME=fsruby-server-edition-apt-repo \ |
| 82 | +ARCHIVE_REPO_BUCKET_NAME=fsruby-server-edition-apt-repo-archive \ |
| 83 | +./internal-scripts/ci-cd/archive/migrate-apt-to-archive.rb |
| 84 | +~~~ |
| 85 | + |
| 86 | +**Repeat for YUM:** |
| 87 | + |
| 88 | +~~~bash |
| 89 | +PRODUCTION_REPO_BUCKET_NAME=fsruby-server-edition-yum-repo \ |
| 90 | +ARCHIVE_REPO_BUCKET_NAME=fsruby-server-edition-yum-repo-archive \ |
| 91 | +./internal-scripts/ci-cd/archive/migrate-yum-to-archive.rb --dry-run |
| 92 | + |
| 93 | +PRODUCTION_REPO_BUCKET_NAME=fsruby-server-edition-yum-repo \ |
| 94 | +ARCHIVE_REPO_BUCKET_NAME=fsruby-server-edition-yum-repo-archive \ |
| 95 | +./internal-scripts/ci-cd/archive/migrate-yum-to-archive.rb |
| 96 | +~~~ |
| 97 | + |
| 98 | +### Step 3: Restart the web server |
| 99 | + |
| 100 | +After migration, restart the web server so Caddy picks up the new version numbers: |
| 101 | + |
| 102 | +~~~bash |
| 103 | +curl -X POST https://apt.fullstaqruby.org/admin/restart_web_server \ |
| 104 | + -H "Authorization: Bearer $ID_TOKEN" |
| 105 | +~~~ |
| 106 | + |
| 107 | +Or restart the Caddy service directly via Ansible/SSH. |
| 108 | + |
| 109 | +### Step 4: Verify |
| 110 | + |
| 111 | +~~~bash |
| 112 | +# Archive should list the archived distros |
| 113 | +curl -s https://apt-archive.fullstaqruby.org/dists/ |
| 114 | + |
| 115 | +# Main repo should only contain supported distros |
| 116 | +curl -s https://apt.fullstaqruby.org/dists/ |
| 117 | + |
| 118 | +# Verify state archive size decreased |
| 119 | +gsutil ls -l gs://fsruby-server-edition-apt-repo/versions/*/state.tar.zst | tail -5 |
| 120 | +~~~ |
| 121 | + |
| 122 | +## Pruning EOL Ruby versions |
| 123 | + |
| 124 | +After removing a Ruby version from `config.yml`, its packages persist in the Aptly state. Run the pruning scripts to remove them. |
| 125 | + |
| 126 | +**Dry run:** |
| 127 | + |
| 128 | +~~~bash |
| 129 | +PRODUCTION_REPO_BUCKET_NAME=fsruby-server-edition-apt-repo \ |
| 130 | +./internal-scripts/ci-cd/archive/prune-apt-packages.rb --dry-run |
| 131 | +~~~ |
| 132 | + |
| 133 | +The script compares packages in the Aptly state against `minor_version_packages` in `config.yml` and identifies any `fullstaq-ruby-X.Y*` packages where `X.Y` is not an active minor version. |
| 134 | + |
| 135 | +**Execute:** |
| 136 | + |
| 137 | +~~~bash |
| 138 | +PRODUCTION_REPO_BUCKET_NAME=fsruby-server-edition-apt-repo \ |
| 139 | +./internal-scripts/ci-cd/archive/prune-apt-packages.rb |
| 140 | +~~~ |
| 141 | + |
| 142 | +**Repeat for YUM:** |
| 143 | + |
| 144 | +~~~bash |
| 145 | +PRODUCTION_REPO_BUCKET_NAME=fsruby-server-edition-yum-repo \ |
| 146 | +./internal-scripts/ci-cd/archive/prune-yum-packages.rb --dry-run |
| 147 | + |
| 148 | +PRODUCTION_REPO_BUCKET_NAME=fsruby-server-edition-yum-repo \ |
| 149 | +./internal-scripts/ci-cd/archive/prune-yum-packages.rb |
| 150 | +~~~ |
| 151 | + |
| 152 | +Restart the web server after pruning (same as above). |
| 153 | + |
| 154 | +## Execution order |
| 155 | + |
| 156 | +When performing both distro archival and package pruning in the same session, always run distro archival **first**. This ensures the archive captures the full historical packages for EOL distros before any pruning happens. |
| 157 | + |
| 158 | + 1. `migrate-apt-to-archive.rb` |
| 159 | + 2. `prune-apt-packages.rb` |
| 160 | + 3. `migrate-yum-to-archive.rb` |
| 161 | + 4. `prune-yum-packages.rb` |
| 162 | + 5. Restart web server |
| 163 | + |
| 164 | +## Rollback |
| 165 | + |
| 166 | +The versioned bucket structure makes rollback straightforward. Each migration creates a new version — the old version is never modified. |
| 167 | + |
| 168 | +**Revert the main APT repo to a previous version:** |
| 169 | + |
| 170 | +~~~bash |
| 171 | +# Find the pre-migration version number |
| 172 | +gsutil cat gs://fsruby-server-edition-apt-repo/versions/latest_version.txt |
| 173 | + |
| 174 | +# Point back to the old version |
| 175 | +echo -n "OLD_VERSION" | gsutil -h Content-Type:text/plain -h Cache-Control:no-store cp - gs://fsruby-server-edition-apt-repo/versions/latest_version.txt |
| 176 | +~~~ |
| 177 | + |
| 178 | +**Revert the archive to a previous version:** |
| 179 | + |
| 180 | +~~~bash |
| 181 | +gsutil cat gs://fsruby-server-edition-apt-repo-archive/versions/latest_version.txt |
| 182 | + |
| 183 | +echo -n "OLD_VERSION" | gsutil -h Content-Type:text/plain -h Cache-Control:no-store cp - gs://fsruby-server-edition-apt-repo-archive/versions/latest_version.txt |
| 184 | +~~~ |
| 185 | + |
| 186 | +**Delete all archive contents** (if no users depend on it yet): |
| 187 | + |
| 188 | +~~~bash |
| 189 | +gsutil -m rm -r gs://fsruby-server-edition-apt-repo-archive/versions/ |
| 190 | +~~~ |
| 191 | + |
| 192 | +## How the migration scripts work |
| 193 | + |
| 194 | +### APT migration (`migrate-apt-to-archive.rb`) |
| 195 | + |
| 196 | + 1. Downloads the current Aptly state archive from the main bucket. |
| 197 | + 2. Identifies EOL distros (published in Aptly but not in `config.yml`). |
| 198 | + 3. Fetches the existing archive state (if any) so new distros are merged into it. |
| 199 | + 4. Creates or extends the archive Aptly instance with EOL distro data and package pool. |
| 200 | + 5. Publishes all archive distros (existing + newly archived). |
| 201 | + 6. Drops the EOL distro repos from the main Aptly database. |
| 202 | + 7. Runs `aptly db cleanup` to compact the database and reclaim pool space. |
| 203 | + 8. Re-publishes remaining distros in the main repo. |
| 204 | + 9. Uploads the merged archive as a new archive version (N+1). |
| 205 | + 10. Uploads the trimmed state as a new version of the main repo. |
| 206 | + |
| 207 | +### YUM migration (`migrate-yum-to-archive.rb`) |
| 208 | + |
| 209 | + 1. Downloads the current YUM repo from the main bucket via `gsutil rsync`. |
| 210 | + 2. Identifies EOL distro directories. |
| 211 | + 3. Fetches the existing archive repo (if any) so new distros are merged into it. |
| 212 | + 4. Copies EOL distro directories into the local archive copy. |
| 213 | + 5. Uploads the merged archive as a new archive version (N+1). |
| 214 | + 6. Removes EOL distro directories from the main local copy. |
| 215 | + 7. Uploads the trimmed repo as a new version of the main bucket. |
| 216 | + |
| 217 | +### APT pruning (`prune-apt-packages.rb`) |
| 218 | + |
| 219 | + 1. Downloads the Aptly state. |
| 220 | + 2. Scans all packages across all distro repos, matching `fullstaq-ruby-X.Y*` against active minor versions. |
| 221 | + 3. Removes EOL Ruby packages using `aptly repo remove`. |
| 222 | + 4. Compacts, re-publishes, and uploads. |
| 223 | + |
| 224 | +### YUM pruning (`prune-yum-packages.rb`) |
| 225 | + |
| 226 | + 1. Downloads the YUM repo. |
| 227 | + 2. Deletes RPM files matching EOL Ruby versions from the filesystem. |
| 228 | + 3. Regenerates `repodata/` with `createrepo_c` and re-signs. |
| 229 | + 4. Uploads as a new version. |
0 commit comments