Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Strategies for Creating an Offline APT Repository on Ubuntu

Tech 1

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

  1. 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.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.