Strategies for Creating an Offline APT Repository on Ubuntu
Building a robust offline package repository is crucial for managing software installations in air-gapped environments or on servers with limited internet access. This approach ensures that all necessary dependencies are available locally, preventing installation failures and maintaining consistency across systems.
Disabling Automatic System Updates
Before creating an offline repository or operating in an air-gapped setup, it's essential to prevent unintended package updates from occurring. Ubuntu's unattended-upgrades service can automatically download and install security updates, which might introduce newer package versions not present in your designated offline source, leading to dependency conflicts. To disable this service, remove its configuration file:
sudo rm /etc/apt/apt.conf.d/20auto-upgrades
Preparing for Package Download (Initial Setup)
To populate your local repository, you first need to download packages from official Ubuntu mirrors or other online sources. This typically requires temporary internet access on the machine used for downloading.
First, ensure your sources.list is configured to point to reliable online mirrors. The following commands will configure your system to use standard Ubuntu archive mirrors based on your distribution's codename:
#!/bin/bash
# Get the Ubuntu codename (e.g., 'focal', 'jammy')
UBUNTU_CODENAME=$(lsb_release -sc)
# Backup current sources.list
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
# Configure standard online sources
sudo tee /etc/apt/sources.list << EOF
deb http://archive.ubuntu.com/ubuntu/ ${UBUNTU_CODENAME} main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu/ ${UBUNTU_CODENAME}-updates main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu/ ${UBUNTU_CODENAME}-backports main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu/ ${UBUNTU_CODENAME}-security main restricted universe multiverse
EOF
# Update package lists from the newly configured online sources
sudo apt update -y
Downloading Specific Packages and Their Dependencies
The apt-get install -d command is usedd to download packages without installling them, placing the .deb files into the /var/cache/apt/archives/ directory. The --reinstall flag ensures that packages are downloaded even if they are already installed.
General Package Download
To download a specific package and its direct dependencies:
sudo apt-get clean # Clear old cached packages first
sudo apt-get install -d --reinstall -y <package-name-1> <package-name-2> ...
Downloading GCC and its Ecosystem
For complex packages like GCC, apt-rdepends is invaluable for identifying the full transitive dependency tree. This ensures all necessary components (libraries, compilers, binutils, cpp) are included in your offline repository.
# Install apt-rdepends if not already present
sudo apt-get install -y apt-rdepends
# Get a list of GCC and its core dependencies, then download them
# Filtering for common components like lib, gcc, binutils, cpp
GCC_CORE_DEPENDENCIES=$(apt-rdepends -s depends gcc | grep -E "^(lib|gcc|binutils|cpp).*" | tr '\n' ' ')
sudo apt-get clean
sudo apt-get install -d --reinstall -y $GCC_CORE_DEPENDENCIES
Downloading Docker and NVIDIA Container Toolkit
These components require adding external GPG keys and custom APT repositories before their packages can be downloaded.
# Install prerequisites for adding repositories
sudo apt-get install -y ca-certificates curl gnupg
# Setup Docker repository
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=\"$(dpkg --print-architecture)\" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \n \"$(. /etc/os-release && echo \"$VERSION_CODENAME\")\" stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Setup NVIDIA Container Toolkit repository
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
# Update APT package lists after adding new repositories
sudo apt-get update
# Download Docker and NVIDIA Container Toolkit packages
sudo apt-get clean
sudo apt-get install -d --reinstall -y docker-ce docker-ce-cli containerd.io nvidia-container-toolkit
Building the Local Repository Index
After downloading all desired packages to /var/cache/apt/archives/, these files need to be consolidated into a dedicated directory and an index file (Packages.gz) generated. This index allows apt to understand the available packages in your local repository.
# Create a directory for your local repository packages
mkdir -p ~/offline_repo_data
# Copy downloaded .deb files from APT cache to your local repository directory
# Exclude lock files and partial downloads
rsync -az /var/cache/apt/archives/ ~/offline_repo_data/ --exclude "lock" --exclude "partial"
# Generate the Packages.gz index file for the local repository
# The /dev/null argument is for the override file, which is not needed here
dpkg-scanpackages ~/offline_repo_data/ /dev/null | gzip -9c > ~/offline_repo_data/Packages.gz
# Clean the APT cache after synchronization
sudo apt-get clean
echo "Local repository created at ~/offline_repo_data/"
Automating Offline Repository Management
For streamlined management, a shell script can automate the process of setting up online sources, downloading packages, building the local repository index, and switching between online/offline modes.
Consider the following script, offline_repo_manager.sh:
#!/usr/bin/env bash
LOCAL_REPO_DIRECTORY="./local_repo_data"
APT_CACHE_ARCHIVES="/var/cache/apt/archives"
PACKAGE_DOWNLOAD_LOG="package_downloads.log"
SYSTEM_LOCAL_REPO_PATH="/var/local_repo"
# --- Helper Functions ---
log_message() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1"
}
error_message() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1" >&2
exit 1
}
ensure_repo_dir() {
mkdir -p "${LOCAL_REPO_DIRECTORY}" || error_message "Failed to create local repository directory."
log_message "Local repository directory ensured: ${LOCAL_REPO_DIRECTORY}"
}
# --- Core Functionality ---
# Configures APT to use standard online Ubuntu mirrors
configure_online_sources() {
log_message "Configuring APT to use online mirror sources..."
local os_codename=$(lsb_release -sc)
if [ -z "${os_codename}" ]; then
error_message "Could not determine Ubuntu codename. Exiting."
fi
# Backup current sources.list before modification
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak_online_$(date +%s)
sudo tee /etc/apt/sources.list << EOF > /dev/null
deb http://archive.ubuntu.com/ubuntu/ ${os_codename} main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu/ ${os_codename}-updates main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu/ ${os_codename}-backports main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu/ ${os_codename}-security main restricted universe multiverse
EOF
log_message "Running apt update with online sources..."
sudo apt update -y || error_message "Failed to update package lists from online sources."
log_message "Online sources configured and updated successfully."
}
# Downloads specified packages to the APT cache
download_packages_to_cache() {
if [[ -z "$@" ]]; then
error_message "No packages specified for download."
fi
log_message "Cleaning APT cache..."
sudo apt-get clean > /dev/null
log_message "Attempting to download packages: $@"
# Use --reinstall to ensure packages are downloaded even if already installed
sudo apt-get install -d --reinstall -y "$@" || error_message "Failed to download some packages."
echo "Downloaded: apt install -d --reinstall -y $@" | tee -a "${PACKAGE_DOWNLOAD_LOG}"
log_message "Packages downloaded to cache."
}
# Copies cached packages to local_repo_data and generates Packages.gz
build_local_repository_index() {
ensure_repo_dir
log_message "Synchronizing downloaded packages from ${APT_CACHE_ARCHIVES} to ${LOCAL_REPO_DIRECTORY}..."
rsync -az "${APT_CACHE_ARCHIVES}/" "${LOCAL_REPO_DIRECTORY}/" --exclude "lock" --exclude "partial" \
|| error_message "Failed to synchronize packages to local repository directory."
log_message "Generating Packages.gz index for the local repository..."
dpkg-scanpackages "${LOCAL_REPO_DIRECTORY}/" /dev/null | gzip -9c > "${LOCAL_REPO_DIRECTORY}/Packages.gz" \
|| error_message "Failed to generate Packages.gz index."
sudo apt-get clean > /dev/null
log_message "Local repository index built successfully."
}
# Configures APT to use the local offline repository
configure_local_repository_sources() {
log_message "Configuring APT to use the local offline repository at file://${SYSTEM_LOCAL_REPO_PATH}/..."
sudo mkdir -p "${SYSTEM_LOCAL_REPO_PATH}" || error_message "Failed to create system local repo path."
# Sync the generated repo data to a system-wide accessible location
sudo rsync -a --delete "${LOCAL_REPO_DIRECTORY}/" "${SYSTEM_LOCAL_REPO_PATH}/" \
|| error_message "Failed to synchronize local repository data to system path."
# Backup current sources.list before modification
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak_local_$(date +%s)
sudo tee /etc/apt/sources.list << EOF > /dev/null
deb [trusted=yes] file://${SYSTEM_LOCAL_REPO_PATH}/ ./
EOF
log_message "Running apt update with local sources..."
sudo apt update -y -q || error_message "Failed to update package lists from the local repository."
log_message "Local sources configured and updated successfully."
}
# --- Main Script Logic ---
case "$1" in
"download")
shift # Remove 'download' from arguments
log_message "Initiating package download from online sources and building local repository."
configure_online_sources
download_packages_to_cache "$@"
build_local_repository_index
log_message "Download and local repository build complete."
;;
"download-gcc")
log_message "Identifying and downloading GCC and its dependencies."
configure_online_sources
# Install apt-rdepends if not already present
sudo apt-get install -y apt-rdepends || error_message "Failed to install apt-rdepends."
local gcc_deps=$(apt-rdepends -s depends gcc | grep -E "^(lib|gcc|binutils|cpp).*" | tr '\n' ' ')
if [[ -n "${gcc_deps}" ]]; then
log_message "Identified GCC dependencies: ${gcc_deps}"
download_packages_to_cache ${gcc_deps}
build_local_repository_index
log_message "GCC download and local repository build complete."
else
error_message "Could not identify GCC dependencies using apt-rdepends."
fi
;;
"download-docker-nvidia")
log_message "Setting up sources and downloading Docker and NVIDIA Container Toolkit components."
configure_online_sources
log_message "Adding Docker GPG key and repository..."
sudo install -m 0755 -d /etc/apt/keyrings || error_message "Failed to create /etc/apt/keyrings."
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg || error_message "Failed to add Docker GPG key."
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=\"$(dpkg --print-architecture)\" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -sc) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
log_message "Adding NVIDIA Container Toolkit GPG key and repository..."
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg || error_message "Failed to add NVIDIA GPG key."
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list > /dev/null
log_message "Updating package lists after adding Docker/NVIDIA repositories..."
sudo apt update -y || error_message "Failed to update after adding Docker/NVIDIA sources."
local docker_nvidia_pkgs="docker-ce docker-ce-cli containerd.io nvidia-container-toolkit"
download_packages_to_cache ${docker_nvidia_pkgs}
build_local_repository_index
log_message "Docker/NVIDIA download and local repository build complete."
;;
"build-index")
log_message "Only building index from existing cached packages in ${APT_CACHE_ARCHIVES}."
build_local_repository_index
;;
"set-local")
log_message "Switching APT to use the local offline repository."
configure_local_repository_sources
log_message "System configured to use local repository."
;;
"set-online")
log_message "Switching APT to use online mirror sources."
configure_online_sources
log_message "System configured to use online sources."
;;
"install")
shift # Remove 'install' from arguments
if [[ -z "$@" ]]; then
error_message "No packages specified for installation.\nUsage: $0 install <package_name>..."
fi
log_message "Attempting to install packages: $@ using current APT sources."
sudo apt install -y "$@" || error_message "Failed to install packages. Check local repository configuration."
log_message "Packages installed successfully."
;;
*) # Default usage instructions
echo "Usage: $0 <command> [arguments]"
echo "Commands:"
echo " download <package_name>... - Configures online sources, downloads specified packages, and builds local index."
echo " download-gcc - Downloads GCC and its dependencies."
echo " download-docker-nvidia - Downloads Docker and NVIDIA Container Toolkit components."
echo " build-index - Builds local repository index from existing cached packages."
echo " set-local - Configures APT to use the local offline repository."
echo " set-online - Configures APT to use default online mirror sources."
echo " install <package_name>... - Installs packages using the currently configured APT sources."
exit 1
;;
esac
Using the offline_repo_manager.sh Script
- Download specific packages:
sudo bash offline_repo_manager.sh download htop nginx
2. **Download GCC and its dependencies:**bash
sudo bash offline_repo_manager.sh download-gcc
3. **Download Docker and NVIDIA Container Toolkit components:**bash
sudo bash offline_repo_manager.sh download-docker-nvidia
4. **Build or rebuild the local repository index (e.g., after manually adding .deb files):**bash
sudo bash offline_repo_manager.sh build-index
5. **Switch APT to use your local offline repository:**bash
sudo bash offline_repo_manager.sh set-local
6. **Switch APT back to online mirrors (if temporary internet access is restored):**bash
sudo bash offline_repo_manager.sh set-online
7. **Install packages from the current configured repository (local or online):**bash
sudo bash offline_repo_manager.sh install htop
```
This script provides a structured way to manage packages for offline environments, ensuring that dependencies are correctly identified and included in your custom local repository.