Skip to content

Create a Self-Hosted APT Repository using Nexus

Configuration

Steps to Take

Assumed is that a Nexus server is installed already.

The example here is based on having the Ubuntu 'jammy' distribution.

1) Create GPG Private and Public Keys

Generate a new gpg key on Nexus-repository.

# When not installed.
apt-get update && apt-get install gpg
# Generate a key.
gpg --full-generate-key
# Alternative.
gpg --gen-key

Get the key ID which was added.

gpg --list-keys

Export the created keys as files.

gpg --armor --output nexus-apt-repo.private.gpg.key --export-secret-key <gpg key Id>
gpg --armor --output nexus-apt-repo.public.gpg.key --export <gpg key Id>
gpg --output nexus-apt-repo.public.gpg --export <gpg key Id>
gpg --output nexus-apt-repo.private.gpg --export-secret-key <gpg key Id>

2) Set up Nexus APT-Repository

Paste the ASCII content of file nexus-repo.private.gpg.key into the APT-repository settings 'APT Settings > Signing Key'.

Assign a role to the uploader user account containing at least:

nx-repository-view-*-*-edit
nx-repository-view-*-*-read

3) Setup Client OS

3a) Add GPG-public-key

Copy the GPG-public-key nexus-repo.public.gpg to directory and file /etc/apt/trusted.gpg.d/nexus-repo.public.gpg.

For the Scanframe apt repository the key is located in the raw shared repository and can be added using the wget command.

wget https://nexus.scanframe.com/repository/shared/apt-repo-signatures/nexus-apt-repo.public.gpg \
-O /etc/apt/trusted.gpg.d/nexus-apt-repo.public.gpg
3b) Add Nexus Repository to the APT Source-list
Solution with sources-file

Add a source-list file scanframe-repo.sources to the directory /etc/apt/sources.list.d/.

cat <<EOD | sudo tee /etc/apt/sources.list.d/scanframe-repo.sources >/dev/null
Types: deb
URIs: https://nexus.scanframe.com/repository/apt-hosted
Suites: jammy
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By:
$(wget -qO- https://nexus.scanframe.com/repository/shared/apt-repo-signatures/nexus-apt-repo.public.asc | sed 's/^/ /')
EOD
Solution with list-file

The sources.list format is deb [options] repository_url distribution <components...> where in our case:

Field Description
options Like arch=amd64 to only look for 64-bit packages.
repository_url Formated like https://<nexus-hostname>/repository/<nexus-repo-name>.
distribution For example jammy as the Ubuntu named distribution.
components Always main is the default and fixed single component of the Nexus APT-repository.

So for the file named /etc/apt/sources.list.d/scanframe-repo.list the following is inserted.

deb [arch=amd64] https://nexus.scanframe.com/repository/apt-hosted jammy main
echo "deb [arch=$(dpkg --print-architecture)] https://nexus.scanframe.com/repository/apt-hosted jammy main" \
  > /etc/apt/sources.list.d/scanframe-repo.list
Solution using add-apt-repository

Alternatively use the add-apt-repository command from the package software-properties-common when installed.

add-apt-repository --yes "https://nexus.scanframe.com/repository/apt-hosted"

Check if the repository was added.

add-apt-repository --list

Uploading from Command-Line (CI-Pipeline)

To upload to the APT-repository use the next command/script.

#!/usr/bin/env bash

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CRED_FILE="${SCRIPT_DIR}/.apt-repo-credentials"

# Include the WriteLog function.
source "${SCRIPT_DIR}/inc/WriteLog.sh"

# Prints the help to stderr.
#
function ShowHelp() {
    echo "Usage: ${0} <file-to-upload...>
  Uploads files to a Sonatype Nexus repository allowing wildcards.

  When the credentials are not passed as environment variables a file named
  '${CRED_FILE}'
  is sourced containing the following variables e.g.:
    NEXUS_USER='uploader'
    NEXUS_PASSWORD='<uploader-password>'
    NEXUS_SERVER_URL='https://nexus.scanframe.com'
    NEXUS_REPO_NAME='apt-hosted'

  These environment variables can be set in GitLab for a project for CI-pipeline
  or partially by the pipeline configuration.
"
}

function PrependAndEscape() {
    while read -r line; do
        WriteLog -e "${1}${line}"
    done
}

# When no arguments are passed show help.
if [[ $# -eq 0 ]]; then
    ShowHelp
    exit 0
fi

# Import the credentials and repo details...
#NEXUS_USER="uploader"
#NEXUS_PASSWORD="<password>"
#NEXUS_SERVER_URL="https://nexus.scanframe.com"
#NEXUS_REPO_NAME="apt-hosted"

# Check if the user was configured and if not try to read the credentials file.
if [[ -z "${NEXUS_USER}" ]]; then
    WriteLog "# Reading credentials file: ${CRED_FILE}"
    if [[ ! -f "${CRED_FILE}" ]]; then
        WriteLog "Credential file not found: ${CRED_FILE} !"
        exit 1
    fi
    source "${SCRIPT_DIR}/.apt-repo-credentials"
fi

# Check if the credentials were set fully.
if [[ -z "${NEXUS_USER}" || -z "${NEXUS_PASSWORD}" || -z "${NEXUS_SERVER_URL}" || -z "${NEXUS_REPO_NAME}" ]]; then
    WriteLog "Credentials are not fully set!"
    exit 1
fi

# iterate over all the command-line arguments.
for UPLOAD_FILE in "$@"; do
    # Check if the file exists.
    if [[ ! -f "${UPLOAD_FILE}" ]]; then
        WriteLog "File not found: ${UPLOAD_FILE} !"
        exit 1
    else
        WriteLog "- Uploading file: ${UPLOAD_FILE}"
    fi
    # Perform curl and retrieve the HTTP response_code.
    response_code="$(curl \
        --silent --include \
        --request 'POST' \
        --user "${NEXUS_USER}:${NEXUS_PASSWORD}" \
        --header 'Accept: application/json' \
        --header 'Content-Type: multipart/form-data' \
        --form "apt.asset=@${UPLOAD_FILE};type=application/vnd.debian.binary-package" \
        "${NEXUS_SERVER_URL}/service/rest/v1/components?repository=${NEXUS_REPO_NAME}" |
        tee >(cat | PrependAndEscape "- " 1>&2) | grep -P "^HTTP/" | tail -n 1 | cut -d$' ' -f2)"
    # Check the response code for failure.
    if [[ "${response_code}" -lt 200 || "${response_code}" -ge 300 ]]; then
        WriteLog "Upload failed (${response_code}) of file: ${UPLOAD_FILE} !"
        exit 1
    fi
done

Possible Issues

GPG error NO_PUBKEY

When this error occurs it could be the GPG key private has been changed.

W: GPG error: https://nexus.scanframe.com/repository/apt-hosted jammy InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY

An update in the settings seems not to update the metadata folder in the repository.
To resolve this the metadata/dist folder needs to be removed/deleted.

The metadata folder is created and content is generated when the repository is connected to using apt-get update.