A Docker-based tool to download RPM packages and their dependencies from configured YUM/DNF repositories and package them into a tarball. This tarball can then be transferred to an air-gapped environment to set up a local, offline repository.
- Offline Repository Creation: Simplifies creating mirrors of YUM/DNF repositories for offline use.
- Dependency Resolution: Automatically downloads all necessary dependencies (newest versions only by default).
- GPG Key Handling: Attempts to import GPG keys specified in
.repofiles, allowing forgpgcheck=1on the client side. - Dockerized: Ensures a consistent and reproducible build environment using AlmaLinux 8.
- Customizable: Configure repositories via standard
.repofiles. - Organized Output: Each synced repository is placed in its own subdirectory within the output tarball.
- Web Server Ready: Includes a basic
index.htmlfor browsing the repositories if served via HTTP. - Standard Tools: Uses
dnf-utils(reposync) andcreaterepo.
- Docker
- Docker Compose (Recommended for ease of use)
- Git (to clone this repository)
- Internet access on the machine running
repo-builder(to download packages and GPG keys).
.
├── docker-compose.yml # Defines the Docker service for easy execution
├── Dockerfile # Defines the Docker image
├── entrypoint.sh # Core script: downloads packages, imports GPG keys, creates repo metadata & tarball
├── yum.conf # Custom YUM/DNF configuration (used by dnf and reposync)
├── yum.repos.d/ # Directory to place your .repo files
│ └── example.repo # (You should create/add your actual .repo files here)
├── LICENSE # Your MIT License file
└── out/ # (Created by the script) Output directory for the generated tarball(s)
-
Clone the repository:
git clone https://github.com/zx900930/repo-builder.git cd repo-builder -
Configure Repositories:
- Place your YUM/DNF repository definition files (
.repofiles) into theyum.repos.d/directory. The script will copy these into the container's/etc/yum.repos.d/. - Important for GPG Keys: If your
.repofiles specifygpgkey=http://...orgpgkey=file:///..., theentrypoint.shscript will attempt to download/access and import these keys usingrpm --import. This allows you to potentially usegpgcheck=1in your offline repository configuration. Exampleyum.repos.d/almalinux.repo:[baseos] name=AlmaLinux $releasever - BaseOS mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/baseos enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux # (This key is part of the base almalinux image) # http://example.com/keys/RPM-GPG-KEY-mycustomrepo (This would be downloaded) [appstream] name=AlmaLinux $releasever - AppStream mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/appstream enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux
- (Optional) Modify
yum.confif you need specific global DNF/YUM configurations (e.g., proxy settings). This file will be used bydnfandreposyncinside the container.
- Place your YUM/DNF repository definition files (
-
Build and Run: The
docker-compose.ymlis configured to build the image and run the container. The output tarball will be placed in the./outdirectory on your host machine.# Ensure you are in the repo-builder project root docker-compose up --buildThis command will:
- Build the Docker image (or use
triatk/repo-builder:${REPO_BUILDER_VERSION}if available, then fall back to build). - Run the
entrypoint.shscript inside the container. - Mount the local
./outdirectory to/outputinside the container.
You can also specify a version for the image:
REPO_BUILDER_VERSION=1.0 docker-compose up --build
After the script finishes, the container will stop. You will find a tarball (e.g.,
repo-YYYYMMDD-HHMMSS.tar.gz) in the./outdirectory. - Build the Docker image (or use
- The
Dockerfilesets up an AlmaLinux 8 environment withdnf-utils,createrepo_c,tar, andcurl. - Your custom
yum.conf(from the project root) and all.repofiles fromyum.repos.d/are copied into the Docker image. - When the container starts,
entrypoint.shexecutes:- Copies the
.repofiles from/app/yum.repos.d/(in-container staging) to/etc/yum.repos.d/. - GPG Key Import: Parses
gpgkey=lines from the.repofiles. For HTTP/FTP URLs, it downloads the key usingcurland imports it withrpm --import. Forfile:///paths, it imports directly. - Identifies Enabled Repos: Uses
dnf repolist --enabledor parses[repo_id]sections from the.repofiles to find which repositories to sync. - For each enabled repository (
REPO_ID):- Uses
reposyncto download packages into${REPO_BASE_PATH}/${REPO_ID}(default:/home/var/www/html/${REPO_ID}). It downloads only the newest packages, deletes obsolete ones, and fetchescomps.xmland other metadata. - Determines the directory containing the RPMs. Some repositories place RPMs in a
Packages/subdirectory (e.g.,${REPO_BASE_PATH}/${REPO_ID}/Packages/). The script checks for this. - Runs
createrepo_c --updateon the directory containing the RPMs (either${REPO_BASE_PATH}/${REPO_ID}/or${REPO_BASE_PATH}/${REPO_ID}/Packages/) to generate/update therepodatadirectory.
- Uses
- Generates HTML files: Creates
index.html(listing synced repositories) and a generic50x.htmlin$REPO_BASE_PATH. - Creates Tarball: Archives the entire contents of
$REPO_BASE_PATH(which now contains subdirectories for each synced repo, each with its packages andrepodata, plus the HTML files) into a timestamped.tar.gzfile (e.g.,repo-YYYYMMDD-HHMMSS.tar.gz) in the/outputdirectory (mapped to your host's./out).
- Copies the
The generated tarball (e.g., repo-20231027-103000.tar.gz) will have the following structure when extracted:
.
├── repo_id_1/
│ ├── (Packages/ or RPMs directly here)
│ │ ├── some-package-1.rpm
│ │ └── ...
│ └── repodata/
├── repo_id_2/
│ ├── (Packages/ or RPMs directly here)
│ │ ├── another-package.rpm
│ │ └── ...
│ └── repodata/
├── ... (other repo_id directories)
├── index.html # Simple HTML page listing the repo directories
└── 50x.html # Generic error page
- If
reposyncfor a givenrepo_id_1created aPackages/subdirectory and RPMs were downloaded there, thenrepodata/will be insiderepo_id_1/Packages/. - Otherwise, RPMs and
repodata/will be directly underrepo_id_1/.
-
Transfer: Copy the generated
.tar.gzfile (e.g.,repo-YYYYMMDD-HHMMSS.tar.gz) from the./outdirectory to your air-gapped machine. -
Extract: On the air-gapped machine, choose a directory to host your local repository (e.g.,
/srv/local-repos) and extract the tarball:sudo mkdir -p /srv/local-repos sudo tar -xzf repo-YYYYMMDD-HHMMSS.tar.gz -C /srv/local-repos
This will create subdirectories like
/srv/local-repos/repo_id_1/,/srv/local-repos/repo_id_2/, etc. -
Configure Local Repository: For each repository you want to use from the tarball, create a new
.repofile on the air-gapped machine (e.g., in/etc/yum.repos.d/local-offline.repo).Example
/etc/yum.repos.d/local-offline.repo:[local-repo_id_1] name=Local Offline Repo ID 1 # Adjust baseurl based on actual structure within the tarball for repo_id_1 # Option 1: If RPMs and repodata are directly under repo_id_1/ baseurl=file:///srv/local-repos/repo_id_1/ # Option 2: If RPMs and repodata are under repo_id_1/Packages/ # baseurl=file:///srv/local-repos/repo_id_1/Packages/ enabled=1 gpgcheck=1 # Recommended if GPG keys were successfully imported during build # and you trust them. Otherwise, set to 0. # If gpgcheck=1, you might need to ensure the GPG keys are known to the system's RPM DB. # The build script attempts to import them into the container's RPM DB, # but those keys aren't transferred with the tarball directly for the client system's RPM DB. # If you used `gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux` and the client # also has this key, it will work. For custom keys downloaded via HTTP, you might need to # separately transfer and import the .asc/.gpg public key files onto the airgapped clients. # For simplicity in an airgapped setup after verifying packages, `gpgcheck=0` is often used. gpgkey=file:///srv/local-repos/repo_id_1/your-gpg-key.asc # If you copied a key file into the repo structure # Or point to system keys if they match e.g. file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux [local-repo_id_2] name=Local Offline Repo ID 2 baseurl=file:///srv/local-repos/repo_id_2/ enabled=1 gpgcheck=0 # Or 1, see notes for local-repo_id_1
- Crucial
baseurl: Carefully check the extracted structure inside/srv/local-repos/YOUR_REPO_ID/. If it contains aPackages/subdirectory which in turn contains the RPMs andrepodata/, yourbaseurlmust point to.../YOUR_REPO_ID/Packages/. Otherwise, it points directly to.../YOUR_REPO_ID/. gpgcheck:- Set to
1if you are confident the GPG keys were correctly handled byrepo-builderand are available/trusted on the client. - If using
gpgcheck=1with keys fetched via HTTP during the build, you'll need to ensure those public GPG key files are also transferred to the air-gapped system and referenced correctly viagpgkey=(or imported into the client's RPM database manually). The simplest way is to ensuregpgkeypoints to afile:///path within your extracted repository structure if you include the key files there. - Set to
0to disable GPG signature checking if managing keys is too complex for your scenario.
- Set to
- Crucial
-
Clean Cache and Verify:
sudo dnf clean all # or sudo yum clean all sudo dnf repolist # or sudo yum repolist
You should see your
local-*repositories listed. Now you can install packages:sudo dnf install <package-name> # or sudo yum install <package-name>
- Repositories: Add/modify
.repofiles in theyum.repos.d/directory. Ensuregpgkeylines are correct if you want GPG key handling. yum.conf: Modify the rootyum.conffor global DNF/YUM settings (e.g., proxy, specific dnf variables) to be used during thereposyncprocess.entrypoint.sh: For advanced changes (e.g., differentreposyncflags, alternative tarball structure), modify this script.REPO_BASE_PATH(in Dockerfile): Defaults to/home/var/www/html. This is the internal path in the container where repositories are built before tarring.
FROM almalinux:8: Uses AlmaLinux 8 as the base image.RUN dnf install -y ...: Installs necessary tools:dnf-utils: Providesreposyncfor downloading repositories.createrepo_c: Creates repository metadata.tar: For creating the tarball.findutils,coreutils,curl: General utilities.
WORKDIR /app: Sets the working directory inside the container.COPY yum.conf /app/yum.conf: Copies your custom DNF configuration.COPY ./yum.repos.d/ /app/yum.repos.d/: Copies all your repository definition files.COPY entrypoint.sh /app/entrypoint.sh: Copies the main script.RUN chmod +x /app/entrypoint.sh: Makes the script executable.ENV REPO_BASE_PATH /home/var/www/html: Sets an environment variable that might be used byentrypoint.sh.ENTRYPOINT ["/app/entrypoint.sh"]: Specifies the script to run when the container starts.CMD ["--help"]: Default command ifentrypoint.shis run without arguments (or if the entrypoint is overridden). This suggests yourentrypoint.shmight support a--helpflag.
version: '3.8': Specifies the Docker Compose file format version.services: repo-builder:: Defines a service namedrepo-builder.build: context: . dockerfile: Dockerfile: Tells Docker Compose to build an image from theDockerfilein the current directory.image: triatk/repo-builder:${REPO_BUILDER_VERSION:-latest}:- If an image named
triatk/repo-builderwith the specified tag (orlatest) exists locally or can be pulled, Docker Compose will use it. - If not, and
build:is specified, it will build the image locally and tag it with this name. This allows you to potentially push your built image to a registry like Docker Hub undertriatk/repo-builder.
- If an image named
volumes: - ./out:/output: Mounts the./outdirectory from your host machine to the/outputdirectory inside the container. This is how the generated tarball is persisted on your host.# environment: ...: Commented out; allows you to pass environment variables to theentrypoint.shscript if needed.# restart: 'no': Default behavior; the container will stop after theentrypoint.shscript completes.
Contributions are welcome! Please feel free to submit a pull request or open an issue.
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature) - Commit your Changes (
git commit -m 'Add some AmazingFeature') - Push to the Branch (
git push origin feature/AmazingFeature) - Open a Pull Request
Distributed under the MIT License.