Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63178574ce | ||
|
|
a3856eed3b | ||
|
|
eac54f8c79 | ||
|
|
a8dfd7017b | ||
|
|
99f4cf0220 |
No files matched your search
@@ -33,11 +33,17 @@ jobs:
|
||||
asset: tsunagi-linux-x86_64
|
||||
archive: tar.gz
|
||||
zig: true
|
||||
deb: true
|
||||
deb_arch: amd64
|
||||
pkg_arch: x86_64
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-musl
|
||||
asset: tsunagi-linux-aarch64
|
||||
archive: tar.gz
|
||||
zig: true
|
||||
deb: true
|
||||
deb_arch: arm64
|
||||
pkg_arch: aarch64
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
asset: tsunagi-macos-aarch64
|
||||
@@ -63,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
|
||||
@@ -114,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)
|
||||
@@ -133,13 +140,48 @@ jobs:
|
||||
tar -C dist/pkg -czf "${archive}" "${{ matrix.asset }}"
|
||||
fi
|
||||
|
||||
# A dependency-free .deb for the x86_64 and aarch64 Linux builds: it drops
|
||||
# the static binary, the systemd service and the polkit rule. Built with
|
||||
# dpkg-deb (present on the runner); see dist/deb/build-deb.sh.
|
||||
- name: Build the Debian package
|
||||
if: matrix.deb
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="${GITHUB_REF_NAME#v}"
|
||||
bash dist/deb/build-deb.sh \
|
||||
"target/${{ matrix.target }}/release/tsunagi" \
|
||||
"${{ matrix.deb_arch }}" \
|
||||
"$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 show up under the run and hands it to `publish`.
|
||||
- name: Upload the archive as a run artifact
|
||||
# what makes the archive (and the .deb, where built) show up under the run
|
||||
# and hands it to `publish`.
|
||||
- name: Upload the build outputs as run artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.asset }}
|
||||
path: ${{ matrix.asset }}-*.*
|
||||
path: |
|
||||
${{ matrix.asset }}-*.*
|
||||
*.deb
|
||||
*.pkg.tar.zst
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
@@ -211,5 +253,5 @@ jobs:
|
||||
cat up.json >&2 || true
|
||||
rc=1
|
||||
fi
|
||||
done < <(find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' \) -print0)
|
||||
done < <(find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' -o -name '*.deb' -o -name '*.pkg.tar.zst' \) -print0)
|
||||
exit "$rc"
|
||||
@@ -0,0 +1,228 @@
|
||||
name: Release
|
||||
|
||||
# GitHub version of the release pipeline. The build half mirrors the Gitea one
|
||||
# in .gitea/workflows/release.yml (static musl Linux via cargo-zigbuild, native
|
||||
# macOS and Windows); only the publish half differs: GitHub needs the v4
|
||||
# artifact backend and fills the release with `gh`.
|
||||
#
|
||||
# Every job is gated on `github.server_url == 'https://github.com'`, so this file
|
||||
# runs only on GitHub. Gitea runs its own copy in .gitea/workflows; should a
|
||||
# Gitea instance also read .github/workflows, the guard skips these jobs there
|
||||
# so they never fail on the @v4 artifact backend or `gh`.
|
||||
on:
|
||||
push:
|
||||
tags: ["v*"]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_NET_RETRY: "10"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.asset }}
|
||||
if: ${{ github.server_url == 'https://github.com' }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-musl
|
||||
asset: tsunagi-linux-x86_64
|
||||
archive: tar.gz
|
||||
zig: true
|
||||
deb: true
|
||||
deb_arch: amd64
|
||||
pkg_arch: x86_64
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-musl
|
||||
asset: tsunagi-linux-aarch64
|
||||
archive: tar.gz
|
||||
zig: true
|
||||
deb: true
|
||||
deb_arch: arm64
|
||||
pkg_arch: aarch64
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
asset: tsunagi-macos-aarch64
|
||||
archive: tar.gz
|
||||
zig: false
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
asset: tsunagi-windows-x86_64
|
||||
archive: zip
|
||||
zig: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.target }}
|
||||
|
||||
# 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 and packaging tooling
|
||||
if: matrix.zig
|
||||
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
|
||||
with:
|
||||
# Pin a real, archived Zig release. Left unpinned the action resolves
|
||||
# to the newest version it knows of (a yet-unreleased 0.16.0-dev),
|
||||
# whose tarball 404s on ziglang.org while the community mirrors flake.
|
||||
version: 0.14.1
|
||||
- name: Install cargo-zigbuild
|
||||
if: matrix.zig
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-zigbuild
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${{ matrix.zig }}" == "true" ]]; then
|
||||
# zig's linker ignores the `-O1` GNU-ld optimization flag Rust passes
|
||||
# and says so on stderr, which rustc now surfaces as a linker_messages
|
||||
# warning. It is cosmetic — the binary is correct — so quiet just that
|
||||
# one lint to keep the musl build log clean. A real link error still
|
||||
# fails the build.
|
||||
export RUSTFLAGS="${RUSTFLAGS:-} -A linker_messages"
|
||||
cargo zigbuild --release --locked --target "${{ matrix.target }}" --bin tsunagi
|
||||
else
|
||||
cargo build --release --locked --target "${{ matrix.target }}" --bin tsunagi
|
||||
fi
|
||||
|
||||
- name: Package
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
version="${GITHUB_REF_NAME#v}"
|
||||
stage="dist/pkg/${{ matrix.asset }}"
|
||||
mkdir -p "$stage"
|
||||
|
||||
bin="tsunagi"
|
||||
[[ "${{ runner.os }}" == "Windows" ]] && bin="tsunagi.exe"
|
||||
cp "target/${{ matrix.target }}/release/${bin}" "$stage/"
|
||||
|
||||
# The top-level README, plus the per-OS install notes and integration
|
||||
# files, so the archive carries what the platform needs to set up.
|
||||
cp README.md "$stage/"
|
||||
case "${{ runner.os }}" in
|
||||
Linux)
|
||||
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)
|
||||
cp dist/macos/cy.hexor.tsunagi.plist "$stage/"
|
||||
cp dist/macos/README.md "$stage/INSTALL.md"
|
||||
;;
|
||||
Windows)
|
||||
cp dist/windows/README.md "$stage/INSTALL.md"
|
||||
;;
|
||||
esac
|
||||
|
||||
archive="${{ matrix.asset }}-${version}.${{ matrix.archive }}"
|
||||
if [[ "${{ matrix.archive }}" == "zip" ]]; then
|
||||
(cd dist/pkg && 7z a "../../${archive}" "${{ matrix.asset }}")
|
||||
else
|
||||
tar -C dist/pkg -czf "${archive}" "${{ matrix.asset }}"
|
||||
fi
|
||||
|
||||
# A dependency-free .deb for the x86_64 and aarch64 Linux builds: it drops
|
||||
# the static binary, the systemd service and the polkit rule. Built with
|
||||
# dpkg-deb (present on the runner); see dist/deb/build-deb.sh.
|
||||
- name: Build the Debian package
|
||||
if: matrix.deb
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="${GITHUB_REF_NAME#v}"
|
||||
bash dist/deb/build-deb.sh \
|
||||
"target/${{ matrix.target }}/release/tsunagi" \
|
||||
"${{ matrix.deb_arch }}" \
|
||||
"$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:
|
||||
name: ${{ matrix.asset }}
|
||||
path: |
|
||||
${{ matrix.asset }}-*.*
|
||||
*.deb
|
||||
*.pkg.tar.zst
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
name: Publish release
|
||||
needs: build
|
||||
if: ${{ github.server_url == 'https://github.com' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download every build artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Show what was collected
|
||||
run: find artifacts -type f
|
||||
|
||||
- name: Create the release and upload the archives
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# 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' -o -name '*.pkg.tar.zst' \))
|
||||
printf 'asset: %s\n' "${files[@]}"
|
||||
|
||||
prerelease=()
|
||||
case "$TAG" in *-*) prerelease=(--prerelease) ;; esac
|
||||
|
||||
# Create the release on the first run; on a re-run, add/replace assets
|
||||
# on the release that is already there.
|
||||
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
gh release upload "$TAG" "${files[@]}" --repo "$GITHUB_REPOSITORY" --clobber
|
||||
else
|
||||
gh release create "$TAG" "${files[@]}" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "tsunagi $TAG" \
|
||||
--generate-notes \
|
||||
--verify-tag \
|
||||
"${prerelease[@]}"
|
||||
fi
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Vendored
+52
@@ -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.
|
||||
+95
@@ -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"
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
# Assemble a dependency-free Debian package from a prebuilt static tsunagi
|
||||
# binary, using only dpkg-deb (no cargo-deb, no nfpm). The binary is statically
|
||||
# linked, so the package declares no Depends and runs on any Debian/Ubuntu.
|
||||
#
|
||||
# Usage: build-deb.sh <binary> <deb_arch> <version> <output.deb>
|
||||
# <binary> path to the built tsunagi executable
|
||||
# <deb_arch> Debian architecture: amd64 or arm64
|
||||
# <version> package version (the tag without its leading v)
|
||||
# <output> path to write the .deb to
|
||||
set -euo pipefail
|
||||
|
||||
binary="$1"
|
||||
arch="$2"
|
||||
version="$3"
|
||||
output="$4"
|
||||
|
||||
here="$(cd "$(dirname "$0")" && pwd)"
|
||||
root="$(mktemp -d)"
|
||||
trap 'rm -rf "$root"' EXIT
|
||||
|
||||
# Payload. Debian installs to /usr/bin (not /usr/local), so the service's
|
||||
# ExecStart is rewritten to match; the source unit targets /usr/local/bin for
|
||||
# the tarball installer.
|
||||
install -D -m 0755 "$binary" "$root/usr/bin/tsunagi"
|
||||
|
||||
mkdir -p "$root/lib/systemd/system"
|
||||
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/../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"
|
||||
|
||||
mkdir -p "$root/DEBIAN"
|
||||
|
||||
cat > "$root/DEBIAN/control" <<CTRL
|
||||
Package: tsunagi
|
||||
Version: ${version}
|
||||
Architecture: ${arch}
|
||||
Maintainer: AB <ab@hexor.cy>
|
||||
Section: net
|
||||
Priority: optional
|
||||
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 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
|
||||
|
||||
# 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
|
||||
exit 0
|
||||
POSTINST
|
||||
|
||||
cat > "$root/DEBIAN/prerm" <<'PRERM'
|
||||
#!/bin/sh
|
||||
set -e
|
||||
if [ "$1" = remove ] || [ "$1" = purge ]; then
|
||||
if [ -d /run/systemd/system ]; then
|
||||
systemctl disable --now tsunagi.service >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
PRERM
|
||||
|
||||
cat > "$root/DEBIAN/postrm" <<'POSTRM'
|
||||
#!/bin/sh
|
||||
set -e
|
||||
if [ -d /run/systemd/system ]; then
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
fi
|
||||
exit 0
|
||||
POSTRM
|
||||
|
||||
chmod 0755 "$root/DEBIAN/postinst" "$root/DEBIAN/prerm" "$root/DEBIAN/postrm"
|
||||
|
||||
# --root-owner-group writes the payload as root:root without needing fakeroot.
|
||||
dpkg-deb --root-owner-group --build "$root" "$output"
|
||||
echo "built $output"
|
||||
Vendored
+23
@@ -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;
|
||||
}
|
||||
});
|
||||
Vendored
+17
-12
@@ -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
|
||||
|
||||
|
||||
Vendored
+27
-19
@@ -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
|
||||
Vendored
+22
-12
@@ -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
|
||||
Reference in new issue
Block a user