diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml
index 72c9c14cdb..d3dda80ee9 100644
--- a/.buildkite/pipeline.yml
+++ b/.buildkite/pipeline.yml
@@ -26,10 +26,11 @@ steps:
- exit_status: 255 # Forced agent shutdown
timeout_in_minutes: 10
- wait
- - label: "Package :debian: :openwrt:"
+ - label: "Package :debian: :openwrt: :rpm:"
command: |
make dist-deb BFLAGS="--file_name_version=${SCION_VERSION}"
make dist-openwrt BFLAGS="--file_name_version=${SCION_VERSION}"
+ make dist-rpm BFLAGS="--file_name_version=${SCION_VERSION}"
artifact_paths:
- "installables/scion_*.tar.gz"
plugins:
@@ -42,6 +43,7 @@ steps:
tar -chaf scion_${SCION_VERSION}_deb_i386.tar.gz *_${SCION_VERSION}_i386.deb
tar -chaf scion_${SCION_VERSION}_deb_armel.tar.gz *_${SCION_VERSION}_armel.deb
tar -chaf scion_${SCION_VERSION}_openwrt_x86_64.tar.gz *_${SCION_VERSION}_x86_64.ipk
+ tar -chaf scion_${SCION_VERSION}_rpm_x86_64.tar.gz *_${SCION_VERSION}_x86_64.rpm
popd
ls installables
post-artifact: |
@@ -53,6 +55,8 @@ steps:
- armel
#### Packages :openwrt:
- x86_64
+ #### Packages :rpm:
+ - x86_64
EOF
key: dist
retry: *automatic-retry
diff --git a/Makefile b/Makefile
index 52aa4a9acc..8a43295c70 100644
--- a/Makefile
+++ b/Makefile
@@ -31,6 +31,12 @@ dist-openwrt-testing:
@ mkdir -p installables
@ cd installables ; ln -sfv ../bazel-out/*/bin/dist/*.ipk .
+dist-rpm:
+ bazel build //dist:rpm_all $(BFLAGS)
+ @ # These artefacts have unique names but varied locations. Link them somewhere convenient.
+ @ mkdir -p installables
+ @ cd installables ; ln -sfv ../bazel-out/*/bin/dist/*.rpm .
+
# all: performs the code-generation steps and then builds; the generated code
# is git controlled, and therefore this is only necessary when changing the
# sources for the code generation.
diff --git a/WORKSPACE b/WORKSPACE
index 29a41c1913..1bd17a7c1a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -182,6 +182,7 @@ oci_pull(
repository = "library/debian",
)
+# Debian packaging
http_archive(
name = "rules_debian_packages",
sha256 = "0ae3b332f9d894e57693ce900769d2bd1b693e1f5ea1d9cdd82fa4479c93bcc8",
@@ -206,6 +207,14 @@ load("@tester_debian10_packages//:packages.bzl", tester_debian_packages_install_
tester_debian_packages_install_deps()
+# RPM packaging
+load("@rules_pkg//toolchains/rpm:rpmbuild_configure.bzl", "find_system_rpmbuild")
+
+find_system_rpmbuild(
+ name = "rules_pkg_rpmbuild",
+ verbose = False,
+)
+
# Buf CLI
http_archive(
name = "buf",
diff --git a/dist/BUILD.bazel b/dist/BUILD.bazel
index 5422f1fae0..221e68638b 100644
--- a/dist/BUILD.bazel
+++ b/dist/BUILD.bazel
@@ -1,5 +1,4 @@
-load(":package.bzl", "scion_pkg_deb")
-load(":package.bzl", "scion_pkg_ipk")
+load(":package.bzl", "scion_pkg_deb", "scion_pkg_ipk", "scion_pkg_rpm")
load(":platform.bzl", "multiplatform_filegroup")
load(":git_version.bzl", "git_version")
@@ -10,6 +9,10 @@ DEB_PLATFORMS = [
"@io_bazel_rules_go//go/toolchain:linux_arm",
]
+RPM_PLATFORMS = [
+ "@io_bazel_rules_go//go/toolchain:linux_amd64",
+]
+
# TODO(jice@scion.org):
# For now only a single openwrt platform can be in this list. If we allow several, they get
# built in parallel, which breaks on non-reentrant openwrt makefiles. For a single platform
@@ -221,3 +224,116 @@ multiplatform_filegroup(
target_platforms = OPENWRT_PLATFORMS,
visibility = ["//dist:__subpackages__"],
)
+
+scion_pkg_rpm(
+ name = "router_rpm",
+ depends = [
+ "/sbin/adduser",
+ ],
+ description = "SCION inter-domain network architecture border router",
+ executables = {
+ "//router/cmd/router:router": "scion-router",
+ },
+ package = "scion-router",
+ postinst = "rpm/scion.postinst",
+ systemds = ["systemd/scion-router@.service"],
+ version_file = ":git_version",
+)
+
+scion_pkg_rpm(
+ name = "control_rpm",
+ configs = [],
+ depends = [
+ "/sbin/adduser",
+ "scion-dispatcher",
+ ],
+ description = "SCION inter-domain network architecture control service",
+ executables = {
+ "//control/cmd/control:control": "scion-control",
+ },
+ package = "scion-control",
+ systemds = ["systemd/scion-control@.service"],
+ version_file = ":git_version",
+)
+
+scion_pkg_rpm(
+ name = "dispatcher_rpm",
+ configs = ["conffiles/dispatcher.toml"],
+ depends = [
+ "/sbin/adduser",
+ ],
+ description = "SCION dispatcher",
+ executables = {
+ "//dispatcher/cmd/dispatcher:dispatcher": "scion-dispatcher",
+ },
+ package = "scion-dispatcher",
+ postinst = "rpm/scion.postinst",
+ systemds = ["systemd/scion-dispatcher.service"],
+ version_file = ":git_version",
+)
+
+scion_pkg_rpm(
+ name = "daemon_rpm",
+ configs = ["conffiles/daemon.toml"],
+ depends = [
+ "/sbin/adduser",
+ ],
+ description = "SCION daemon",
+ executables = {
+ "//daemon/cmd/daemon:daemon": "scion-daemon",
+ },
+ package = "scion-daemon",
+ postinst = "rpm/scion.postinst",
+ systemds = ["systemd/scion-daemon.service"],
+ version_file = ":git_version",
+)
+
+scion_pkg_rpm(
+ name = "gateway_rpm",
+ configs = [
+ "conffiles/gateway.json",
+ "conffiles/gateway.toml",
+ ],
+ depends = [
+ "/sbin/adduser",
+ "scion-dispatcher",
+ "scion-daemon",
+ ],
+ description = "SCION-IP Gateway",
+ executables = {
+ "//gateway/cmd/gateway:gateway": "scion-ip-gateway",
+ },
+ package = "scion-ip-gateway",
+ systemds = ["systemd/scion-ip-gateway.service"],
+ version_file = ":git_version",
+)
+
+scion_pkg_rpm(
+ name = "tools_rpm",
+ depends = [
+ "/sbin/adduser",
+ "scion-dispatcher",
+ "scion-daemon",
+ ],
+ description = "SCION tools",
+ executables = {
+ "//scion/cmd/scion:scion": "scion",
+ "//scion-pki/cmd/scion-pki:scion-pki": "scion-pki",
+ },
+ package = "scion-tools",
+ version_file = ":git_version",
+)
+
+multiplatform_filegroup(
+ name = "rpm",
+ srcs = [
+ "control_rpm",
+ "daemon_rpm",
+ "dispatcher_rpm",
+ "gateway_rpm",
+ "router_rpm",
+ "tools_rpm",
+ ],
+ target_platforms = RPM_PLATFORMS,
+ visibility = ["//dist:__subpackages__"],
+)
diff --git a/dist/package.bzl b/dist/package.bzl
index 88c9e1544e..95a4c48f67 100644
--- a/dist/package.bzl
+++ b/dist/package.bzl
@@ -1,6 +1,8 @@
load("@rules_pkg//pkg:pkg.bzl", "pkg_deb", "pkg_tar")
+load("@rules_pkg//pkg:rpm.bzl", "pkg_rpm")
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
load("@rules_pkg//pkg:providers.bzl", "PackageVariablesInfo")
+load("@rules_pkg//pkg:mappings.bzl", "pkg_attributes", "pkg_files")
SCION_PKG_HOMEPAGE = "https://github.com/scionproto/scion"
SCION_PKG_MAINTAINER = "SCION Contributors"
@@ -30,6 +32,106 @@ name_elems = rule(
},
)
+def scion_pkg_rpm(name, package, executables = {}, systemds = [], configs = [], **kwargs):
+ """
+ The package content, the _data_ arg for the pkg_rpm rule, is assembled from:
+
+ - executables: Map Label (the executable) -> string, the basename of the executable in the package
+ Executables are installed to /usr/bin/
+ - systemds: List[string], the systemd unit files to be installed in /lib/systemd/system/
+ - configs: List[string], the configuration files to be installed in /etc/scion/
+
+ The values for the following pkg_rpm args are set to a default value:
+ - url
+ - license
+ - architecture, set based on the platform.
+
+ The caller needs to set:
+ - package: the name of the package (e.g. scion-router)
+ - description: one-liner
+ - version/version_file: One can use the label ":git_version"
+ and any of the optional control directives.
+
+ The version string gets edited to meet rpm requirements: dashes are replaced with ^.
+ """
+
+ kwargs.setdefault("url", SCION_PKG_HOMEPAGE)
+ kwargs.setdefault("license", SCION_PKG_LICENSE)
+
+ if "architecture" not in kwargs:
+ kwargs["architecture"] = select({
+ "@platforms//cpu:x86_64": "x86_64",
+ "@platforms//cpu:x86_32": "i386",
+ "@platforms//cpu:aarch64": "arm64",
+ "@platforms//cpu:armv7": "armel",
+ "@platforms//cpu:s390x": "s390x",
+ # Note: some rules_go toolchains don't (currently) seem to map (cleanly) to @platforms//cpu.
+ # "@platforms//cpu:ppc": "ppc64",
+ # "@platforms//cpu:ppc64le": "ppc64le",
+ })
+
+ name_elems(
+ name = "package_file_naming_" + name,
+ file_name_version = "@@//:file_name_version",
+ architecture = kwargs["architecture"],
+ package = package,
+ )
+
+ # Note that our "executables" parameter is a dictionary label->file_name; exactly what pkg_files
+ # wants for its "renames" param.
+ pkg_files(name = "%s_configs" % name, prefix = "/etc/scion/", srcs = configs)
+ pkg_files(name = "%s_systemds" % name, prefix = "/lib/systemd/system/", srcs = systemds)
+ pkg_files(
+ name = "%s_execs" % name,
+ prefix = "/usr/bin/",
+ srcs = executables.keys(),
+ attributes = pkg_attributes(mode = "0755"),
+ renames = executables,
+ )
+
+ if kwargs.get("version_file"):
+ native.genrule(
+ name = "%s_version" % name,
+ srcs = [kwargs["version_file"]],
+ outs = ["%s_version_file" % name],
+ cmd = "sed 's/-/^/g' < $< > $@",
+ )
+ kwargs.pop("version_file")
+ elif kwargs.get("version"):
+ native.genrule(
+ name = "%s_version" % name,
+ srcs = [],
+ outs = ["%s_version_file" % name],
+ cmd = "echo \"%s\" | sed 's/-/^/g' > $@" % kwargs["version"],
+ )
+ kwargs.pop("version")
+
+ # Use the same attributes as scion_pkg_deb, in view of may-be simplifying BUILD.bazel later.
+ deps = kwargs.get("depends")
+ if deps:
+ kwargs.pop("depends")
+ else:
+ deps = []
+
+ post = kwargs.get("postinst")
+ if post:
+ kwargs.pop("postinst")
+
+ pkg_rpm(
+ name = name,
+ summary = kwargs["description"],
+ srcs = ["%s_configs" % name, "%s_systemds" % name, "%s_execs" % name],
+ target_compatible_with = ["@platforms//os:linux"],
+ package_file_name = "{package}_{file_name_version}_{architecture}.rpm",
+ package_variables = ":package_file_naming_" + name,
+ package_name = package,
+ release = "%autorelease",
+ version_file = ":%s_version" % name,
+ requires = deps,
+ post_scriptlet_file = post,
+ **kwargs
+ )
+
def scion_pkg_deb(name, executables = {}, systemds = [], configs = [], **kwargs):
"""
The package content, the _data_ arg for the pkg_deb rule, is assembled from:
diff --git a/dist/rpm/scion.postinst b/dist/rpm/scion.postinst
new file mode 100644
index 0000000000..3dbae88ebb
--- /dev/null
+++ b/dist/rpm/scion.postinst
@@ -0,0 +1,13 @@
+#!/bin/bash
+set -e
+
+# Create system user/group
+groupadd --system -f scion
+useradd --system --gid scion -M -s /sbin/nologin scion >& /dev/null || true
+
+# Create configuration directory
+mkdir /etc/scion/ >& /dev/null || true
+mkdir /var/lib/scion/ >& /dev/null || true
+chown scion:scion /etc/scion/ /var/lib/scion
+
+
diff --git a/dist/test/BUILD.bazel b/dist/test/BUILD.bazel
index c2cd205e12..1d0f820c76 100644
--- a/dist/test/BUILD.bazel
+++ b/dist/test/BUILD.bazel
@@ -29,3 +29,19 @@ sh_test(
"integration",
],
)
+
+sh_test(
+ name = "rpm_test",
+ srcs = ["rpm_test.sh"],
+ data = [
+ "Dockerfile.rpm",
+ "//dist:rpm",
+ ],
+ env = {
+ "SCION_RPM_PACKAGES": "$(locations //dist:rpm)",
+ },
+ tags = [
+ "exclusive",
+ "integration",
+ ],
+)
diff --git a/dist/test/Dockerfile.rpm b/dist/test/Dockerfile.rpm
new file mode 100644
index 0000000000..38a696f908
--- /dev/null
+++ b/dist/test/Dockerfile.rpm
@@ -0,0 +1,8 @@
+FROM fedora:40
+
+RUN dnf --assumeyes install systemd
+
+ENV container docker
+
+# Only "boot" a minimal system with journald and nothing else
+CMD ["/usr/lib/systemd/systemd", "--unit", "systemd-journald.service"]
diff --git a/dist/test/rpm_test.sh b/dist/test/rpm_test.sh
new file mode 100755
index 0000000000..fb62359081
--- /dev/null
+++ b/dist/test/rpm_test.sh
@@ -0,0 +1,144 @@
+#!/bin/bash
+
+set -euo pipefail
+
+set -x
+if [ -n ${SCION_RPM_PACKAGES+x} ]; then
+ # Invocation from bazel:
+ # SCION_RPM_PACKAGES is a space-separated list of filenames of (symlinks to) .rpm packages.
+ # Below we mount this stuff into a docker container, which won't work with symlinks.
+ # Copy everything into a tmp directory.
+ tmpdir="${TEST_TMPDIR?}"
+ cp ${SCION_RPM_PACKAGES} "${tmpdir}"
+ SCION_RPM_PACKAGES_DIR=$(realpath ${tmpdir})
+else
+ SCION_ROOT=$(realpath $(dirname $0)/../../)
+ SCION_RPM_PACKAGES_DIR=${SCION_RPM_PACKAGES_DIR:-${SCION_ROOT}/rpm}
+fi
+DEBUG=${DEBUG:-0}
+set +x
+
+function cleanup {
+ docker container rm -f fedora-systemd || true
+ docker image rm --no-prune fedora-systemd || true
+}
+cleanup
+
+if [ "$DEBUG" == 0 ]; then # if DEBUG: keep container fedora-systemd running after test
+ trap cleanup EXIT
+fi
+
+# Note: specify absolute path to Dockerfile because docker will not follow bazel's symlinks.
+# Luckily we don't need anything else in this directory.
+docker build -t fedora-systemd -f $(realpath dist/test/Dockerfile.rpm) dist/test
+
+# Start container with systemd in PID 1.
+# Note: there are ways to avoid --privileged, but its unreliable and appears to depend on the host system
+docker run -d --rm --name fedora-systemd -t \
+ --tmpfs /tmp \
+ --tmpfs /run \
+ --tmpfs /run/lock \
+ --tmpfs /run/shm \
+ -v $SCION_RPM_PACKAGES_DIR:/rpm \
+ --privileged \
+ fedora-systemd:latest
+
+docker exec -i fedora-systemd /bin/bash <<'EOF'
+ set -xeuo pipefail
+ arch=$(arch)
+
+ # check that the rpm files are all here (avoid cryptic error from rpm)
+ stat /rpm/scion-{router,control,dispatcher,daemon,ip-gateway,tools}_*_${arch}.rpm > /dev/null
+
+ # router
+ rpm -iv /rpm/scion-router_*_${arch}.rpm
+ cat > /etc/scion/br-1.toml < /etc/scion/topology.json < /etc/scion/cs-1.toml << INNER_EOF
+ general.id = "cs-1"
+ general.config_dir = "/etc/scion"
+ trust_db.connection = "/var/lib/scion/cs-1.trust.db"
+ beacon_db.connection = "/var/lib/scion/cs-1.beacon.db"
+ path_db.connection = "/var/lib/scion/cs-1.path.db"
+INNER_EOF
+ systemctl enable --now scion-control@cs-1.service
+ sleep 1
+ systemctl status scion-control@cs-1.service
+ systemctl is-active scion-dispatcher.service # should be re-started as dependency
+ systemctl stop scion-control@cs-1.service scion-dispatcher.service
+
+ # daemon
+ systemctl enable --now scion-daemon.service
+ systemctl status scion-daemon.service
+ sleep 1
+ systemctl is-active scion-dispatcher.service # should be re-started as dependency
+ # ... tools (continued)
+ # now with the daemon running, we can test `scion` e.g. to inspect our local SCION address
+ scion address
+ systemctl stop scion-daemon.service scion-dispatcher.service
+
+ # scion-ip-gateway
+ rpm -i /rpm/scion-ip-gateway_*_${arch}.rpm
+ systemctl start scion-ip-gateway.service
+ sleep 1
+ # Note: this starts even if the default sig.json is not a valid configuration
+ systemctl status scion-ip-gateway.service
+ systemctl is-active scion-dispatcher.service scion-daemon.service # should be re-started as dependency
+ # Note: the gateway will only create a tunnel device once a session with a
+ # neighbor is up. This is too complicated to arrange in this test. Instead,
+ # we just ensure that the process has the required capabilities to do so.
+ getpcaps $(pidof scion-ip-gateway) | tee /dev/stderr | grep -q "cap_net_admin" || echo "missing capability 'cap_net_admin'"
+
+ echo "Success!"
+EOF