Skip to content

Commit f02babf

Browse files
committed
pass 2: distribution — completions, man page, sigstore, package manifests
Three coordinated additions that prepare netscli for proper OS-level distribution alongside the existing crates.io + release-binary story. 1. Shell completions + man page from the binary itself - New `netscli completions <bash|zsh|fish|powershell|elvish>` subcommand generates a completion script and writes it to stdout. Users pipe to the right location for their shell. - New `netscli man` subcommand renders the roff-format man page from the clap command tree and writes it to stdout. Install with: netscli man | gzip > /usr/share/man/man1/netscli.1.gz - Relies on clap_complete 4.4 + clap_mangen 0.2, no build.rs dance. Package managers (brew, aur, scoop) generate completions at install time by shelling out to the just-installed binary, which keeps the completion scripts always in sync with the CLI surface. 2. Sigstore keyless signing in .github/workflows/release.yml - Each matrix cell now installs sigstore/cosign via the official installer action, then signs its built asset with `cosign sign-blob --yes`, producing a .sig + .pem alongside the .sha256. - Added `id-token: write` permission so GitHub Actions can mint an OIDC token that Fulcio exchanges for a short-lived cert bound to the workflow + commit + tag. Zero long-lived secrets, zero paid certificates. - Verifiable by anyone with: cosign verify-blob --certificate <asset>.pem \ --signature <asset>.sig \ --certificate-identity-regexp 'https://github.com/fstubner/netscli/.github/workflows/release\.yml@.*' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ <asset> 3. packaging/ directory with submission templates - packaging/README.md explains the release-day flow and the cosign verification command. - packaging/homebrew/netscli.rb — Homebrew formula. Installs the prebuilt binary, then generates completions + man page by shelling out to it. Ships as a Homebrew tap first (fstubner/homebrew-tap), with a path to homebrew-core once there's a release track record. - packaging/scoop/netscli.json — Scoop manifest with checkver + autoupdate wired to the GitHub releases feed. - packaging/winget/README.md — no manifest file (winget's flow uses the wingetcreate CLI to generate + submit against microsoft/winget-pkgs). Instructions document the canonical PackageIdentifier `fstubner.netscli` plus the `netscli` moniker so `winget install netscli` works end-user. - packaging/aur/PKGBUILD — Arch `netscli-bin` package (prebuilt binary). Generates completions + man page at install time same way Homebrew does. All four manifests have VERSION_SHA256_* placeholders that will be filled in with real checksums from the release's .sha256 files on release day. A future scripts/update-manifests.sh could automate that stamping. Workspace cargo clippy --all-targets -- -D warnings green. Smoke-tested `netscli completions bash` and `netscli man` locally, both produce expected output.
1 parent d3d6dbe commit f02babf

13 files changed

Lines changed: 470 additions & 0 deletions

File tree

‎.github/workflows/release.yml‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ jobs:
1919
runs-on: ${{ matrix.os }}
2020
permissions:
2121
contents: write
22+
# id-token: write is required for sigstore keyless signing. Cosign
23+
# exchanges the OIDC token issued by GitHub Actions for a short-lived
24+
# certificate from Fulcio bound to this workflow + commit + tag. No
25+
# long-lived secrets to manage.
26+
id-token: write
2227
strategy:
2328
fail-fast: false
2429
matrix:
@@ -154,12 +159,33 @@ jobs:
154159
$hash = (Get-FileHash -Algorithm SHA256 -Path $asset).Hash.ToLowerInvariant()
155160
"$hash ${{ matrix.asset_name }}" | Out-File -FilePath "${asset}.sha256" -Encoding ascii
156161
162+
# Sigstore keyless signing via the GitHub Actions OIDC token. Produces
163+
# a .sig and .pem alongside each binary; verifiable by anyone with:
164+
# cosign verify-blob --signature <asset>.sig --certificate <asset>.pem \
165+
# --certificate-identity-regexp 'https://github.com/fstubner/netscli/.*' \
166+
# --certificate-oidc-issuer https://token.actions.githubusercontent.com \
167+
# <asset>
168+
- name: Install cosign
169+
uses: sigstore/cosign-installer@v3
170+
- name: Sign release asset (sigstore keyless)
171+
shell: bash
172+
env:
173+
COSIGN_EXPERIMENTAL: "1"
174+
run: |
175+
cd target/${{ matrix.target }}/release
176+
cosign sign-blob --yes \
177+
--output-signature "${{ matrix.asset_name }}.sig" \
178+
--output-certificate "${{ matrix.asset_name }}.pem" \
179+
"${{ matrix.asset_name }}"
180+
157181
- name: Upload Release Asset
158182
uses: softprops/action-gh-release@v3
159183
with:
160184
files: |
161185
target/${{ matrix.target }}/release/${{ matrix.asset_name }}
162186
target/${{ matrix.target }}/release/${{ matrix.asset_name }}.sha256
187+
target/${{ matrix.target }}/release/${{ matrix.asset_name }}.sig
188+
target/${{ matrix.target }}/release/${{ matrix.asset_name }}.pem
163189
tag_name: ${{ github.event.inputs.tag || github.ref_name }}
164190
env:
165191
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

