Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
90aae26
feat: add RPM packaging with packit and COPR integration
maxamillion Apr 28, 2026
c5431f3
fix(rpm): harden gateway systemd units and sysconfig defaults
maxamillion Apr 28, 2026
c4b24db
fix(rpm): auto-generate SSH handshake secret for user unit
maxamillion Apr 28, 2026
b3ab92c
fix(rpm): depend on podman.service instead of podman.socket
maxamillion Apr 28, 2026
0785d0d
feat(ci): add supervisor-sideload as distinct build and release target
maxamillion Apr 28, 2026
8600efc
feat(server): make gateway bind address configurable via OPENSHELL_BI…
maxamillion Apr 29, 2026
8ef26a5
fix(cli): gateway destroy on non-Docker systems fails looking for doc…
maxamillion Apr 29, 2026
5df8059
refactor(docker): consolidate supervisor and supervisor-sideload into…
maxamillion Apr 30, 2026
f6ff116
feat(podman): enable mTLS by default for RPM gateway packaging
maxamillion Apr 30, 2026
c9ac037
refactor(podman): rename tls_ca/cert/key to guest_tls_ca/cert/key
maxamillion Apr 30, 2026
a3e3fcc
fix(cli): skip cert extraction when TLS certs already exist on disk
maxamillion Apr 30, 2026
62b3f2b
fix(cli): warn on http:// registration when mTLS certs exist and vali…
maxamillion Apr 30, 2026
4911c3d
fix(cli): resolve loopback gateway name for mTLS cert detection
maxamillion Apr 30, 2026
e289e73
fix(podman): resolve mTLS cert path mismatch and SELinux bind-mount d…
maxamillion May 1, 2026
0642bf8
feat(rpm): overhaul packaging and documentation for Podman/systemd de…
maxamillion May 1, 2026
e4f28b0
fix: resolve auto-merge compilation errors from rebase
maxamillion May 1, 2026
c12c79d
feat(rpm): add cargo-rpm-macros for bundled crate provides
maxamillion May 1, 2026
c1009d4
feat(ci): add RPM package build and publish via Packit CLI
maxamillion May 1, 2026
55f1752
fix(rpm): align bind config with runtime
maxamillion May 4, 2026
dd942e3
fix(rpm): move EnvironmentFile after Environment directives
maxamillion May 5, 2026
2dc7fcc
Revert "fix(rpm): move EnvironmentFile after Environment directives"
maxamillion May 5, 2026
d715f54
fix(rpm): harden TLS validation, atomic PKI, and security docs
maxamillion May 5, 2026
2558078
fix(rpm): update release tag and hardcode Python SDK version
maxamillion May 5, 2026
8259824
fix(cli): install CryptoProvider in gateway_add tests
maxamillion May 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions .github/workflows/release-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -635,12 +635,20 @@ jobs:
checkout-ref: ${{ github.sha }}
secrets: inherit

build-rpm:
name: Build RPM Packages
needs: [compute-versions]
uses: ./.github/workflows/rpm-package.yml
with:
checkout-ref: ${{ github.sha }}
secrets: inherit

# ---------------------------------------------------------------------------
# Create / update the dev GitHub Release with CLI binaries and wheels
# ---------------------------------------------------------------------------
release-dev:
name: Release Dev
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]
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]
runs-on: linux-amd64-cpu8
timeout-minutes: 10
outputs:
Expand Down Expand Up @@ -683,6 +691,13 @@ jobs:
path: release/
merge-multiple: true

- name: Download RPM package artifacts
uses: actions/download-artifact@v4
with:
pattern: rpm-linux-*
path: release/
merge-multiple: true

- name: Capture wheel filenames
id: wheel_filenames
run: |
Expand All @@ -700,6 +715,7 @@ jobs:
openshell-aarch64-unknown-linux-musl.tar.gz \
openshell-aarch64-apple-darwin.tar.gz \
openshell_*.deb \
openshell-*.rpm \
*.whl > openshell-checksums-sha256.txt
cat openshell-checksums-sha256.txt
sha256sum \
Expand All @@ -712,7 +728,7 @@ jobs:
openshell-sandbox-aarch64-unknown-linux-gnu.tar.gz > openshell-sandbox-checksums-sha256.txt
cat openshell-sandbox-checksums-sha256.txt

- name: Prune stale wheel and deb assets from dev release
- name: Prune stale wheel, deb, and rpm assets from dev release
uses: actions/github-script@v7
env:
WHEEL_VERSION: ${{ needs.compute-versions.outputs.python_version }}
Expand Down Expand Up @@ -744,13 +760,17 @@ jobs:
core.info(` ${String(a.id).padStart(12)} ${a.name}`);
}

