快速搭建 apt 仓库构建环境

步骤一、创建子目录

$ mkdir -p ./packages/

步骤二、创建配置信息文件 meta.json

{
  "container_name": "aptly-repo-builder",
  "remote_server": "xx.xx.xx.xx",
  "remote_dir": "<remote-dir>",
  "docker_image": "<local-target-docker-image-address>",
  "distribution": "latest",
  "component": "main"
}

步骤三、创建 build.sh 文件

#!/usr/bin/env bash

set -e

# 日志函数
# 日志颜色代码:URL_ADDRESS# 日志颜色代码:https://misc.flogisoft.com/bash/tip_colors_and_formatting
# 日志格式:时间 [日志级别] 日志内容
# 日志级别:DEBUG, INFO, OK, WARN, ERROR
# 日志颜色:蓝色, 青色, 绿色, 黄色, 红色
# 日志输出:标准输出, 标准错误
# 日志示例:2021-01-01 00:00:00 +0800 [ DEBUG ] hello world.
# 日志示例:2021-01-01 00:00:00 +0800 [ INFO ] hello world.
# 日志示例:2021-01-01 00:00:00 +0800 [ OK ] hello world.
# 日志示例:2021-01-01 00:00:00 +0800 [ WARN ] hello world.
# 日志示例:2021-01-01 00:00:00 +0800 [ ERROR ] hello world.
if [[ "${TERM}" =~ ^(xterm|screen|vt100) ]]; then
  log_debug() {   echo -e "$(date '+%Y-%m-%d %H:%M:%S %z') [ \033[34mDEBUG\033[0m ] $*"; }
  log_info() {    echo -e "$(date '+%Y-%m-%d %H:%M:%S %z') [ \033[36mINFO\033[0m ] $*"; }
  log_success() { echo -e "$(date '+%Y-%m-%d %H:%M:%S %z') [ \033[32mOK\033[0m ] $*"; }
  log_warn() {    echo -e "$(date '+%Y-%m-%d %H:%M:%S %z') [ \033[33mWARN\033[0m ] $*" >&2; }
  log_error() {   echo -e "$(date '+%Y-%m-%d %H:%M:%S %z') [ \033[31mERROR\033[0m ] $*" >&2; }
else
  log_debug() {   echo -e "$(date '+%Y-%m-%d %H:%M:%S %z') [ DEBUG ] $*"; }
  log_info() {    echo -e "$(date '+%Y-%m-%d %H:%M:%S %z') [ INFO ] $*"; }
  log_success() { echo -e "$(date '+%Y-%m-%d %H:%M:%S %z') [ OK ] $*"; }
  log_warn() {    echo -e "$(date '+%Y-%m-%d %H:%M:%S %z') [ WARN ] $*" >&2; }
  log_error() {   echo -e "$(date '+%Y-%m-%d %H:%M:%S %z') [ ERROR ] $*" >&2; }
fi

# Get to workdir
cd "$(realpath "$(dirname "$(realpath "${BASH_SOURCE[0]}")")")"

# jq command is required
command -v jq >/dev/null 2>/dev/null || (log_error "command required: jq" && false)

# aptly command is required
command -v aptly >/dev/null 2>/dev/null || (log_error "command required: aptly" && false)

workdir="$(pwd)"
export APTLY_ROOT_DIR="${workdir}/aptly"

mkdir -p "${APTLY_ROOT_DIR}"

tmpdir="$(mktemp -d)"

config_file="${tmpdir:?}/aptly.conf.json"

< aptly.conf.json.envsubst envsubst > "${config_file}"

distribution="$(jq -r '.distribution' meta.json)"
component="$(jq -r '.component' meta.json)"
log_info "distribution: ${distribution}"
log_info "component: ${component}"

if ! aptly -config="${config_file}" repo show "${distribution}"; then
  # 创建 repo
  aptly -config="${config_file}" repo create -distribution="${distribution}" -component="${component}" "${distribution}"
  # 添加包
  aptly -config="${config_file}" repo add "${distribution}" ./packages/
  # 发布时禁用硬链接
  aptly -config="${config_file}" publish repo "${distribution}"
