Skip to content

Commit d8b8477

Browse files
authored
feat(rpm): add RPM packaging with Packit/COPR and GHA release publishing (NVIDIA#1126)
1 parent 5b29189 commit d8b8477

37 files changed

Lines changed: 2858 additions & 123 deletions

.github/workflows/release-dev.yml

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -635,12 +635,20 @@ jobs:
635635
checkout-ref: ${{ github.sha }}
636636
secrets: inherit
637637

638+
build-rpm:
639+
name: Build RPM Packages
640+
needs: [compute-versions]
641+
uses: ./.github/workflows/rpm-package.yml
642+
with:
643+
checkout-ref: ${{ github.sha }}
644+
secrets: inherit
645+
638646
# ---------------------------------------------------------------------------
639647
# Create / update the dev GitHub Release with CLI binaries and wheels
640648
# ---------------------------------------------------------------------------
641649
release-dev:
642650
name: Release Dev
643-
needs: [compute-versions, build-cli-linux, build-cli-macos, build-gateway-binary-linux, build-gateway-binary-macos, build-supervisor-binary-linux, build-python-wheels-linux, build-python-wheel-macos, build-deb]
651+
needs: [compute-versions, build-cli-linux, build-cli-macos, build-gateway-binary-linux, build-gateway-binary-macos, build-supervisor-binary-linux, build-python-wheels-linux, build-python-wheel-macos, build-deb, build-rpm]
644652
runs-on: linux-amd64-cpu8
645653
timeout-minutes: 10
646654
outputs:
@@ -683,6 +691,13 @@ jobs:
683691
path: release/
684692
merge-multiple: true
685693

694+
- name: Download RPM package artifacts
695+
uses: actions/download-artifact@v4
696+
with:
697+
pattern: rpm-linux-*
698+
path: release/
699+
merge-multiple: true
700+
686701
- name: Capture wheel filenames
687702
id: wheel_filenames
688703
run: |
@@ -700,6 +715,7 @@ jobs:
700715
openshell-aarch64-unknown-linux-musl.tar.gz \
701716
openshell-aarch64-apple-darwin.tar.gz \
702717
openshell_*.deb \
718+
openshell-*.rpm \
703719
*.whl > openshell-checksums-sha256.txt
704720
cat openshell-checksums-sha256.txt
705721
sha256sum \
@@ -712,7 +728,7 @@ jobs:
712728
openshell-sandbox-aarch64-unknown-linux-gnu.tar.gz > openshell-sandbox-checksums-sha256.txt
713729
cat openshell-sandbox-checksums-sha256.txt
714730
715-
- name: Prune stale wheel and deb assets from dev release
731+
- name: Prune stale wheel, deb, and rpm assets from dev release
716732
uses: actions/github-script@v7
717733
env:
718734
WHEEL_VERSION: ${{ needs.compute-versions.outputs.python_version }}
@@ -744,13 +760,17 @@ jobs:
744760
core.info(` ${String(a.id).padStart(12)} ${a.name}`);
745761
}
746762
747-
// Delete stale wheels
748-
let kept = 0, deleted = 0, debDeleted = 0;
763+
// Delete stale wheels, debs, and rpms
764+
let kept = 0, deleted = 0, debDeleted = 0, rpmDeleted = 0;
749765
for (const asset of assets) {
750766
if (asset.name.endsWith('.deb')) {
751767
core.info(`Deleting stale deb package: ${asset.name} (id=${asset.id})`);
752768
await github.rest.repos.deleteReleaseAsset({ owner, repo, asset_id: asset.id });
753769
debDeleted++;
770+
} else if (asset.name.endsWith('.rpm')) {
771+
core.info(`Deleting stale rpm package: ${asset.name} (id=${asset.id})`);
772+
await github.rest.repos.deleteReleaseAsset({ owner, repo, asset_id: asset.id });
773+
rpmDeleted++;
754774
} else if (asset.name.endsWith('.whl') && asset.name.startsWith(currentPrefix)) {
755775
core.info(`Keeping current wheel: ${asset.name}`);
756776
kept++;
@@ -760,7 +780,7 @@ jobs:
760780
deleted++;
761781
}
762782
}
763-
core.info(`Summary: kept_wheels=${kept}, deleted_wheels=${deleted}, deleted_debs=${debDeleted}`);
783+
core.info(`Summary: kept_wheels=${kept}, deleted_wheels=${deleted}, deleted_debs=${debDeleted}, deleted_rpms=${rpmDeleted}`);
764784
765785
- name: Move dev tag
766786
run: |
@@ -792,6 +812,7 @@ jobs:
792812
release/openshell-aarch64-unknown-linux-musl.tar.gz
793813
release/openshell-aarch64-apple-darwin.tar.gz
794814
release/openshell_*.deb
815+
release/openshell-*.rpm
795816
release/openshell-gateway-x86_64-unknown-linux-gnu.tar.gz
796817
release/openshell-gateway-aarch64-unknown-linux-gnu.tar.gz
797818
release/openshell-gateway-aarch64-apple-darwin.tar.gz

