User:Grawlinson/Packaging notes

From ArchWiki
Jump to navigation Jump to search

Systemd

Units

  • Use the upstream unit files whenever they exist
  • Try not to do anything Arch-specific. This will maximize chances of not having to change behavior in the future once the unit files are provided by upstream. In particular avoid 12 EnvironmentFile=, especially if it points to the Arch-specific /etc/conf.d
  • Always separate initialization behavior from the actual daemon behavior. If necessary, use a separate unit for the initialization, blocked on a ConditionFoo from systemd.unit(5). An example of this is sshd.service and sshdgenkeys.service.

Not using an EnvironmentFile= is OK if:

  • Either the daemon has its own configuration file where the same settings can be specified
  • The default service file "just works" in the most common case. Users who want to change the behavior should then override the default service file. If it is not possible to provide a sane default service file, it should be discussed on a case-by-case basis

A few comments about service files, assuming current behavior should be roughly preserved, and fancy behavior avoided:

  • If your service requires the network to be configured before it starts, use After=network.target. Do not use Wants=network.target or Requires=network.target
  • Use Type=forking, unless you know it's not necessary
    • Many daemons use the exit of the first process to signal that they are ready, so to minimize problems, it is safest to use this mode
    • To make sure that systemd is able to figure out which process is the main process, tell the daemon to write a pidfile and point systemd to it using PIDFile=
    • If the daemon in question is dbus-activated, socket-activated, or specifically supports Type=notify, that's a different matter, but currently only the case for a minority of daemons
  • Arch's rc scripts do not support dependencies, but with systemd they should be added add where necessary
    • The most typical case is that A requires the service B to be running before A is started. In that case add Requires=B and After=B to A.
    • If the dependency is optional then add Wants=B and After=B instead
    • Dependencies are typically placed on services and not on targets

If you want to get fancy, you should know what you are doing.

Note: Keep in mind that values to keys such as ExecStart and ExecStop are not run within a shell, but only passed to execv

Users and groups

  • Instead of creating users/groups in PKGBUILD/$pkgname.install, ship a sysusers.d(5) config file in /usr/lib/sysusers.d.
  • A pacman hook included in systemd will run systemd-sysusers foo.conf upon install to ensure the necessary users/groups are created right away, not just on the next boot.

Temporary files and directories

  • Instead of creating necessary runtime directories and files when a service is started (as some rc scripts do), ship a tmpfiles.d(5) config file in /usr/lib/tmpfiles.d.
  • A pacman hook included in systemd will run systemd-tmpfiles --create foo.conf upon install to ensure the necessary runtime files are created right away, not just on the next boot
Tip: This feature can be used for a whole lot of other things, e.g. for writing to arbitrary files, even in /sys

Nginx modules

All modules must be compiled dynamically. Upstream has documentation on converting static modules to dynamic modules.

PKGBUILD Example

Tip: For nginx-mainline, substitute the package dependencies.
PKGBUILD
pkgname=nginx-mod-module
_modname="${pkgname#nginx-mod-}"
depends=(nginx)
makedepends=(nginx-src)

prepare() {
  # prepare nginx-src
  cp -r /usr/src/nginx .
}

build() {
  cd "$srcdir"/nginx
  ./configure --add-dynamic-module=../$_modname-$pkgver
  make modules
}

package() {
  cd "$srcdir"/nginx/objs
  for mod in ngx_*.so; do
    install -Dm755 $mod "$pkgdir"/usr/lib/nginx/modules/$mod
  done
}

Language Specific

For language specific dependencies, e.g. Python (python-*) and Ruby (ruby-*), it is slightly easier to list dependencies like this:

PKGBUILD
_deps=('cachecontrol' 'cachy' 'cleo' 'html5lib' 'lockfile'
       'packaging' 'pkginfo' 'poetry-core' 'requests' 'requests-toolbelt'
       'shellingham' 'tomlkit' 'keyring' 'pexpect' 'virtualenv')
depends=("${_deps[@]/#/python-}")

Go

Upstream project without go modules:

PKGBUILD
url=https://github.com/upstream_user/upstream_project

prepare() {
  cd "$pkgname-$pkgver"
  go mod init "${url#https://}"
  go mod tidy
}

Ensure that go test skips specific tests:

PKGBUILD
test() {
  cd "$pkgname-$pkgver"
  go test -v $(go list ./... | grep -v "filetoremovehere")
}

Ruby

Example package function:

PKGBUILD
package() {
  local _gemdir="$(ruby -e'puts Gem.default_dir')"

  gem install \
    --ignore-dependencies \
    --no-user-install \
    --no-document \
    --install-dir "$pkgdir/$_gemdir" \
    --bindir "$pkgdir/usr/bin" \
    "$_gemname-$pkgver.gem"

  # delete cache
  cd "$pkgdir/$_gemdir"
  rm -rf cache

  # delete unnecessary files & folders
  cd "gems/$_gemname-$pkgver"
  rm -rf .github .gitignore .rspec spec \
    Changelog Gemfile LICENSE Rakefile appveyor.yml

  # move documentation
  install -vd "$pkgdir/usr/share/doc/$pkgname"
  mv README.md examples "$pkgdir/usr/share/doc/$pkgname"

  # move license notice
  install -vd "$pkgdir/usr/share/licenses/$pkgname"
  mv NOTICE "$pkgdir/usr/share/licenses/$pkgname"
}