diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..616bc5a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: ci +on: + push: + branches: + - main + - master + pull_request: + branches: + - main + - master +jobs: + test: + name: test + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + rust: [1.84.0, stable] + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Install Rust + uses: hecrj/setup-rust-action@v2 + with: + rust-version: ${{ matrix.rust }} + - if: matrix.rust == 'stable' + run: rustup component add clippy + - if: matrix.rust == 'stable' + run: cargo clippy -- -D warnings + - run: cargo build --verbose --locked + - run: cargo test --verbose diff --git a/.github/workflows/pkg.yml b/.github/workflows/pkg.yml new file mode 100644 index 0000000..542edbc --- /dev/null +++ b/.github/workflows/pkg.yml @@ -0,0 +1,36 @@ +name: Packaging + +on: + push: + branches: + - main + - master + tags: + - v* + + # Triggering on PRs and arbitrary branch pushes is not enabled because most of the time only the CI build should be + # triggered, not the packaging build. In cases where you want to test changes to this workflow this trigger enables + # you to manually invoke this workflow on an arbitrary branch as needed. + workflow_dispatch: + +jobs: + package: + # See: https://github.com/NLnetLabs/ploutos + uses: NLnetLabs/ploutos/.github/workflows/pkg-rust.yml@v7 + secrets: + DOCKER_HUB_ID: ${{ vars.DOCKER_HUB_ID }} + DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }} + with: + cross_build_args: + cross_max_wait_mins: 20 + + docker_org: nlnetlabs + docker_repo: rrdpit + docker_build_rules: pkg/rules/docker-images-to-build.yml + docker_sanity_check_command: rrdpit --version + + package_build_rules: pkg/rules/packages-to-build.yml + package_test_rules: pkg/rules/packages-to-test.yml + package_test_scripts_path: pkg/test-scripts/test-.sh + + deb_extra_build_packages: libssl-dev diff --git a/Cargo.lock b/Cargo.lock index 5a85900..ffb98a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,9 +100,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.9" +version = "1.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" +checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2" dependencies = [ "shlex", ] @@ -115,18 +115,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.26" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.26" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", diff --git a/Cargo.toml b/Cargo.toml index 043b7bf..ff651a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,46 @@ hex = "^0.3" ring = "^0.17" uuid = { version = "^0.7", features = ["v4"] } xml-rs = "0.8.25" + +# ------------------------------------------------------------------------------ +# START DEBIAN PACKAGING +# +# Configurations for the cargo-deb cargo plugin which builds Debian packages in +# target/debian/ when invoked with: cargo deb. +# +# TODO: +# - Build packages with GH Actions +# - Add man page? +# - Add changelog +# +# NOTE: +# - There is a single binary only (no daemon yet) + +[package.metadata.deb] +name = "rrdpit" +priority = "optional" +section = "net" +extended-description-file = "pkg/debian/description.txt" +license-file = ["LICENSE", "0"] +depends = "" +maintainer-scripts = "pkg/debian/" +changelog = "target/debian/changelog" # this will be generated by the pkg workflow +copyright = "Copyright (c) 2025, NLnet Labs. All rights reserved." +assets = [["target/release/rrdpit", "/usr/bin/rrdpit", "755"]] + +# List target variants +[package.metadata.deb.variants.ubuntu-focal] + +[package.metadata.deb.variants.ubuntu-jammy] + +[package.metadata.deb.variants.ubuntu-noble] + +[package.metadata.deb.variants.debian-buster] + +[package.metadata.deb.variants.debian-bullseye] + +[package.metadata.deb.variants.debian-bookworm] + +# END DEBIAN PACKAGING +# ------------------------------------------------------------------------------ + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f5d7378 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,172 @@ +# This is a multi-stage Dockerfile, with a selectable first stage. With this +# approach we get: +# +# 1. Separation of dependencies needed to build our app in the 'build' stage +# and those needed to run our app in the 'final' stage, as we don't want +# the build-time dependencies to be included in the final Docker image. +# +# 2. Support for either building our app for the architecture of the base +# image using MODE=build (the default) or for externally built app +# binaries (e.g. cross-compiled) using MODE=copy. +# +# In total there are four stages consisting of: +# - Two possible first stages: 'build' or 'copy'. +# - A special 'source' stage which selects either 'build' or 'copy' as the +# source of binaries to be used by ... +# - The 'final' stage. + + +### +### ARG DEFINITIONS ########################################################### +### + +# This section defines arguments that can be overriden on the command line +# when invoking `docker build` using the argument form: +# +# `--build-arg =`. + +# MODE +# ==== +# Supported values: build (default), copy +# +# By default this Dockerfile will build our app from sources. If the sources +# have already been (cross) compiled by some external process and you wish to +# use the resulting binaries from that process, then: +# +# 1. Create a directory on the host called 'dockerbin/$TARGETPLATFORM' +# containing the already compiled app binaries (where $TARGETPLATFORM +# is a special variable set by Docker BuiltKit). +# 2. Supply arguments `--build-arg MODE=copy` to `docker build`. +ARG MODE=build + + +# BASE_IMG +# ======== +# +# Only used when MODE=build. +ARG BASE_IMG=alpine:3.21 + + +# CARGO_ARGS +# ========== +# +# Only used when MODE=build. +# +# This ARG can be used to control the features enabled when compiling the app +# or other compilation settings as necessary. +ARG CARGO_ARGS + + +### +### BUILD STAGES ############################################################## +### + + +# ----------------------------------------------------------------------------- +# Docker stage: build +# ----------------------------------------------------------------------------- +# +# Builds our app binaries from sources. +FROM ${BASE_IMG} AS build +ARG CARGO_ARGS + +RUN apk add --no-cache rust cargo openssl-dev + +WORKDIR /tmp/build +COPY . . + +# `CARGO_HTTP_MULTIPLEXING` forces Cargo to use HTTP/1.1 without pipelining +# instead of HTTP/2 with multiplexing. This seems to help with various +# "spurious network error" warnings when Cargo attempts to fetch from crates.io +# when building this image on Docker Hub and GitHub Actions build machines. +# +# `cargo install` is used instead of `cargo build` because it places just the +# binaries we need into a predictable output directory. We can't control this +# with arguments to cargo build as `--out-dir` is unstable and contentious and +# `--target-dir` still requires us to know which profile and target the +# binaries were built for. By using `cargo install` we can also avoid needing +# to hard-code the set of binary names to copy so that if we add or remove +# built binaries in future this will "just work". Note that `--root /tmp/out` +# actually causes the binaries to be placed in `/tmp/out/bin/`. `cargo install` +# will create the output directory for us. +RUN CARGO_HTTP_MULTIPLEXING=false cargo install \ + --locked \ + --path . \ + --root /tmp/out/ \ + ${CARGO_ARGS} + + +# ----------------------------------------------------------------------------- +# Docker stage: copy +# ----------------------------------------------------------------------------- +# Only used when MODE=copy. +# +# Copy binaries from the host directory 'dockerbin/$TARGETPLATFORM' directory +# into this build stage to the same predictable location that binaries would be +# in if MODE were 'build'. +# +# Requires that `docker build` be invoked with variable `DOCKER_BUILDKIT=1` set +# in the environment. This is necessary so that Docker will skip the unused +# 'build' stage and so that the magic $TARGETPLATFORM ARG will be set for us. +FROM ${BASE_IMG} AS copy +ARG TARGETPLATFORM +ONBUILD COPY dockerbin/$TARGETPLATFORM /tmp/out/bin/ + + +# ----------------------------------------------------------------------------- +# Docker stage: source +# ----------------------------------------------------------------------------- +# This is a "magic" build stage that "labels" a chosen prior build stage as the +# one that the build stage after this one should copy application binaries +# from. It also causes the ONBUILD COPY command from the 'copy' stage to be run +# if needed. Finally, we ensure binaries have the executable flag set because +# when copied in from outside they may not have the flag set, especially if +# they were uploaded as a GH actions artifact then downloaded again which +# causes file permissions to be lost. +# See: https://github.com/actions/upload-artifact#permission-loss +FROM ${MODE} AS source +RUN chmod a+x /tmp/out/bin/* + + +# ----------------------------------------------------------------------------- +# Docker stage: final +# ----------------------------------------------------------------------------- +# Create an image containing just the binaries, configs & scripts needed to run +# our app, and not the things needed to build it. +# +# The previous build stage from which binaries are copied is controlled by the +# MODE ARG (see above). +FROM ${BASE_IMG} AS final + +# Copy binaries from the 'source' build stage into the image we are building +COPY --from=source /tmp/out/bin/* /usr/local/bin/ + +# Build variables for uid and guid of user to run container +ARG RUN_USER=rrdpit +ARG RUN_USER_UID=1012 +ARG RUN_USER_GID=1012 + +# Install required runtime dependencies +RUN apk add --no-cache bash libgcc openssl tini tzdata util-linux + +# Create the user and group to run the application as +RUN addgroup -g ${RUN_USER_GID} ${RUN_USER} && \ + adduser -D -u ${RUN_USER_UID} -G ${RUN_USER} ${RUN_USER} + +# Create the data directories and create a volume for them +VOLUME /data +RUN mkdir -p /data/source /data/target && \ + chown -R ${RUN_USER_UID}:${RUN_USER_GID} /data + +# Install a Docker entrypoint script that will be executed when the container +# runs +COPY docker/entrypoint.sh /opt/ +RUN chown ${RUN_USER}: /opt/entrypoint.sh + +# Switch to our applications user +USER ${RUN_USER} + +# Use Tini to ensure that our application responds to CTRL-C when run in the +# foreground without the Docker argument "--init" (which is actually another +# way of activating Tini, but cannot be enabled from inside the Docker image). +ENTRYPOINT ["/sbin/tini", "--", "/opt/entrypoint.sh"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..5bd30ac --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# run rrdpit with variable interpolation +set -e +DATA="${DATA:-/data}" +SOURCE_DIR="${SOURCE_DIR:-$DATA/source}" +TARGET_DIR="${TARGET_DIR:-$DATA/target}" +RSYNC_URI="${RSYNC_URI:-rsync://example.org/test/}" +HTTPS_URI="${HTTPS_URI:-https://example.org/}" + +exec /usr/local/bin/rrdpit \ + --source ${SOURCE_DIR} \ + --target ${TARGET_DIR} \ + --rsync ${RSYNC_URI} \ + --https ${HTTPS_URI} \ + "$@" diff --git a/pkg/debian/description.txt b/pkg/debian/description.txt new file mode 100644 index 0000000..c545d0a --- /dev/null +++ b/pkg/debian/description.txt @@ -0,0 +1,7 @@ +"rrdpit" is a small little tool that can be pointed at a directory on your +system, and produce RPKI RRDP (RFC 8182) notification, snapshot, and +delta files. You will need to use an http server of your preferred +flavour to deliver these files to the world. + +Read more here: +https://github.com/NLnetLabs/rrdpit/ diff --git a/pkg/debian/postinst b/pkg/debian/postinst new file mode 100644 index 0000000..3dcf55a --- /dev/null +++ b/pkg/debian/postinst @@ -0,0 +1,18 @@ +#!/bin/sh +set -e + +KRILL_HOME="/var/lib/rrdpit/" +KRILL_USER="rrdpit" + +create_user() { + if id ${KRILL_USER} > /dev/null 2>&1; then return; fi + adduser --system --home "${KRILL_HOME}" --group ${KRILL_USER} +} + +case "$1" in +configure) + create_user + ;; +esac + +#DEBHELPER# diff --git a/pkg/rules/docker-images-to-build.yml b/pkg/rules/docker-images-to-build.yml new file mode 100644 index 0000000..7e55db9 --- /dev/null +++ b/pkg/rules/docker-images-to-build.yml @@ -0,0 +1,21 @@ +# See: https://github.com/NLnetLabs/ploutos/blob/main/docs/docker_packaging.md#docker-build-rules +--- +include: + - platform: 'linux/amd64' + shortname: 'amd64' + mode: 'build' + + - platform: 'linux/arm/v6' + shortname: 'armv6' + crosstarget: 'arm-unknown-linux-musleabihf' + mode: 'copy' + + - platform: 'linux/arm/v7' + shortname: 'armv7' + crosstarget: 'armv7-unknown-linux-musleabihf' + mode: 'copy' + + - platform: 'linux/arm64' + shortname: 'arm64' + crosstarget: 'aarch64-unknown-linux-musl' + mode: 'copy' diff --git a/pkg/rules/packages-to-build.yml b/pkg/rules/packages-to-build.yml new file mode 100644 index 0000000..89e2a3c --- /dev/null +++ b/pkg/rules/packages-to-build.yml @@ -0,0 +1,12 @@ +--- +pkg: + - "rrdpit" +image: + - "ubuntu:focal" # ubuntu/20.04 + - "ubuntu:jammy" # ubuntu/22.04 + - "ubuntu:noble" # ubuntu/24.04 + - "debian:buster" # debian/10 + - "debian:bullseye" # debian/11 + - "debian:bookworm" # debian/12 +target: + - "x86_64" diff --git a/pkg/rules/packages-to-test.yml b/pkg/rules/packages-to-test.yml new file mode 100644 index 0000000..87bcc37 --- /dev/null +++ b/pkg/rules/packages-to-test.yml @@ -0,0 +1,14 @@ +--- +pkg: + - "rrdpit" +image: + - "ubuntu:focal" # ubuntu/20.04 + - "ubuntu:jammy" # ubuntu/22.04 + - "ubuntu:noble" # ubuntu/24.04 + - "debian:buster" # debian/10 + - "debian:bullseye" # debian/11 + - "debian:bookworm" # debian/12 +mode: + - "fresh-install" +target: + - "x86_64" diff --git a/pkg/test-scripts/test-rrdpit.sh b/pkg/test-scripts/test-rrdpit.sh new file mode 100755 index 0000000..fed85f0 --- /dev/null +++ b/pkg/test-scripts/test-rrdpit.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -eo pipefail +set -x + +case $1 in + post-install) + echo -e "\nRRDPIT VERSION:" + rrdpit --version + ;; + + post-upgrade) + ;; +esac \ No newline at end of file