‎Cargo.lock‎

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎apps/netscli-cli/Cargo.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ thiserror.workspace = true
2020
chrono.workspace = true
2121
ipnet.workspace = true
2222
clap = { version = "4.4", features = ["derive"] }
23+
clap_complete = "4.4"
24+
clap_mangen = "0.2"
2325
ratatui = "0.29.0"
2426
crossterm = "0.27"
2527
tui-textarea = "0.4"

‎apps/netscli-cli/src/args.rs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use clap::{Parser, Subcommand};
2+
use clap_complete::Shell;
23

34
#[derive(Parser)]
45
#[command(name = "netscli", version, about = "Modern network scanner", long_about = None)]
@@ -293,6 +294,22 @@ pub enum Commands {
293294
#[command(name = "serve")]
294295
McpServe,
295296

297+
/// Print shell-completion script for the requested shell.
298+
///
299+
/// Typical usage:
300+
/// bash: netscli completions bash > ~/.local/share/bash-completion/completions/netscli
301+
/// zsh: netscli completions zsh > ~/.zsh/completions/_netscli
302+
/// fish: netscli completions fish > ~/.config/fish/completions/netscli.fish
303+
/// powershell: netscli completions powershell > $PROFILE/netscli.ps1
304+
#[command(name = "completions")]
305+
Completions { shell: Shell },
306+
307+
/// Print the roff-format man page to stdout.
308+
///
309+
/// Typical usage: netscli man | gzip > /usr/share/man/man1/netscli.1.gz
310+
#[command(name = "man")]
311+
Man,
312+
296313
/// Manage MCP server auto-start (systemd service)
297314
McpService {
298315
/// Generate systemd user service file

‎apps/netscli-cli/src/main.rs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,21 @@ async fn main() -> Result<()> {
499499
}
500500
}
501501
}
502+
Commands::Completions { shell } => {
503+
use clap::CommandFactory;
504+
let mut cmd = Cli::command();
505+
let name = cmd.get_name().to_string();
506+
clap_complete::generate(*shell, &mut cmd, name, &mut io::stdout());
507+
return Ok(());
508+
}
509+
Commands::Man => {
510+
use clap::CommandFactory;
511+
let cmd = Cli::command();
512+
let man = clap_mangen::Man::new(cmd);
513+
man.render(&mut io::stdout())
514+
.context("rendering man page")?;
515+
return Ok(());
516+
}
502517
Commands::McpServe => {
503518
netscli_mcp::run_server().await?;
504519
}