// Delete stale wheels
let kept = 0, deleted = 0, debDeleted = 0;
// Delete stale wheels, debs, and rpms
let kept = 0, deleted = 0, debDeleted = 0, rpmDeleted = 0;
for (const asset of assets) {
if (asset.name.endsWith('.deb')) {
core.info(`Deleting stale deb package: ${asset.name} (id=${asset.id})`);
await github.rest.repos.deleteReleaseAsset({ owner, repo, asset_id: asset.id });
debDeleted++;
} else if (asset.name.endsWith('.rpm')) {
core.info(`Deleting stale rpm package: ${asset.name} (id=${asset.id})`);
await github.rest.repos.deleteReleaseAsset({ owner, repo, asset_id: asset.id });
rpmDeleted++;
} else if (asset.name.endsWith('.whl') && asset.name.startsWith(currentPrefix)) {
core.info(`Keeping current wheel: ${asset.name}`);
kept++;
Expand All @@ -760,7 +780,7 @@ jobs:
deleted++;
}
}
core.info(`Summary: kept_wheels=${kept}, deleted_wheels=${deleted}, deleted_debs=${debDeleted}`);
core.info(`Summary: kept_wheels=${kept}, deleted_wheels=${deleted}, deleted_debs=${debDeleted}, deleted_rpms=${rpmDeleted}`);

- name: Move dev tag
run: |
Expand Down Expand Up @@ -792,6 +812,7 @@ jobs:
release/openshell-aarch64-unknown-linux-musl.tar.gz
release/openshell-aarch64-apple-darwin.tar.gz
release/openshell_*.deb
release/openshell-*.rpm
release/openshell-gateway-x86_64-unknown-linux-gnu.tar.gz
release/openshell-gateway-aarch64-unknown-linux-gnu.tar.gz
release/openshell-gateway-aarch64-apple-darwin.tar.gz
Expand Down
19 changes: 18 additions & 1 deletion .github/workflows/release-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -662,12 +662,20 @@ jobs:
checkout-ref: ${{ inputs.tag || github.ref }}
secrets: inherit

build-rpm:
name: Build RPM Packages
needs: [compute-versions]
uses: ./.github/workflows/rpm-package.yml
with:
checkout-ref: ${{ inputs.tag || github.ref }}
secrets: inherit

# ---------------------------------------------------------------------------
# Create a tagged GitHub Release with CLI binaries and wheels
# ---------------------------------------------------------------------------
release:
name: Release
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]
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]
runs-on: linux-amd64-cpu8
timeout-minutes: 10
outputs:
Expand Down Expand Up @@ -712,6 +720,13 @@ jobs:
path: release/
merge-multiple: true

- name: Download RPM package artifacts
uses: actions/download-artifact@v4
with:
pattern: rpm-linux-*
path: release/
merge-multiple: true

- name: Capture wheel filenames
id: wheel_filenames
run: |
Expand All @@ -729,6 +744,7 @@ jobs:
openshell-aarch64-unknown-linux-musl.tar.gz \
openshell-aarch64-apple-darwin.tar.gz \
openshell_*.deb \
openshell-*.rpm \
*.whl > openshell-checksums-sha256.txt
cat openshell-checksums-sha256.txt
sha256sum \
Expand Down Expand Up @@ -762,6 +778,7 @@ jobs:
release/openshell-aarch64-unknown-linux-musl.tar.gz
release/openshell-aarch64-apple-darwin.tar.gz
release/openshell_*.deb
release/openshell-*.rpm
release/openshell-gateway-x86_64-unknown-linux-gnu.tar.gz
release/openshell-gateway-aarch64-unknown-linux-gnu.tar.gz
release/openshell-gateway-aarch64-apple-darwin.tar.gz
Expand Down
71 changes: 71 additions & 0 deletions .github/workflows/rpm-package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

name: RPM Package

on:
workflow_call:
inputs:
checkout-ref:
required: true
type: string

permissions:
contents: read

defaults:
run:
shell: bash

jobs:
build-rpm-linux:
name: Build RPM Package (Linux ${{ matrix.arch }})
strategy:
matrix:
include:
- arch: x86_64
runner: build-amd64
- arch: aarch64
runner: build-arm64
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
container:
image: fedora:latest
steps:
- name: Install build dependencies
run: |
dnf install -y \
packit rpm-build \
rust cargo gcc gcc-c++ make cmake pkg-config \
clang-devel z3-devel systemd-rpm-macros \
pandoc python3-devel git-core \
cargo-rpm-macros

- uses: actions/checkout@v6
with:
ref: ${{ inputs.checkout-ref }}
fetch-depth: 0

- name: Mark workspace safe for git
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"

- name: Fetch tags
run: git fetch --tags --force

- name: Build RPMs via Packit
run: packit build locally

- name: Collect RPM artifacts
run: |
set -euo pipefail
mkdir -p artifacts
find ~/rpmbuild/RPMS/ -name '*.rpm' -exec cp {} artifacts/ \;
echo "=== Built RPMs ==="
ls -lah artifacts/

