Хочете пакувати свою службу під systemd у зручний .deb і розгортати її через локальний APT-репозиторій? Зараз покажу простий і надійний шлях, який працює і на робочих станціях, і на сервер Linux. Будемо працювати через термінал Linux, писати мінімальні bash скрипти й використовувати зрозумілі apt команди. Поїхали! 🚀

Що будемо робити

  • Створимо мінімальний .deb-пакет для служби systemd
  • Додамо preinst/postinst/prerm/postrm скрипти
  • Підпишемо репозиторій GPG-ключем
  • Піднімемо локальний APT-репозиторій та встановимо пакет через нього

Підготовка структури пакета

Приклад пакета hello-service: служба друкує повідомлення в журнал кожні 5 секунд. Створіть робочу структуру:

mkdir -p pkg/hello-service_1.0-1/DEBIAN
mkdir -p pkg/hello-service_1.0-1/usr/bin
mkdir -p pkg/hello-service_1.0-1/lib/systemd/system

Скрипт служби

Це простий демон на bash, який пише в stdout (journald збере лог).

cat > pkg/hello-service_1.0-1/usr/bin/hello-service <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
while true; do
  echo "$(date -Is) Hello from hello-service"
  sleep 5
done
EOF
chmod 0755 pkg/hello-service_1.0-1/usr/bin/hello-service

Юніт systemd

Опишемо службу, яка працює від ізольованого системного користувача hello з базовим hardening.

cat > pkg/hello-service_1.0-1/lib/systemd/system/hello-service.service <<'EOF'
[Unit]
Description=Hello demo service
After=network.target

[Service]
Type=simple
User=hello
Group=hello
ExecStart=/usr/bin/hello-service
Restart=on-failure
RestartSec=3
ProtectSystem=full
ProtectHome=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
EOF

Control і maintainer-скрипти

Створіть файл control із метаданими:

cat > pkg/hello-service_1.0-1/DEBIAN/control <<'EOF'
Package: hello-service
Version: 1.0-1
Section: utils
Priority: optional
Architecture: all
Depends: systemd
Maintainer: Your Name <you@example.com>
Description: Demo systemd service that prints hello every 5s
 A minimal demo service managed by systemd.
EOF

Тепер maintainer-скрипти (вони мають бути виконувані):

cat > pkg/hello-service_1.0-1/DEBIAN/preinst <<'EOF'
#!/bin/bash
set -e
# Створюємо системного користувача/групу для служби
if ! id -u hello >/dev/null 2>&1; then
  adduser --system --group --no-create-home \
    --home /nonexistent --shell /usr/sbin/nologin hello
fi
EOF

cat > pkg/hello-service_1.0-1/DEBIAN/postinst <<'EOF'
#!/bin/bash
set -e
chown root:root /usr/bin/hello-service
chmod 0755 /usr/bin/hello-service
systemctl daemon-reload || true
# Після встановлення/оновлення — вмикаємо та перезапускаємо
if [ "$1" = "configure" ] || [ "$1" = "upgrade" ]; then
  systemctl enable hello-service || true
  systemctl restart hello-service || true
fi
EOF

cat > pkg/hello-service_1.0-1/DEBIAN/prerm <<'EOF'
#!/bin/bash
set -e
# Перед видаленням зупинимо й вимкнемо
if [ "$1" = "remove" ]; then
  systemctl stop hello-service || true
  systemctl disable hello-service || true
fi
EOF

cat > pkg/hello-service_1.0-1/DEBIAN/postrm <<'EOF'
#!/bin/bash
set -e
systemctl daemon-reload || true
# На purge — прибираємо користувача/групу
if [ "$1" = "purge" ]; then
  deluser --system hello 2>/dev/null || true
  delgroup hello 2>/dev/null || true
fi
EOF

