Compare commits

...
Author SHA1 Message Date
UltradesuandClaude Opus 4.8 63178574ce Package as a dedicated tsunagi user; add Arch packages
Release / tsunagi-linux-aarch64 (push) Successful in 2m39s
Release / tsunagi-linux-x86_64 (push) Successful in 2m32s
Release / tsunagi-macos-aarch64 (push) Canceled after 0s
Release / tsunagi-windows-x86_64 (push) Canceled after 0s
Release / Publish release (push) Canceled after 0s
Reworks the Linux packaging so the service runs as an unprivileged, dedicated
"tsunagi" user instead of root — the point of a native package is to set things
up properly for that OS's users — and adds Arch Linux packages beside the deb.
Both package families are built from the already-compiled static binary; nothing
is recompiled.

Service model (shared systemd unit, dist/linux/tsunagi.service):
- User=tsunagi with AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE, so
  it creates the TUN and binds low ports without root.
- StateDirectory/CacheDirectory/RuntimeDirectory give it /var/lib/tsunagi,
  /var/cache/tsunagi and /run/tsunagi, and the agent is pointed at them (and the
  socket at /run/tsunagi/agent.sock) through TSUNAGI_* env in the unit.
- The polkit rule now grants the resolve1 actions to the "tsunagi" group; the
  service user is in it, and admins can add themselves.

