Skip to content

Commit 6f23950

Browse files
committed
Added python build for rpm
1 parent 9d21077 commit 6f23950

3 files changed

Lines changed: 346 additions & 0 deletions

File tree

docs/build-and-podman.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,16 @@ which listens on port `80` by default; the bundled systemd unit grants only
440440
`CAP_NET_BIND_SERVICE` so the service can bind `80/443` while still running as
441441
the unprivileged `fluxheim` user.
442442

443+
For local binary RPM smoke builds, use the containerized helper:
444+
445+
```bash
446+
scripts/build_fluxheim_rpm.py 1.0.0 --target opensuse-tumbleweed
447+
scripts/build_fluxheim_rpm.py 1.0.0 native --target fedora-44
448+
```
449+
450+
This helper is intended for installation testing on RPM-based hosts. The
451+
release-grade RPM source of truth remains `packaging/rpm/fluxheim.spec`.
452+
443453
## Codex And Rootless Podman
444454

445455
When running Codex in a sandbox, include the rootless Podman runtime directories

scripts/build_fluxheim_rpm.py

Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
#!/usr/bin/env python3
2+
"""Build a binary Fluxheim RPM inside a disposable Linux container.
3+
4+
This is a convenience helper for local package testing. The release-grade RPM
5+
source of truth remains packaging/rpm/fluxheim.spec.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
import argparse
11+
import re
12+
import shlex
13+
import shutil
14+
import subprocess
15+
import sys
16+
import tempfile
17+
from pathlib import Path
18+
from typing import Sequence
19+
20+
21+
OS_CONTAINERS = {
22+
"fedora-44": "registry.fedoraproject.org/fedora:44",
23+
"opensuse-tumbleweed": "registry.opensuse.org/opensuse/tumbleweed:latest",
24+
"opensuse-leap-15": "registry.opensuse.org/opensuse/leap:15.6",
25+
"opensuse-leap-16": "registry.opensuse.org/opensuse/leap:16.0",
26+
"ubi-9": "registry.access.redhat.com/ubi9/ubi:latest",
27+
"ubi-10": "registry.access.redhat.com/ubi10/ubi:latest",
28+
}
29+
30+
SAFE_VERSION_TAG = re.compile(r"^(latest|v?[0-9]+(?:\.[0-9A-Za-z_+]+)*)$")
31+
32+
33+
def run_command(command: Sequence[str], cwd: Path | None = None) -> None:
34+
print(f"Executing: {shlex.join(command)}")
35+
subprocess.run(command, cwd=cwd, check=True)
36+
37+
38+
def get_container_tool(preferred: str | None) -> str:
39+
if preferred:
40+
if shutil.which(preferred):
41+
return preferred
42+
raise SystemExit(f"error: requested container tool is not installed: {preferred}")
43+
for tool in ("podman", "docker"):
44+
if shutil.which(tool):
45+
return tool
46+
raise SystemExit("error: neither podman nor docker is installed")
47+
48+
49+
def parse_args() -> argparse.Namespace:
50+
parser = argparse.ArgumentParser(
51+
description="Build a binary Fluxheim RPM in a container."
52+
)
53+
parser.add_argument(
54+
"version_tag",
55+
nargs="?",
56+
default="latest",
57+
help="Fluxheim tag version, for example 0.5.0, v0.5.0, or latest",
58+
)
59+
parser.add_argument(
60+
"build_type",
61+
nargs="?",
62+
default="generic",
63+
choices=["generic", "native"],
64+
help="Use 'native' to build with RUSTFLAGS='-C target-cpu=native'.",
65+
)
66+
parser.add_argument(
67+
"--target",
68+
choices=sorted(OS_CONTAINERS),
69+
help="Build target container. If omitted, the script prompts interactively.",
70+
)
71+
parser.add_argument(
72+
"--container-tool",
73+
choices=["podman", "docker"],
74+
help="Container runtime to use. Defaults to podman, then docker.",
75+
)
76+
parser.add_argument(
77+
"--list-targets",
78+
action="store_true",
79+
help="List supported build targets and exit.",
80+
)
81+
return parser.parse_args()
82+
83+
84+
def print_targets() -> None:
85+
for name, image in OS_CONTAINERS.items():
86+
print(f"{name}: {image}")
87+
88+
89+
def choose_target(target: str | None) -> tuple[str, str]:
90+
if target:
91+
return target, OS_CONTAINERS[target]
92+
93+
print("Available build targets:")
94+
target_names = sorted(OS_CONTAINERS)
95+
for index, name in enumerate(target_names, 1):
96+
print(f"{index}. {name} ({OS_CONTAINERS[name]})")
97+
98+
while True:
99+
choice = input("Select the target to build for (number): ").strip()
100+
try:
101+
selected = target_names[int(choice) - 1]
102+
return selected, OS_CONTAINERS[selected]
103+
except (ValueError, IndexError):
104+
print("Please enter a valid target number.")
105+
106+
107+
def validate_version_tag(version_tag: str) -> None:
108+
if not SAFE_VERSION_TAG.fullmatch(version_tag):
109+
raise SystemExit(
110+
"error: version_tag must be 'latest' or a simple release version such as 1.0.0 or v1.0.0"
111+
)
112+
113+
114+
def generate_build_script(script_path: Path) -> None:
115+
script_content = r"""#!/usr/bin/env bash
116+
set -euo pipefail
117+
118+
TAG="$1"
119+
BUILD_TYPE="$2"
120+
WORKSPACE="/workspace"
121+
REPO_URL="https://github.com/valkyoth/fluxheim.git"
122+
123+
echo "--- Installing dependencies ---"
124+
if command -v zypper >/dev/null 2>&1; then
125+
zypper --non-interactive in ca-certificates curl git-core rpm-build gcc gcc-c++ make cmake pkgconf-pkg-config perl tar gzip
126+
elif command -v dnf >/dev/null 2>&1; then
127+
dnf install -y ca-certificates curl git rpm-build gcc gcc-c++ make cmake pkgconf-pkg-config perl tar gzip
128+
elif command -v microdnf >/dev/null 2>&1; then
129+
microdnf install -y ca-certificates curl git rpm-build gcc gcc-c++ make cmake pkgconf-pkg-config perl tar gzip
130+
elif command -v apt-get >/dev/null 2>&1; then
131+
apt-get update
132+
apt-get install -y ca-certificates curl git rpm gcc g++ make cmake pkg-config perl tar gzip
133+
else
134+
echo "unsupported package manager" >&2
135+
exit 1
136+
fi
137+
138+
echo "--- Installing Rust via rustup ---"
139+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
140+
export PATH="${HOME}/.cargo/bin:${PATH}"
141+
142+
echo "--- Cloning Fluxheim ---"
143+
cd "$WORKSPACE"
144+
git clone --depth 1 "$REPO_URL" repo
145+
cd repo
146+
147+
if [ "$TAG" != "latest" ]; then
148+
git fetch --depth 1 origin "refs/tags/${TAG}:refs/tags/${TAG}" || true
149+
if [[ "$TAG" != v* ]]; then
150+
git fetch --depth 1 origin "refs/tags/v${TAG}:refs/tags/v${TAG}" || true
151+
git checkout "v${TAG}" 2>/dev/null || git checkout "$TAG"
152+
else
153+
git checkout "$TAG"
154+
fi
155+
fi
156+
157+
echo "--- Building Fluxheim ---"
158+
RPM_SUFFIX=""
159+
if [ "$BUILD_TYPE" = "native" ]; then
160+
echo "using target-cpu=native"
161+
export RUSTFLAGS="-C target-cpu=native"
162+
RPM_SUFFIX=".native"
163+
fi
164+
cargo build --release --locked
165+
166+
echo "--- Staging files ---"
167+
INSTALL_ROOT="${WORKSPACE}/install_root"
168+
mkdir -p \
169+
"${INSTALL_ROOT}/usr/bin" \
170+
"${INSTALL_ROOT}/usr/lib/tmpfiles.d" \
171+
"${INSTALL_ROOT}/usr/lib/sysusers.d" \
172+
"${INSTALL_ROOT}/usr/lib/systemd/system" \
173+
"${INSTALL_ROOT}/usr/share/doc/fluxheim" \
174+
"${INSTALL_ROOT}/usr/share/licenses/fluxheim" \
175+
"${INSTALL_ROOT}/etc/fluxheim/conf.d" \
176+
"${INSTALL_ROOT}/etc/fluxheim/tls" \
177+
"${INSTALL_ROOT}/etc/sysconfig" \
178+
"${INSTALL_ROOT}/srv/fluxheim" \
179+
"${INSTALL_ROOT}/var/lib/fluxheim" \
180+
"${INSTALL_ROOT}/var/cache/fluxheim" \
181+
"${INSTALL_ROOT}/var/log/fluxheim"
182+
183+
install -Dm0755 target/release/fluxheim "${INSTALL_ROOT}/usr/bin/fluxheim"
184+
install -Dm0644 packaging/default/fluxheim.toml "${INSTALL_ROOT}/etc/fluxheim/fluxheim.toml"
185+
install -Dm0644 packaging/default/index.html "${INSTALL_ROOT}/srv/fluxheim/index.html"
186+
install -Dm0644 packaging/rpm/fluxheim.tmpfiles "${INSTALL_ROOT}/usr/lib/tmpfiles.d/fluxheim.conf"
187+
install -Dm0644 packaging/systemd/fluxheim.service "${INSTALL_ROOT}/usr/lib/systemd/system/fluxheim.service"
188+
install -Dm0644 packaging/systemd/fluxheim.env "${INSTALL_ROOT}/etc/sysconfig/fluxheim"
189+
install -Dm0644 packaging/systemd/fluxheim.sysusers "${INSTALL_ROOT}/usr/lib/sysusers.d/fluxheim.conf"
190+
install -Dm0644 LICENSE "${INSTALL_ROOT}/usr/share/licenses/fluxheim/LICENSE"
191+
for doc in README.md CHANGELOG.md ROADMAP.md SECURITY.md; do
192+
if [ -f "$doc" ]; then
193+
install -Dm0644 "$doc" "${INSTALL_ROOT}/usr/share/doc/fluxheim/$doc"
194+
fi
195+
done
196+
197+
echo "--- Generating binary RPM spec ---"
198+
RPMBUILD_ROOT="${WORKSPACE}/rpmbuild"
199+
mkdir -p "${RPMBUILD_ROOT}/"{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
200+
201+
VERSION="${TAG#v}"
202+
if [ "$VERSION" = "latest" ]; then
203+
VERSION="0.0.0.git$(date -u +%Y%m%d)"
204+
fi
205+
206+
SPEC_PATH="${RPMBUILD_ROOT}/SPECS/fluxheim.spec"
207+
cat > "$SPEC_PATH" <<SPEC_EOF
208+
Name: fluxheim
209+
Version: ${VERSION}
210+
Release: 1${RPM_SUFFIX}%{?dist}
211+
Summary: Memory-safe edge server and reverse proxy built on Pingora
212+
License: EUPL-1.2
213+
URL: https://github.com/valkyoth/fluxheim
214+
215+
%description
216+
Fluxheim is a memory-safe edge server and reverse proxy built on Pingora.
217+
This binary RPM was generated by scripts/build_fluxheim_rpm.py for local
218+
installation testing.
219+
220+
%pre
221+
getent group fluxheim >/dev/null || groupadd -r fluxheim
222+
getent passwd fluxheim >/dev/null || \\
223+
useradd -r -g fluxheim -d /var/lib/fluxheim \\
224+
-s /sbin/nologin -c "Fluxheim service user" fluxheim
225+
exit 0
226+
227+
%post
228+
if command -v systemd-sysusers >/dev/null 2>&1; then
229+
systemd-sysusers /usr/lib/sysusers.d/fluxheim.conf || :
230+
fi
231+
if command -v systemd-tmpfiles >/dev/null 2>&1; then
232+
systemd-tmpfiles --create /usr/lib/tmpfiles.d/fluxheim.conf || :
233+
else
234+
chown fluxheim:fluxheim /var/lib/fluxheim /var/cache/fluxheim /var/log/fluxheim /srv/fluxheim /srv/fluxheim/index.html || :
235+
chmod 0750 /var/lib/fluxheim /var/cache/fluxheim /var/log/fluxheim || :
236+
chmod 0755 /srv/fluxheim || :
237+
chmod 0644 /srv/fluxheim/index.html || :
238+
fi
239+
if command -v systemctl >/dev/null 2>&1; then
240+
systemctl daemon-reload || :
241+
fi
242+
243+
%postun
244+
if command -v systemctl >/dev/null 2>&1; then
245+
systemctl daemon-reload || :
246+
fi
247+
248+
%prep
249+
250+
%build
251+
252+
%install
253+
rm -rf %{buildroot}
254+
mkdir -p %{buildroot}
255+
cp -a "${INSTALL_ROOT}/." %{buildroot}/
256+
257+
%files
258+
%license /usr/share/licenses/fluxheim/LICENSE
259+
%doc /usr/share/doc/fluxheim/*
260+
/usr/bin/fluxheim
261+
/usr/lib/tmpfiles.d/fluxheim.conf
262+
/usr/lib/sysusers.d/fluxheim.conf
263+
/usr/lib/systemd/system/fluxheim.service
264+
%dir /etc/fluxheim
265+
%dir /etc/fluxheim/conf.d
266+
%dir /etc/fluxheim/tls
267+
%config(noreplace) /etc/fluxheim/fluxheim.toml
268+
%config(noreplace) /etc/sysconfig/fluxheim
269+
%dir /var/lib/fluxheim
270+
%dir /var/cache/fluxheim
271+
%dir /var/log/fluxheim
272+
%dir /srv/fluxheim
273+
%config(noreplace) /srv/fluxheim/index.html
274+
SPEC_EOF
275+
276+
echo "--- Building RPM ---"
277+
rpmbuild --define "_topdir ${RPMBUILD_ROOT}" --buildroot "${RPMBUILD_ROOT}/BUILDROOT" -bb "$SPEC_PATH"
278+
279+
echo "--- Copying RPM out ---"
280+
find "${RPMBUILD_ROOT}/RPMS" -name "*.rpm" -exec cp {} "$WORKSPACE/" \;
281+
"""
282+
script_path.write_text(script_content, encoding="utf-8")
283+
284+
285+
def main() -> int:
286+
args = parse_args()
287+
if args.list_targets:
288+
print_targets()
289+
return 0
290+
291+
validate_version_tag(args.version_tag)
292+
target_name, container_image = choose_target(args.target)
293+
container_tool = get_container_tool(args.container_tool)
294+
295+
print(f"Selected target: {target_name} ({container_image})")
296+
print(f"Version: {args.version_tag}")
297+
print(f"Build type: {args.build_type}")
298+
print(f"Container tool: {container_tool}")
299+
300+
output_dir = Path.cwd().resolve()
301+
with tempfile.TemporaryDirectory(prefix="fluxheim-rpm-") as tmp:
302+
work_dir = Path(tmp)
303+
build_script_path = work_dir / "build_in_container.sh"
304+
generate_build_script(build_script_path)
305+
build_script_path.chmod(0o755)
306+
307+
run_command(
308+
[
309+
container_tool,
310+
"run",
311+
"--rm",
312+
"-v",
313+
f"{work_dir}:/workspace:Z",
314+
container_image,
315+
"bash",
316+
"/workspace/build_in_container.sh",
317+
args.version_tag,
318+
args.build_type,
319+
]
320+
)
321+
322+
rpms = sorted(work_dir.glob("*.rpm"))
323+
if not rpms:
324+
raise SystemExit("error: no RPM files were produced")
325+
326+
for rpm in rpms:
327+
destination = output_dir / rpm.name
328+
shutil.copy2(rpm, destination)
329+
print(f"Created: {destination}")
330+
331+
return 0
332+
333+
334+
if __name__ == "__main__":
335+
raise SystemExit(main())

scripts/checks.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ cargo test --no-default-features --features proxy,tls-rustls,acme
9494
cargo test --no-default-features --features proxy,web,tls-rustls,privacy-mode
9595
cargo check --no-default-features --features proxy,tls
9696
cargo check --no-default-features --features proxy,tls-rustls
97+
python3 -m py_compile scripts/prepare-server.py scripts/build_fluxheim_rpm.py
9798
scripts/validate-tls-backends.sh check
9899
cargo run --quiet -- --check-config --config examples/fluxheim.toml >/dev/null
99100
cargo run --quiet -- --check-config --config examples/admin.toml >/dev/null

0 commit comments

Comments
 (0)