.github/workflows/release-tag.yml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,12 +662,20 @@ jobs:
662662
checkout-ref: ${{ inputs.tag || github.ref }}
663663
secrets: inherit
664664

665+
build-rpm:
666+
name: Build RPM Packages
667+
needs: [compute-versions]
668+
uses: ./.github/workflows/rpm-package.yml
669+
with:
670+
checkout-ref: ${{ inputs.tag || github.ref }}
671+
secrets: inherit
672+
665673
# ---------------------------------------------------------------------------
666674
# Create a tagged GitHub Release with CLI binaries and wheels
667675
# ---------------------------------------------------------------------------
668676
release:
669677
name: Release
670-
needs: [compute-versions, build-cli-linux, build-cli-macos, build-gateway-binary-linux, build-gateway-binary-macos, build-supervisor-binary-linux, build-python-wheels-linux, build-python-wheel-macos, tag-ghcr-release, build-deb]
678+
needs: [compute-versions, build-cli-linux, build-cli-macos, build-gateway-binary-linux, build-gateway-binary-macos, build-supervisor-binary-linux, build-python-wheels-linux, build-python-wheel-macos, tag-ghcr-release, build-deb, build-rpm]
671679
runs-on: linux-amd64-cpu8
672680
timeout-minutes: 10
673681
outputs:
@@ -712,6 +720,13 @@ jobs:
712720
path: release/
713721
merge-multiple: true
714722

723+
- name: Download RPM package artifacts
724+
uses: actions/download-artifact@v4
725+
with:
726+
pattern: rpm-linux-*
727+
path: release/
728+
merge-multiple: true
729+
715730
- name: Capture wheel filenames
716731
id: wheel_filenames
717732
run: |
@@ -729,6 +744,7 @@ jobs:
729744
openshell-aarch64-unknown-linux-musl.tar.gz \
730745
openshell-aarch64-apple-darwin.tar.gz \
731746
openshell_*.deb \
747+
openshell-*.rpm \
732748
*.whl > openshell-checksums-sha256.txt
733749
cat openshell-checksums-sha256.txt
734750
sha256sum \
@@ -762,6 +778,7 @@ jobs:
762778
release/openshell-aarch64-unknown-linux-musl.tar.gz
763779
release/openshell-aarch64-apple-darwin.tar.gz
764780
release/openshell_*.deb
781+
release/openshell-*.rpm
765782
release/openshell-gateway-x86_64-unknown-linux-gnu.tar.gz
766783
release/openshell-gateway-aarch64-unknown-linux-gnu.tar.gz
767784
release/openshell-gateway-aarch64-apple-darwin.tar.gz

