11 Commits

Author SHA1 Message Date
a5bc992590 fix(rpm): explicitly Provides user(cortex)/group(cortex)
Some checks failed
CI / Format, lint, build, test (push) Successful in 1m4s
CI / Build cortex SRPM (push) Successful in 44s
CI / Build neuron SRPM (push) Successful in 1m46s
CI / Publish cortex to COPR (push) Successful in 8m49s
CI / Publish neuron to COPR (push) Successful in 9m51s
CI / Bump version in source (push) Failing after 47s
dnf5 was silently rejecting neuron-0.1.3 with "Nothing to do" because
it had an unresolvable Requires. Inspection showed:

  Requires: user(cortex)               ← unversioned
  Provides: user(cortex) = <base64>    ← versioned only, no unversioned

rpm's sysusers provides-generator only emits the unversioned user()
provide when the u-line is minimal. Our sysusers.conf specifies GECOS,
home dir, and shell, which pushes the generator to versioned-only.
The matching Requires (auto-generated from %attr(,,cortex) on config
files) is unversioned, so resolution failed silently.

Explicitly declare Provides: user(cortex) and Provides: group(cortex)
to guarantee the unversioned forms exist. group(cortex) was already
emitted unversioned but adding it for symmetry and to protect against
future generator changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 12:04:19 +03:00
5a86f7cc16 ci: dump COPR per-chroot build logs to CI output
Previously the COPR publish steps only surfaced copr-cli's status
updates (pending/importing/running). When a build failed, diagnosing
required clicking through to the COPR web UI. Now we submit with
--nowait, watch the build, then use copr-cli download-build to fetch
each chroot's builder-live.log and cat them as collapsible ::group::
blocks in the CI output.

Logic is factored into .gitea/scripts/copr-build.sh so cortex and
neuron jobs share it. Both COPR jobs now check out the repo to access
the script.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 11:41:15 +03:00
ba5020138f fix(rpm): rename sysusers files to match package names
All checks were successful
CI / Format, lint, build, test (push) Successful in 3m35s
CI / Build cortex SRPM (push) Successful in 1m46s
CI / Build neuron SRPM (push) Successful in 1m41s
CI / Publish cortex to COPR (push) Successful in 7m14s
CI / Publish neuron to COPR (push) Successful in 5m44s
CI / Bump version in source (push) Successful in 30s
cortex-gateway.conf/cortex-neuron.conf implied a hierarchy or coupling
that doesn't exist — cortex and neuron are independent packages.
Each package's sysusers.d file now matches the package name:
cortex ships cortex.conf, neuron ships neuron.conf. Content is still
identical (both create the cortex system user/group), and filenames
remain distinct so the packages can coinstall.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 11:20:08 +03:00
209150771e fix(rpm): use sysusers.d for cortex user/group creation
Both packages set %attr(...,cortex) on their config files, which
caused RPM's auto-dep-generator to emit Requires: group(cortex) /
user(cortex). The %pre scriptlets that actually created the group
ran too late — dnf rejected neuron installation on hosts without
cortex because nothing Provided group(cortex).

Switch to systemd-sysusers declarative user creation: each package
ships its own named sysusers.d file (cortex-gateway.conf and
cortex-neuron.conf — different names so both packages can coinstall)
with identical content defining the cortex user/group. RPM's
user/group dep generator now emits Provides: user(cortex) and
Provides: group(cortex) automatically from the sysusers.d files,
satisfying the auto-generated Requires. Either package installs
standalone; both can coinstall on the gateway host if desired.

Also added Requires: systemd since %sysusers_create_compat depends
on systemd-sysusers being present on the target.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 11:18:37 +03:00
Gitea Actions
7c60af3464 chore: bump version to 0.1.2 2026-04-16 11:03:29 +03:00
ada76b0153 fix(rpm): add missing native build dependencies
All checks were successful
CI / Format, lint, build, test (push) Successful in 4m34s
CI / Build neuron SRPM (push) Successful in 1m49s
CI / Build cortex SRPM (push) Successful in 44s
CI / Publish cortex to COPR (push) Successful in 7m14s
CI / Publish neuron to COPR (push) Successful in 5m43s
CI / Bump version in source (push) Successful in 52s
COPR build failed on openssl-sys because openssl headers were not
available in the mock chroot. Adding:

- pkgconfig(openssl): fixes the immediate openssl-sys failure.
  Kept as a build dep because we plan to add optional mTLS between
  cortex and neuron, which requires native-tls/openssl at build time.
