Хочете свій локальний APT-репозиторій для Debian/Ubuntu, де всі пакети під рукою і без зайвого трафіку? З reprepro це реально просто, а завдяки systemd timers можна автоматично чистити старі версії пакетів і тримати репозиторій в ідеальному стані. Усе робимо в термінал Linux, акуратно і без магії 🪄
Що будемо робити
Короткий план: встановлюємо reprepro, створюємо структуру репозиторію, генеруємо GPG-ключ, налаштовуємо конфіг, додаємо .deb-пакети, публікуємо через HTTP (nginx опціонально) та налаштовуємо автоматичну ротацію через cron та systemd timers. По дорозі використаємо зручні apt команди і трохи bash скрипти.
Вимоги та підготовка
Підійде будь-який сервер чи VM з Debian/Ubuntu (або інший сумісний сервер Linux). Потрібні права sudo та відкритий TCP-порт 80 (якщо публікуєте через HTTP).
sudo apt update
sudo apt install -y reprepro gnupg nginx curl
Налаштування reprepro
Створюємо каталоги
sudo mkdir -p /srv/apt-repo/{conf,dists,pool}
sudo chown -R "$USER":"$USER" /srv/apt-repo
Генеруємо GPG-ключ для підпису
Ключ буде використовуватися для підпису метаданих репозиторію. Його публічну частину потім додамо на клієнти.
gpg --quick-generate-key "Local Repo <repo@example.com>" default default never
# Подивитися ключі та забрати KEYID
KEYID=$(gpg --list-keys --with-colons | awk -F: '/^pub:/ {print $5; exit}')
echo "Ваш KEYID: $KEYID"
Файли конфігурації reprepro
Далі опишемо дистрибутив (codename — наприклад, bookworm для Debian 12 або jammy для Ubuntu 22.04).
cat <<'EOF' | sed "s/@KEYID@/$KEYID/" | sudo tee /srv/apt-repo/conf/distributions > /dev/null
Origin: Local
Label: Local
Suite: stable
Codename: bookworm
Architectures: amd64 source
Components: main
SignWith: @KEYID@
Description: Local APT repo (bookworm)
EOF
cat <<'EOF' | sudo tee /srv/apt-repo/conf/options > /dev/null
basedir /srv/apt-repo
verbose
ask-passphrase
EOF
Додаємо пакети до репозиторію
Скопіюйте свої .deb у зручне місце і включайте їх за допомогою reprepro. Після кожного додавання репозиторій оновлюється автоматично.
# приклад: додаємо пакет для bookworm
reprepro -b /srv/apt-repo includedeb bookworm /path/to/package_1.0.0_amd64.deb
Публікація через HTTP (nginx, опціонально)
Найпростіше — віддати репозиторій статично через nginx. Зробимо symlink у веб-каталог і невеличкий сайт.
sudo ln -s /srv/apt-repo /var/www/html/repo
cat <<'EOF' | sudo tee /etc/nginx/sites-available/localrepo > /dev/null
server {
listen 80;
server_name _;
access_log /var/log/nginx/localrepo.access.log;
error_log /var/log/nginx/localrepo.error.log;
location /repo/ {
autoindex on;
alias /var/www/html/repo/;
}
}
EOF
sudo ln -s /etc/nginx/sites-available/localrepo /etc/nginx/sites-enabled/localrepo
sudo nginx -t && sudo systemctl reload nginx
Підключення клієнтів до вашого репозиторію
Експортуємо публічний ключ і додамо його на клієнтах з опцією signed-by (найкраща практика безпечних apt команди).
# на сервері: покласти ключ у веб-каталог
gpg --export -a "$KEYID" | sudo tee /var/www/html/repo/KEY.gpg > /dev/null
# на клієнті:
sudo curl -fsSL http://<SERVER-IP>/repo/KEY.gpg \
| sudo gpg --dearmor -o /usr/share/keyrings/localrepo.gpg
echo "deb [signed-by=/usr/share/keyrings/localrepo.gpg] http://<SERVER-IP>/repo bookworm main" \
| sudo tee /etc/apt/sources.list.d/localrepo.list
sudo apt update
sudo apt install your-package-name
Автоматична ротація старих пакетів через systemd timers
Репозиторії ростуть швидко. Збережемо лише N останніх версій кожного пакета й прибиратимемо інші. Для цього зробимо невеликий bash-скрипт і таймер systemd. Це куди надійніше, ніж cron, бо має журналювання і облік станів.
Скрипт ротації
sudo tee /usr/local/bin/repo-rotate.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
BASE="/srv/apt-repo"
DIST="bookworm"
KEEP_N=3
LOGTAG="repo-rotate"
# Отримати список "pkg version" для поточного DIST (без source рядків)
mapfile -t entries < <(reprepro -b "$BASE" list "$DIST" | awk -F': ' '{print $2}' | awk '{print $1, $2}' | sed '/^$/d')
# Побудувати асоціативний масив pkg -> версії
declare -A pkg_versions
for line in "${entries[@]}"; do
pkg=$(awk '{print $1}' <<<"$line")
ver=$(awk '{print $2}' <<<"$line")
pkg_versions[$pkg]="${pkg_versions[$pkg]:-} $ver"
done
removed=0
for pkg in "${!pkg_versions[@]}"; do
# Відсортувати за версіями (спадно), залишити перші KEEP_N
all_vers=$(echo "${pkg_versions[$pkg]}" | xargs -n1 | sort -Vr || true)
keep=$(echo "$all_vers" | head -n "$KEEP_N")
drop=$(comm -23 <(echo "$all_vers" | sort -V) <(echo "$keep" | sort -V) || true)
while read -r ver; do
[[ -z "$ver" ]] && continue
# Видалити конкретну версію пакета з дистрибутива
if reprepro -b "$BASE" remove "$DIST" "${pkg}=$ver"; then
logger -t "$LOGTAG" "removed $pkg=$ver"
removed=$((removed+1))
fi
done < <(echo "$drop")
done
# Прибрати нереференсні файли з pool/
reprepro -b "$BASE" deleteunreferenced || true
logger -t "$LOGTAG" "rotation done; versions removed: $removed"
EOF
sudo chmod +x /usr/local/bin/repo-rotate.sh
Юніт і таймер systemd
# Сервіс
sudo tee /etc/systemd/system/repo-rotate.service > /dev/null <<'EOF'
[Unit]
Description=Rotate old packages in local APT repo
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/repo-rotate.sh
User=root
Group=root
Nice=10
EOF
# Таймер (щодня о 03:17)
sudo tee /etc/systemd/system/repo-rotate.timer > /dev/null <<'EOF'
[Unit]
Description=Daily rotation for local APT repo
[Timer]
OnCalendar=*-*-* 03:17:00
Persistent=true
RandomizedDelaySec=5m
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now repo-rotate.timer
systemctl list-timers --all | grep repo-rotate || true
Альтернативні способи
- Інструмент aptly — потужний конкурент reprepro з власними механізмами snapshots та публікації. Якщо плануєте складні сценарії, розгляньте його.
- Замість systemd timers можна використати cron, але я раджу timers заради логів і гнучкості.
- Публікація без nginx: достатньо віддати /srv/apt-repo через будь-який статичний сервер або навіть через Python http.server у тестових умовах.
GUI-спосіб (якщо не любите суцільний CLI)
- Утиліта Seahorse допоможе керувати GPG-ключами графічно: створіть ключ, експортуйте публічну частину як KEY.gpg.
- Через файловий менеджер можна просто копіювати .deb у підготовлену папку і запускати включення пакетів командою в термінал Linux.
- Для моніторингу доступу з клієнтів зручно скористатися веб-логами в лог-в’юверах типу gnome-logs.
FAQ
Чому клієнт каже, що репозиторій не підписаний?
Перевірте, що ви експортували правильний публічний ключ і джерело у .list використовує опцію signed-by з шляхом до ключа. Також упевніться, що SignWith у distributions вказує коректний KEYID.
Помилка 403 у клієнта
Перевірте права на /srv/apt-repo і що nginx має доступ до файлів (читайте error.log). Symlink має вести в коректний каталог і не блокуватися SELinux/AppArmor.
Клієнт каже «Release file is not valid»
Невірна комбінація Suite/Codename або зіпсутий підпис. Заново запустіть включення пакета або явно виконайте reprepro -b /srv/apt-repo export і перевірте, що ключ існує та доступний gpg.
Як додати пакети для іншої версії, наприклад jammy?
Створіть ще один блок у conf/distributions з Codename: jammy і повторіть включення: reprepro includedeb jammy your.deb.
Скрипт ротації видаляє «замало» або «забагато»
Відкоригуйте KEEP_N. Якщо формати версій дуже кастомні, замініть sort -V на порівняння через dpkg --compare-versions у власній функції.
Чи можна зберігати джерельні пакети?
Так, додайте Architectures: source і використовуйте reprepro includedsc для .dsc. Скрипт ротації можна розширити, щоб обробляти й source.
Чи варто ставити пароль на GPG-ключ?
Так, бажано. Тоді при підписі буде запитуватися пароль (або використовуйте gpg-agent). У варіантах CI/CD — окремий менш привілейований ключ.
Порада від Kernelka
Тримайте Codename і Suite узгодженими: якщо у клієнта «bookworm», то й у conf/distributions має бути Codename: bookworm. А ще — версії з префіксами типу 1.0.0~rc1 будуть сортуватися інакше; перевірте свою стратегічку ротації на тестовому репо перед продом 🚀
Підсумок
- Встановили reprepro і підготували структуру репозиторію.
- Згенерували GPG-ключ та налаштували підпис.
- Додали .deb пакети й опційно видали репозиторій через nginx.
- Налаштували автоматичну ротацію старих версій через systemd timers.
- Підключили клієнтів через signed-by і перевірили apt update/apt install.

Прокоментувати
На сайті відображається лише твоє ім'я та коментар. Електронна пошта зберігається виключно для зв'язку з тобою за потреби та в жодному разі не передається стороннім особам.