.github/workflows/rpm-package.yml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
name: RPM Package
5+
6+
on:
7+
workflow_call:
8+
inputs:
9+
checkout-ref:
10+
required: true
11+
type: string
12+
13+
permissions:
14+
contents: read
15+
16+
defaults:
17+
run:
18+
shell: bash
19+
20+
jobs:
21+
build-rpm-linux:
22+
name: Build RPM Package (Linux ${{ matrix.arch }})
23+
strategy:
24+
matrix:
25+
include:
26+
- arch: x86_64
27+
runner: build-amd64
28+
- arch: aarch64
29+
runner: build-arm64
30+
runs-on: ${{ matrix.runner }}
31+
timeout-minutes: 60
32+
container:
33+
image: fedora:latest
34+
steps:
35+
- name: Install build dependencies
36+
run: |
37+
dnf install -y \
38+
packit rpm-build \
39+
rust cargo gcc gcc-c++ make cmake pkg-config \
40+
clang-devel z3-devel systemd-rpm-macros \
41+
pandoc python3-devel git-core \
42+
cargo-rpm-macros
43+
44+
- uses: actions/checkout@v6
45+
with:
46+
ref: ${{ inputs.checkout-ref }}
47+
fetch-depth: 0
48+
49+
- name: Mark workspace safe for git
50+
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
51+
52+
- name: Fetch tags
53+
run: git fetch --tags --force
54+
55+
- name: Build RPMs via Packit
56+
run: packit build locally
57+
58+
- name: Collect RPM artifacts
59+
run: |
60+
set -euo pipefail
61+
mkdir -p artifacts
62+
find ~/rpmbuild/RPMS/ -name '*.rpm' -exec cp {} artifacts/ \;
63+
echo "=== Built RPMs ==="
64+
ls -lah artifacts/
65+
66+
- name: Upload RPM artifacts
67+
uses: actions/upload-artifact@v4
68+
with:
69+
name: rpm-linux-${{ matrix.arch }}
70+
path: artifacts/*.rpm
71+
retention-days: 5

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,5 +206,11 @@ rfc.md
206206
.worktrees
207207
.z3-trace
208208

209+
# RPM build artifacts
210+
*.src.rpm
211+
*.tar.gz
212+
*.tar.xz
213+
*.tar.bz2
214+
209215
# Markdown/mermaid lint tooling deps
210216
scripts/lint-mermaid/node_modules/

.markdownlint-cli2.jsonc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
".opencode/**",
1111
".github/**",
1212
"THIRD-PARTY-NOTICES/**",
13-
"CLAUDE.md"
13+
"CLAUDE.md",
14+
// Man page sources use pandoc markdown with multiple H1 sections
15+
// (NAME, SYNOPSIS, DESCRIPTION, etc.) which is standard for man
16+
// pages but violates MD025.
17+
"deploy/man/**"
1418
],
1519
"config": {
1620
"default": true,

.packit.yaml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
# Packit configuration for OpenShell RPM builds via Fedora COPR.
5+
# See https://packit.dev/docs/configuration for full reference.
6+
7+
upstream_tag_template: "v{version}"
8+
upstream_package_name: openshell
9+
downstream_package_name: openshell
10+
specfile_path: openshell.spec
11+
12+
# Packages needed in the SRPM build environment to create vendor tarball
13+
srpm_build_deps:
14+
- rust
15+
- cargo
16+
- git-core
17+
18+
actions:
19+
get-current-version:
20+
# Derive version from the latest upstream tag on the current branch.
21+
- 'bash -c "git describe --tags --match ''v*'' --abbrev=0 HEAD | sed ''s/^v//''"'
22+
23+
create-archive:
24+
# Step 1: Create source tarball from git working tree.
25+
# Uses git ls-files + tar instead of git archive so the tarball
26+
# reflects any patching that Packit may have done (e.g. version bumps).
27+
- 'bash -c "VERSION=${PACKIT_PROJECT_VERSION} && TMPDIR=$(mktemp -d) && DIR=openshell-${VERSION} && mkdir -p ${TMPDIR}/${DIR} && git ls-files -z | xargs -0 tar cf - | tar xf - -C ${TMPDIR}/${DIR}/ && tar -czf openshell-${VERSION}.tar.gz -C ${TMPDIR} ${DIR} && rm -rf ${TMPDIR}"'
28+
# Step 2: Create vendored Cargo dependencies tarball for offline RPM build.
29+
- 'bash -c "VERSION=${PACKIT_PROJECT_VERSION} && cargo vendor --quiet && tar -cJf openshell-${VERSION}-vendor.tar.xz vendor/ && rm -rf vendor/"'
30+
# Step 3: Return BOTH archive names. Packit maps each line to Source0, Source1, etc.
31+
- 'bash -c "echo openshell-${PACKIT_PROJECT_VERSION}.tar.gz && echo openshell-${PACKIT_PROJECT_VERSION}-vendor.tar.xz"'
32+
33+
fix-spec-file:
34+
# Update Source0 to the generated tarball name
35+
- 'bash -c "sed -i \"s|^Source0:.*|Source0: openshell-${PACKIT_PROJECT_VERSION}.tar.gz|\" openshell.spec"'
36+
# Update Source1 to the generated vendor tarball name
37+
- 'bash -c "sed -i \"s|^Source1:.*|Source1: openshell-${PACKIT_PROJECT_VERSION}-vendor.tar.xz|\" openshell.spec"'
38+
# Update Version
39+
- 'bash -c "sed -i -r \"s/^Version:(\\s*)\\S+/Version:\\1${PACKIT_RPMSPEC_VERSION}/\" openshell.spec"'
40+
# Update Release
41+
- 'bash -c "sed -i -r \"s/^Release:(\\s*)\\S+/Release:\\1${PACKIT_RPMSPEC_RELEASE}%{?dist}/\" openshell.spec"'
42+
43+
jobs:
44+
# Build on every pull request targeting main for CI validation
45+
- job: copr_build
46+
trigger: pull_request
47+
branch: main
48+
identifier: main-pr
49+
targets:
50+
- fedora-all
51+
- epel-10
52+
53+
# Build into maxamillion/openshell on every commit to main
54+
# for continuous development and testing builds.
55+
- job: copr_build
56+
trigger: commit
57+
branch: main
58+
owner: "maxamillion"
59+
project: "openshell"
60+
identifier: main-commit
61+
targets:
62+
- fedora-all
63+
- epel-10
64+
preserve_project: true
65+
list_on_homepage: true
66+
67+
# Build on GitHub releases for publishable RPMs.
68+
# See: https://packit.dev/docs/configuration/upstream/copr_build#using-a-custom-copr-project
69+
- job: copr_build
70+
trigger: release
71+
owner: "maxamillion"
72+
project: "openshell"
73+
targets:
74+
- fedora-all
75+
- epel-10
76+
preserve_project: true
77+
list_on_homepage: true

architecture/podman-driver.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,19 @@ sequenceDiagram
100100
C->>C: entrypoint: /opt/openshell/bin/openshell-sandbox
101101
```
102102

103-
The supervisor image is a `FROM scratch` image containing only the prebuilt `openshell-sandbox` binary. It is built by the `supervisor-output` target in `deploy/docker/Dockerfile.images`. The `image_volumes` field in the container spec mounts this image's filesystem at `/opt/openshell/bin` with `rw: false`, making it a read-only overlay that the sandbox cannot tamper with.
103+
The supervisor image is a `FROM scratch` image containing only the prebuilt `openshell-sandbox` binary. It is built by the `supervisor` target in `deploy/docker/Dockerfile.images`. The `image_volumes` field in the container spec mounts this image's filesystem at `/opt/openshell/bin` with `rw: false`, making it a read-only overlay that the sandbox cannot tamper with.
104+
105+
## TLS
106+
107+
When the Podman driver's TLS configuration is set (`tls_ca`, `tls_cert`, `tls_key` in `PodmanComputeConfig`), the driver:
108+
109+
1. Switches the auto-detected endpoint scheme from `http://` to `https://`
110+
2. Bind-mounts the client cert files (read-only) into the container at `/etc/openshell/tls/client/`
111+
3. Sets `OPENSHELL_TLS_CA`, `OPENSHELL_TLS_CERT`, `OPENSHELL_TLS_KEY` env vars pointing to the container-side paths
112+
113+
The supervisor reads these env vars and uses them to establish an mTLS connection back to the gateway.
114+
115+
The RPM packaging auto-generates a self-signed PKI on first start via `init-pki.sh`. Client certs are placed in the CLI auto-discovery directory (`~/.config/openshell/gateways/openshell/mtls/`) so the CLI connects with mTLS without manual configuration. See `deploy/rpm/CONFIGURATION.md` for the full RPM configuration reference and `deploy/rpm/QUICKSTART.md` for the quick start guide.
104116

105117
## Network Model
106118

@@ -158,7 +170,7 @@ The SSH handshake secret is injected via Podman's `secret_env` API rather than a
158170
| gRPC endpoint (`OPENSHELL_ENDPOINT`) | Plaintext env var, override-protected | Yes | Yes |
159171
| Supervisor relay socket path (`OPENSHELL_SSH_SOCKET_PATH`) | Plaintext env var, override-protected (same value as `PodmanComputeConfig::sandbox_ssh_socket_path`) | Yes | Yes |
160172

161-
The `build_env()` function in `container.rs` inserts user-supplied variables first, then unconditionally overwrites all security-critical variables to prevent spoofing via sandbox templates: `OPENSHELL_SANDBOX`, `OPENSHELL_SANDBOX_ID`, `OPENSHELL_ENDPOINT`, `OPENSHELL_SSH_SOCKET_PATH`, `OPENSHELL_SSH_LISTEN_ADDR`, `OPENSHELL_SSH_HANDSHAKE_SKEW_SECS`, `OPENSHELL_CONTAINER_IMAGE`, `OPENSHELL_SANDBOX_COMMAND`.
173+
The `build_env()` function in `container.rs` inserts user-supplied variables first, then unconditionally overwrites all security-critical variables to prevent spoofing via sandbox templates: `OPENSHELL_SANDBOX`, `OPENSHELL_SANDBOX_ID`, `OPENSHELL_ENDPOINT`, `OPENSHELL_SSH_SOCKET_PATH`, `OPENSHELL_SSH_HANDSHAKE_SKEW_SECS`, `OPENSHELL_CONTAINER_IMAGE`, `OPENSHELL_SANDBOX_COMMAND`.
162174

163175
The `PodmanComputeConfig::Debug` impl redacts the handshake secret as `[REDACTED]`.
164176

0 commit comments

Comments
 (0)