- cmake, gcc-c++: aws-lc-sys (pulled via rustls) compiles libcrypto
  via cmake and includes C++ sources. Would be the next failure after
  openssl.
- perl-interpreter: catchall for -sys crate build scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 10:49:20 +03:00
15ded3a5bd ci: cache target/, disable incremental, drop redundant build
Three complementary tweaks to close the gap sccache alone can't:

- CARGO_INCREMENTAL=0: reclaims the 17 incremental-mode cache misses
  per run and prevents cargo from writing incremental fingerprints
  that defeat sccache. Incremental mode is useless in CI anyway since
  each run starts from scratch.
- actions/cache for ~/.cargo and target/: sidesteps sccache's
  structural limits (proc-macro non-cacheables, clippy-vs-rustc
  separate namespaces) by caching the whole build output keyed on
  Cargo.lock. Also caches ~/.cargo/bin so the installed sccache
  binary survives between runs.
- Drop the separate 'cargo build' step: 'cargo test --workspace'
  builds everything anyway, so the standalone build was a full
  redundant workspace compile pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 09:44:45 +03:00
7befa882d5 fix: yaml syntax
Some checks failed
CI / Format, lint, build, test (push) Successful in 1m42s
CI / Build neuron SRPM (push) Successful in 42s
CI / Build cortex SRPM (push) Successful in 1m40s
CI / Publish neuron to COPR (push) Failing after 4m11s
CI / Publish cortex to COPR (push) Failing after 3m16s
CI / Bump version in source (push) Has been skipped
2026-04-16 09:25:02 +03:00
d03fae960a fix(ci): unset RUSTC_WRAPPER during sccache install
All checks were successful
CI / Format, lint, build, test (push) Successful in 2m40s
CI / Build cortex SRPM (push) Has been skipped
CI / Build neuron SRPM (push) Has been skipped
CI / Publish cortex to COPR (push) Has been skipped
CI / Publish neuron to COPR (push) Has been skipped
CI / Bump version in source (push) Has been skipped
The workflow-level env set RUSTC_WRAPPER=sccache for every step,
including the install step itself. cargo install sccache then
tried to invoke `sccache rustc -vV` to detect the toolchain before
sccache existed on PATH, failing with "No such file or directory".
Override RUSTC_WRAPPER to empty on the install step so cargo uses
rustc directly; subsequent steps still inherit the wrapper.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 08:31:26 +03:00
7b2235d56b fix(ci): install sccache with S3 feature if missing
Some checks failed
CI / Format, lint, build, test (push) Failing after 4s
CI / Build cortex SRPM (push) Has been skipped
CI / Publish cortex to COPR (push) Has been skipped
CI / Build neuron SRPM (push) Has been skipped
CI / Publish neuron to COPR (push) Has been skipped
CI / Bump version in source (push) Has been skipped
The distro sccache package lacks S3 support. Install from cargo
with --features s3 if the existing binary can't connect to the
S3 backend. Skips install if already present and working.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:44:21 +03:00
54f9f3dc36 ci: add sccache with MinIO backend for build caching
Some checks failed
CI / Format, lint, build, test (push) Failing after 3s
CI / Build cortex SRPM (push) Has been skipped
CI / Build neuron SRPM (push) Has been skipped
CI / Publish cortex to COPR (push) Has been skipped
CI / Publish neuron to COPR (push) Has been skipped
CI / Bump version in source (push) Has been skipped
All Rust compilation steps now use sccache backed by MinIO S3
at caveman.kosherinata.internal:9000. Credentials via repo secrets
SCCACHE_S3_ACCESS_KEY and SCCACHE_S3_SECRET_KEY. Cache is shared
across all bare metal runners.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:38:13 +03:00
7 changed files with 145 additions and 20 deletions

61
.gitea/scripts/copr-build.sh Executable file
View File

