Загрузка данных
# Rocky Linux 9.6 to 9.7 Air-Gapped Upgrade Procedure
This procedure upgrades a Rocky Linux 9.6 machine to Rocky Linux 9.7 using only an internal HTTP/HTTPS repository server.
It is written for a host whose enabled repositories currently point at internal `9.6` paths and where equivalent `9.7` repository paths exist on the same internal server.
Important date note: this runbook was re-audited on 2026-06-11. As of that date, Rocky publishes a current 9.8 release note page, and the detailed Rocky version table lists 9.8 as supported while 9.7 is superseded after 2026-05-28. This procedure still targets 9.7 because that is the requested internal air-gapped target. If your policy requires the currently supported Rocky 9 minor, retarget the same procedure to the internal 9.8 repositories instead.
## Strategy
Rocky 9.6 to 9.7 is a same-major upgrade. Rocky's 9.7 release notes say Rocky 9.x systems can upgrade to 9.7 with `dnf upgrade`.
In an air-gapped environment, the critical part is not internet access. The critical part is making sure DNF sees a complete, internally mirrored, mutually consistent set of 9.7 repositories before any transaction is applied.
This runbook uses these guardrails:
1. Record the current 9.6 state.
2. Verify the internal 9.7 repository metadata from the client.
3. Back up the current repository configuration.
4. Retarget the enabled repository set from `9.6` to `9.7`.
5. Clean metadata and prove DNF is resolving only against 9.7 repos.
6. Run dry-run transactions before applying.
7. Apply the upgrade.
8. Reboot.
9. Verify the OS, repositories, DNF state, package consistency, and pending update state.
For the apply step, this procedure prefers `dnf distro-sync` after a clean dry run. `dnf upgrade` is the standard same-major upgrade command, but `distro-sync` is stricter and is better when repository definitions have been retargeted or when the host might already be partially mixed between 9.6 and 9.7. If `distro-sync --assumeno` proposes unexpected removals or downgrades, stop and fix the repository set first.
## Assumptions
Replace these values with your real environment:
```bash
REPO_ROOT_URL='https://repo-hosting-server.example.local/path/to/rocky/9.7'
```
The examples use `sudo`. If you work from a root shell, omit `sudo`.
Run the command blocks from `bash`, not plain `sh`. A few verification blocks use Bash's `PIPESTATUS` array so that DNF's real exit code is captured even when output is also written through `tee`.
The examples use `$basearch` inside repo files. On most x86_64 systems this resolves to `x86_64`; on ARM it might resolve to `aarch64`.
Do not add `--releasever=9.7` to DNF commands unless your internal repo files deliberately use `$releasever` and you have tested that behavior. With explicit `9.7` `baseurl=` entries, the repository file is the source of truth.
This procedure does not cover major-version upgrades such as Rocky 8 to Rocky 9 or Rocky 9 to Rocky 10.
## Hard Stop Conditions
These conditions are phase gates, not a one-time checklist. Check them as you go through the full procedure, and check the apply-time conditions again immediately before running `dnf distro-sync` or `dnf upgrade`.
At the beginning of the procedure, enabled repos pointing to `9.6` are expected because that is the current state being discovered. That becomes a hard stop only after the repo retargeting step, during Phase 8 and later. In other words: finding `9.6` in Phase 2 or Phase 4 tells you what must be changed; finding an enabled `9.6` repo after Phase 7 means do not apply the upgrade.
Stop before applying the upgrade if any of these are true:
- The internal server does not expose `repodata/repomd.xml` for every required 9.7 repo.
- After repo retargeting, any enabled repo still points to a 9.6 path.
- Any required repo from the 9.6 configuration has no matching 9.7 equivalent.
- `dnf makecache --refresh` fails.
- `dnf check` reports unresolved package problems.
- A dry run proposes removing critical packages such as `kernel`, `systemd`, `dnf`, `rpm`, `glibc`, `openssh-server`, the bootloader, storage/network drivers, or important application packages.
- A dry run proposes switching to 9.8 or another version instead of 9.7.
- You do not have a rollback path.
Do not work around these with `--skip-broken`, `--nobest`, or `--allowerasing` unless you have reviewed the exact package impact and accepted it.
Use these gates in the full procedure:
- Before changing repos, Phases 0-3 check rollback readiness, disk space, RPM/DNF health, excludes, and version locks.
- Before retargeting repos, Phases 4-5 verify that every required enabled 9.6 repo has a reachable 9.7 equivalent.
- Before any package transaction, Phases 8-13 verify no enabled repo still points to 9.6, no unintended 9.8 repo is enabled, 9.7 metadata builds successfully, release packages are available, dependency checks are clean, and the dry runs are acceptable.
- Before declaring success, Phases 15-19 verify the completed transaction, rebooted OS state, enabled repo set, dependency state, pending sync/update state, and service health.
## Phase 0 - Maintenance Window and Rollback
Schedule a maintenance window. A same-major Rocky upgrade is normally routine, but this transaction can update the kernel, glibc, systemd, crypto libraries, DNF/RPM, OpenSSH, database clients, and other shared components.
Have one of these rollback options before continuing:
- VM snapshot taken while the machine is stopped or quiesced.
- Hypervisor storage snapshot with a tested rollback process.
- Bare-metal image backup.
- Tested application and data restore process.
DNF history rollback is not a complete disaster-recovery plan for a system upgrade. It can help in narrow cases, but it depends on the old repositories still being available and cannot be treated as equivalent to a VM or disk snapshot.
## Phase 1 - Create an Evidence Directory
Create a directory to capture pre-checks, transaction output, and post-checks. This directory is under `/var/tmp` so the same sudo user can read it throughout the procedure.
```bash
TS="$(date +%F-%H%M%S)"
EVIDENCE="/var/tmp/rocky-9.7-upgrade-${TS}"
sudo install -d -m 0750 -o "$(id -u)" -g "$(id -g)" "$EVIDENCE"
echo "$EVIDENCE"
```
## Phase 2 - Capture Current State
Run these before changing repo files.
```bash
cat /etc/rocky-release | sudo tee "$EVIDENCE/rocky-release.before.txt"
cat /etc/os-release | sudo tee "$EVIDENCE/os-release.before.txt"
uname -a | sudo tee "$EVIDENCE/uname.before.txt"
rpm -q rocky-release rocky-repos rocky-gpg-keys kernel-core dnf rpm glibc \
| sudo tee "$EVIDENCE/core-rpms.before.txt"
dnf repolist --enabled | sudo tee "$EVIDENCE/dnf-repolist-enabled.before.txt"
dnf repolist --all | sudo tee "$EVIDENCE/dnf-repolist-all.before.txt"
grep -RHiE '^[[:space:]]*(\[|name=|baseurl=|metalink=|mirrorlist=|enabled=|gpgcheck=|repo_gpgcheck=|gpgkey=)' \
/etc/yum.repos.d/*.repo \
| sudo tee "$EVIDENCE/repo-definitions.before.txt"
rpm -qa --qf '%{NAME} %{EPOCHNUM}:%{VERSION}-%{RELEASE}.%{ARCH}\n' \
| sort \
| sudo tee "$EVIDENCE/rpm-qa.before.txt"
dnf module list --enabled | sudo tee "$EVIDENCE/dnf-modules-enabled.before.txt"
dnf history list | head -40 | sudo tee "$EVIDENCE/dnf-history.before.txt"
```
Expected state:
- `/etc/rocky-release` says Rocky Linux 9.6.
- Enabled internal Rocky repos point to paths containing `9.6`.
- No enabled public internet mirrors are present.
If the OS already says 9.7 but repos still point to 9.6, treat the system as partially upgraded and continue carefully. The `distro-sync` phase is especially important in that case.
## Phase 3 - Health Checks Before Touching Repos
Check disk space. Pay attention to `/`, `/var`, `/var/cache/dnf`, and `/boot`.
```bash
df -hT / /var /var/cache/dnf /boot 2>/dev/null | sudo tee "$EVIDENCE/df.before.txt"
df -ih / /var /boot 2>/dev/null | sudo tee "$EVIDENCE/df-inodes.before.txt"
```
Recommended minimums:
- `/var` or `/var/cache/dnf`: at least 5 GB free.
- `/`: at least 5 GB free.
- `/boot`: enough room for a new kernel and initramfs; 250 MB free is a practical minimum, more is better.
Check RPM/DNF consistency.
```bash
sudo rpmdb --verifydb 2>&1 | sudo tee "$EVIDENCE/rpmdb-verifydb.before.txt"
rpm -qa >/dev/null
sudo dnf check | sudo tee "$EVIDENCE/dnf-check.before.txt"
dnf repoquery --unsatisfied | sudo tee "$EVIDENCE/repoquery-unsatisfied.before.txt"
```
Expected state:
- `rpmdb --verifydb` returns no error.
- `rpm -qa` completes successfully.
- `dnf check` returns no dependency errors.
- `dnf repoquery --unsatisfied` returns nothing.
Check for version locks and excludes that might prevent the upgrade.
```bash
grep -RHiE '^[[:space:]]*(exclude|excludepkgs|includepkgs)=' \
/etc/dnf /etc/yum.repos.d 2>/dev/null \
| sudo tee "$EVIDENCE/dnf-excludes.before.txt"
sudo dnf versionlock list > "$EVIDENCE/dnf-versionlocks.before.txt" 2>&1 || true
cat "$EVIDENCE/dnf-versionlocks.before.txt"
sudo find /etc/dnf -maxdepth 4 -type f -iname '*versionlock*' -print -exec sed -n '1,200p' {} \; \
| sudo tee "$EVIDENCE/versionlock-files.before.txt"
```
If `dnf versionlock list` reports that the command is unknown, the versionlock plugin is not installed; continue with the file inspection. If version locks exist for core packages, remove or document them before the upgrade.
## Phase 4 - Identify Required 9.7 Repositories
List enabled repo IDs and base URLs.
```bash
dnf repolist --enabled -v \
| awk '/^Repo-id[[:space:]]*:/ || /^Repo-name[[:space:]]*:/ || /^Repo-baseurl[[:space:]]*:/ {print}' \
| sudo tee "$EVIDENCE/enabled-repo-details.before.txt"
```
Also find all repo files that currently reference `9.6`.
```bash
grep -RIl '9\.6' /etc/yum.repos.d/*.repo \
| sudo tee "$EVIDENCE/repo-files-containing-9.6.txt"
```
For each enabled 9.6 repo, verify that an equivalent 9.7 repo exists on the internal server.
Common Rocky 9 repo IDs include:
- `baseos`
- `appstream`
- `extras`
- `crb` (often disabled by default, but required by some packages)
- `highavailability`
- `resilientstorage`
- other site-specific or vendor repos
Do not assume BaseOS/AppStream/Extras are enough. The enabled 9.7 repo set should match the enabled 9.6 repo set unless you intentionally disable a repo and know no installed packages depend on it.
## Phase 5 - Verify 9.7 Repository Metadata From the Client
The exact path names on your internal server might be uppercase or lowercase. Use the same path style already present in `/etc/yum.repos.d/*.repo`, changing only `9.6` to `9.7`.
For a typical Rocky layout, check:
```bash
ARCH="$(rpm --eval '%{_arch}')"
REPO_ROOT_URL='https://repo-hosting-server.example.local/path/to/rocky/9.7'
curl -fsSLo /dev/null "${REPO_ROOT_URL}/BaseOS/${ARCH}/os/repodata/repomd.xml"
curl -fsSLo /dev/null "${REPO_ROOT_URL}/AppStream/${ARCH}/os/repodata/repomd.xml"
curl -fsSLo /dev/null "${REPO_ROOT_URL}/extras/${ARCH}/os/repodata/repomd.xml"
curl -fsSLo /dev/null "${REPO_ROOT_URL}/CRB/${ARCH}/os/repodata/repomd.xml"
```
If your repo paths are lowercase, use the lowercase form:
```bash
curl -fsSLo /dev/null "${REPO_ROOT_URL}/baseos/${ARCH}/os/repodata/repomd.xml"
curl -fsSLo /dev/null "${REPO_ROOT_URL}/appstream/${ARCH}/os/repodata/repomd.xml"
curl -fsSLo /dev/null "${REPO_ROOT_URL}/extras/${ARCH}/os/repodata/repomd.xml"
curl -fsSLo /dev/null "${REPO_ROOT_URL}/crb/${ARCH}/os/repodata/repomd.xml"
```
Also verify the 9.7 counterpart of every currently enabled `baseurl` repository as DNF sees it:
```bash
dnf repolist --enabled -v \
| awk -F': ' '/^Repo-baseurl[[:space:]]*:/ {print $2}' \
| sed -E 's#9\.6#9.7#g' \
| while IFS= read -r baseurl; do
[ -n "$baseurl" ] || continue
repomd="${baseurl%/}/repodata/repomd.xml"
echo "Checking $repomd"
curl -fsSLo /dev/null "$repomd" || {
echo "STOP: cannot fetch $repomd"
exit 1
}
done
```
If an enabled repo uses `mirrorlist=` or `metalink=` instead of `baseurl=`, replace it with an internal `baseurl=` before continuing. In an air-gapped network, DNF should not need mirror manager or public metalink discovery.
Expected state:
- Every required URL can be fetched successfully.
- `repodata/repomd.xml` exists for every required repo.
- The 9.7 server has the same repo families currently used by the 9.6 machine.
If a required repo is missing, fix the internal mirror first. Do not start the upgrade.
## Phase 6 - Back Up Repository Configuration
Back up the active repo directory outside `/etc/yum.repos.d`.
```bash
REPO_BACKUP="/root/yum.repos.d.before-rocky-9.7.${TS}"
sudo mkdir -p "$REPO_BACKUP"
sudo cp -a /etc/yum.repos.d/*.repo "$REPO_BACKUP"/
echo "$REPO_BACKUP" | sudo tee "$EVIDENCE/repo-backup-dir.txt"
```
Confirm the backup exists:
```bash
sudo ls -l "$REPO_BACKUP" | sudo tee "$EVIDENCE/repo-backup-list.txt"
```
## Phase 7 - Retarget Repos From 9.6 to 9.7
Recommended method: retarget the existing repo files after backup. This preserves repo IDs and avoids duplicate repo ID problems.
Review exactly which files will be changed:
```bash
cat "$EVIDENCE/repo-files-containing-9.6.txt"
```
Apply the `9.6` to `9.7` retarget:
```bash
while IFS= read -r f; do
[ -n "$f" ] || continue
sudo cp -a "$f" "${f}.before-97"
sudo sed -i -E 's/9\.6/9.7/g' "$f"
done < "$EVIDENCE/repo-files-containing-9.6.txt"
```
If you prefer to create new 9.7 repo files instead, make sure the old 9.6 repo IDs are not duplicated in the new files. DNF repository IDs must be unique. Either move the old 9.6 `.repo` files out of `/etc/yum.repos.d`, or give every new 9.7 repo a unique ID such as `baseos-97-airgap`.
If any repo file contains `mirrorlist=` or `metalink=`, disable those entries for the air-gapped setup and use `baseurl=` only:
```bash
grep -RHiE '^[[:space:]]*(mirrorlist|metalink)=' /etc/yum.repos.d/*.repo
```
For each active repo, the effective definition should look like this pattern:
```ini
[baseos]
name=Rocky Linux 9.7 - BaseOS - Internal
baseurl=https://repo-hosting-server.example.local/path/to/rocky/9.7/BaseOS/$basearch/os/
enabled=1
gpgcheck=1
repo_gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
```
Use the repo names and path case that match your internal server.
## Phase 8 - Verify Repo Retargeting Before Metadata Refresh
Record the new repo definitions.
```bash
grep -RHiE '^[[:space:]]*(\[|name=|baseurl=|metalink=|mirrorlist=|enabled=|gpgcheck=|repo_gpgcheck=|gpgkey=)' \
/etc/yum.repos.d/*.repo \
| sudo tee "$EVIDENCE/repo-definitions.after-retarget.txt"
```
Check for any enabled repo still referencing `9.6`.
```bash
dnf repolist --enabled -v \
| awk '/^Repo-id[[:space:]]*:/ || /^Repo-baseurl[[:space:]]*:/ {print}' \
| sudo tee "$EVIDENCE/enabled-repo-details.after-retarget.txt"
if dnf repolist --enabled -v | grep -E '9\.6'; then
echo 'STOP: enabled repo still points to 9.6'
exit 1
fi
if dnf repolist --enabled -v | grep -E '9\.8'; then
echo 'STOP: enabled repo points to 9.8, not requested 9.7'
exit 1
fi
```
Expected state:
- Enabled repo base URLs contain `9.7`.
- No enabled repo base URL contains `9.6`.
- No enabled repo points to the internet.
## Phase 9 - Verify GPG Key
The Rocky 9 GPG key should normally already exist.
```bash
ls -l /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9 \
| sudo tee "$EVIDENCE/rocky-gpg-key-file.txt"
rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\n' \
| sort \
| sudo tee "$EVIDENCE/imported-gpg-keys.before.txt"
```
Import it if present:
```bash
sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
```
If the key file is missing, copy it from trusted Rocky installation media or from the trusted internal mirror. Do not permanently disable `gpgcheck` to get around a signing error.
## Phase 10 - Clean DNF Metadata and Build 9.7 Cache
Clean stale 9.6 metadata and rebuild the cache from the internal 9.7 repos.
```bash
sudo dnf clean all
sudo dnf makecache --refresh 2>&1 | sudo tee "$EVIDENCE/dnf-makecache-97.txt"
```
Expected state:
- `dnf makecache --refresh` succeeds.
- Metadata is downloaded from only internal 9.7 URLs.
- There are no 404, TLS, GPG, checksum, or repodata errors.
Now record the enabled repo view again:
```bash
dnf repolist --enabled -v \
| sudo tee "$EVIDENCE/dnf-repolist-enabled-verbose.97.txt"
```
## Phase 11 - Prove the 9.7 Release Packages Are Available
Check that the 9.7 release packages are visible from the enabled repos.
```bash
dnf repoquery --available \
--qf '%{name} %{epoch}:%{version}-%{release}.%{arch} %{reponame}' \
rocky-release rocky-repos rocky-gpg-keys \
| sort \
| sudo tee "$EVIDENCE/repoquery-rocky-release-available.97.txt"
```
Expected state:
- `rocky-release` is available as a 9.7 package.
- `rocky-repos` and `rocky-gpg-keys` are available from the same internal 9.7 repository set.
Check several core packages:
```bash
dnf repoquery --available --latest-limit=10 \
--qf '%{name} %{epoch}:%{version}-%{release}.%{arch} %{reponame}' \
kernel kernel-core systemd dnf rpm glibc openssl openssl-libs \
| sort \
| sudo tee "$EVIDENCE/repoquery-core-packages-available.97.txt"
```
Expected state:
- Core package candidates come from internal 9.7 repos.
- Kernel candidates should be in the Rocky 9.7 kernel family, commonly `5.14.0-611...` for 9.7 content. Exact errata release depends on your mirror date.
- Candidates should not come from 9.6 or 9.8 repos.
## Phase 12 - Pre-Transaction Consistency Checks
Run these checks after retargeting repos and building the 9.7 cache.
```bash
sudo dnf check 2>&1 | sudo tee "$EVIDENCE/dnf-check.after-retarget.txt"
dnf repoquery --unsatisfied 2>&1 | sudo tee "$EVIDENCE/repoquery-unsatisfied.after-retarget.txt"
dnf repoquery --extras 2>&1 | sudo tee "$EVIDENCE/repoquery-extras.after-retarget.txt"
dnf repoquery --duplicates 2>&1 | sudo tee "$EVIDENCE/repoquery-duplicates.after-retarget.txt"
```
Expected state:
- `dnf check` returns no dependency errors.
- `repoquery --unsatisfied` returns nothing.
- `repoquery --extras` contains only packages you intentionally manage outside the Rocky 9.7 repo set, such as local or vendor packages. Old install-only kernel packages can also appear and are not automatically a failure.
- `repoquery --duplicates` contains no unexpected duplicate non-kernel packages.
If errors appear, usually one of these is true:
- A required 9.7 repo is missing or disabled.
- CRB is needed but disabled.
- A vendor repo used by installed packages is still missing.
- A version lock or `exclude=` line blocks an update.
- The internal 9.7 mirror is incomplete.
Fix those causes before continuing.
## Phase 13 - Dry-Run the Upgrade
First run the normal Rocky same-major upgrade dry run:
```bash
sudo dnf --refresh upgrade --assumeno 2>&1 \
| sudo tee "$EVIDENCE/dnf-upgrade-dryrun.97.txt"
```
Then run the stricter sync dry run:
```bash
sudo dnf --refresh distro-sync --assumeno 2>&1 \
| sudo tee "$EVIDENCE/dnf-distro-sync-dryrun.97.txt"
```
Review the transaction summaries.
Acceptable signs:
- The transaction mainly contains `Upgrading` and possibly `Installing` for new dependencies.
- `rocky-release` moves to 9.7, unless it is already 9.7.
- Kernel packages are installed from 9.7 content.
- Repositories shown in the transaction are internal 9.7 repos.
Stop signs:
- Unexpected `Removing` entries.
- Unexpected `Downgrading` entries, unless you deliberately need to bring a partially upgraded host back to 9.7 from a newer internal repo.
- Any package source repo is still 9.6.
- Any package source repo is 9.8 or another unintended version.
- DNF says it cannot find a package, cannot satisfy dependencies, or cannot download metadata.
If the dry-run output is large, extract the transaction sections:
```bash
grep -nE '^(Installing|Upgrading|Downgrading|Removing|Transaction Summary|Error|Problem)' \
"$EVIDENCE/dnf-distro-sync-dryrun.97.txt"
```
Optional stricter test: if your DNF version supports RPM transaction test flags, run:
```bash
sudo dnf --refresh distro-sync --setopt=tsflags=test 2>&1 \
| sudo tee "$EVIDENCE/dnf-distro-sync-transaction-test.97.txt"
```
Expected state:
- The transaction test completes without dependency or scriptlet-preparation errors.
- No packages are actually installed because `tsflags=test` asks RPM to test the transaction.
If your DNF build does not support this option, skip this optional test and rely on the dry-run plus the normal transaction confirmation.
## Phase 14 - Apply the Upgrade
Only continue if the dry runs are clean.
Preferred apply command for this pinned 9.7 repo procedure:
```bash
sudo dnf -y --refresh distro-sync 2>&1 \
| sudo tee "$EVIDENCE/dnf-distro-sync-apply.97.txt"
```
Alternative apply command if your policy requires the exact Rocky release-note upgrade command:
```bash
sudo dnf -y --refresh upgrade 2>&1 \
| sudo tee "$EVIDENCE/dnf-upgrade-apply.97.txt"
```
If you use the alternative `upgrade` command, run the `distro-sync --assumeno` check again after the reboot. If it proposes corrective actions, review them and apply `distro-sync` after confirming the repository set is correct.
Do not interrupt this transaction. If the SSH session is unstable, run from a console, through a management interface, or inside `tmux`/`screen`.
## Phase 15 - Immediate Checks Before Reboot
After the transaction completes, check the release package and kernel inventory.
```bash
cat /etc/rocky-release | sudo tee "$EVIDENCE/rocky-release.after-transaction-before-reboot.txt"
rpm -q rocky-release rocky-repos rocky-gpg-keys kernel-core dnf rpm glibc \
| sudo tee "$EVIDENCE/core-rpms.after-transaction-before-reboot.txt"
rpm -qa 'kernel*' --qf '%{NAME} %{EPOCHNUM}:%{VERSION}-%{RELEASE}.%{ARCH}\n' \
| sort \
| sudo tee "$EVIDENCE/kernels.after-transaction-before-reboot.txt"
dnf history list | head -20 \
| sudo tee "$EVIDENCE/dnf-history.after-transaction-before-reboot.txt"
```
Expected state:
- `/etc/rocky-release` should now say Rocky Linux 9.7.
- A 9.7 kernel should be installed.
- DNF history should show the transaction completed.
Check whether a reboot is required if the tool is available:
```bash
if command -v needs-restarting >/dev/null 2>&1; then
sudo needs-restarting -r | sudo tee "$EVIDENCE/needs-restarting.before-reboot.txt"
else
sudo dnf needs-restarting -r 2>&1 | sudo tee "$EVIDENCE/needs-restarting.before-reboot.txt" || true
fi
```
For a minor release upgrade, assume a reboot is required even if the tool is unavailable.
## Phase 16 - Reboot
```bash
sudo systemctl reboot
```
Wait for the host to come back.
## Phase 17 - Post-Reboot Verification
Log back in and recreate the variables if needed:
```bash
EVIDENCE="$(ls -td /var/tmp/rocky-9.7-upgrade-* | head -1)"
echo "$EVIDENCE"
```
Verify OS release and running kernel:
```bash
cat /etc/rocky-release | sudo tee "$EVIDENCE/rocky-release.after-reboot.txt"
cat /etc/os-release | sudo tee "$EVIDENCE/os-release.after-reboot.txt"
uname -a | sudo tee "$EVIDENCE/uname.after-reboot.txt"
rpm -q rocky-release rocky-repos rocky-gpg-keys kernel-core \
| sudo tee "$EVIDENCE/core-rpms.after-reboot.txt"
```
Expected state:
- `/etc/rocky-release` says `Rocky Linux release 9.7`.
- `VERSION_ID` in `/etc/os-release` is `9.7`.
- The running kernel is from the 9.7 kernel family, commonly `5.14.0-611...` for 9.7 content. Exact errata release depends on the internal mirror date.
Verify enabled repositories:
```bash
dnf repolist --enabled -v \
| sudo tee "$EVIDENCE/dnf-repolist-enabled-verbose.after-reboot.txt"
if dnf repolist --enabled -v | grep -E '9\.6'; then
echo 'STOP: enabled repo still points to 9.6'
exit 1
fi
if dnf repolist --enabled -v | grep -E '9\.8'; then
echo 'STOP: enabled repo points to 9.8, not requested 9.7'
exit 1
fi
```
Expected state:
- All enabled Rocky repos point to internal 9.7 URLs.
- No enabled repo points to 9.6.
- No enabled repo points to 9.8 unless you intentionally changed the target.
Verify package/dependency consistency:
```bash
sudo dnf check 2>&1 | sudo tee "$EVIDENCE/dnf-check.after-reboot.txt"
dnf repoquery --unsatisfied 2>&1 | sudo tee "$EVIDENCE/repoquery-unsatisfied.after-reboot.txt"
dnf repoquery --extras 2>&1 | sudo tee "$EVIDENCE/repoquery-extras.after-reboot.txt"
dnf repoquery --duplicates 2>&1 | sudo tee "$EVIDENCE/repoquery-duplicates.after-reboot.txt"
```
Expected state:
- No dependency problems.
- No unsatisfied package requirements.
- No unexpected extras or duplicate non-kernel packages.
Verify no sync actions remain:
```bash
sudo dnf --refresh distro-sync --assumeno 2>&1 \
| sudo tee "$EVIDENCE/dnf-distro-sync-dryrun.after-reboot.txt"
```
Expected state:
- Ideally, DNF reports nothing to do.
- If it proposes additional upgrades from 9.7 repos only, review and apply them.
- If it proposes removals or unexpected downgrades, stop and inspect repo completeness.
Verify no ordinary updates remain in the pinned 9.7 repo set:
```bash
set +e
sudo dnf --refresh check-update 2>&1 \
| sudo tee "$EVIDENCE/dnf-check-update.after-reboot.txt"
CHECK_UPDATE_RC=${PIPESTATUS[0]}
echo "$CHECK_UPDATE_RC" | sudo tee "$EVIDENCE/dnf-check-update.after-reboot.rc"
```
DNF exit codes matter here:
- Exit code `0`: no updates available.
- Exit code `100`: updates are available.
- Other non-zero exit code: DNF error.
If updates are available from the intended 9.7 repos, apply them:
```bash
sudo dnf -y --refresh upgrade 2>&1 \
| sudo tee "$EVIDENCE/dnf-upgrade-final-after-reboot.txt"
sudo systemctl reboot
```
Then repeat the post-reboot verification.
## Phase 18 - Check Important Packages and Services
Check core package versions:
```bash
rpm -q rocky-release kernel-core systemd dnf rpm glibc openssl openssl-libs \
| sudo tee "$EVIDENCE/important-packages.after-reboot.txt"
```
Check OpenSSL runtime:
```bash
openssl version -a | sudo tee "$EVIDENCE/openssl-version.after-reboot.txt"
```
Check failed systemd units:
```bash
systemctl --failed | sudo tee "$EVIDENCE/systemctl-failed.after-reboot.txt"
```
Check important services for your machine. Replace the example services with the real services hosted on this server:
```bash
systemctl status sshd --no-pager
systemctl status chronyd --no-pager 2>/dev/null || true
```
If this machine runs application services, verify them now with application-specific health checks.
## Phase 19 - Look for Remaining 9.6 References
Repository references to 9.6 should be gone from active `.repo` files.
```bash
grep -RHi '9\.6' /etc/yum.repos.d/*.repo \
| sudo tee "$EVIDENCE/remaining-96-references-in-active-repos.txt" || true
```
Expected state:
- No active `.repo` file points to a 9.6 base URL.
Package names may still contain release strings like `.el9_6` for packages that were not rebuilt for 9.7. That alone is not a failure. The authoritative checks are:
- `rocky-release` says 9.7.
- Enabled repos point to 9.7.
- `dnf check` is clean.
- `dnf distro-sync --assumeno` has nothing unexpected to do.
- `dnf check-update` has no unintended pending updates.
If you want an informational list only:
```bash
rpm -qa | grep -E '(\.el9_6|rocky-release-9\.6)' \
| sort \
| sudo tee "$EVIDENCE/installed-packages-with-96-in-release-string.info.txt" || true
```
Do not treat that list as a failure by itself.
## Phase 20 - Clean Up Old Repo Backups Later
Do not delete backups immediately. Keep at least:
- `$EVIDENCE`
- `$REPO_BACKUP`
- any `*.before-97` repo-file copies
After the machine has passed application validation and run normally for an agreed period, you can archive the evidence directory and remove local temporary backup files if your policy allows.
## Troubleshooting
### `dnf makecache --refresh` fails with 404
Likely causes:
- Wrong path case, such as `BaseOS` versus `baseos`.
- Wrong architecture path.
- Missing `repodata/repomd.xml`.
- Repo server has packages but not repository metadata.
- A 9.6 URL was not fully changed to 9.7.
Fix the repo URL or the mirror. Do not continue.
### GPG check fails
Check:
```bash
ls -l /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\n' | sort
```
If packages are not signed with the expected Rocky key, stop and verify the mirror provenance. Do not permanently disable `gpgcheck`.
### Dry run proposes removals
Most common causes:
- A required repo is missing from the 9.7 set.
- CRB was used on 9.6 but is disabled or missing for 9.7.
- HighAvailability, ResilientStorage, NFV, RT, SAP, EPEL, or vendor repos were used before and are not available now.
- Version locks or excludes prevent required updates.
- The internal mirror is incomplete.
Compare the enabled 9.6 repo list from `$EVIDENCE/dnf-repolist-enabled.before.txt` with the enabled 9.7 repo list.
### Dry run proposes downgrades
This can be acceptable only if the machine was already partially upgraded beyond 9.7 and you intentionally want to pin it to 9.7.
If you did not expect downgrades, check for:
- Accidental 9.8 packages already installed.
- Old testing or vendor repos.
- A 9.7 mirror that is older than the content currently installed on the host.
### `rocky-release` does not become 9.7
Check:
```bash
dnf repoquery --available --show-duplicates rocky-release
dnf repoquery --installed rocky-release
dnf repolist --enabled -v
```
If the enabled repos do not provide `rocky-release` 9.7, the repo set is not a valid 9.7 upgrade source.
### The machine boots the old kernel
Check installed kernels and the default boot entry:
```bash
rpm -qa 'kernel*' | sort
sudo grubby --default-kernel
sudo grubby --info=ALL | grep -E '^(kernel|title)='
```
If a 9.7 kernel is installed but not selected, inspect bootloader configuration before removing old kernels.
### DNF says updates remain after reboot
If updates are from intended 9.7 repos, apply them and reboot again:
```bash
sudo dnf -y --refresh upgrade
sudo systemctl reboot
```
If updates are from 9.6, 9.8, public internet, or unintended vendor repos, stop and fix repo configuration.
## Minimal Command Path
Use this only after reading the full runbook. This compressed path still has hard-stop gates; stop at the gate where a failure appears. Do not treat the initial discovery of `9.6` repo URLs as a failure. Treat enabled `9.6` repo URLs as a hard stop after the retarget step and before the dry run/apply step.
```bash
TS="$(date +%F-%H%M%S)"
EVIDENCE="/var/tmp/rocky-9.7-upgrade-${TS}"
REPO_BACKUP="/root/yum.repos.d.before-rocky-9.7.${TS}"
sudo install -d -m 0750 -o "$(id -u)" -g "$(id -g)" "$EVIDENCE"
sudo mkdir -p "$REPO_BACKUP"
sudo cp -a /etc/yum.repos.d/*.repo "$REPO_BACKUP"/
cat /etc/rocky-release | sudo tee "$EVIDENCE/rocky-release.before.txt"
dnf repolist --enabled -v | sudo tee "$EVIDENCE/repos.before.txt"
sudo rpmdb --verifydb 2>&1 | sudo tee "$EVIDENCE/rpmdb-verifydb.before.txt"
rpm -qa >/dev/null
sudo dnf check | sudo tee "$EVIDENCE/dnf-check.before.txt"
dnf repoquery --unsatisfied | sudo tee "$EVIDENCE/unsatisfied.before.txt"
dnf repoquery --extras | sudo tee "$EVIDENCE/extras.before.txt"
grep -RIl '9\.6' /etc/yum.repos.d/*.repo \
| sudo tee "$EVIDENCE/repo-files-containing-9.6.txt"
while IFS= read -r f; do
[ -n "$f" ] || continue
sudo cp -a "$f" "${f}.before-97"
sudo sed -i -E 's/9\.6/9.7/g' "$f"
done < "$EVIDENCE/repo-files-containing-9.6.txt"
# Hard-stop gate: after retargeting, enabled repos must not point to 9.6 or unintended 9.8.
sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
sudo dnf clean all
sudo dnf makecache --refresh | sudo tee "$EVIDENCE/makecache.97.txt"
dnf repolist --enabled -v | sudo tee "$EVIDENCE/repos.97.txt"
if dnf repolist --enabled -v | grep -E '9\.6'; then
echo 'STOP: repo still points to 9.6'
exit 1
fi
if dnf repolist --enabled -v | grep -E '9\.8'; then
echo 'STOP: repo points to 9.8, not requested 9.7'
exit 1
fi
dnf repoquery --available --show-duplicates rocky-release rocky-repos rocky-gpg-keys \
| sudo tee "$EVIDENCE/rocky-release-available.97.txt"
# Hard-stop gate: review this dry run before applying. Unexpected removals,
# unexpected downgrades, dependency errors, 9.6 sources, or 9.8 sources mean stop.
sudo dnf --refresh distro-sync --assumeno \
| sudo tee "$EVIDENCE/distro-sync-dryrun.97.txt"
```
If the dry run is clean:
```bash
sudo dnf -y --refresh distro-sync \
| sudo tee "$EVIDENCE/distro-sync-apply.97.txt"
sudo systemctl reboot
```
After reboot:
```bash
EVIDENCE="$(ls -td /var/tmp/rocky-9.7-upgrade-* | head -1)"
cat /etc/rocky-release | sudo tee "$EVIDENCE/rocky-release.after-reboot.txt"
cat /etc/os-release | sudo tee "$EVIDENCE/os-release.after-reboot.txt"
uname -r | sudo tee "$EVIDENCE/kernel.after-reboot.txt"
dnf repolist --enabled -v | sudo tee "$EVIDENCE/repos.after-reboot.txt"
sudo dnf check | sudo tee "$EVIDENCE/dnf-check.after-reboot.txt"
dnf repoquery --unsatisfied | sudo tee "$EVIDENCE/unsatisfied.after-reboot.txt"
dnf repoquery --extras | sudo tee "$EVIDENCE/extras.after-reboot.txt"
sudo dnf --refresh distro-sync --assumeno \
| sudo tee "$EVIDENCE/distro-sync-dryrun.after-reboot.txt"
set +e
sudo dnf --refresh check-update \
| sudo tee "$EVIDENCE/check-update.after-reboot.txt"
CHECK_UPDATE_RC=${PIPESTATUS[0]}
echo "$CHECK_UPDATE_RC" | sudo tee "$EVIDENCE/check-update.after-reboot.rc"
systemctl --failed | sudo tee "$EVIDENCE/systemctl-failed.after-reboot.txt"
```
Final expected result:
- `/etc/rocky-release` says Rocky Linux release 9.7.
- `/etc/os-release` has `VERSION_ID="9.7"`.
- Enabled repos point to internal 9.7 URLs.
- `dnf check` is clean.
- `dnf repoquery --unsatisfied` is empty.
- `dnf repoquery --extras` has only intentionally external/local packages.
- `dnf distro-sync --assumeno` has no unexpected work remaining.
- `dnf check-update` has no unintended updates remaining.
- No failed systemd units are caused by the upgrade.
## References
- Rocky Linux 9.7 release notes: https://docs.rockylinux.org/10/release_notes/9_7/
- Rocky Linux 9.8 release notes: https://docs.rockylinux.org/10/release_notes/9_8/
- Rocky Linux repository overview: https://wiki.rockylinux.org/rocky/repo/
- Rocky Linux release and version guide: https://wiki.rockylinux.org/rocky/version/
- DNF command reference: https://dnf.readthedocs.io/en/latest/command_ref.html
- Red Hat Enterprise Linux 9 DNF repository management documentation: https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html-single/managing_software_with_the_dnf_tool/index