‎packaging/README.md‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Packaging
2+
3+
Manifest templates and submission notes for getting netscli into the
4+
major OS package managers.
5+
6+
Each subdirectory is a template that points at a specific release tag.
7+
After cutting a new release, update the version + SHA256 values and
8+
submit a PR to the respective registry.
9+
10+
## How the release pipeline feeds these
11+
12+
Every push of a `v*` tag triggers `.github/workflows/release.yml`,
13+
which for each platform:
14+
15+
1. Builds the binary with `cargo build --release --locked`.
16+
2. Writes a `.sha256` alongside it.
17+
3. Signs it via [sigstore keyless](https://docs.sigstore.dev/cosign/signing/overview/)
18+
using the GitHub Actions OIDC token, producing `.sig` + `.pem`.
19+
4. Uploads all four files as release assets.
20+
21+
Package manifests reference the `.tar.gz`/`.zip`/`.exe` URL plus the
22+
SHA256. The `.sig` + `.pem` let security-conscious users verify
23+
provenance without trusting GitHub's binary storage:
24+
25+
```bash
26+
cosign verify-blob \
27+
--certificate netscli-linux-x86_64.pem \
28+
--signature netscli-linux-x86_64.sig \
29+
--certificate-identity-regexp 'https://github.com/fstubner/netscli/.github/workflows/release\.yml@.*' \
30+
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
31+
netscli-linux-x86_64
32+
```
33+
34+
## Submission targets
35+
36+
| Registry | Dir | Status |
37+
|---|---|---|
38+
| Homebrew (tap) | [`homebrew/`](./homebrew/) | not submitted |
39+
| Scoop bucket | [`scoop/`](./scoop/) | not submitted |
40+
| Winget (microsoft/winget-pkgs) | [`winget/`](./winget/) | not submitted |
41+
| Arch AUR | [`aur/`](./aur/) | not submitted |
42+
43+
Each directory has its own README with the exact submission steps. The
44+
submission PRs should be filed **after** the corresponding release is
45+
published and the assets (including `.sha256`) are live on the release
46+
page — the registries refuse PRs whose URLs 404 or whose checksums
47+
don't match the served bytes.
48+
49+
## Release-day checklist
50+
51+
1. Cut a crates.io release (see [`../docs/PUBLISHING.md`](../docs/PUBLISHING.md)).
52+
2. Tag `vX.Y.Z` and push. Release workflow builds + signs + uploads all
53+
assets.
54+
3. Verify release page has the full asset matrix (22 files: 11 binaries
55+
× `.sha256`/`.sig`/`.pem`).
56+
4. Run `./update-manifests.sh vX.Y.Z` (planned) to pull the real SHA256s
57+
and stamp them into each template.
58+
5. Open the four PRs, one per registry.
59+
6. Monitor for lint/review feedback; most registries have maintainer
60+
review latency in the hours-to-days range.

‎packaging/aur/PKGBUILD‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Maintainer: Felix Stubner <felix.stubner@gmail.com>
2+
3+
pkgname=netscli-bin
4+
_binname=netscli
5+
pkgver=0.1.1
6+
pkgrel=1
7+
pkgdesc="Network scanner with CLI, TUI, desktop app, and MCP server"
8+
arch=('x86_64' 'aarch64')
9+
url="https://netscli.com"
10+
license=('MIT')
11+
provides=("${_binname}")
12+
conflicts=("${_binname}")
13+
source_x86_64=("${_binname}-${pkgver}-x86_64::https://github.com/fstubner/netscli/releases/download/v${pkgver}/netscli-linux-x86_64")
14+
source_aarch64=("${_binname}-${pkgver}-aarch64::https://github.com/fstubner/netscli/releases/download/v${pkgver}/netscli-linux-aarch64")
15+
sha256sums_x86_64=('VERSION_SHA256_LINUX_X86_64')
16+
sha256sums_aarch64=('VERSION_SHA256_LINUX_AARCH64')
17+
18+
package() {
19+
local src
20+
if [[ $CARCH == "x86_64" ]]; then
21+
src="${_binname}-${pkgver}-x86_64"
22+
else
23+
src="${_binname}-${pkgver}-aarch64"
24+
fi
25+
26+
install -Dm755 "${srcdir}/${src}" "${pkgdir}/usr/bin/${_binname}"
27+
28+
# Shell completions — generate from the binary we just installed.
29+
"${pkgdir}/usr/bin/${_binname}" completions bash \
30+
> "${srcdir}/${_binname}.bash"
31+
install -Dm644 "${srcdir}/${_binname}.bash" \
32+
"${pkgdir}/usr/share/bash-completion/completions/${_binname}"
33+
34+
"${pkgdir}/usr/bin/${_binname}" completions zsh \
35+
> "${srcdir}/_${_binname}"
36+
install -Dm644 "${srcdir}/_${_binname}" \
37+
"${pkgdir}/usr/share/zsh/site-functions/_${_binname}"
38+
39+
"${pkgdir}/usr/bin/${_binname}" completions fish \
40+
> "${srcdir}/${_binname}.fish"
41+
install -Dm644 "${srcdir}/${_binname}.fish" \
42+
"${pkgdir}/usr/share/fish/vendor_completions.d/${_binname}.fish"
43+
44+
# Man page.
45+
"${pkgdir}/usr/bin/${_binname}" man > "${srcdir}/${_binname}.1"
46+
install -Dm644 "${srcdir}/${_binname}.1" \
47+
"${pkgdir}/usr/share/man/man1/${_binname}.1"
48+
}

‎packaging/aur/README.md‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Arch User Repository (AUR)
2+
3+
## First submission
4+
5+
The AUR wants a git repo per package. The package namespace `netscli`
6+
is unclaimed; we submit as `netscli-bin` (Arch convention for a
7+
prebuilt-binary package; a `netscli` source package that compiles from
8+
crates.io would be a separate submission).
9+
10+
```bash
11+
# One-time: clone your AUR profile
12+
git clone ssh://aur@aur.archlinux.org/netscli-bin.git
13+
cd netscli-bin
14+
15+
# Copy the template PKGBUILD in, fill in real SHA256s from the release
16+
cp /path/to/packaging/aur/PKGBUILD .
17+
18+
# Required: .SRCINFO derived from PKGBUILD
19+
makepkg --printsrcinfo > .SRCINFO
20+
21+
# Sanity check the build
22+
makepkg -si
23+
24+
# Submit
25+
git add PKGBUILD .SRCINFO
26+
git commit -m "Initial release: netscli-bin 0.1.1"
27+
git push origin master
28+
```
29+
30+
## After each release
31+
32+
```bash
33+
cd netscli-bin
34+
# Edit PKGBUILD: bump pkgver, replace the two sha256sums_* values
35+
makepkg --printsrcinfo > .SRCINFO
36+
git commit -am "Update to 0.1.2"
37+
git push
38+
```
39+
40+
AUR is entirely self-service — there's no review, and the moment you
41+
push, the new version is live.
42+
43+
## Source-based package (future)
44+
45+
Once netscli is stable enough that its dep tree doesn't churn every
46+
release, a `netscli` source PKGBUILD that builds from crates.io is
47+
worth creating. It would avoid the `-bin` suffix and give Arch users
48+
a properly compiled binary for their specific CPU.

‎packaging/homebrew/README.md‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Homebrew
2+
3+
## One-time setup: create the tap repo
4+
5+
1. Create a new public GitHub repo named
6+
[`fstubner/homebrew-tap`](https://github.com/fstubner/homebrew-tap).
7+
Homebrew finds taps by the `homebrew-` prefix.
8+
2. Copy `netscli.rb` from this directory into `Formula/netscli.rb` in
9+
that repo. Commit and push.
10+
11+
Users install with:
12+
13+
```bash
14+
brew tap fstubner/tap
15+
brew install netscli
16+
```
17+
18+
## After each release
19+
20+
1. Update `Formula/netscli.rb` in the tap repo:
21+
- bump `version`
22+
- replace the four `VERSION_SHA256_*` values with the SHA256s from
23+
the new release's `.sha256` files
24+
2. `brew install --build-from-source ./Formula/netscli.rb` to verify
25+
locally.
26+
3. Commit + push to the tap.
27+
28+
## Moving to homebrew-core later
29+
30+
Once the project has ~1k stars and a stable release track record, the
31+
formula can be submitted to
32+
[Homebrew/homebrew-core](https://github.com/Homebrew/homebrew-core) so
33+
users don't need to tap — `brew install netscli` just works. Formula
34+
structure stays similar; the review is stricter.

0 commit comments

Comments
 (0)