else
  # 更新包
  aptly -config="${config_file}" repo add "${distribution}" ./packages/
  # 更新发布时禁用硬链接
  aptly -config="${config_file}" publish update "${distribution}"
fi

echo "$(date -R) [ INFO ] aptly build repo successfully."

rm -rf "${tmpdir:?}/"

步骤四、创建 Dockerfile 文件

FROM docker.1ms.run/library/debian:13
COPY debian.sources /etc/apt/sources.list.d/debian.sources
RUN \
    apt update && \
    apt upgrade -y && \
    apt install -y aptly jq gettext-base

步骤五、创建文件 debian.sources

Types: deb
URIs: http://mirrors.tuna.tsinghua.edu.cn/debian
Suites: trixie trixie-updates trixie-backports
Components: main contrib non-free non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg

Types: deb
URIs: http://mirrors.tuna.tsinghua.edu.cn/debian-security
Suites: trixie-security
Components: main contrib non-free non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg

步骤六、创建文件 aptly.conf.json.envsubst

{
  "rootDir": "${APTLY_ROOT_DIR}",
  "downloadConcurrency": 4,
  "downloadSpeedLimit": 0,
  "downloadRetries": 0,
  "downloader": "default",
  "databaseOpenAttempts": -1,
  "architectures": [],
  "dependencyFollowSuggests": false,
  "dependencyFollowRecommends": false,
  "dependencyFollowAllVariants": false,
  "dependencyFollowSource": false,
  "dependencyVerboseResolve": false,
  "gpgDisableSign": true,
  "gpgDisableVerify": true,
  "gpgProvider": "gpg",
  "downloadSourcePackages": false,
  "skipLegacyPool": true,
  "ppaDistributorID": "debian",
  "ppaCodename": "",
  "skipContentsPublishing": false,
  "skipBz2Publishing": false,
  "FileSystemPublishEndpoints": {},
  "S3PublishEndpoints": {},
  "SwiftPublishEndpoints": {},
  "AzurePublishEndpoints": {},
  "AsyncAPI": false,
  "enableMetricsEndpoint": false
}

步骤七、创建 Makefile 文件

SHELL := /bin/bash

container_name := $(shell jq -r '.container_name' meta.json)
remote_server  := $(shell jq -r '.remote_server' meta.json)
remote_dir     := $(shell jq -r '.remote_dir' meta.json)
docker_image   := $(shell jq -r '.docker_image' meta.json)

help:
    @cat Makefile | grep '# `' | grep -v '@cat Makefile'

.PHONY: docker-image
docker-image:
    docker build \
        --progress plain \
        --pull \
        --tag ${docker_image} \
        --file Dockerfile \
        .

# `make build`                     Build
.PHONY: build
build: docker-image
    docker run \
        --rm \
        --name "${container_name}" \
        -v "$$(pwd)/:/data/" \
        ${docker_image} \
            bash /data/build.sh

# `make shell`                     Shell
.PHONY: shell
shell: docker-image
    docker run \
        --rm \
        -it \
        -v "$$(pwd)/:/data/" \
        --workdir /data \
        ${docker_image} \
            bash

# `make publish`                   Publish
.PHONY: publish
publish:
    ssh 'root@${remote_server}' mkdir -p '${remote_path}/'
    rsync \
        -a -r --no-i-r \
        --info=progress2 --info=name0 \
        --no-owner --no-group --no-perms \
        --delete \
        "$$(pwd)/aptly/public/" \
        'root@${remote_server}:${remote_dir}/'

# `make clean`                     Clean
.PHONY: clean
clean:
    docker stop ${container_name} >/dev/null 2>&1 || true
    docker rm   ${container_name} >/dev/null 2>&1 || true
Author: ismdeep
License: Copyright (c) 2025 CC-BY-NC-4.0 LICENSE