# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 %global crate openshell # The debugsource package carries the vendored crate sources. brp-mangle-shebangs # mistakes Rust inner attributes such as #![allow(...)] at the top of those files # for malformed shebang lines and fails the build, so skip /usr/src/debug. %global __brp_mangle_shebangs_exclude_from ^/usr/src/debug/.*$ # Run the Rust unit tests in %%check. Disable with --without check. %bcond_without check %global openshell_version 0.1.6 %global openshell_cargo_version %{openshell_version} # Python dist-info metadata intentionally follows the RPM Version. Dev build # identity is represented by Release for RPM packages. %global openshell_python_version %{openshell_version} Name: openshell Version: %{openshell_version} Release: %autorelease Summary: Safe sandbox runtimes for autonomous AI agents # OpenShell's own code is Apache-2.0. The binaries statically link third-party # crates; this is the minimal SPDX expression covering every crate linked into # openshell, openshell-gateway and openshell-prover (computed from `cargo tree` # license metadata, treating "X OR MIT" as satisfied by MIT or Apache-2.0). # The docs, man page sources, config templates and Python SDK sources carry # Apache-2.0 SPDX headers or fall under the repository LICENSE. The unit file is # generated from this spec. Re-check when Cargo.lock changes. The tag was # re-checked against the %%cargo_license_summary output of a 0.1.4 build: every # expression is satisfiable using only these identifiers. # # TLS uses rustls with the aws-lc-rs / aws-lc-sys crates, statically linked from # the vendored sources (symbols are prefixed, so nothing collides with system # OpenSSL). FESCo ticket 3679 allows aws-lc through these crates for TLS via # Rustls. Rustls cannot read the system crypto policies. License: Apache-2.0 AND BSD-3-Clause AND CC0-1.0 AND CDLA-Permissive-2.0 AND ISC AND MIT AND Unicode-3.0 AND Zlib URL: https://github.com/NVIDIA/OpenShell Source0: %{url}/archive/refs/tags/v%{version}/%{name}-%{version}.tar.gz # Source1 is a `cargo vendor` tarball with a top-level vendor/ directory. It is # generated from the Source0 tree after applying Patch0, because the patch # changes Cargo.lock: # tar -xzf openshell-%%{version}.tar.gz && cd OpenShell-%%{version} # patch -p1 < 0001-unbundle-sqlite.patch # cargo vendor vendor && tar -cJf ../openshell-%%{version}-vendor.tar.xz vendor Source1: %{name}-%{version}-vendor.tar.xz # Link the system SQLite instead of the copy that libsqlite3-sys bundles: # sqlx's "sqlite" feature enables sqlite-bundled, "sqlite-unbundled" links # libsqlite3 through bindgen. The sources use none of the other features that # "sqlite" enables (deserialize, load-extension, unlock-notify). Adds the # bindgen build dependency set to Cargo.lock. Patch0: 0001-unbundle-sqlite.patch # Upstream ships man pages for openshell and openshell-gateway only. Source2: %{name}-prover.1.md # Upstream only builds and tests x86_64 and aarch64. Other Fedora arches have # not been validated; TODO: build on ppc64le and s390x, and file a bug per arch # for any that fail (ExcludeArch) instead of listing arches here. ExclusiveArch: x86_64 aarch64 noarch BuildRequires: rust BuildRequires: cargo BuildRequires: gcc BuildRequires: gcc-c++ BuildRequires: make BuildRequires: cmake BuildRequires: pkgconfig BuildRequires: pkgconfig(z3) BuildRequires: pkgconfig(sqlite3) BuildRequires: clang-devel BuildRequires: protobuf-compiler BuildRequires: protobuf-devel BuildRequires: cargo-rpm-macros >= 25 BuildRequires: systemd-rpm-macros # Man page generation BuildRequires: pandoc # Python sub-package build dependencies. grpcio-tools generates the protobuf # stubs, which upstream does not commit. BuildRequires: python3-devel BuildRequires: python3-grpcio-tools # Runtime: container runtime for package-managed gateway sandboxes. # The gateway auto-detects Podman when the package-managed service starts. Recommends: podman # The CLI runs ssh for sandbox connect and exec, and git for git-aware uploads. Requires: openssh-clients Recommends: git-core Suggests: lsof %description OpenShell provides safe sandbox runtimes for autonomous AI agents. It offers a CLI for managing gateway registrations, sandboxes, and providers, with policy-enforced egress routing, a credential proxy, and privacy-aware profile-backed model-provider access. # --- Gateway sub-package --- %package gateway Summary: OpenShell gateway server with Podman sandbox driver Requires: podman Requires: %{name}%{?_isa} = %{version}-%{release} %description gateway OpenShell gateway server providing the control-plane API for managing the life cycle of sandboxes. This package installs Podman-oriented defaults in gateway TOML while leaving compute driver selection to gateway auto-detection or explicit operator configuration. # --- Standalone policy prover sub-package --- %package prover Summary: Standalone OpenShell policy boundary prover %description prover OpenShell policy prover for checking whether a local candidate policy stays within an operator-supplied maximum without connecting to a gateway. # --- Python SDK sub-package --- %package -n python3-%{name} Summary: OpenShell Python SDK for agent execution and management # Pure Python; links none of the crates covered by the main License tag. License: Apache-2.0 BuildArch: noarch # Runtime dependencies come from the dist-info METADATA through the Python # dependency generator. Recommends: %{name} %description -n python3-%{name} Python SDK for OpenShell providing programmatic access to sandbox management, agent execution, and provider access through the gateway API. %prep %autosetup -n OpenShell-%{version} -p1 # Extract vendored Cargo dependencies and configure offline build tar xf %{SOURCE1} %cargo_prep -v vendor # Fedora-specific: upstream keeps the workspace version at the placeholder 0.0.0 # and stamps it at release time (see crates/openshell-core/build.rs), so patch it # to the package version. Not upstreamable. sed -i 's/^version = "0.0.0"/version = "%{openshell_cargo_version}"/' Cargo.toml grep -q 'version = "%{openshell_cargo_version}"' Cargo.toml || (echo "ERROR: Cargo.toml version patch failed" && exit 1) %build export CARGO_BUILD_JOBS=%{_smp_build_ncpus} # Use the distro protoc instead of the protoc-bin-vendored binary. export PROTOC=%{_bindir}/protoc export PROTOC_INCLUDE=%{_includedir} %cargo_build -- --bin openshell --bin openshell-gateway --bin openshell-prover # Generate vendored crate manifest and license metadata. # cargo-vendor.txt is consumed by an RPM generator (from cargo-rpm-macros) # to emit Provides: bundled(crate(...)) = version for every vendored dep. %cargo_vendor_manifest %{cargo_license_summary} %{cargo_license} > LICENSE.dependencies # Generate the Python protobuf stubs (upstream gitignores them). Fedora's # grpcio-tools is 1.48, whose protoc has no --pyi_out, so skip the .pyi files. %{python3} tasks/scripts/generate_python_proto.py --no-pyi # Build man pages from markdown pandoc -f markdown-smart -s -t man deploy/man/openshell.1.md -o openshell.1 pandoc -f markdown-smart -s -t man deploy/man/openshell-gateway.8.md -o openshell-gateway.8 pandoc -f markdown-smart -s -t man %{SOURCE2} -o openshell-prover.1 %install # --- CLI binary --- install -Dpm 0755 "target/rpm/%{name}" %{buildroot}%{_bindir}/%{name} # --- Standalone policy prover --- install -Dpm 0755 "target/rpm/%{name}-prover" %{buildroot}%{_bindir}/%{name}-prover # --- Gateway binary --- install -Dpm 0755 "target/rpm/%{name}-gateway" %{buildroot}%{_bindir}/%{name}-gateway # --- Default gateway TOML config template --- # Shipped as a read-only reference in %%{_datadir}. The systemd unit seeds a # user-level copy at ~/.config/openshell/gateway.toml on first start. install -Dpm 0644 deploy/rpm/gateway.toml.default %{buildroot}%{_datadir}/%{name}-gateway/gateway.toml.default install -Dpm 0644 deploy/rpm/gateway.toml.default.v1 %{buildroot}%{_datadir}/%{name}-gateway/gateway.toml.default.v1 install -Dpm 0755 deploy/rpm/migrate-gateway-config.sh %{buildroot}%{_libexecdir}/%{name}-gateway-migrate-config # --- System gateway entry for the CLI --- # The CLI reads read-only gateway entries from %%{_sysconfdir}/openshell, in # the same layout as per-user config (active_gateway plus # gateways//metadata.json). Shipping one for the packaged user service # on its default endpoint lets the CLI find the gateway without running # `openshell gateway add`. A user's own registration with the same name # shadows this entry. The client certificates stay per user, generated on # first start in ~/.config/openshell/gateways/openshell/mtls/. install -d %{buildroot}%{_sysconfdir}/%{name}/gateways/%{name} printf '%%s\n' '%{name}' > %{buildroot}%{_sysconfdir}/%{name}/active_gateway cat > %{buildroot}%{_sysconfdir}/%{name}/gateways/%{name}/metadata.json << 'EOF' { "name": "openshell", "gateway_endpoint": "https://127.0.0.1:17670", "is_remote": false, "gateway_port": 0, "auth_mode": "mtls" } EOF # --- Gateway systemd user unit --- # Installed to the systemd user unit directory so any user can run: # systemctl --user enable --now openshell-gateway.service install -d %{buildroot}%{_userunitdir} cat > %{buildroot}%{_userunitdir}/%{name}-gateway.service << 'EOF' [Unit] Description=OpenShell Gateway (user) Documentation=man:openshell-gateway(8) file://%{_docdir}/%{name}-gateway/QUICKSTART.md https://github.com/NVIDIA/OpenShell After=podman.socket Wants=podman.socket [Service] Type=exec # On first start the unit seeds a default TOML config and generates PKI. # Client certs are placed in ~/.config/openshell/gateways/openshell/mtls/ so # the CLI discovers them automatically. # See /usr/share/doc/openshell-gateway/ for details. # Seed a default TOML config on first start. On upgrade, replace only the exact # schema-v1 config previously seeded by this package; preserve edited files. # %%E expands to $XDG_CONFIG_HOME (~/.config) in user units. ExecStartPre=%{_libexecdir}/%{name}-gateway-migrate-config %%E/openshell/gateway.toml %{_datadir}/%{name}-gateway/gateway.toml.default %{_datadir}/%{name}-gateway/gateway.toml.default.v1 # Reject an invalid selected configuration before generating certificates or # starting the gateway. The environment file below applies to every command. ExecStartPre=%{_bindir}/%{name}-gateway config preflight # Auto-generate PKI on first start if not present. # The default local TLS dir uses %%h because %%S resolves differently across # systemd user-manager versions. gateway.env may override this path. Environment=OPENSHELL_LOCAL_TLS_DIR=%%h/.local/state/openshell/tls ExecStartPre=%{_bindir}/%{name}-gateway generate-certs --output-dir ${OPENSHELL_LOCAL_TLS_DIR} --server-san host.openshell.internal # gateway.env is honored for backward compatibility with pre-1415 installs. # New installs use runtime defaults; create gateway.toml to override. # See TROUBLESHOOTING.md for the env-to-TOML migration guide. EnvironmentFile=-%%E/openshell/gateway.env ExecStart=%{_bindir}/%{name}-gateway StateDirectory=openshell Restart=on-failure RestartSec=5 # Security hardening NoNewPrivileges=yes ProtectSystem=strict PrivateTmp=yes RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX [Install] WantedBy=default.target EOF chmod 0644 %{buildroot}%{_userunitdir}/%{name}-gateway.service # --- Man pages --- install -Dpm 0644 openshell.1 %{buildroot}%{_mandir}/man1/openshell.1 install -Dpm 0644 openshell-gateway.8 %{buildroot}%{_mandir}/man8/openshell-gateway.8 install -Dpm 0644 openshell-prover.1 %{buildroot}%{_mandir}/man1/openshell-prover.1 # --- Python SDK --- # Install Python SDK modules (test files are intentionally excluded) install -d %{buildroot}%{python3_sitelib}/%{name} install -d %{buildroot}%{python3_sitelib}/%{name}/_proto install -pm 0644 python/%{name}/__init__.py %{buildroot}%{python3_sitelib}/%{name}/ install -pm 0644 python/%{name}/errors.py %{buildroot}%{python3_sitelib}/%{name}/ install -pm 0644 python/%{name}/mutations.py %{buildroot}%{python3_sitelib}/%{name}/ install -pm 0644 python/%{name}/providers.py %{buildroot}%{python3_sitelib}/%{name}/ install -pm 0644 python/%{name}/sandbox.py %{buildroot}%{python3_sitelib}/%{name}/ install -pm 0644 python/%{name}/py.typed %{buildroot}%{python3_sitelib}/%{name}/ install -pm 0644 python/%{name}/_proto/__init__.py %{buildroot}%{python3_sitelib}/%{name}/_proto/ install -pm 0644 python/%{name}/_proto/*.py %{buildroot}%{python3_sitelib}/%{name}/_proto/ # Create dist-info so importlib.metadata resolves the package version and the # dependency generator emits the runtime requirements. Upstream declares # grpcio >= 1.60 and protobuf >= 4.25, which Fedora cannot satisfy (grpcio 1.48, # protobuf 3.19), so those two are left unversioned here. The SDK imports and # runs against the Fedora versions (stubs generated with grpcio-tools 1.48, and # a live-gateway test on Fedora 44 passed). install -d %{buildroot}%{python3_sitelib}/%{name}-%{openshell_python_version}.dist-info cat > %{buildroot}%{python3_sitelib}/%{name}-%{openshell_python_version}.dist-info/METADATA << EOF Metadata-Version: 2.1 Name: %{name} Version: %{openshell_python_version} Summary: OpenShell Python SDK for agent execution and management License: Apache-2.0 Requires-Python: >=3.11 Requires-Dist: cloudpickle>=3.0 Requires-Dist: googleapis-common-protos>=1.63 Requires-Dist: grpcio Requires-Dist: httpx>=0.27 Requires-Dist: protobuf EOF # INSTALLER marker per PEP 376 echo "rpm" > %{buildroot}%{python3_sitelib}/%{name}-%{openshell_python_version}.dist-info/INSTALLER %check %if %{with check} # Run the policy prover unit tests, which link the system Z3 (106 tests pass # against Fedora's z3-devel). The rest of the workspace tests are not run yet: # they have not been reviewed for network and container-runtime requirements. %cargo_test -- -p openshell-prover --lib %endif # Smoke-test the CLI binary %{buildroot}%{_bindir}/%{name} --version # Smoke-test the standalone policy prover %{buildroot}%{_bindir}/%{name}-prover --version # Smoke-test the gateway binary %{buildroot}%{_bindir}/%{name}-gateway --version # Smoke-test the Python SDK version metadata via importlib.metadata. # We query the dist-info directly rather than importing the package because # the full import pulls in grpcio and other runtime deps not present in the # build environment. PYTHONPATH=%{buildroot}%{python3_sitelib} %{python3} -c "from importlib.metadata import version; v = version('openshell'); print(v); assert v == '%{openshell_python_version}', f'expected %{openshell_python_version}, got {v}'" # Verify every module the installed SDK pulls in with a relative import was # installed, including the generated protobuf stubs imported with # "from ._proto import x_pb2". Importing the package needs grpcio, which the # build environment does not have, so check the imports statically. %{python3} - %{buildroot}%{python3_sitelib}/%{name} <<'PY' import ast, pathlib, sys pkg = pathlib.Path(sys.argv[1]) missing = [] def exists(path): return path.with_suffix(".py").exists() or (path / "__init__.py").exists() for source in pkg.glob("*.py"): for node in ast.walk(ast.parse(source.read_text())): if not (isinstance(node, ast.ImportFrom) and node.level == 1 and node.module): continue base = pkg.joinpath(*node.module.split(".")) if not exists(base): missing.append(f"{source.name}: .{node.module}") continue if base.is_dir(): init = (base / "__init__.py").read_text() for alias in node.names: if alias.name != "*" and alias.name not in init and not exists(base / alias.name): missing.append(f"{source.name}: .{node.module}.{alias.name}") assert not missing, f"installed SDK imports modules that were not installed: {missing}" PY # Verify the RPM default TOML config template was installed. # A missing template means first-start seeding silently falls back to the # binary default of 127.0.0.1, which breaks Podman sandbox connectivity. test -f %{buildroot}%{_datadir}/%{name}-gateway/gateway.toml.default test -f %{buildroot}%{_datadir}/%{name}-gateway/gateway.toml.default.v1 test -x %{buildroot}%{_libexecdir}/%{name}-gateway-migrate-config # Verify the system gateway entry names the packaged default endpoint. test "$(cat %{buildroot}%{_sysconfdir}/%{name}/active_gateway)" = '%{name}' grep -q '"gateway_endpoint": "https://127.0.0.1:17670"' %{buildroot}%{_sysconfdir}/%{name}/gateways/%{name}/metadata.json # Verify the systemd unit invokes exact-default migration before startup. grep -q '%{name}-gateway-migrate-config' %{buildroot}%{_userunitdir}/%{name}-gateway.service grep -q 'gateway.toml.default.v1' %{buildroot}%{_userunitdir}/%{name}-gateway.service %post gateway %systemd_user_post %{name}-gateway.service %preun gateway %systemd_user_preun %{name}-gateway.service %postun gateway %systemd_user_postun_with_restart %{name}-gateway.service %files %license LICENSE %license LICENSE.dependencies %license cargo-vendor.txt %doc README.md %{_bindir}/%{name} %{_mandir}/man1/openshell.1* %files prover %license LICENSE %license LICENSE.dependencies %license cargo-vendor.txt %doc README.md %{_bindir}/%{name}-prover %{_mandir}/man1/%{name}-prover.1* %files gateway %license LICENSE %license LICENSE.dependencies %license cargo-vendor.txt %doc deploy/rpm/QUICKSTART.md %doc deploy/rpm/CONFIGURATION.md %doc deploy/rpm/TROUBLESHOOTING.md %{_bindir}/%{name}-gateway %{_userunitdir}/%{name}-gateway.service %{_libexecdir}/%{name}-gateway-migrate-config %dir %{_sysconfdir}/%{name} %dir %{_sysconfdir}/%{name}/gateways %dir %{_sysconfdir}/%{name}/gateways/%{name} %config(noreplace) %{_sysconfdir}/%{name}/active_gateway %config(noreplace) %{_sysconfdir}/%{name}/gateways/%{name}/metadata.json %dir %{_datadir}/%{name}-gateway %{_datadir}/%{name}-gateway/gateway.toml.default %{_datadir}/%{name}-gateway/gateway.toml.default.v1 %{_mandir}/man8/openshell-gateway.8* %files -n python3-%{name} %license LICENSE %doc README.md %{python3_sitelib}/%{name}/ %{python3_sitelib}/%{name}-%{openshell_python_version}.dist-info/ %changelog %autochangelog