Completing an Air-Gap Transfer
Goal
You build an import bundle, verify it with the manifest, pack it into an archive and unpack it, set up a repository, and install from it alone. You pass through the entire air-gapped network procedure in one go.
Why it matters
An import medium must not hold only rpms. It needs all six: rpm, repodata, GPG key, manifest, checksums, and an install procedure document, and the ones most often missing in practice are the manifest and the GPG key.
And verification must be two-way. Is every file in the manifest on the medium (the missing check), and is there a file on the medium that is not in the manifest (the added check). A missing file shows up quickly as an install failure, but an added file passes without anyone knowing — from the viewpoint of import review, this one is more dangerous.
Steps
- Create the
/root/airgap/rpmsdirectory and downloadtreeandnanointo that directory with all their dependencies (including those already installed). - Create
/root/airgap/MANIFEST.tsv. The first line is the headerNAME EPOCH VERSION RELEASE ARCH SHA256 FILE(tab-separated), and you must fill in those values one line per rpm. Fill in0for a package with no epoch. - Bundle the
rpmsdirectory and the manifest into/root/airgap/bundle.tar.gz, and save that archive's sha256 to/root/airgap/bundle.sha256. - Unpack the archive into
/root/airgap/unpacked/, and save the result of comparing the manifest's checksums against the actual files to/root/airgap/verify.txt. And write the number of files that are in the directory but not in the manifest as a single numeric line in/root/airgap/extra.txt(it must be 0). - Build metadata in
/root/airgap/unpacked/rpmsand write/etc/yum.repos.d/airgap.repo. The repository ID isairgap-local, and it must includesnapshotand a date inname,baseurl=file:///root/airgap/unpacked/rpms,enabled=1,gpgcheck=0, andmetadata_expire=-1. - With all other repositories turned off and only
airgap-localon, reinstalltree, and save the output to/root/airgap/install.txt. - Leave state records. Save the module list to
/root/airgap/state-modules.txtand the user-installed list to/root/airgap/state-userinstalled.txt. - Make
/root/airgap/report.txtwith the following 6 lines.BUNDLE_ID=airgap-<오늘 날짜 YYYY-MM-DD>/PACKAGES=<rpms 안 .rpm 개수>/MANIFEST_LINES=<매니페스트의 헤더 제외 줄 수>/VERIFY=OK/EXTRA=0/INSTALLED_FROM=airgap-local(that is, today's date in YYYY-MM-DD, the number of .rpm files in rpms, and the number of manifest lines excluding the header)
Notes
- Downloading is
dnf download --resolve --alldeps --destdir=/root/airgap/rpms tree nano. - Pulling NEVRA:
rpm -qp --queryformat '%{NAME}\t%|EPOCH?{%{EPOCH}}:{0}|\t%{VERSION}\t%{RELEASE}\t%{ARCH}' <파일>(the placeholder is the file) - Finding files not in the manifest:
comm -13 <(정렬된 매니페스트 파일명) <(정렬된 실제 파일명)(the two placeholders are the sorted manifest file names and the sorted actual file names) - Using just one repository:
dnf --disablerepo='*' --enablerepo=airgap-local install -y tree(if it is already installed, use thereinstallsubcommand instead ofinstall --reinstall— in this dnf the former form is an error) - Common mistake 1: the epoch goes in as
(none)in step 2. Fill in 0 with the ternary notation. - Common mistake 2: making the manifest with absolute paths in step 4 so that the comparison breaks from a different directory.
Collecting the import bundle
Create the /root/airgap/rpms directory and download tree and nano into that directory with all their dependencies (including those already installed).
You need an option combination that downloads the dependencies but does not skip those already installed.
Generating the manifest
Create /root/airgap/MANIFEST.tsv. The first line is the header NAME EPOCH VERSION RELEASE ARCH SHA256 FILE (tab-separated), and you must fill in those values one line per rpm. Fill in 0 for a package with no epoch.
It holds the NEVRA and the checksum together. There is a notation that fills in 0 for a package with no epoch.
Packing into an archive
Bundle the rpms directory and the manifest into /root/airgap/bundle.tar.gz, and save that archive's sha256 to /root/airgap/bundle.sha256.
You make a single object to put on the medium. Leave the checksum of the archive itself as well.
Unpack and verify
Unpack the archive into /root/airgap/unpacked/, and save the result of comparing the manifest's checksums against the actual files to /root/airgap/verify.txt. And write the number of files that are in the directory but not in the manifest as a single numeric line in /root/airgap/extra.txt (it must be 0).
Unpack to a different path and compare the checksums. Also check for files not in the manifest.
Configuring the repository
Build metadata in /root/airgap/unpacked/rpms and write /etc/yum.repos.d/airgap.repo. The repository ID is airgap-local, and it must include snapshot and a date in name, baseurl=file:///root/airgap/unpacked/rpms, enabled=1, gpgcheck=0, and metadata_expire=-1.
Build the metadata and write the .repo file. It is an air-gapped network, so adjust the expiry setting.
Installing from that repository alone
With all other repositories turned off and only airgap-local on, reinstall tree, and save the output to /root/airgap/install.txt.
Install with all the other repositories turned off and only the import repository turned on.
Recording the module state
Leave state records. Save the module list to /root/airgap/state-modules.txt and the user-installed list to /root/airgap/state-userinstalled.txt.
Leave the module list and the user-installed list. They are part of the import record.
Import report
Make /root/airgap/report.txt with the following 6 lines.
BUNDLE_ID=airgap-<오늘 날짜 YYYY-MM-DD> / PACKAGES=<rpms 안 .rpm 개수> / MANIFEST_LINES=<매니페스트의 헤더 제외 줄 수> / VERIFY=OK / EXTRA=0 / INSTALLED_FROM=airgap-local (that is, today's date in YYYY-MM-DD, the number of .rpm files in rpms, and the number of manifest lines excluding the header)
Gather the bundle information and the verification result. The values must be actual check results.