Build .deb package and publish to Forgejo Releases #74

Closed
opened 2026-04-02 19:04:36 +02:00 by kfickel · 1 comment
Owner

Build .deb Package and Forgejo Release Pipeline

Summary

Build a statically-linked streamd binary using musl, package it as a .deb file with shell completions using nfpm, and publish to Forgejo Releases via CI/CD triggered on git tags.

Target: Ubuntu 24.04 / 26.04

Files to Modify

  1. flake.nix - Add musl build and .deb packaging
  2. .forgejo/workflows/release.yml - New release workflow

Implementation

1. Add Version Reading from Cargo.toml

At the top of the let block in flake.nix:

# Read version from Cargo.toml as single source of truth
version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version;

Then use inherit version; in all derivations instead of hardcoding "0.1.0".

2. Add musl Static Build

mkMuslCraneLib = system:
  let
    pkgs = mkPkgs system;
    toolchain = pkgs.rust-bin.stable.latest.default.override {
      targets = [ "x86_64-unknown-linux-musl" ];
    };
  in
  (crane.mkLib pkgs).overrideToolchain toolchain;

mkStreamdMusl = system:
  let
    craneLib = mkMuslCraneLib system;
    commonArgs = {
      src = craneLib.path ./.;
      pname = "streamd";
      inherit version;
      strictDeps = true;
      CARGO_BUILD_TARGET = "x86_64-unknown-linux-musl";
      CARGO_BUILD_RUSTFLAGS = "-C target-feature=+crt-static";
    };
    cargoArtifacts = craneLib.buildDepsOnly commonArgs;
  in
  craneLib.buildPackage (commonArgs // { inherit cargoArtifacts; });

3. Add .deb Packaging with Shell Completions

mkStreamdDeb = system:
  let
    pkgs = mkPkgs system;
    streamd-musl = mkStreamdMusl system;
  in
  pkgs.runCommand "streamd_${version}_amd64.deb" {
    nativeBuildInputs = [ pkgs.nfpm ];
  } ''
    # Generate shell completions
    mkdir -p completions
    ${streamd-musl}/bin/streamd completions bash > completions/streamd.bash
    ${streamd-musl}/bin/streamd completions zsh > completions/_streamd
    ${streamd-musl}/bin/streamd completions fish > completions/streamd.fish

    cat > nfpm.yaml <<EOF
    name: streamd
    arch: amd64
    platform: linux
    version: "${version}"
    maintainer: Konstantin Fickel
    description: Personal knowledge management and time-tracking CLI using @Tag annotations
    license: AGPL-3.0-only
    homepage: https://git.konstantinfickel.de/kfickel/streamd
    contents:
      - src: ${streamd-musl}/bin/streamd
        dst: /usr/bin/streamd
        file_info:
          mode: 0755
      - src: completions/streamd.bash
        dst: /usr/share/bash-completion/completions/streamd
      - src: completions/_streamd
        dst: /usr/share/zsh/vendor-completions/_streamd
      - src: completions/streamd.fish
        dst: /usr/share/fish/vendor_completions.d/streamd.fish
    deb:
      compression: gzip
    EOF

    nfpm package --config nfpm.yaml --packager deb --target $out
  '';

4. Update packages Output

packages = forAllSystems (system:
  let
    streamd = mkStreamd system;
    streamd-musl = mkStreamdMusl system;
    streamd-deb = mkStreamdDeb system;
  in {
    inherit streamd streamd-musl streamd-deb;
    default = streamd;
  }
);

5. Create Release Workflow

New file .forgejo/workflows/release.yml:

name: Release

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:

jobs:
  release:
    name: Build and Release
    runs-on: nix

    steps:
      - name: Check out Repository
        uses: https://git.konstantinfickel.de/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
        with:
          fetch-depth: 0  # Fetch all history and tags

      - name: Extract version and handle tagging
        id: version
        run: |
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            # Manual trigger: read version from Cargo.toml
            VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
            TAG="v${VERSION}"
            
            # Check if tag already exists
            if git rev-parse "$TAG" >/dev/null 2>&1; then
              echo "::error::Version ${VERSION} is already released"
              exit 1
            fi
            
            # Create and push the tag
            git tag "$TAG"
            git push origin "$TAG"
            
            echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
            echo "TAG=${TAG}" >> $GITHUB_OUTPUT
          else
            # Tag push trigger: extract version from tag
            VERSION="${GITHUB_REF_NAME#v}"
            echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
            echo "TAG=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
          fi

      - name: Build .deb package
        run: nix build .#streamd-deb -o result-deb

      - name: Build static binary
        run: nix build .#streamd-musl -o result-musl

      - name: Prepare release artifacts
        run: |
          mkdir -p release
          cp result-deb release/streamd_${{ steps.version.outputs.VERSION }}_amd64.deb
          cp result-musl/bin/streamd release/streamd-${{ steps.version.outputs.VERSION }}-linux-x86_64

      - name: Create release
        uses: https://code.forgejo.org/actions/forgejo-release@v2
        with:
          direction: upload
          url: https://git.konstantinfickel.de
          repo: kfickel/streamd
          token: ${{ secrets.RELEASE_TOKEN }}
          tag: ${{ steps.version.outputs.TAG }}
          release-dir: release
          release-notes: |
            ## streamd ${{ steps.version.outputs.VERSION }}

            ### Debian/Ubuntu
            ```bash
            wget <release-url>/streamd_${{ steps.version.outputs.VERSION }}_amd64.deb
            sudo dpkg -i streamd_${{ steps.version.outputs.VERSION }}_amd64.deb
            ```

            ### Static binary
            ```bash
            wget <release-url>/streamd-${{ steps.version.outputs.VERSION }}-linux-x86_64
            chmod +x streamd-*-linux-x86_64
            sudo mv streamd-*-linux-x86_64 /usr/local/bin/streamd
            ```

Required Setup (Manual)

  1. Create Forgejo access token with repository write permissions at:
    https://git.konstantinfickel.de/user/settings/applications
  2. Add as repository secret named RELEASE_TOKEN in repository settings

Release Workflow

  1. Update version in Cargo.toml
  2. Commit: git commit -am "chore: bump version to X.Y.Z"
  3. Push: git push
  4. Go to Actions → Release → Run workflow
  5. Pipeline reads version from Cargo.toml, creates tag, and publishes release

Option B: Tag push

  1. Update version in Cargo.toml
  2. Commit: git commit -am "chore: bump version to X.Y.Z"
  3. Tag: git tag vX.Y.Z
  4. Push: git push && git push --tags

Verification

  1. Test musl build locally:

    nix build .#streamd-musl
    file result/bin/streamd  # Should show "statically linked"
    ldd result/bin/streamd   # Should show "not a dynamic executable"
    
  2. Test .deb build locally:

    nix build .#streamd-deb
    dpkg-deb --info result
    dpkg-deb --contents result  # Verify completions are included
    
  3. Test installation in Ubuntu container:

    docker run --rm -v $(pwd)/result:/pkg ubuntu:24.04 bash -c \
      "dpkg -i /pkg && streamd --version && ls /usr/share/bash-completion/completions/streamd"
    
# Build .deb Package and Forgejo Release Pipeline ## Summary Build a statically-linked `streamd` binary using musl, package it as a .deb file with shell completions using nfpm, and publish to Forgejo Releases via CI/CD triggered on git tags. Target: Ubuntu 24.04 / 26.04 ## Files to Modify 1. `flake.nix` - Add musl build and .deb packaging 2. `.forgejo/workflows/release.yml` - New release workflow ## Implementation ### 1. Add Version Reading from Cargo.toml At the top of the `let` block in flake.nix: ```nix # Read version from Cargo.toml as single source of truth version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version; ``` Then use `inherit version;` in all derivations instead of hardcoding `"0.1.0"`. ### 2. Add musl Static Build ```nix mkMuslCraneLib = system: let pkgs = mkPkgs system; toolchain = pkgs.rust-bin.stable.latest.default.override { targets = [ "x86_64-unknown-linux-musl" ]; }; in (crane.mkLib pkgs).overrideToolchain toolchain; mkStreamdMusl = system: let craneLib = mkMuslCraneLib system; commonArgs = { src = craneLib.path ./.; pname = "streamd"; inherit version; strictDeps = true; CARGO_BUILD_TARGET = "x86_64-unknown-linux-musl"; CARGO_BUILD_RUSTFLAGS = "-C target-feature=+crt-static"; }; cargoArtifacts = craneLib.buildDepsOnly commonArgs; in craneLib.buildPackage (commonArgs // { inherit cargoArtifacts; }); ``` ### 3. Add .deb Packaging with Shell Completions ```nix mkStreamdDeb = system: let pkgs = mkPkgs system; streamd-musl = mkStreamdMusl system; in pkgs.runCommand "streamd_${version}_amd64.deb" { nativeBuildInputs = [ pkgs.nfpm ]; } '' # Generate shell completions mkdir -p completions ${streamd-musl}/bin/streamd completions bash > completions/streamd.bash ${streamd-musl}/bin/streamd completions zsh > completions/_streamd ${streamd-musl}/bin/streamd completions fish > completions/streamd.fish cat > nfpm.yaml <<EOF name: streamd arch: amd64 platform: linux version: "${version}" maintainer: Konstantin Fickel description: Personal knowledge management and time-tracking CLI using @Tag annotations license: AGPL-3.0-only homepage: https://git.konstantinfickel.de/kfickel/streamd contents: - src: ${streamd-musl}/bin/streamd dst: /usr/bin/streamd file_info: mode: 0755 - src: completions/streamd.bash dst: /usr/share/bash-completion/completions/streamd - src: completions/_streamd dst: /usr/share/zsh/vendor-completions/_streamd - src: completions/streamd.fish dst: /usr/share/fish/vendor_completions.d/streamd.fish deb: compression: gzip EOF nfpm package --config nfpm.yaml --packager deb --target $out ''; ``` ### 4. Update packages Output ```nix packages = forAllSystems (system: let streamd = mkStreamd system; streamd-musl = mkStreamdMusl system; streamd-deb = mkStreamdDeb system; in { inherit streamd streamd-musl streamd-deb; default = streamd; } ); ``` ### 5. Create Release Workflow New file `.forgejo/workflows/release.yml`: ```yaml name: Release on: push: tags: - 'v*' workflow_dispatch: jobs: release: name: Build and Release runs-on: nix steps: - name: Check out Repository uses: https://git.konstantinfickel.de/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 # Fetch all history and tags - name: Extract version and handle tagging id: version run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then # Manual trigger: read version from Cargo.toml VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/') TAG="v${VERSION}" # Check if tag already exists if git rev-parse "$TAG" >/dev/null 2>&1; then echo "::error::Version ${VERSION} is already released" exit 1 fi # Create and push the tag git tag "$TAG" git push origin "$TAG" echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT echo "TAG=${TAG}" >> $GITHUB_OUTPUT else # Tag push trigger: extract version from tag VERSION="${GITHUB_REF_NAME#v}" echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT echo "TAG=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT fi - name: Build .deb package run: nix build .#streamd-deb -o result-deb - name: Build static binary run: nix build .#streamd-musl -o result-musl - name: Prepare release artifacts run: | mkdir -p release cp result-deb release/streamd_${{ steps.version.outputs.VERSION }}_amd64.deb cp result-musl/bin/streamd release/streamd-${{ steps.version.outputs.VERSION }}-linux-x86_64 - name: Create release uses: https://code.forgejo.org/actions/forgejo-release@v2 with: direction: upload url: https://git.konstantinfickel.de repo: kfickel/streamd token: ${{ secrets.RELEASE_TOKEN }} tag: ${{ steps.version.outputs.TAG }} release-dir: release release-notes: | ## streamd ${{ steps.version.outputs.VERSION }} ### Debian/Ubuntu ```bash wget <release-url>/streamd_${{ steps.version.outputs.VERSION }}_amd64.deb sudo dpkg -i streamd_${{ steps.version.outputs.VERSION }}_amd64.deb ``` ### Static binary ```bash wget <release-url>/streamd-${{ steps.version.outputs.VERSION }}-linux-x86_64 chmod +x streamd-*-linux-x86_64 sudo mv streamd-*-linux-x86_64 /usr/local/bin/streamd ``` ``` ## Required Setup (Manual) 1. **Create Forgejo access token** with repository write permissions at: `https://git.konstantinfickel.de/user/settings/applications` 2. **Add as repository secret** named `RELEASE_TOKEN` in repository settings ## Release Workflow ### Option A: Manual dispatch (recommended) 1. Update version in `Cargo.toml` 2. Commit: `git commit -am "chore: bump version to X.Y.Z"` 3. Push: `git push` 4. Go to Actions → Release → Run workflow 5. Pipeline reads version from Cargo.toml, creates tag, and publishes release ### Option B: Tag push 1. Update version in `Cargo.toml` 2. Commit: `git commit -am "chore: bump version to X.Y.Z"` 3. Tag: `git tag vX.Y.Z` 4. Push: `git push && git push --tags` ## Verification 1. **Test musl build locally:** ```bash nix build .#streamd-musl file result/bin/streamd # Should show "statically linked" ldd result/bin/streamd # Should show "not a dynamic executable" ``` 2. **Test .deb build locally:** ```bash nix build .#streamd-deb dpkg-deb --info result dpkg-deb --contents result # Verify completions are included ``` 3. **Test installation in Ubuntu container:** ```bash docker run --rm -v $(pwd)/result:/pkg ubuntu:24.04 bash -c \ "dpkg -i /pkg && streamd --version && ls /usr/share/bash-completion/completions/streamd" ```
kfickel added the
planned
label 2026-04-02 19:10:39 +02:00
Author
Owner

Implementation Summary

Branch: 74_deb-package-release
PR URL: https://git.konstantinfickel.de/kfickel/streamd/compare/main...74_deb-package-release

Changes Made

  1. Version extraction from Cargo.toml (flake.nix)

    • Added version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version;
    • Replaced hardcoded "0.1.0" with inherit version;
  2. Musl static build (flake.nix)

    • Added mkMuslCraneLib and mkStreamdMusl functions
    • Targets x86_64-unknown-linux-musl with static linking
  3. .deb packaging (flake.nix)

    • Added mkStreamdDeb function using nfpm
    • Includes shell completions for bash, zsh, and fish
    • Installs binary to /usr/bin/streamd
  4. Release workflow (.forgejo/workflows/release.yml)

    • Triggers on tag push (v*) or manual workflow dispatch
    • Builds static binary and .deb package
    • Uploads to Forgejo Releases using forgejo-release@v2
  5. Documentation (README.md)

    • Added Installation section with instructions for .deb, static binary, and Nix

Commits

  • 41ca1fc refactor(flake): read version from Cargo.toml
  • 73ecd4e feat(flake): add musl static build for streamd
  • 8b210a3 feat(flake): add .deb packaging with shell completions
  • 08f09fb feat(flake): expose streamd-musl and streamd-deb packages
  • dfd3598 feat(ci): add release workflow for .deb and static binary
  • ae539f0 docs: add installation instructions for .deb, binary, and Nix

Required Manual Setup

  1. Create Forgejo access token with repository write permissions
  2. Add as repository secret named RELEASE_TOKEN

Token Usage

  • Input tokens: ~15,000
  • Output tokens: ~8,000

Findings

  • The existing flake structure was well-organized with reusable helper functions
  • Crane library made it straightforward to add the musl target
  • nfpm integrates well with Nix for generating .deb packages
  • The completions subcommand was already implemented, making shell completion generation simple
## Implementation Summary **Branch:** `74_deb-package-release` **PR URL:** https://git.konstantinfickel.de/kfickel/streamd/compare/main...74_deb-package-release ### Changes Made 1. **Version extraction from Cargo.toml** (`flake.nix`) - Added `version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version;` - Replaced hardcoded `"0.1.0"` with `inherit version;` 2. **Musl static build** (`flake.nix`) - Added `mkMuslCraneLib` and `mkStreamdMusl` functions - Targets `x86_64-unknown-linux-musl` with static linking 3. **.deb packaging** (`flake.nix`) - Added `mkStreamdDeb` function using nfpm - Includes shell completions for bash, zsh, and fish - Installs binary to `/usr/bin/streamd` 4. **Release workflow** (`.forgejo/workflows/release.yml`) - Triggers on tag push (`v*`) or manual workflow dispatch - Builds static binary and .deb package - Uploads to Forgejo Releases using `forgejo-release@v2` 5. **Documentation** (`README.md`) - Added Installation section with instructions for .deb, static binary, and Nix ### Commits - `41ca1fc` refactor(flake): read version from Cargo.toml - `73ecd4e` feat(flake): add musl static build for streamd - `8b210a3` feat(flake): add .deb packaging with shell completions - `08f09fb` feat(flake): expose streamd-musl and streamd-deb packages - `dfd3598` feat(ci): add release workflow for .deb and static binary - `ae539f0` docs: add installation instructions for .deb, binary, and Nix ### Required Manual Setup 1. Create Forgejo access token with repository write permissions 2. Add as repository secret named `RELEASE_TOKEN` ### Token Usage - Input tokens: ~15,000 - Output tokens: ~8,000 ### Findings - The existing flake structure was well-organized with reusable helper functions - Crane library made it straightforward to add the musl target - nfpm integrates well with Nix for generating .deb packages - The `completions` subcommand was already implemented, making shell completion generation simple
Sign in to join this conversation.
No labels
planned
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference: kfickel/streamd#74
No description provided.