@@ -0,0 +1,61 @@
#!/bin/bash
# Submit an SRPM to COPR, watch the build, and dump per-chroot build logs
# to stdout so they are captured in CI output.
#
# Usage: copr-build.sh <project> <srpm> [srpm...]
# Example: copr-build.sh helexa/cortex ./cortex-0.1.2-1.fc43.src.rpm
set -o pipefail
PROJECT="$1"
shift
if [ -z "$PROJECT" ] || [ "$#" -eq 0 ]; then
echo "usage: $0 <project> <srpm> [srpm...]" >&2
exit 2
fi
# Submit without waiting; capture the build ID from stdout.
SUBMIT_OUT=$(copr-cli build --nowait "$PROJECT" "$@")
echo "$SUBMIT_OUT"
BUILD_ID=$(echo "$SUBMIT_OUT" | grep -oP 'Created builds: \K[0-9]+' | head -n1)
if [ -z "$BUILD_ID" ]; then
echo "error: could not parse build ID from copr-cli output" >&2
exit 1
fi
echo
echo "Build $BUILD_ID submitted to $PROJECT"
echo "Follow live: https://copr.fedorainfracloud.org/coprs/build/$BUILD_ID"
echo
# Watch the build; captures status transitions to stdout. Exit non-zero
# on build failure, but defer propagating that until after we've fetched
# logs so the CI output contains diagnostics either way.
if copr-cli watch-build "$BUILD_ID"; then
STATUS=0
else
STATUS=$?
fi
# Fetch per-chroot results (logs + rpms). Anonymous download — no auth needed.
mkdir -p copr-logs
copr-cli download-build --dest copr-logs "$BUILD_ID" || {
echo "warning: failed to download build artifacts" >&2
}
# Dump each chroot's builder-live.log as a collapsible group.
for chroot_dir in copr-logs/*/; do
[ -d "$chroot_dir" ] || continue
chroot=$(basename "$chroot_dir")
log="${chroot_dir}builder-live.log"
if [ -f "$log" ]; then
echo
echo "::group::${chroot} builder-live.log"
cat "$log"
echo "::endgroup::"
fi
done
exit "$STATUS"

View File

@@ -2,11 +2,21 @@ name: CI
on: on:
push: push:
branches: ['**'] branches: ["**"]
tags: ['v*'] tags: ["v*"]
pull_request: pull_request:
branches: [main] branches: [main]
env:
CARGO_INCREMENTAL: "0"
RUSTC_WRAPPER: sccache
SCCACHE_BUCKET: sccache
SCCACHE_ENDPOINT: http://caveman.kosherinata.internal:9000
SCCACHE_REGION: auto
SCCACHE_S3_USE_SSL: "false"
AWS_ACCESS_KEY_ID: ${{ secrets.SCCACHE_S3_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_S3_SECRET_KEY }}
jobs: jobs:
check: check:
name: Format, lint, build, test name: Format, lint, build, test
@@ -14,18 +24,41 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Cache cargo registry and target
uses: actions/cache@v4
with:
path: |
~/.cargo/bin
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Ensure sccache with S3 support
env:
RUSTC_WRAPPER: ""
run: |
if sccache --version 2>/dev/null && sccache --show-stats 2>/dev/null; then
echo "sccache with S3 support already installed"
else
cargo install sccache --features s3 --locked
fi
- name: Check formatting - name: Check formatting
run: cargo fmt --check --all run: cargo fmt --check --all
- name: Clippy - name: Clippy
run: cargo clippy --workspace -- -D warnings run: cargo clippy --workspace -- -D warnings
- name: Build
run: cargo build --workspace
- name: Test - name: Test
run: cargo test --workspace run: cargo test --workspace
- name: Show sccache stats
run: sccache --show-stats
srpm-cortex: srpm-cortex:
name: Build cortex SRPM name: Build cortex SRPM
runs-on: fedora runs-on: fedora
@@ -76,7 +109,7 @@ jobs:
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: srpm-cortex name: srpm-cortex
path: '*.src.rpm' path: "*.src.rpm"
srpm-neuron: srpm-neuron:
name: Build neuron SRPM name: Build neuron SRPM
@@ -128,13 +161,15 @@ jobs:
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: srpm-neuron name: srpm-neuron
path: '*.src.rpm' path: "*.src.rpm"
copr-cortex: copr-cortex:
name: Publish cortex to COPR name: Publish cortex to COPR
runs-on: fedora runs-on: fedora
needs: srpm-cortex needs: srpm-cortex
steps: steps:
- uses: actions/checkout@v4
- name: Download SRPM - name: Download SRPM
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
@@ -146,13 +181,15 @@ jobs:
echo "${{ secrets.COPR_CONFIG }}" > ~/.config/copr echo "${{ secrets.COPR_CONFIG }}" > ~/.config/copr
- name: Submit build to COPR - name: Submit build to COPR
run: copr-cli build helexa/cortex *.src.rpm run: bash .gitea/scripts/copr-build.sh helexa/cortex *.src.rpm
copr-neuron: copr-neuron:
name: Publish neuron to COPR name: Publish neuron to COPR
runs-on: fedora runs-on: fedora
needs: srpm-neuron needs: srpm-neuron
steps: steps:
- uses: actions/checkout@v4
- name: Download SRPM - name: Download SRPM
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
@@ -164,7 +201,7 @@ jobs:
echo "${{ secrets.COPR_CONFIG }}" > ~/.config/copr echo "${{ secrets.COPR_CONFIG }}" > ~/.config/copr
- name: Submit build to COPR - name: Submit build to COPR
run: copr-cli build helexa/neuron *.src.rpm run: bash .gitea/scripts/copr-build.sh helexa/neuron *.src.rpm
bump-version: bump-version:
name: Bump version in source name: Bump version in source

8
Cargo.lock generated
View File

@@ -351,7 +351,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]] [[package]]
name = "cortex-cli" name = "cortex-cli"
version = "0.1.0" version = "0.1.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -366,7 +366,7 @@ dependencies = [
[[package]] [[package]]
name = "cortex-core" name = "cortex-core"
version = "0.1.0" version = "0.1.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -381,7 +381,7 @@ dependencies = [
[[package]] [[package]]
name = "cortex-gateway" name = "cortex-gateway"
version = "0.1.0" version = "0.1.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
@@ -1184,7 +1184,7 @@ dependencies = [
[[package]] [[package]]
name = "neuron" name = "neuron"
version = "0.1.0" version = "0.1.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",

View File

@@ -8,7 +8,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.1.0" version = "0.1.2"
edition = "2024" edition = "2024"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
repository = "https://git.lair.cafe/helexa/cortex" repository = "https://git.lair.cafe/helexa/cortex"

View File

@@ -1,5 +1,5 @@
Name: cortex Name: cortex
Version: 0.1.0 Version: 0.1.2
Release: 1%{?dist} Release: 1%{?dist}
Summary: Inference gateway for multi-node GPU clusters Summary: Inference gateway for multi-node GPU clusters
@@ -13,9 +13,20 @@ ExclusiveArch: x86_64
BuildRequires: rust >= 1.85 BuildRequires: rust >= 1.85
BuildRequires: cargo BuildRequires: cargo
BuildRequires: gcc BuildRequires: gcc
BuildRequires: gcc-c++
BuildRequires: cmake
BuildRequires: perl-interpreter
BuildRequires: pkgconfig(openssl)
BuildRequires: systemd-rpm-macros BuildRequires: systemd-rpm-macros
Requires(pre): shadow-utils Requires(pre): shadow-utils
Requires: systemd
# rpm's sysusers provides-generator only emits versioned user(cortex) when
# the u-line has GECOS/home/shell fields. %attr(,,cortex) in %files emits
# an unversioned Requires: user(cortex), so we provide it explicitly.
Provides: user(cortex)
Provides: group(cortex)
%description %description
Cortex is a Rust reverse-proxy that sits in front of multiple inference Cortex is a Rust reverse-proxy that sits in front of multiple inference
@@ -41,13 +52,13 @@ cargo build --release -p cortex-cli
%install %install
install -Dm755 target/release/cortex %{buildroot}%{_bindir}/cortex install -Dm755 target/release/cortex %{buildroot}%{_bindir}/cortex
install -Dm644 data/cortex.service %{buildroot}%{_unitdir}/cortex.service install -Dm644 data/cortex.service %{buildroot}%{_unitdir}/cortex.service
install -Dm644 data/cortex-sysusers.conf %{buildroot}%{_sysusersdir}/cortex.conf
install -dm750 %{buildroot}%{_sysconfdir}/cortex install -dm750 %{buildroot}%{_sysconfdir}/cortex
install -Dm640 cortex.example.toml %{buildroot}%{_sysconfdir}/cortex/cortex.toml install -Dm640 cortex.example.toml %{buildroot}%{_sysconfdir}/cortex/cortex.toml
install -Dm640 models.example.toml %{buildroot}%{_sysconfdir}/cortex/models.toml install -Dm640 models.example.toml %{buildroot}%{_sysconfdir}/cortex/models.toml
%pre %pre
getent group cortex >/dev/null || groupadd -r cortex %sysusers_create_compat %{_builddir}/%{name}-%{version}/data/cortex-sysusers.conf
getent passwd cortex >/dev/null || useradd -r -g cortex -d /var/lib/cortex -s /sbin/nologin cortex
%post %post
%systemd_post cortex.service %systemd_post cortex.service
@@ -63,6 +74,7 @@ getent passwd cortex >/dev/null || useradd -r -g cortex -d /var/lib/cortex -s /s
%doc README.md %doc README.md
%{_bindir}/cortex %{_bindir}/cortex
%{_unitdir}/cortex.service %{_unitdir}/cortex.service
%{_sysusersdir}/cortex.conf
%dir %attr(750,root,cortex) %{_sysconfdir}/cortex %dir %attr(750,root,cortex) %{_sysconfdir}/cortex
%config(noreplace) %attr(640,root,cortex) %{_sysconfdir}/cortex/cortex.toml %config(noreplace) %attr(640,root,cortex) %{_sysconfdir}/cortex/cortex.toml
%config(noreplace) %attr(640,root,cortex) %{_sysconfdir}/cortex/models.toml %config(noreplace) %attr(640,root,cortex) %{_sysconfdir}/cortex/models.toml

View File

@@ -0,0 +1,3 @@
g cortex - -
u cortex - "Cortex inference cluster" /var/lib/cortex /sbin/nologin
m cortex cortex

View File

@@ -1,5 +1,5 @@
Name: neuron Name: neuron
Version: 0.1.0 Version: 0.1.2
Release: 1%{?dist} Release: 1%{?dist}
Summary: Per-node GPU discovery and harness management daemon for cortex Summary: Per-node GPU discovery and harness management daemon for cortex
@@ -13,9 +13,20 @@ ExclusiveArch: x86_64
BuildRequires: rust >= 1.85 BuildRequires: rust >= 1.85
BuildRequires: cargo BuildRequires: cargo
BuildRequires: gcc BuildRequires: gcc
BuildRequires: gcc-c++
BuildRequires: cmake
BuildRequires: perl-interpreter
BuildRequires: pkgconfig(openssl)
BuildRequires: systemd-rpm-macros BuildRequires: systemd-rpm-macros
Requires(pre): shadow-utils Requires(pre): shadow-utils
Requires: systemd
# rpm's sysusers provides-generator only emits versioned user(cortex) when
# the u-line has GECOS/home/shell fields. %attr(,,cortex) in %files emits
# an unversioned Requires: user(cortex), so we provide it explicitly.
Provides: user(cortex)
Provides: group(cortex)
%description %description
Neuron is a per-node daemon for cortex inference clusters. It discovers Neuron is a per-node daemon for cortex inference clusters. It discovers
@@ -40,12 +51,12 @@ cargo build --release -p neuron
%install %install
install -Dm755 target/release/neuron %{buildroot}%{_bindir}/neuron install -Dm755 target/release/neuron %{buildroot}%{_bindir}/neuron
install -Dm644 data/neuron.service %{buildroot}%{_unitdir}/neuron.service install -Dm644 data/neuron.service %{buildroot}%{_unitdir}/neuron.service
install -Dm644 data/cortex-sysusers.conf %{buildroot}%{_sysusersdir}/neuron.conf
install -dm750 %{buildroot}%{_sysconfdir}/cortex install -dm750 %{buildroot}%{_sysconfdir}/cortex
install -Dm640 neuron.example.toml %{buildroot}%{_sysconfdir}/cortex/neuron.toml install -Dm640 neuron.example.toml %{buildroot}%{_sysconfdir}/cortex/neuron.toml
%pre %pre
getent group cortex >/dev/null || groupadd -r cortex %sysusers_create_compat %{_builddir}/%{name}-%{version}/data/cortex-sysusers.conf
getent passwd cortex >/dev/null || useradd -r -g cortex -d /var/lib/cortex -s /sbin/nologin cortex
%post %post
%systemd_post neuron.service %systemd_post neuron.service
@@ -61,6 +72,7 @@ getent passwd cortex >/dev/null || useradd -r -g cortex -d /var/lib/cortex -s /s
%doc README.md %doc README.md
%{_bindir}/neuron %{_bindir}/neuron
%{_unitdir}/neuron.service %{_unitdir}/neuron.service
%{_sysusersdir}/neuron.conf
%dir %attr(750,root,cortex) %{_sysconfdir}/cortex %dir %attr(750,root,cortex) %{_sysconfdir}/cortex
%config(noreplace) %attr(640,root,cortex) %{_sysconfdir}/cortex/neuron.toml %config(noreplace) %attr(640,root,cortex) %{_sysconfdir}/cortex/neuron.toml