CLI: when no state dir is chosen, the control commands prefer the system
service's socket at /run/tsunagi/agent.sock when it exists, so `sudo tsunagi
status`/`join` reach the packaged daemon with no flags. A per-user agent (its
socket absent from /run) still uses its own.

Packaging:
- deb (build-deb.sh): postinst now creates the tsunagi system user+group; unit
  installed to /lib/systemd/system, binary to /usr/bin.
- Arch (new dist/arch/build-pkg.sh): a dependency-free .pkg.tar.zst assembled
  with bsdtar + zstd (no makepkg, no Arch host) — .PKGINFO, a .INSTALL scriptlet
  that creates the user and reloads systemd, and a gzipped .MTREE; unit to
  /usr/lib/systemd/system.
- The polkit rule moved to dist/linux (shared by deb, Arch and the tarball), and
  the tarball install.sh now creates the user and installs the unit + rule.
- Both release workflows build the deb and the Arch package on the two Linux
  matrix entries (deb_arch/pkg_arch), install libarchive-tools+zstd for it, and
  attach both to the release (Gitea API loop / gh).

Not exercised in this session: the .deb and .pkg.tar.zst build and install only
on Linux (no dpkg-deb/bsdtar+pacman locally); CLI build, clippy, fmt and tests
pass, and all shell/YAML is syntax-checked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-02 13:46:27 +01:00
11 changed files with 306 additions and 81 deletions

No files matched your search

+24 -4
View File
@@ -35,6 +35,7 @@ jobs:
zig: true
deb: true
deb_arch: amd64
pkg_arch: x86_64
- os: ubuntu-latest
target: aarch64-unknown-linux-musl
asset: tsunagi-linux-aarch64
@@ -42,6 +43,7 @@ jobs:
zig: true
deb: true
deb_arch: arm64
pkg_arch: aarch64
- os: macos-latest
target: aarch64-apple-darwin
asset: tsunagi-macos-aarch64
@@ -67,9 +69,9 @@ jobs:
# aws-lc-sys (iroh's rustls backend) and the bundled sqlite are C, and the
# musl targets have no prebuilt bindings, so bindgen needs libclang and the
# C build needs cmake. zig is the cross C compiler cargo-zigbuild drives.
- name: Install musl build tooling
- name: Install musl build and packaging tooling
if: matrix.zig
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cmake clang libclang-dev
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cmake clang libclang-dev libarchive-tools zstd
- name: Install zig
if: matrix.zig
uses: mlugg/setup-zig@v2
@@ -118,7 +120,8 @@ jobs:
cp README.md "$stage/"
case "${{ runner.os }}" in
Linux)
cp dist/linux/tsunagi.service dist/linux/install.sh "$stage/"
cp dist/linux/tsunagi.service dist/linux/install.sh \
dist/linux/50-tsunagi-resolved.rules "$stage/"
cp dist/linux/README.md "$stage/INSTALL.md"
;;
macOS)
@@ -152,6 +155,22 @@ jobs:
"$version" \
"tsunagi_${version}_${{ matrix.deb_arch }}.deb"
# A dependency-free Arch package (.pkg.tar.zst), assembled with bsdtar and
# zstd (no makepkg); same binary, service and polkit rule. See
# dist/arch/build-pkg.sh.
- name: Build the Arch package
if: matrix.pkg_arch
shell: bash
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
pkgver="${version//-/_}"
bash dist/arch/build-pkg.sh \
"target/${{ matrix.target }}/release/tsunagi" \
"${{ matrix.pkg_arch }}" \
"$version" \
"tsunagi-${pkgver}-1-${{ matrix.pkg_arch }}.pkg.tar.zst"
# @v3 on purpose: Gitea does not support the v4 artifact backend. This is
# what makes the archive (and the .deb, where built) show up under the run
# and hands it to `publish`.
@@ -162,6 +181,7 @@ jobs:
path: |
${{ matrix.asset }}-*.*
*.deb
*.pkg.tar.zst
if-no-files-found: error
publish:
@@ -233,5 +253,5 @@ jobs:
cat up.json >&2 || true
rc=1
fi
done < <(find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' -o -name '*.deb' \) -print0)
done < <(find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' -o -name '*.deb' -o -name '*.pkg.tar.zst' \) -print0)
exit "$rc"
+24 -4
View File
@@ -36,6 +36,7 @@ jobs:
zig: true
deb: true
deb_arch: amd64
pkg_arch: x86_64
- os: ubuntu-latest
target: aarch64-unknown-linux-musl
asset: tsunagi-linux-aarch64
@@ -43,6 +44,7 @@ jobs:
zig: true
deb: true
deb_arch: arm64
pkg_arch: aarch64
- os: macos-latest
target: aarch64-apple-darwin
asset: tsunagi-macos-aarch64
@@ -68,9 +70,9 @@ jobs:
# aws-lc-sys (iroh's rustls backend) and the bundled sqlite are C, and the
# musl targets have no prebuilt bindings, so bindgen needs libclang and the
# C build needs cmake. zig is the cross C compiler cargo-zigbuild drives.
- name: Install musl build tooling
- name: Install musl build and packaging tooling
if: matrix.zig
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cmake clang libclang-dev
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cmake clang libclang-dev libarchive-tools zstd
- name: Install zig
if: matrix.zig
uses: mlugg/setup-zig@v2
@@ -119,7 +121,8 @@ jobs:
cp README.md "$stage/"
case "${{ runner.os }}" in
Linux)
cp dist/linux/tsunagi.service dist/linux/install.sh "$stage/"
cp dist/linux/tsunagi.service dist/linux/install.sh \
dist/linux/50-tsunagi-resolved.rules "$stage/"
cp dist/linux/README.md "$stage/INSTALL.md"
;;
macOS)
@@ -153,6 +156,22 @@ jobs:
"$version" \
"tsunagi_${version}_${{ matrix.deb_arch }}.deb"
# A dependency-free Arch package (.pkg.tar.zst), assembled with bsdtar and
# zstd (no makepkg); same binary, service and polkit rule. See
# dist/arch/build-pkg.sh.
- name: Build the Arch package
if: matrix.pkg_arch
shell: bash
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
pkgver="${version//-/_}"
bash dist/arch/build-pkg.sh \
"target/${{ matrix.target }}/release/tsunagi" \
"${{ matrix.pkg_arch }}" \
"$version" \
"tsunagi-${pkgver}-1-${{ matrix.pkg_arch }}.pkg.tar.zst"
- name: Upload the build outputs as run artifacts
uses: actions/upload-artifact@v4
with:
@@ -160,6 +179,7 @@ jobs:
path: |
${{ matrix.asset }}-*.*
*.deb
*.pkg.tar.zst
if-no-files-found: error
publish:
@@ -188,7 +208,7 @@ jobs:
# download-artifact@v4 nests each artifact under its own directory, so
# the archives are artifacts/<asset>/<file>.
mapfile -t files < <(find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' -o -name '*.deb' \))
mapfile -t files < <(find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' -o -name '*.deb' -o -name '*.pkg.tar.zst' \))
printf 'asset: %s\n' "${files[@]}"
prerelease=()
+11
View File
@@ -761,6 +761,17 @@ fn control_socket(
if !_is_custom_state_dir {
return tsunagi::ipc::default_control_socket_path();
}
// When the user did not choose a state directory, prefer a running system
// service's socket, so `sudo tsunagi status` reaches the packaged daemon —
// which binds there via TSUNAGI_CONTROL_SOCKET — with no flags. Only when it
// is actually present, so a per-user agent still uses its own socket.
#[cfg(all(unix, not(target_os = "macos")))]
if !_is_custom_state_dir {
let system = std::path::Path::new("/run/tsunagi/agent.sock");
if system.exists() {
return system.to_path_buf();
}
}
tsunagi::ipc::control_socket_path(&paths.state_dir)
}
+52
View File
@@ -0,0 +1,52 @@
# Running tsunagi on Arch Linux
Install the package built for your architecture:
```sh
sudo pacman -U tsunagi-*-x86_64.pkg.tar.zst
```
It drops a static, dependency-free binary at `/usr/bin/tsunagi`, a systemd
service, and a polkit rule, and creates a dedicated system user `tsunagi` the
service runs as.
Start it:
```sh
sudo systemctl enable --now tsunagi
```
Control it with `sudo` (the agent's control socket is private to the service
user; `sudo` reaches it with no extra flags, because the CLI finds the running
service's socket at `/run/tsunagi/agent.sock`):
```sh
sudo tsunagi status
sudo tsunagi join <network-name> <tsn1…secret>
```
## How it runs
The service runs as the unprivileged `tsunagi` user with just two capabilities
(`CAP_NET_ADMIN`, `CAP_NET_BIND_SERVICE`) granted by systemd — no root. systemd
creates and owns its directories:
| what | path |
|---|---|
| state | `/var/lib/tsunagi` |
| cache | `/var/cache/tsunagi` |
| control socket | `/run/tsunagi/agent.sock` |
The polkit rule lets anyone in the `tsunagi` group configure systemd-resolved
for the overlay zones (the service user is in that group). To run the agent as
your own user as well, add yourself to the group: `sudo usermod -aG tsunagi
"$USER"`.
## Removal
```sh
sudo pacman -R tsunagi
```
stops and disables the service. The `tsunagi` user and `/var/lib/tsunagi` are
left in place; remove them by hand if you want them gone.
Vendored Executable
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env bash
# Assemble an Arch Linux package (.pkg.tar.zst) from a prebuilt static tsunagi
# binary, using only bsdtar + zstd on a non-Arch host — no makepkg, no Arch
# container. The binary is static, so the package has no dependencies.
#
# Usage: build-pkg.sh <binary> <arch> <version> <output.pkg.tar.zst>
# <binary> path to the built tsunagi executable
# <arch> pacman architecture: x86_64 or aarch64
# <version> package version (the tag without its leading v)
# <output> path to write the package to
#
# Needs bsdtar (libarchive-tools) and zstd on the builder.
set -euo pipefail
binary="$1"
arch="$2"
version="$3"
output="$4"
here="$(cd "$(dirname "$0")" && pwd)"
root="$(mktemp -d)"
trap 'rm -rf "$root"' EXIT
# pacman pkgver may not contain '-', so a prerelease tag like 0.1.0-rc.9 becomes
# 0.1.0_rc.9.
pkgver="$(printf '%s' "$version" | tr '-' '_')"
pkgrel=1
# Payload. Arch keeps units under /usr/lib/systemd/system and installs to
# /usr/bin, so the service's ExecStart is rewritten to match.
install -D -m 0755 "$binary" "$root/usr/bin/tsunagi"
mkdir -p "$root/usr/lib/systemd/system"
sed 's#/usr/local/bin/tsunagi#/usr/bin/tsunagi#' \
"$here/../linux/tsunagi.service" > "$root/usr/lib/systemd/system/tsunagi.service"
chmod 0644 "$root/usr/lib/systemd/system/tsunagi.service"
install -D -m 0644 "$here/../linux/50-tsunagi-resolved.rules" \
"$root/usr/share/polkit-1/rules.d/50-tsunagi-resolved.rules"
install -D -m 0644 "$here/../../README.md" "$root/usr/share/doc/tsunagi/README.md"
install -D -m 0644 "$here/../linux/README.md" "$root/usr/share/doc/tsunagi/INSTALL.md"
# Installed size (payload only), before the metadata files are written.
size="$(du -sb "$root" | cut -f1)"
cat > "$root/.PKGINFO" <<PKGINFO
pkgname = tsunagi
pkgver = ${pkgver}-${pkgrel}
pkgdesc = Serverless private mesh networking agent
url = https://github.com/Ultradesu/tsunagi
builddate = $(date +%s)
packager = AB <ab@hexor.cy>
size = ${size}
arch = ${arch}
license = MIT
license = Apache
PKGINFO
# Scriptlet: create the dedicated system user the service runs as, and keep
# systemd in step. The "tsunagi" user is in the "tsunagi" group, which the
# polkit rule grants the resolver actions.
cat > "$root/.INSTALL" <<'INSTALL'
post_install() {
getent group tsunagi >/dev/null 2>&1 || groupadd -r tsunagi
getent passwd tsunagi >/dev/null 2>&1 || \
useradd -r -g tsunagi -d /var/lib/tsunagi -s /usr/bin/nologin -c "tsunagi agent" tsunagi
systemctl daemon-reload >/dev/null 2>&1 || true
}
post_upgrade() {
post_install
}
pre_remove() {
systemctl disable --now tsunagi.service >/dev/null 2>&1 || true
}
post_remove() {
systemctl daemon-reload >/dev/null 2>&1 || true
}
INSTALL
# .MTREE over the metadata and payload (not over itself), gzip-compressed, as
# pacman expects.
(
cd "$root"
LANG=C bsdtar -czf .MTREE --format=mtree \
--options='!all,use-set,type,uid,gid,mode,time,size,md5,sha256,link' \
.PKGINFO .INSTALL usr
)
# Pack with .PKGINFO first, then the rest, and zstd-compress.
(
cd "$root"
bsdtar -cf - .PKGINFO .MTREE .INSTALL usr
) | zstd -c -19 -T0 > "$output"
echo "built $output"
-25
View File
@@ -1,25 +0,0 @@
// Installed by the tsunagi package.
//
// Lets a non-root agent configure systemd-resolved for its overlay zones — the
// four resolve1 actions it calls, and nothing else — for any user in the
// "tsunagi" group (created by the package). The packaged systemd service runs
// as root and does not need this rule; it is for running the agent as your own
// user instead:
//
// sudo usermod -aG tsunagi "$USER"
// sudo setcap cap_net_admin,cap_net_bind_service+p /usr/bin/tsunagi
// tsunagi up
//
// polkit decides by user/group id, not by capability, which is why the resolver
// needs this rule even once the binary has the capability.
polkit.addRule(function(action, subject) {
var allowed = [
"org.freedesktop.resolve1.set-dns-servers",
"org.freedesktop.resolve1.set-domains",
"org.freedesktop.resolve1.set-default-route",
"org.freedesktop.resolve1.revert"
];
if (allowed.indexOf(action.id) >= 0 && subject.isInGroup("tsunagi")) {
return polkit.Result.YES;
}
});
+11 -5
View File
@@ -29,7 +29,7 @@ sed 's#/usr/local/bin/tsunagi#/usr/bin/tsunagi#' \
"$here/../linux/tsunagi.service" > "$root/lib/systemd/system/tsunagi.service"
chmod 0644 "$root/lib/systemd/system/tsunagi.service"
install -D -m 0644 "$here/50-tsunagi-resolved.rules" \
install -D -m 0644 "$here/../linux/50-tsunagi-resolved.rules" \
"$root/usr/share/polkit-1/rules.d/50-tsunagi-resolved.rules"
install -D -m 0644 "$here/../../README.md" "$root/usr/share/doc/tsunagi/README.md"
install -D -m 0644 "$here/../linux/README.md" "$root/usr/share/doc/tsunagi/INSTALL.md"
@@ -47,18 +47,24 @@ Homepage: https://github.com/Ultradesu/tsunagi
Description: Serverless private mesh networking agent
tsunagi forms small private mesh IP networks between peers, with NAT
traversal and no central server. This package installs a static,
dependency-free binary, a systemd service that runs the agent as root, and a
polkit rule that lets a non-root agent configure systemd-resolved.
dependency-free binary, a systemd service that runs the agent as a dedicated
unprivileged "tsunagi" user with only CAP_NET_ADMIN and CAP_NET_BIND_SERVICE,
and a polkit rule for the local DNS resolver.
CTRL
# A system group for the optional non-root run (the polkit rule grants it the
# resolve1 actions). The packaged service runs as root and does not need it.
# The dedicated system user the service runs as. It is in the "tsunagi" group,
# which the polkit rule grants the resolve1 actions. systemd creates the state,
# cache and runtime directories for it on first start.
cat > "$root/DEBIAN/postinst" <<'POSTINST'
#!/bin/sh
set -e
if ! getent group tsunagi >/dev/null 2>&1; then
addgroup --system tsunagi >/dev/null 2>&1 || true
fi
if ! getent passwd tsunagi >/dev/null 2>&1; then
adduser --system --ingroup tsunagi --home /var/lib/tsunagi --no-create-home \
--gecos "tsunagi agent" --disabled-login tsunagi >/dev/null 2>&1 || true
fi
if [ -d /run/systemd/system ]; then
systemctl daemon-reload >/dev/null 2>&1 || true
fi
+23
View File
@@ -0,0 +1,23 @@
// Installed by the tsunagi packages.
//
// polkit decides who may configure systemd-resolved by user/group id, not by
// capability, so the agent needs this even though it holds CAP_NET_ADMIN. It
// grants exactly the four resolve1 actions the agent calls, and nothing else,
// to anyone in the "tsunagi" group.
//
// The packaged service runs as the "tsunagi" user (which is in that group), so
// its resolver configuration is allowed. To also run the agent as your own
// user, add yourself to the group:
//
// sudo usermod -aG tsunagi "$USER"
polkit.addRule(function(action, subject) {
var allowed = [
"org.freedesktop.resolve1.set-dns-servers",
"org.freedesktop.resolve1.set-domains",
"org.freedesktop.resolve1.set-default-route",
"org.freedesktop.resolve1.revert"
];
if (allowed.indexOf(action.id) >= 0 && subject.isInGroup("tsunagi")) {
return polkit.Result.YES;
}
});
+17 -12
View File
@@ -2,40 +2,45 @@
This archive holds a **static** `tsunagi` binary (musl), so it runs on any
distribution — Arch, Alpine, NixOS, old glibc systems — with no shared-library
dependencies. Alongside it are a systemd unit and an installer.
dependencies. Alongside it are a systemd unit, a polkit rule, and an installer.
Creating the TUN interface needs `CAP_NET_ADMIN`, and configuring
systemd-resolved is decided by polkit (by user id, not capability). There are
three ways to satisfy that; pick one.
systemd-resolved is decided by polkit. There are three ways to run it; pick one.
## Quick install — systemd service as root
> On Debian/Ubuntu and Arch, prefer the native package (`.deb` / `.pkg.tar.zst`)
> from the release — it does all of the below for you.
## Quick install — systemd service as a dedicated user
```sh
sudo ./install.sh
sudo systemctl enable --now tsunagi
```
The service runs the agent as root, so the interface, low ports and the
resolver all just work (root is not subject to polkit). Control it **as root**,
so the client resolves the same state directory the daemon uses:
The service runs as an unprivileged `tsunagi` user with just `CAP_NET_ADMIN`
and `CAP_NET_BIND_SERVICE` (granted by systemd), not root. systemd creates its
state (`/var/lib/tsunagi`), cache (`/var/cache/tsunagi`) and runtime
(`/run/tsunagi`) directories, and the polkit rule lets it configure the
resolver. Control it with `sudo` — the CLI finds the running service's socket at
`/run/tsunagi/agent.sock` with no flags:
```sh
sudo tsunagi status
sudo tsunagi join <network-name> <tsn1…secret>
```
## As your own user — one capability, no root at runtime
## As your own user — one capability, no service
```sh
sudo install -m 0755 tsunagi /usr/local/bin/tsunagi
sudo setcap cap_net_admin,cap_net_bind_service+p /usr/local/bin/tsunagi
sudo usermod -aG tsunagi "$USER" # so the resolver works; log in again after
tsunagi up
```
Nothing is left behind and nothing runs as root. The capability is lost on every
rebuild or copy of the binary, so re-run `setcap` after replacing it. For the
local DNS resolver, run `tsunagi dns` once: it prints the exact polkit rule that
lets your user configure systemd-resolved, ready to paste.
Nothing runs as root. The capability is lost on every rebuild or copy of the
binary, so re-run `setcap` after replacing it. The agent uses your per-user
state directory and socket, so plain `tsunagi status` (no sudo) reaches it.
## Without touching the OS
+27 -19
View File
@@ -1,10 +1,11 @@
#!/usr/bin/env sh
# Installs the tsunagi binary and, where systemd is present, a root service.
# Run it as root (it writes under /usr/local and /etc):
# Installs the static tsunagi binary and, where systemd is present, a service
# that runs the agent as a dedicated unprivileged "tsunagi" user. Run as root:
#
# sudo ./install.sh
#
# PREFIX overrides the install prefix (default /usr/local).
# PREFIX overrides the install prefix (default /usr/local); if you change it,
# edit ExecStart in tsunagi.service to match.
set -eu
PREFIX="${PREFIX:-/usr/local}"
@@ -19,8 +20,23 @@ echo "Installing tsunagi to ${PREFIX}/bin"
install -d "${PREFIX}/bin"
install -m 0755 "${here}/tsunagi" "${PREFIX}/bin/tsunagi"
# The dedicated system user the service runs as (works with either shadow's
# useradd or Debian's adduser).
if ! getent group tsunagi >/dev/null 2>&1; then
groupadd -r tsunagi 2>/dev/null || addgroup --system tsunagi 2>/dev/null || true
fi
if ! getent passwd tsunagi >/dev/null 2>&1; then
useradd -r -g tsunagi -d /var/lib/tsunagi -s /usr/sbin/nologin -c "tsunagi agent" tsunagi 2>/dev/null \
|| adduser --system --ingroup tsunagi --home /var/lib/tsunagi --no-create-home \
--gecos "tsunagi agent" --disabled-login tsunagi 2>/dev/null || true
fi
# polkit rule so the agent (as the tsunagi user) may configure systemd-resolved.
install -d /etc/polkit-1/rules.d
install -m 0644 "${here}/50-tsunagi-resolved.rules" /etc/polkit-1/rules.d/50-tsunagi-resolved.rules
if command -v systemctl >/dev/null 2>&1; then
echo "Installing the systemd service (runs the agent as root)"
echo "Installing the systemd service (runs as the tsunagi user)"
install -m 0644 "${here}/tsunagi.service" /etc/systemd/system/tsunagi.service
systemctl daemon-reload
cat <<EOF
@@ -29,7 +45,7 @@ Done. Start the agent with:
sudo systemctl enable --now tsunagi
Control it as root, so the client shares the agent's state directory:
Control it with sudo (the CLI finds the service's socket automatically):
sudo tsunagi status
sudo tsunagi join <network-name> <tsn1…secret>
@@ -39,19 +55,11 @@ else
Installed the binary; no systemd found, so no service was set up. Either:
* run it directly as root:
sudo tsunagi up
* or grant the capability and run it as your own user (no root at runtime):
sudo setcap cap_net_admin,cap_net_bind_service+p ${PREFIX}/bin/tsunagi
tsunagi up
For the local DNS resolver as a non-root user, run \`tsunagi dns\` once; it
prints the polkit rule that lets your user configure systemd-resolved.
* or skip the interface entirely with \`tsunagi up --no-tun\`: tunnels still
form, they just do not reach the operating system.
* run it as root: sudo ${PREFIX}/bin/tsunagi up
* or as your own user: sudo setcap cap_net_admin,cap_net_bind_service+p ${PREFIX}/bin/tsunagi
tsunagi up
(add yourself to the tsunagi group so the resolver works:
sudo usermod -aG tsunagi "\$USER")
* or without touching the OS: tsunagi up --no-tun
EOF
fi
+22 -12
View File
@@ -6,21 +6,31 @@ Wants=network-online.target
[Service]
Type=simple
# Runs as a dedicated unprivileged user, not root. The two capabilities are all
# it needs: CAP_NET_ADMIN to create and configure the TUN interface, and
# CAP_NET_BIND_SERVICE for low ports. The packages create the `tsunagi` user.
User=tsunagi
Group=tsunagi
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
# systemd creates and owns these for the service user, and the agent is pointed
# at them: state in /var/lib/tsunagi, cache in /var/cache/tsunagi, and the
# control socket in /run/tsunagi, which stays private to the service user.
StateDirectory=tsunagi
StateDirectoryMode=0700
CacheDirectory=tsunagi
RuntimeDirectory=tsunagi
RuntimeDirectoryMode=0755
Environment=TSUNAGI_STATE_DIR=%S/tsunagi
Environment=TSUNAGI_CACHE_DIR=%C/tsunagi
Environment=TSUNAGI_CONTROL_SOCKET=%t/tsunagi/agent.sock
# The tarball installer puts the binary here; the deb and the Arch package
# rewrite this to /usr/bin where they install it.
ExecStart=/usr/local/bin/tsunagi up
Restart=on-failure
RestartSec=5
# This unit runs the agent as root, which covers everything it needs: creating
# the TUN interface (CAP_NET_ADMIN), binding low ports, and configuring
# systemd-resolved — root is not subject to polkit, so no polkit rule is needed.
#
# To run as a non-root user instead, uncomment the three lines below, set a
# User=, and install the polkit rule that `tsunagi dns` prints (resolved decides
# by user id, not capability, so the rule is what lets a non-root agent set the
# resolver):
#User=tsunagi
#AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
#CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target