- name: Upload RPM artifacts
uses: actions/upload-artifact@v4
with:
name: rpm-linux-${{ matrix.arch }}
path: artifacts/*.rpm
retention-days: 5
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -206,5 +206,11 @@ rfc.md
.worktrees
.z3-trace

# RPM build artifacts
*.src.rpm
*.tar.gz
*.tar.xz
*.tar.bz2

# Markdown/mermaid lint tooling deps
scripts/lint-mermaid/node_modules/
6 changes: 5 additions & 1 deletion .markdownlint-cli2.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
".opencode/**",
".github/**",
"THIRD-PARTY-NOTICES/**",
"CLAUDE.md"
"CLAUDE.md",
// Man page sources use pandoc markdown with multiple H1 sections
// (NAME, SYNOPSIS, DESCRIPTION, etc.) which is standard for man
// pages but violates MD025.
"deploy/man/**"
],
"config": {
"default": true,
Expand Down
77 changes: 77 additions & 0 deletions .packit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# Packit configuration for OpenShell RPM builds via Fedora COPR.
# See https://packit.dev/docs/configuration for full reference.

upstream_tag_template: "v{version}"
upstream_package_name: openshell
downstream_package_name: openshell
specfile_path: openshell.spec

# Packages needed in the SRPM build environment to create vendor tarball
srpm_build_deps:
- rust
- cargo
- git-core

actions:
get-current-version:
# Derive version from the latest upstream tag on the current branch.
- 'bash -c "git describe --tags --match ''v*'' --abbrev=0 HEAD | sed ''s/^v//''"'

create-archive:
# Step 1: Create source tarball from git working tree.
# Uses git ls-files + tar instead of git archive so the tarball
# reflects any patching that Packit may have done (e.g. version bumps).
- '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}"'
# Step 2: Create vendored Cargo dependencies tarball for offline RPM build.
- 'bash -c "VERSION=${PACKIT_PROJECT_VERSION} && cargo vendor --quiet && tar -cJf openshell-${VERSION}-vendor.tar.xz vendor/ && rm -rf vendor/"'
# Step 3: Return BOTH archive names. Packit maps each line to Source0, Source1, etc.
- 'bash -c "echo openshell-${PACKIT_PROJECT_VERSION}.tar.gz && echo openshell-${PACKIT_PROJECT_VERSION}-vendor.tar.xz"'

fix-spec-file:
# Update Source0 to the generated tarball name
- 'bash -c "sed -i \"s|^Source0:.*|Source0: openshell-${PACKIT_PROJECT_VERSION}.tar.gz|\" openshell.spec"'
# Update Source1 to the generated vendor tarball name
- 'bash -c "sed -i \"s|^Source1:.*|Source1: openshell-${PACKIT_PROJECT_VERSION}-vendor.tar.xz|\" openshell.spec"'
# Update Version
- 'bash -c "sed -i -r \"s/^Version:(\\s*)\\S+/Version:\\1${PACKIT_RPMSPEC_VERSION}/\" openshell.spec"'
# Update Release
- 'bash -c "sed -i -r \"s/^Release:(\\s*)\\S+/Release:\\1${PACKIT_RPMSPEC_RELEASE}%{?dist}/\" openshell.spec"'

jobs:
# Build on every pull request targeting main for CI validation
- job: copr_build
trigger: pull_request
branch: main
identifier: main-pr
targets:
- fedora-all
- epel-10

# Build into maxamillion/openshell on every commit to main
# for continuous development and testing builds.
- job: copr_build
trigger: commit
branch: main
owner: "maxamillion"
project: "openshell"
identifier: main-commit
targets:
- fedora-all
- epel-10
preserve_project: true
list_on_homepage: true

# Build on GitHub releases for publishable RPMs.
# See: https://packit.dev/docs/configuration/upstream/copr_build#using-a-custom-copr-project
- job: copr_build
trigger: release
owner: "maxamillion"
project: "openshell"
targets:
- fedora-all
- epel-10
preserve_project: true
list_on_homepage: true
16 changes: 14 additions & 2 deletions architecture/podman-driver.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,19 @@ sequenceDiagram
C->>C: entrypoint: /opt/openshell/bin/openshell-sandbox
```

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.
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.

## TLS

When the Podman driver's TLS configuration is set (`tls_ca`, `tls_cert`, `tls_key` in `PodmanComputeConfig`), the driver:

1. Switches the auto-detected endpoint scheme from `http://` to `https://`
2. Bind-mounts the client cert files (read-only) into the container at `/etc/openshell/tls/client/`
3. Sets `OPENSHELL_TLS_CA`, `OPENSHELL_TLS_CERT`, `OPENSHELL_TLS_KEY` env vars pointing to the container-side paths

The supervisor reads these env vars and uses them to establish an mTLS connection back to the gateway.

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.

## Network Model

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

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`.
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`.

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

Expand Down
Loading
Loading