From 350611b3b1ae53be27f43ea76a551e342906408f Mon Sep 17 00:00:00 2001 From: jasonwitty Date: Thu, 20 Nov 2025 23:37:40 -0800 Subject: [PATCH] Add Debian packaging support with cargo-deb - Add cargo-deb metadata to socktop and socktop_agent Cargo.toml - Create systemd service file for socktop_agent - Add postinst/postrm maintainer scripts for user/group management - Create GitHub Actions workflow to build .deb packages for AMD64 and ARM64 - Add comprehensive documentation in docs/DEBIAN_PACKAGING.md - Packages will be available as artifacts on every push - Automatic GitHub releases for version tags --- .github/workflows/build-deb.yml | 180 ++++++++++++++++++ docs/DEBIAN_PACKAGING.md | 274 ++++++++++++++++++++++++++++ socktop/Cargo.toml | 19 ++ socktop_agent/Cargo.toml | 20 ++ socktop_agent/debian/postinst | 26 +++ socktop_agent/debian/postrm | 34 ++++ socktop_agent/socktop-agent.service | 27 +++ 7 files changed, 580 insertions(+) create mode 100644 .github/workflows/build-deb.yml create mode 100644 docs/DEBIAN_PACKAGING.md create mode 100755 socktop_agent/debian/postinst create mode 100755 socktop_agent/debian/postrm create mode 100644 socktop_agent/socktop-agent.service diff --git a/.github/workflows/build-deb.yml b/.github/workflows/build-deb.yml new file mode 100644 index 0000000..0160424 --- /dev/null +++ b/.github/workflows/build-deb.yml @@ -0,0 +1,180 @@ +name: Build Debian Packages + +on: + push: + branches: + - master + - feature/debian-packaging + tags: + - 'v*' + pull_request: + branches: + - master + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + +jobs: + build-deb: + name: Build .deb for ${{ matrix.target }} + runs-on: ubuntu-latest + strategy: + matrix: + include: + - target: x86_64-unknown-linux-gnu + arch: amd64 + - target: aarch64-unknown-linux-gnu + arch: arm64 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Install cargo-deb + run: cargo install cargo-deb + + - name: Install cross-compilation tools (ARM64) + if: matrix.target == 'aarch64-unknown-linux-gnu' + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross + + - name: Configure cross-compilation (ARM64) + if: matrix.target == 'aarch64-unknown-linux-gnu' + run: | + mkdir -p .cargo + cat >> .cargo/config.toml << EOF + [target.aarch64-unknown-linux-gnu] + linker = "aarch64-linux-gnu-gcc" + EOF + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache cargo index + uses: actions/cache@v4 + with: + path: ~/.cargo/git + key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache target directory + uses: actions/cache@v4 + with: + path: target + key: ${{ runner.os }}-target-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} + + - name: Build socktop .deb package + run: | + cargo deb --package socktop --target ${{ matrix.target }} --no-strip + + - name: Build socktop_agent .deb package + run: | + cargo deb --package socktop_agent --target ${{ matrix.target }} --no-strip + + - name: Rename packages with architecture + run: | + mkdir -p debs + cp target/${{ matrix.target }}/debian/*.deb debs/ + cd debs + for deb in *.deb; do + # Extract package info + pkg_name=$(echo $deb | cut -d_ -f1) + version=$(echo $deb | cut -d_ -f2) + # Rename to include arch + mv "$deb" "${pkg_name}_${version}_${{ matrix.arch }}.deb" + done + + - name: List generated packages + run: ls -lh debs/ + + - name: Upload .deb packages as artifacts + uses: actions/upload-artifact@v4 + with: + name: debian-packages-${{ matrix.arch }} + path: debs/*.deb + if-no-files-found: error + retention-days: 90 + + # Combine all artifacts into a single downloadable archive + combine-artifacts: + name: Combine all .deb packages + needs: build-deb + runs-on: ubuntu-latest + steps: + - name: Download AMD64 packages + uses: actions/download-artifact@v4 + with: + name: debian-packages-amd64 + path: all-debs + + - name: Download ARM64 packages + uses: actions/download-artifact@v4 + with: + name: debian-packages-arm64 + path: all-debs + + - name: List all packages + run: | + echo "All generated .deb packages:" + ls -lh all-debs/ + + - name: Upload combined artifacts + uses: actions/upload-artifact@v4 + with: + name: all-debian-packages + path: all-debs/*.deb + if-no-files-found: error + retention-days: 90 + + - name: Generate checksums + run: | + cd all-debs + sha256sum *.deb > SHA256SUMS + cat SHA256SUMS + + - name: Upload checksums + uses: actions/upload-artifact@v4 + with: + name: checksums + path: all-debs/SHA256SUMS + retention-days: 90 + + # Optional: Create a release with the .deb files if this is a tag + create-release: + name: Create GitHub Release + needs: combine-artifacts + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + permissions: + contents: write + steps: + - name: Download all packages + uses: actions/download-artifact@v4 + with: + name: all-debian-packages + path: release-debs + + - name: Download checksums + uses: actions/download-artifact@v4 + with: + name: checksums + path: release-debs + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: release-debs/* + draft: false + prerelease: false + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/DEBIAN_PACKAGING.md b/docs/DEBIAN_PACKAGING.md new file mode 100644 index 0000000..30a74d8 --- /dev/null +++ b/docs/DEBIAN_PACKAGING.md @@ -0,0 +1,274 @@ +# Debian Packaging for socktop + +This document describes how to build and use Debian packages for socktop and socktop_agent. + +## Prerequisites + +Install `cargo-deb`: + +```bash +cargo install cargo-deb +``` + +## Building Packages Locally + +### Build for your current architecture (x86_64) + +```bash +# Build socktop TUI client +cargo deb --package socktop + +# Build socktop_agent daemon +cargo deb --package socktop_agent +``` + +The `.deb` files will be created in `target/debian/`. + +### Cross-compile for ARM64 (Raspberry Pi, etc.) + +First, install cross-compilation tools: + +```bash +sudo apt-get update +sudo apt-get install gcc-aarch64-linux-gnu libc6-dev-arm64-cross +``` + +Add the ARM64 target: + +```bash +rustup target add aarch64-unknown-linux-gnu +``` + +Configure the linker by creating `.cargo/config.toml`: + +```toml +[target.aarch64-unknown-linux-gnu] +linker = "aarch64-linux-gnu-gcc" +``` + +Build the packages: + +```bash +# Build for ARM64 +cargo deb --package socktop --target aarch64-unknown-linux-gnu +cargo deb --package socktop_agent --target aarch64-unknown-linux-gnu +``` + +## Installing Packages + +### Install socktop TUI client + +```bash +sudo dpkg -i socktop_*.deb +``` + +### Install socktop_agent daemon + +```bash +sudo dpkg -i socktop_agent_*.deb +``` + +The agent package will: +- Create a `socktop` system user and group +- Install the binary to `/usr/bin/socktop_agent` +- Install a systemd service file (disabled by default) +- Create `/var/lib/socktop` for state files + +### Enable and start the agent service + +```bash +# Enable to start on boot +sudo systemctl enable socktop-agent + +# Start the service +sudo systemctl start socktop-agent + +# Check status +sudo systemctl status socktop-agent +``` + +### Configure the agent + +Edit the systemd service to customize settings: + +```bash +sudo systemctl edit socktop-agent +``` + +Add configuration in the override section: + +```ini +[Service] +Environment=SOCKTOP_PORT=8080 +Environment=SOCKTOP_TOKEN=your-secret-token +Environment=RUST_LOG=info +``` + +Then restart: + +```bash +sudo systemctl restart socktop-agent +``` + +## GitHub Actions + +The project includes a GitHub Actions workflow (`.github/workflows/build-deb.yml`) that automatically builds `.deb` packages for both x86_64 and ARM64 architectures on every push to master or when tags are created. + +### Downloading pre-built packages + +1. Go to the [Actions tab](https://github.com/jasonwitty/socktop/actions) +2. Click on the latest "Build Debian Packages" workflow run +3. Download the artifacts: + - `debian-packages-amd64` - x86_64 packages + - `debian-packages-arm64` - ARM64 packages + - `all-debian-packages` - All packages combined + - `checksums` - SHA256 checksums + +### Release packages + +When you create a git tag starting with `v` (e.g., `v1.50.0`), the workflow will automatically create a GitHub Release with all `.deb` packages attached. + +```bash +git tag v1.50.0 +git push origin v1.50.0 +``` + +## Package Details + +### socktop package + +- **Binary**: `/usr/bin/socktop` +- **Documentation**: `/usr/share/doc/socktop/` +- **Size**: ~5-8 MB (depends on architecture) + +### socktop_agent package + +- **Binary**: `/usr/bin/socktop_agent` +- **Service**: `socktop-agent.service` +- **User/Group**: `socktop` +- **State directory**: `/var/lib/socktop` +- **Config directory**: `/etc/socktop` (created but empty by default) +- **Documentation**: `/usr/share/doc/socktop_agent/` +- **Size**: ~5-8 MB (depends on architecture) + +## Uninstalling + +```bash +# Remove packages but keep configuration +sudo apt remove socktop socktop_agent + +# Remove packages and all configuration (purge) +sudo apt purge socktop socktop_agent +``` + +When purging `socktop_agent`, the following are removed: +- The `socktop` user and group +- `/var/lib/socktop` directory +- Empty `/etc/socktop` directory (if empty) + +## Verifying Packages + +Check package contents: + +```bash +dpkg -c socktop_*.deb +dpkg -c socktop_agent_*.deb +``` + +Check package information: + +```bash +dpkg -I socktop_*.deb +dpkg -I socktop_agent_*.deb +``` + +After installation, verify files: + +```bash +dpkg -L socktop +dpkg -L socktop-agent +``` + +## Troubleshooting + +### Service fails to start + +Check logs: + +```bash +sudo journalctl -u socktop-agent -f +``` + +Verify the socktop user exists: + +```bash +id socktop +``` + +### Permission issues + +Ensure the state directory has correct permissions: + +```bash +sudo chown -R socktop:socktop /var/lib/socktop +sudo chmod 755 /var/lib/socktop +``` + +### Missing dependencies + +If installation fails due to missing dependencies: + +```bash +sudo apt --fix-broken install +``` + +## Creating a Local APT Repository (Advanced) + +To create your own APT repository for easy installation: + +1. Install required tools: + ```bash + sudo apt install dpkg-dev + ``` + +2. Create repository structure: + ```bash + mkdir -p ~/socktop-repo/pool/main + cp *.deb ~/socktop-repo/pool/main/ + ``` + +3. Generate package index: + ```bash + cd ~/socktop-repo + dpkg-scanpackages pool/main /dev/null | gzip -9c > pool/main/Packages.gz + ``` + +4. Serve via HTTP (for testing): + ```bash + cd ~/socktop-repo + python3 -m http.server 8000 + ``` + +5. Add to sources on client machines: + ```bash + echo "deb [trusted=yes] http://your-server:8000 pool/main/" | \ + sudo tee /etc/apt/sources.list.d/socktop.list + sudo apt update + sudo apt install socktop socktop-agent + ``` + +## Contributing + +When adding new features that affect packaging: + +1. Update `Cargo.toml` metadata in the `[package.metadata.deb]` section +2. Add new assets to the `assets` array if needed +3. Update maintainer scripts in `socktop_agent/debian/` if needed +4. Test package building locally before committing +5. Update this documentation + +## References + +- [cargo-deb documentation](https://github.com/kornelski/cargo-deb) +- [Debian Policy Manual](https://www.debian.org/doc/debian-policy/) +- [systemd service files](https://www.freedesktop.org/software/systemd/man/systemd.service.html) \ No newline at end of file diff --git a/socktop/Cargo.toml b/socktop/Cargo.toml index b9df459..e7368a8 100644 --- a/socktop/Cargo.toml +++ b/socktop/Cargo.toml @@ -6,6 +6,8 @@ description = "Remote system monitor over WebSocket, TUI like top" edition = "2024" license = "MIT" readme = "README.md" +homepage = "https://github.com/jasonwitty/socktop" +repository = "https://github.com/jasonwitty/socktop" [dependencies] # socktop connector for agent communication @@ -25,3 +27,20 @@ sysinfo = { workspace = true } [dev-dependencies] assert_cmd = "2.0" tempfile = "3" + +[package.metadata.deb] +maintainer = "Jason Witty " +copyright = "2024, Jason Witty " +license-file = ["../LICENSE", "4"] +extended-description = """\ +socktop is a remote system monitor with a rich terminal user interface (TUI) \ +that connects to remote hosts running the socktop_agent over WebSocket. \ +It provides real-time monitoring of CPU, memory, processes, and more with \ +an interface similar to the traditional 'top' command.""" +depends = "$auto" +section = "admin" +priority = "optional" +assets = [ + ["target/release/socktop", "usr/bin/", "755"], + ["../README.md", "usr/share/doc/socktop/", "644"], +] diff --git a/socktop_agent/Cargo.toml b/socktop_agent/Cargo.toml index 78dd506..7244cdc 100644 --- a/socktop_agent/Cargo.toml +++ b/socktop_agent/Cargo.toml @@ -6,6 +6,8 @@ description = "Socktop agent daemon. Serves host metrics over WebSocket." edition = "2024" license = "MIT" readme = "README.md" +homepage = "https://github.com/jasonwitty/socktop" +repository = "https://github.com/jasonwitty/socktop" [dependencies] # Tokio: Use minimal features instead of "full" to reduce binary size @@ -45,3 +47,21 @@ protoc-bin-vendored = "3" assert_cmd = "2.0" tempfile = "3.10" tokio-tungstenite = "0.21" + +[package.metadata.deb] +maintainer = "Jason Witty " +copyright = "2024, Jason Witty " +license-file = ["../LICENSE", "4"] +extended-description = """\ +socktop_agent is the daemon component that runs on remote hosts to collect \ +and serve system metrics over WebSocket. It gathers CPU, memory, disk, network, \ +GPU, and process information that can be monitored remotely by the socktop TUI client.""" +depends = "$auto" +section = "admin" +priority = "optional" +assets = [ + ["target/release/socktop_agent", "usr/bin/", "755"], + ["../README.md", "usr/share/doc/socktop_agent/", "644"], +] +maintainer-scripts = "debian/" +systemd-units = { unit-name = "socktop-agent", unit-scripts = ".", enable = false } diff --git a/socktop_agent/debian/postinst b/socktop_agent/debian/postinst new file mode 100755 index 0000000..00dbdcc --- /dev/null +++ b/socktop_agent/debian/postinst @@ -0,0 +1,26 @@ +#!/bin/sh +set -e + +# Create socktop user and group if they don't exist +if ! getent group socktop >/dev/null; then + addgroup --system socktop +fi + +if ! getent passwd socktop >/dev/null; then + adduser --system --ingroup socktop --home /var/lib/socktop \ + --no-create-home --disabled-password --disabled-login \ + --gecos "Socktop Agent" socktop +fi + +# Create state directory +mkdir -p /var/lib/socktop +chown socktop:socktop /var/lib/socktop +chmod 755 /var/lib/socktop + +# Create config directory if it doesn't exist +mkdir -p /etc/socktop +chmod 755 /etc/socktop + +#DEBHELPER# + +exit 0 diff --git a/socktop_agent/debian/postrm b/socktop_agent/debian/postrm new file mode 100755 index 0000000..f3cad44 --- /dev/null +++ b/socktop_agent/debian/postrm @@ -0,0 +1,34 @@ +#!/bin/sh +set -e + +case "$1" in + purge) + # Remove user and group on purge + if getent passwd socktop >/dev/null; then + deluser --quiet socktop || true + fi + + if getent group socktop >/dev/null; then + delgroup --quiet socktop || true + fi + + # Remove state directory on purge + rm -rf /var/lib/socktop + + # Remove config directory if empty + rmdir --ignore-fail-on-non-empty /etc/socktop 2>/dev/null || true + ;; + + remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + # Do nothing on remove/upgrade + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/socktop_agent/socktop-agent.service b/socktop_agent/socktop-agent.service new file mode 100644 index 0000000..5caaa14 --- /dev/null +++ b/socktop_agent/socktop-agent.service @@ -0,0 +1,27 @@ +[Unit] +Description=Socktop Agent - Remote System Monitor +Documentation=https://github.com/jasonwitty/socktop +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +ExecStart=/usr/bin/socktop_agent --port 3000 +Environment=RUST_LOG=info +# Optional authentication token: +# Environment=SOCKTOP_TOKEN=changeme +Restart=on-failure +RestartSec=5 +User=socktop +Group=socktop +NoNewPrivileges=true + +# Security hardening +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/var/lib/socktop +StateDirectory=socktop + +[Install] +WantedBy=multi-user.target