Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63178574ce | ||
|
|
a3856eed3b | ||
|
|
eac54f8c79 |
No files matched your search
@@ -33,11 +33,17 @@ jobs:
|
|||||||
asset: tsunagi-linux-x86_64
|
asset: tsunagi-linux-x86_64
|
||||||
archive: tar.gz
|
archive: tar.gz
|
||||||
zig: true
|
zig: true
|
||||||
|
deb: true
|
||||||
|
deb_arch: amd64
|
||||||
|
pkg_arch: x86_64
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
target: aarch64-unknown-linux-musl
|
target: aarch64-unknown-linux-musl
|
||||||
asset: tsunagi-linux-aarch64
|
asset: tsunagi-linux-aarch64
|
||||||
archive: tar.gz
|
archive: tar.gz
|
||||||
zig: true
|
zig: true
|
||||||
|
deb: true
|
||||||
|
deb_arch: arm64
|
||||||
|
pkg_arch: aarch64
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
target: aarch64-apple-darwin
|
target: aarch64-apple-darwin
|
||||||
asset: tsunagi-macos-aarch64
|
asset: tsunagi-macos-aarch64
|
||||||
@@ -63,9 +69,9 @@ jobs:
|
|||||||
# aws-lc-sys (iroh's rustls backend) and the bundled sqlite are C, and the
|
# 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
|
# 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.
|
# 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
|
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
|
- name: Install zig
|
||||||
if: matrix.zig
|
if: matrix.zig
|
||||||
uses: mlugg/setup-zig@v2
|
uses: mlugg/setup-zig@v2
|
||||||
@@ -114,7 +120,8 @@ jobs:
|
|||||||
cp README.md "$stage/"
|
cp README.md "$stage/"
|
||||||
case "${{ runner.os }}" in
|
case "${{ runner.os }}" in
|
||||||
Linux)
|
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"
|
cp dist/linux/README.md "$stage/INSTALL.md"
|
||||||
;;
|
;;
|
||||||
macOS)
|
macOS)
|
||||||
@@ -133,13 +140,48 @@ jobs:
|
|||||||
tar -C dist/pkg -czf "${archive}" "${{ matrix.asset }}"
|
tar -C dist/pkg -czf "${archive}" "${{ matrix.asset }}"
|
||||||
fi
|
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
|
# @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`.
|
# what makes the archive (and the .deb, where built) show up under the run
|
||||||
- name: Upload the archive as a run artifact
|
# and hands it to `publish`.
|
||||||
|
- name: Upload the build outputs as run artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.asset }}
|
name: ${{ matrix.asset }}
|
||||||
path: ${{ matrix.asset }}-*.*
|
path: |
|
||||||
|
${{ matrix.asset }}-*.*
|
||||||
|
*.deb
|
||||||
|
*.pkg.tar.zst
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
@@ -211,5 +253,5 @@ jobs:
|
|||||||
cat up.json >&2 || true
|
cat up.json >&2 || true
|
||||||
rc=1
|
rc=1
|
||||||
fi
|
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"
|
exit "$rc"
|
||||||
@@ -5,15 +5,10 @@ name: Release
|
|||||||
# macOS and Windows); only the publish half differs: GitHub needs the v4
|
# macOS and Windows); only the publish half differs: GitHub needs the v4
|
||||||
# artifact backend and fills the release with `gh`.
|
# artifact backend and fills the release with `gh`.
|
||||||
#
|
#
|
||||||
# Gitea reads both .gitea/workflows and .github/workflows, so without a guard it
|
# Every job is gated on `github.server_url == 'https://github.com'`, so this file
|
||||||
# would also try to run this file and fail on @v4 and `gh`. (Gitea reports
|
# runs only on GitHub. Gitea runs its own copy in .gitea/workflows; should a
|
||||||
# github.server_url as https://github.com, so that cannot tell the two apart.)
|
# Gitea instance also read .github/workflows, the guard skips these jobs there
|
||||||
# Instead every job is gated on a repository variable GITHUB_RELEASE=true, which
|
# so they never fail on the @v4 artifact backend or `gh`.
|
||||||
# is set only in the GitHub repository's settings — so on Gitea, where it is
|
|
||||||
# absent, these jobs are skipped and the .gitea file is the one that runs.
|
|
||||||
#
|
|
||||||
# To enable on GitHub: Settings → Secrets and variables → Actions → Variables →
|
|
||||||
# New variable, name GITHUB_RELEASE, value true.
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags: ["v*"]
|
tags: ["v*"]
|
||||||
@@ -28,7 +23,7 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: ${{ matrix.asset }}
|
name: ${{ matrix.asset }}
|
||||||
if: ${{ vars.GITHUB_RELEASE == 'true' }}
|
if: ${{ github.server_url == 'https://github.com' }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -39,11 +34,17 @@ jobs:
|
|||||||
asset: tsunagi-linux-x86_64
|
asset: tsunagi-linux-x86_64
|
||||||
archive: tar.gz
|
archive: tar.gz
|
||||||
zig: true
|
zig: true
|
||||||
|
deb: true
|
||||||
|
deb_arch: amd64
|
||||||
|
pkg_arch: x86_64
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
target: aarch64-unknown-linux-musl
|
target: aarch64-unknown-linux-musl
|
||||||
asset: tsunagi-linux-aarch64
|
asset: tsunagi-linux-aarch64
|
||||||
archive: tar.gz
|
archive: tar.gz
|
||||||
zig: true
|
zig: true
|
||||||
|
deb: true
|
||||||
|
deb_arch: arm64
|
||||||
|
pkg_arch: aarch64
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
target: aarch64-apple-darwin
|
target: aarch64-apple-darwin
|
||||||
asset: tsunagi-macos-aarch64
|
asset: tsunagi-macos-aarch64
|
||||||
@@ -69,9 +70,9 @@ jobs:
|
|||||||
# aws-lc-sys (iroh's rustls backend) and the bundled sqlite are C, and the
|
# 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
|
# 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.
|
# 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
|
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
|
- name: Install zig
|
||||||
if: matrix.zig
|
if: matrix.zig
|
||||||
uses: mlugg/setup-zig@v2
|
uses: mlugg/setup-zig@v2
|
||||||
@@ -120,7 +121,8 @@ jobs:
|
|||||||
cp README.md "$stage/"
|
cp README.md "$stage/"
|
||||||
case "${{ runner.os }}" in
|
case "${{ runner.os }}" in
|
||||||
Linux)
|
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"
|
cp dist/linux/README.md "$stage/INSTALL.md"
|
||||||
;;
|
;;
|
||||||
macOS)
|
macOS)
|
||||||
@@ -139,17 +141,51 @@ jobs:
|
|||||||
tar -C dist/pkg -czf "${archive}" "${{ matrix.asset }}"
|
tar -C dist/pkg -czf "${archive}" "${{ matrix.asset }}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload the archive as a run artifact
|
# 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
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.asset }}
|
name: ${{ matrix.asset }}
|
||||||
path: ${{ matrix.asset }}-*.*
|
path: |
|
||||||
|
${{ matrix.asset }}-*.*
|
||||||
|
*.deb
|
||||||
|
*.pkg.tar.zst
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
name: Publish release
|
name: Publish release
|
||||||
needs: build
|
needs: build
|
||||||
if: ${{ vars.GITHUB_RELEASE == 'true' }}
|
if: ${{ github.server_url == 'https://github.com' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -172,7 +208,7 @@ jobs:
|
|||||||
|
|
||||||
# download-artifact@v4 nests each artifact under its own directory, so
|
# download-artifact@v4 nests each artifact under its own directory, so
|
||||||
# the archives are artifacts/<asset>/<file>.
|
# the archives are artifacts/<asset>/<file>.
|
||||||
mapfile -t files < <(find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' \))
|
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[@]}"
|
printf 'asset: %s\n' "${files[@]}"
|
||||||
|
|
||||||
prerelease=()
|
prerelease=()
|
||||||
|
|||||||
@@ -761,6 +761,17 @@ fn control_socket(
|
|||||||
if !_is_custom_state_dir {
|
if !_is_custom_state_dir {
|
||||||
return tsunagi::ipc::default_control_socket_path();
|
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)
|
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
|
This archive holds a **static** `tsunagi` binary (musl), so it runs on any
|
||||||
distribution — Arch, Alpine, NixOS, old glibc systems — with no shared-library
|
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
|
Creating the TUN interface needs `CAP_NET_ADMIN`, and configuring
|
||||||
systemd-resolved is decided by polkit (by user id, not capability). There are
|
systemd-resolved is decided by polkit. There are three ways to run it; pick one.
|
||||||
three ways to satisfy that; 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
|
```sh
|
||||||
sudo ./install.sh
|
sudo ./install.sh
|
||||||
sudo systemctl enable --now tsunagi
|
sudo systemctl enable --now tsunagi
|
||||||
```
|
```
|
||||||
|
|
||||||
The service runs the agent as root, so the interface, low ports and the
|
The service runs as an unprivileged `tsunagi` user with just `CAP_NET_ADMIN`
|
||||||
resolver all just work (root is not subject to polkit). Control it **as root**,
|
and `CAP_NET_BIND_SERVICE` (granted by systemd), not root. systemd creates its
|
||||||
so the client resolves the same state directory the daemon uses:
|
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
|
```sh
|
||||||
sudo tsunagi status
|
sudo tsunagi status
|
||||||
sudo tsunagi join <network-name> <tsn1…secret>
|
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
|
```sh
|
||||||
sudo install -m 0755 tsunagi /usr/local/bin/tsunagi
|
sudo install -m 0755 tsunagi /usr/local/bin/tsunagi
|
||||||
sudo setcap cap_net_admin,cap_net_bind_service+p /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
|
tsunagi up
|
||||||
```
|
```
|
||||||
|
|
||||||
Nothing is left behind and nothing runs as root. The capability is lost on every
|
Nothing runs as root. The capability is lost on every rebuild or copy of the
|
||||||
rebuild or copy of the binary, so re-run `setcap` after replacing it. For the
|
binary, so re-run `setcap` after replacing it. The agent uses your per-user
|
||||||
local DNS resolver, run `tsunagi dns` once: it prints the exact polkit rule that
|
state directory and socket, so plain `tsunagi status` (no sudo) reaches it.
|
||||||
lets your user configure systemd-resolved, ready to paste.
|
|
||||||
|
|
||||||
## Without touching the OS
|
## Without touching the OS
|
||||||
|
|
||||||
|
|||||||
Vendored
+27
-19
@@ -1,10 +1,11 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
# Installs the tsunagi binary and, where systemd is present, a root service.
|
# Installs the static tsunagi binary and, where systemd is present, a service
|
||||||
# Run it as root (it writes under /usr/local and /etc):
|
# that runs the agent as a dedicated unprivileged "tsunagi" user. Run as root:
|
||||||
#
|
#
|
||||||
# sudo ./install.sh
|
# 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
|
set -eu
|
||||||
|
|
||||||
PREFIX="${PREFIX:-/usr/local}"
|
PREFIX="${PREFIX:-/usr/local}"
|
||||||
@@ -19,8 +20,23 @@ echo "Installing tsunagi to ${PREFIX}/bin"
|
|||||||
install -d "${PREFIX}/bin"
|
install -d "${PREFIX}/bin"
|
||||||
install -m 0755 "${here}/tsunagi" "${PREFIX}/bin/tsunagi"
|
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
|
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
|
install -m 0644 "${here}/tsunagi.service" /etc/systemd/system/tsunagi.service
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
@@ -29,7 +45,7 @@ Done. Start the agent with:
|
|||||||
|
|
||||||
sudo systemctl enable --now tsunagi
|
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 status
|
||||||
sudo tsunagi join <network-name> <tsn1…secret>
|
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:
|
Installed the binary; no systemd found, so no service was set up. Either:
|
||||||
|
|
||||||
* run it directly as root:
|
* 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
|
||||||
sudo tsunagi up
|
tsunagi up
|
||||||
|
(add yourself to the tsunagi group so the resolver works:
|
||||||
* or grant the capability and run it as your own user (no root at runtime):
|
sudo usermod -aG tsunagi "\$USER")
|
||||||
|
* or without touching the OS: tsunagi up --no-tun
|
||||||
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.
|
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
Vendored
+22
-12
@@ -6,21 +6,31 @@ Wants=network-online.target
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
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
|
ExecStart=/usr/local/bin/tsunagi up
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=5
|
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]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
Reference in new issue
Block a user