chmod 0755 pkg/hello-service_1.0-1/DEBIAN/*

Збірка та встановлення .deb

Збираємо пакет і встановлюємо його через apt команди (це коректніше, ніж тільки dpkg -i, бо тягне залежності):

dpkg-deb --build pkg/hello-service_1.0-1
sudo apt install ./hello-service_1.0-1.deb
# Перевірка стану
systemctl status hello-service --no-pager
journalctl -u hello-service -f

Підпис і локальний APT-репозиторій

Тепер зробимо репозиторій у поточній директорії та підпишемо його GPG-ключем. Це зручно для багаторазового розгортання на кількох вузлах (особливо на сервер Linux).

Створення й підпис ключа GPG

sudo apt update && sudo apt install -y dpkg-dev apt-utils gnupg apt-transport-https
# Створюємо ключ (параметри можна змінювати)
gpg --quick-generate-key "Hello Repo (local) <repo@example>" ed25519 sign 2y
# Експортуємо публічний ключ у систему клієнта
gpg --export -a "Hello Repo (local)" > hello-repo.asc
sudo gpg --dearmor -o /usr/share/keyrings/hello-repo-archive.gpg hello-repo.asc

Побудова індексу та Release

mkdir -p repo && cp hello-service_1.0-1.deb repo/
cd repo
# Створюємо Packages.gz
dpkg-scanpackages -m . /dev/null | gzip -9c > Packages.gz
# Створюємо Release
apt-ftparchive release . > Release
# Підписуємо (двома способами: окремий підпис і InRelease)
gpg --yes --armor --detach-sign -o Release.gpg Release
gpg --yes --clearsign -o InRelease Release
# Запускаємо простий HTTP-сервер для роздачі
python3 -m http.server 8080

Додавання репозиторію і встановлення

echo "deb [signed-by=/usr/share/keyrings/hello-repo-archive.gpg] http://localhost:8080 ./" | \
  sudo tee /etc/apt/sources.list.d/hello-local.list
sudo apt update
sudo apt install hello-service

Готово! Тепер ви можете оновлювати пакет, класти нові версії в repo/, регенерувати метадані та розгортати через знайомі apt команди.

Альтернативні способи

  • Швидка збірка через FPM: fpm -s dir -t deb -n hello-service -v 1.0.1 --deb-systemd hello-service.service usr/bin/hello-service. Зручно для прототипів.
  • Профі-вариант через debhelper/dpkg-buildpackage: створіть debian/ (control, rules, install, systemd), збирайте dpkg-buildpackage -us -uc. Це краще для CI/CD.
  • Репозиторій із reprepro: акуратна структура dists//pool/, зручне включення пакетів: reprepro includedeb stable ../hello-service_1.0-1.deb.

GUI-спосіб

  • Встановлення .deb: відкрийте файл у “Software Center” або в GDebi — зручно, якщо не хочете одразу йти в термінал Linux.
  • Додавання репозиторію: “Software & Updates” → вкладка “Other Software” → “Add…” і вставте рядок deb [signed-by=/usr/share/keyrings/hello-repo-archive.gpg] http://localhost:8080 ./. Ключ можна імпортувати через утиліту “Passwords and Keys (Seahorse)”.

FAQ

Служба не стартує після встановлення.
Перевірте журнал: journalctl -u hello-service -b. Найчастіше причина — неправильний ExecStart або відсутній користувач hello (див. preinst).

Помилка в maintainer-скриптах (preinst/postinst).
Переконайтеся, що у файлів права 0755, початок з #!/bin/bash, та додавайте set -e. Тестуйте скрипти окремо.

Зміни в юніті не підхоплюються.
Додайте systemctl daemon-reload у postinst/postrm або виконайте вручну.

APT каже “NO_PUBKEY”.
Експортуйте і встановіть публічний ключ у /usr/share/keyrings/*.gpg і використайте параметр signed-by=... у джерелі.

dpkg-scanpackages не знайдено.
Встановіть інструменти: sudo apt install dpkg-dev.

Як додати залежності (наприклад, bash)?
Вкажіть у Depends: файлу control (наприклад, Depends: systemd, bash (>= 5)). Не зловживайте залежностями.

Пакет встановився, але служба не увімкнена.
Перевірте умовну гілку в postinst і наявність секції [Install] у юніті.

Порада від Kernelka

Для безпеки в systemd-додатку вмикайте hardening: ProtectSystem, NoNewPrivileges, PrivateTmp, а також окремого користувача/групу. Якщо сервісу потрібні файли, робіть каталоги через tmpfiles.d або створюйте їх у preinst з правильними правами. А ще — тестуйте пакет у чистому контейнері (наприклад, через systemd-nspawn або Docker) перед релізом. 📦

Підсумок

  • Зібрали .deb із правильною структурою та systemd-юнітом
  • Додали робочі pre/post-скрипти для керування службою
  • Підписали локальний APT-репозиторій GPG-ключем
  • Підключили репозиторій і встановили пакет через apt
  • Ознайомилися з альтернативами (FPM, debhelper, reprepro) і GUI-варіантами

Тепер у вас є свій шлях від коду до встановлюваного пакета, готового для CI/CD і продакшен-розгортань. Вітаю! 😊