From 093e6a7c14f5364e357ad2ef3b0989256aa1db32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Tue, 29 Sep 2026 13:37:57 +0200 Subject: [PATCH] first commit --- .gitattributes | 5 + .gitignore | 10 ++ Makefile | 4 + README.md | 66 ++++++++++ builder.env.example | 39 ++++++ builder/debian/build-freebsd-cross.sh | 27 ++++ builder/debian/hourly-build.sh | 86 +++++++++++++ builder/debian/install-cross.sh | 22 ++++ builder/debian/install.sh | 29 +++++ builder/debian/rustproxy-opnsense.cron | 3 + builder/freebsd/bootstrap.sh | 35 +++++ builder/freebsd/build-release.sh | 108 ++++++++++++++++ builder/freebsd/create-repo-key.sh | 21 +++ ci/build-release.sh | 120 ++++++++++++++++++ deploy/opnsense/install-repo.sh | 36 ++++++ deploy/opnsense/remove-repo.sh | 6 + deploy/opnsense/update.sh | 5 + docs/ARCHITECTURE.md | 46 +++++++ docs/FIRST_RUN.md | 109 ++++++++++++++++ .../www/rustproxy/+POST_INSTALL.pre | 13 ++ .../www/rustproxy/+PRE_DEINSTALL.pre | 4 + opnsense-plugin/www/rustproxy/Makefile | 9 ++ opnsense-plugin/www/rustproxy/pkg-descr | 4 + .../www/rustproxy/src/etc/rc.d/rustproxy | 45 +++++++ .../src/etc/rc.syshook.d/10-rustproxy.start | 4 + .../RustProxy/Api/ServiceController.php | 18 +++ .../RustProxy/Api/SettingsController.php | 11 ++ .../OPNsense/RustProxy/IndexController.php | 12 ++ .../OPNsense/RustProxy/forms/general.xml | 8 ++ .../app/models/OPNsense/RustProxy/ACL/ACL.xml | 10 ++ .../models/OPNsense/RustProxy/Menu/Menu.xml | 7 + .../models/OPNsense/RustProxy/RustProxy.php | 9 ++ .../models/OPNsense/RustProxy/RustProxy.xml | 13 ++ .../app/views/OPNsense/RustProxy/index.volt | 43 +++++++ .../src/opnsense/scripts/rustproxy/service.sh | 45 +++++++ .../conf/actions.d/actions_rustproxy.conf | 35 +++++ .../templates/OPNsense/RustProxy/+TARGETS | 1 + .../OPNsense/RustProxy/rustproxy.conf | 1 + scripts/check.sh | 43 +++++++ 39 files changed, 1112 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 builder.env.example create mode 100755 builder/debian/build-freebsd-cross.sh create mode 100755 builder/debian/hourly-build.sh create mode 100755 builder/debian/install-cross.sh create mode 100755 builder/debian/install.sh create mode 100644 builder/debian/rustproxy-opnsense.cron create mode 100755 builder/freebsd/bootstrap.sh create mode 100755 builder/freebsd/build-release.sh create mode 100755 builder/freebsd/create-repo-key.sh create mode 100755 ci/build-release.sh create mode 100755 deploy/opnsense/install-repo.sh create mode 100755 deploy/opnsense/remove-repo.sh create mode 100755 deploy/opnsense/update.sh create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/FIRST_RUN.md create mode 100644 opnsense-plugin/www/rustproxy/+POST_INSTALL.pre create mode 100644 opnsense-plugin/www/rustproxy/+PRE_DEINSTALL.pre create mode 100644 opnsense-plugin/www/rustproxy/Makefile create mode 100644 opnsense-plugin/www/rustproxy/pkg-descr create mode 100755 opnsense-plugin/www/rustproxy/src/etc/rc.d/rustproxy create mode 100755 opnsense-plugin/www/rustproxy/src/etc/rc.syshook.d/10-rustproxy.start create mode 100644 opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/controllers/OPNsense/RustProxy/Api/ServiceController.php create mode 100644 opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/controllers/OPNsense/RustProxy/Api/SettingsController.php create mode 100644 opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/controllers/OPNsense/RustProxy/IndexController.php create mode 100644 opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/controllers/OPNsense/RustProxy/forms/general.xml create mode 100644 opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/ACL/ACL.xml create mode 100644 opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/Menu/Menu.xml create mode 100644 opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/RustProxy.php create mode 100644 opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/RustProxy.xml create mode 100644 opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/views/OPNsense/RustProxy/index.volt create mode 100755 opnsense-plugin/www/rustproxy/src/opnsense/scripts/rustproxy/service.sh create mode 100644 opnsense-plugin/www/rustproxy/src/opnsense/service/conf/actions.d/actions_rustproxy.conf create mode 100644 opnsense-plugin/www/rustproxy/src/opnsense/service/templates/OPNsense/RustProxy/+TARGETS create mode 100644 opnsense-plugin/www/rustproxy/src/opnsense/service/templates/OPNsense/RustProxy/rustproxy.conf create mode 100755 scripts/check.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4065553 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +*.sh text eol=lf +*.php text eol=lf +*.xml text eol=lf +*.volt text eol=lf +Makefile text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c49bf4e --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/dist/ +/.build/ +/.cache/ +/app/ +/opnsense-plugin/www/rustproxy/work/ +/opnsense-plugin/www/rustproxy/src/sbin/rust-proxy +*.log +*.key +.env +builder.env diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..09306a2 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +.PHONY: check + +check: + ./scripts/check.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e3a939 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# RustProxy OPNsense packaging repository + +This repository contains only the OPNsense plugin and build/release automation. The RustProxy application source is **not vendored here**. The Debian scheduler downloads it dynamically from: + +`https://git.linuxiarz.pl/gru/RustProxy` + +## What is included + +- `opnsense-plugin/www/rustproxy/` - OPNsense plugin source. +- `builder/debian/` - hourly scheduler and optional cross-build helper. +- `builder/freebsd/` - native FreeBSD release builder and repository signing helper. +- `ci/build-release.sh` - Debian-to-FreeBSD orchestration and publishing. +- `deploy/opnsense/` - custom package repository install/update helpers. +- `docs/` - setup and architecture notes. + +## Source/update flow + +```text +Packaging repository RustProxy upstream +(plugin + scripts) https://git.linuxiarz.pl/gru/RustProxy + | | + +------------------+-------------------+ + v + Debian cron, hourly + | + detects either commit change + | + v + FreeBSD 15.1 amd64 builder + | + cargo test + cargo build + | + OPNsense make package + | + v + signed pkg repository + | + v + OPNsense +``` + +By default `APP_REPO_REF=HEAD`, so every cron run follows the current default branch of the RustProxy repository. You can instead pin a branch/tag/ref in `/etc/rustproxy-opnsense-builder.env`. + +A release rebuild is triggered when either the packaging repository commit or the RustProxy upstream commit changes. The package version is read at build time from the dynamically downloaded `Cargo.toml`. + +## Recommended release build + +Use the Debian VM only as scheduler/orchestrator and a FreeBSD 15.1 amd64 VM as compiler/packager. The included Docker/cross path (`BUILD_MODE=cross-binary`) is for development validation only and does not publish production `.pkg` files. + +## Quick validation + +```sh +./scripts/check.sh +``` + +## Quick setup + +Read `docs/FIRST_RUN.md`, copy `builder.env.example` to `/etc/rustproxy-opnsense-builder.env` on Debian and fill in your packaging-repository, SSH and publication settings. `APP_REPO_URL` is already set to your RustProxy repository. + +## OPNsense UI scope + +The plugin currently exposes only Enabled / Disabled. RustProxy is supervised by FreeBSD `daemon(8)`, runs as user `rustproxy`, and uses `/var/db/rustproxy` as its private data directory. + +## License + +RustProxy's `Cargo.toml` currently does not declare a license and the supplied source snapshot did not contain LICENSE/COPYING, so the plugin manifest remains `UNKNOWN`. Set the correct license before public redistribution. diff --git a/builder.env.example b/builder.env.example new file mode 100644 index 0000000..f77274e --- /dev/null +++ b/builder.env.example @@ -0,0 +1,39 @@ +# This repository: OPNsense plugin + packaging/build scripts. +# Push this generated repository to your own Git server and set its URL here. +PACKAGING_REPO_URL=git@github.com:YOUR_ORG/rustproxy-opnsense.git +PACKAGING_REPO_REF=main +PACKAGING_WORKDIR=/srv/rustproxy-opnsense/packaging + +# RustProxy application source. This is fetched independently on every cron run. +APP_REPO_URL=https://git.linuxiarz.pl/gru/RustProxy +# HEAD follows the default branch of the upstream repository. You can pin a branch, +# tag or other fetchable ref instead, for example: main, stable, v0.21.0. +APP_REPO_REF=HEAD +APP_WORKDIR=/srv/rustproxy-opnsense/source/RustProxy + +# Local Debian state. +STATE_DIR=/var/lib/rustproxy-opnsense-builder + +# Release builder. Use a FreeBSD 15.1 amd64 host for production packages. +BUILD_MODE=native-freebsd +FREEBSD_HOST=freebsd-builder.example.net +FREEBSD_USER=builder +FREEBSD_SSH_KEY=/root/.ssh/rustproxy_freebsd_builder +FREEBSD_REMOTE_DIR=/usr/local/rustproxy-build/project + +# OPNsense branch/ABI targeted by the plugin package. +OPNSENSE_PLUGINS_REF=stable/26.7 +OPNSENSE_ABI=26.7 + +# Paths on the FreeBSD builder. Leave empty for an unsigned development repo. +PKG_REPO_SIGN_KEY=/usr/local/rustproxy-build/keys/rustproxy-repo.key +PKG_REPO_PUBLIC_KEY=/usr/local/rustproxy-build/keys/rustproxy-repo.pub + +# Publish dist/ over SSH/rsync. This should be the document root for RustProxy packages. +PUBLISH_TARGET=repo-user@packages.example.net:/srv/www/rustproxy/ +PUBLISH_SSH_KEY=/root/.ssh/rustproxy_package_publisher + +# Optional automatic upgrade of explicitly listed TEST firewalls after a successful publish. +# Disabled by default. Space-separated SSH targets, for example: root@fw-test-01 root@fw-test-02 +AUTO_DEPLOY_HOSTS= +AUTO_DEPLOY_SSH_KEY=/root/.ssh/rustproxy_opnsense_test diff --git a/builder/debian/build-freebsd-cross.sh b/builder/debian/build-freebsd-cross.sh new file mode 100755 index 0000000..9ee9226 --- /dev/null +++ b/builder/debian/build-freebsd-cross.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +SOURCE_DIR="${RUSTPROXY_SOURCE_DIR:-$ROOT/app}" +TARGET="x86_64-unknown-freebsd" + +if [[ ! -f "$SOURCE_DIR/Cargo.toml" ]]; then + echo "RustProxy source not found. Set RUSTPROXY_SOURCE_DIR to the dynamically fetched checkout." >&2 + exit 1 +fi +if ! command -v docker >/dev/null 2>&1; then + echo "Docker is required for cross mode." >&2 + exit 1 +fi +if ! command -v cross >/dev/null 2>&1; then + echo "cross is not installed. Install it with: cargo install cross --locked" >&2 + exit 1 +fi + +cd "$SOURCE_DIR" +cross build --locked --release --target "$TARGET" +mkdir -p "$ROOT/dist/cross/$TARGET" +cp "target/$TARGET/release/rust-proxy" "$ROOT/dist/cross/$TARGET/rust-proxy" + +echo "Cross-built binary: $ROOT/dist/cross/$TARGET/rust-proxy" +echo "Do not publish this as the production OPNsense package without validation on the target FreeBSD ABI." diff --git a/builder/debian/hourly-build.sh b/builder/debian/hourly-build.sh new file mode 100755 index 0000000..a12a52b --- /dev/null +++ b/builder/debian/hourly-build.sh @@ -0,0 +1,86 @@ +#!/bin/bash +set -euo pipefail + +CONFIG_FILE="${RUSTPROXY_BUILDER_CONFIG:-/etc/rustproxy-opnsense-builder.env}" +if [[ ! -r "$CONFIG_FILE" ]]; then + echo "Missing builder config: $CONFIG_FILE" >&2 + exit 1 +fi +# shellcheck disable=SC1090 +source "$CONFIG_FILE" + +: "${PACKAGING_REPO_URL:?PACKAGING_REPO_URL is required}" +: "${PACKAGING_REPO_REF:=main}" +: "${PACKAGING_WORKDIR:=/srv/rustproxy-opnsense/packaging}" +: "${APP_REPO_URL:=https://git.linuxiarz.pl/gru/RustProxy}" +: "${APP_REPO_REF:=HEAD}" +: "${APP_WORKDIR:=/srv/rustproxy-opnsense/source/RustProxy}" +: "${STATE_DIR:=/var/lib/rustproxy-opnsense-builder}" + +mkdir -p "$STATE_DIR" "$(dirname "$PACKAGING_WORKDIR")" "$(dirname "$APP_WORKDIR")" +exec 9>"$STATE_DIR/build.lock" +if ! flock -n 9; then + echo "Another build is running; exiting." + exit 0 +fi + +SYNC_COMMIT="" +sync_checkout() +{ + local dir="$1" + local url="$2" + local ref="$3" + + if [[ ! -d "$dir/.git" ]]; then + rm -rf "$dir" + mkdir -p "$dir" + git -C "$dir" init + git -C "$dir" remote add origin "$url" + else + git -C "$dir" remote set-url origin "$url" + fi + + echo "Fetching $url ($ref)" >&2 + git -C "$dir" fetch --prune --tags origin "$ref" >&2 + SYNC_COMMIT="$(git -C "$dir" rev-parse FETCH_HEAD)" + git -C "$dir" checkout --detach -f "$SYNC_COMMIT" >&2 + git -C "$dir" reset --hard "$SYNC_COMMIT" >&2 + git -C "$dir" clean -ffd >&2 +} + +# Refresh the packaging repository first so the newest build logic is used. +sync_checkout "$PACKAGING_WORKDIR" "$PACKAGING_REPO_URL" "$PACKAGING_REPO_REF" +packaging_commit="$SYNC_COMMIT" + +# RustProxy is intentionally NOT vendored in the packaging repository. +# Fetch the application directly from its upstream repository every run. +sync_checkout "$APP_WORKDIR" "$APP_REPO_URL" "$APP_REPO_REF" +app_commit="$SYNC_COMMIT" +app_revision="$(git -C "$APP_WORKDIR" rev-list --count "$app_commit")" + +if [[ ! -f "$APP_WORKDIR/Cargo.toml" ]]; then + echo "Upstream checkout does not contain Cargo.toml: $APP_WORKDIR" >&2 + exit 1 +fi + +state_file="$STATE_DIR/last-successful-build" +current_state="packaging=$packaging_commit app=$app_commit" +last_state="$(cat "$state_file" 2>/dev/null || true)" + +if [[ "$current_state" == "$last_state" ]]; then + echo "No changes. packaging=$packaging_commit app=$app_commit" + exit 0 +fi + +echo "Building RustProxy commit $app_commit (revision $app_revision)" +echo "Using packaging commit $packaging_commit" + +RUSTPROXY_SOURCE_DIR="$APP_WORKDIR" \ +RUSTPROXY_SOURCE_URL="$APP_REPO_URL" \ +RUSTPROXY_SOURCE_REF="$APP_REPO_REF" \ +RUSTPROXY_SOURCE_COMMIT="$app_commit" \ +RUSTPROXY_SOURCE_REVISION="$app_revision" \ + "$PACKAGING_WORKDIR/ci/build-release.sh" + +printf '%s\n' "$current_state" > "$state_file" +echo "Build completed: $current_state" diff --git a/builder/debian/install-cross.sh b/builder/debian/install-cross.sh new file mode 100755 index 0000000..1e5ae3e --- /dev/null +++ b/builder/debian/install-cross.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -euo pipefail + +if [[ ${EUID} -ne 0 ]]; then + echo "Run as root" >&2 + exit 1 +fi + +apt-get update +DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io rustup build-essential pkg-config +systemctl enable --now docker + +TARGET_USER="${SUDO_USER:-root}" +if [[ "$TARGET_USER" == "root" ]]; then + rustup default stable + cargo install cross --locked +else + sudo -u "$TARGET_USER" -H sh -c 'rustup default stable && cargo install cross --locked' +fi + +echo "Docker + rustup + cross installed for $TARGET_USER." +echo "Use BUILD_MODE=cross-binary only for development validation." diff --git a/builder/debian/install.sh b/builder/debian/install.sh new file mode 100755 index 0000000..179ecbb --- /dev/null +++ b/builder/debian/install.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -euo pipefail + +if [[ ${EUID} -ne 0 ]]; then + echo "Run as root" >&2 + exit 1 +fi + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" + +apt-get update +DEBIAN_FRONTEND=noninteractive apt-get install -y \ + ca-certificates cron git openssh-client rsync util-linux curl jq + +install -d -m 0755 /srv/rustproxy-opnsense +install -d -m 0755 /var/lib/rustproxy-opnsense-builder +install -m 0755 "$REPO_ROOT/builder/debian/hourly-build.sh" /usr/local/sbin/rustproxy-opnsense-hourly +install -m 0644 "$REPO_ROOT/builder/debian/rustproxy-opnsense.cron" /etc/cron.d/rustproxy-opnsense + +if [[ ! -e /etc/rustproxy-opnsense-builder.env ]]; then + install -m 0600 "$REPO_ROOT/builder.env.example" /etc/rustproxy-opnsense-builder.env + echo "Created /etc/rustproxy-opnsense-builder.env. Edit it before the first cron run." +fi + +touch /var/log/rustproxy-opnsense-builder.log +chmod 0640 /var/log/rustproxy-opnsense-builder.log +systemctl enable --now cron + +echo "Debian scheduler installed." diff --git a/builder/debian/rustproxy-opnsense.cron b/builder/debian/rustproxy-opnsense.cron new file mode 100644 index 0000000..ce0797c --- /dev/null +++ b/builder/debian/rustproxy-opnsense.cron @@ -0,0 +1,3 @@ +SHELL=/bin/bash +PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +17 * * * * root /usr/local/sbin/rustproxy-opnsense-hourly >> /var/log/rustproxy-opnsense-builder.log 2>&1 diff --git a/builder/freebsd/bootstrap.sh b/builder/freebsd/bootstrap.sh new file mode 100755 index 0000000..772c55c --- /dev/null +++ b/builder/freebsd/bootstrap.sh @@ -0,0 +1,35 @@ +#!/bin/sh +set -eu + +if [ "$(id -u)" -ne 0 ]; then + echo "Run as root" >&2 + exit 1 +fi + +BUILDER_USER="${1:-builder}" +BUILDER_GROUP="$BUILDER_USER" + +ASSUME_ALWAYS_YES=yes pkg bootstrap -f +pkg update +pkg install -y git rust python3 pkgconf cmake gmake perl5 rsync + +if ! pw groupshow "$BUILDER_GROUP" >/dev/null 2>&1; then + pw groupadd "$BUILDER_GROUP" +fi +if ! pw usershow "$BUILDER_USER" >/dev/null 2>&1; then + pw useradd "$BUILDER_USER" -g "$BUILDER_GROUP" -m -s /bin/sh -c "RustProxy package builder" +fi + +install -d -o "$BUILDER_USER" -g "$BUILDER_GROUP" -m 0755 /usr/local/rustproxy-build +install -d -o "$BUILDER_USER" -g "$BUILDER_GROUP" -m 0700 /usr/local/rustproxy-build/keys + +echo "FreeBSD builder dependencies installed." +echo "Builder account: $BUILDER_USER" +echo "Add its SSH public key to /home/$BUILDER_USER/.ssh/authorized_keys." +echo "Then log in as $BUILDER_USER and create the repository signing key." +rust_version="$(rustc --version | awk '{print $2}')" +if [ "$(pkg version -t "$rust_version" 1.88)" = "<" ]; then + echo "RustProxy requires Rust >= 1.88, found $rust_version" >&2 + exit 1 +fi +echo "Rust version OK: $rust_version" diff --git a/builder/freebsd/build-release.sh b/builder/freebsd/build-release.sh new file mode 100755 index 0000000..18e3eb7 --- /dev/null +++ b/builder/freebsd/build-release.sh @@ -0,0 +1,108 @@ +#!/bin/sh +set -eu + +ROOT="${1:-$(pwd)}" +APP_DIR="$ROOT/app" +PLUGIN_SOURCE="$ROOT/opnsense-plugin/www/rustproxy" +DIST_DIR="$ROOT/dist" +CACHE_DIR="${RUSTPROXY_BUILD_CACHE:-$HOME/.cache/rustproxy-opnsense}" +PLUGINS_DIR="$CACHE_DIR/opnsense-plugins" +OPNSENSE_PLUGINS_REF="${OPNSENSE_PLUGINS_REF:-stable/26.7}" +OPNSENSE_ABI="${OPNSENSE_ABI:-26.7}" +SOURCE_REVISION="${SOURCE_REVISION:-0}" +SOURCE_COMMIT="${RUSTPROXY_SOURCE_COMMIT:-unknown}" +SOURCE_URL="${RUSTPROXY_SOURCE_URL:-https://git.linuxiarz.pl/gru/RustProxy}" +SOURCE_REF="${RUSTPROXY_SOURCE_REF:-HEAD}" +RUN_TESTS="${RUN_TESTS:-1}" + +if [ "$(uname -s)" != "FreeBSD" ]; then + echo "This release builder must run on FreeBSD." >&2 + exit 1 +fi +case "$(uname -m)" in + amd64|x86_64) ;; + *) echo "Only amd64/x86_64 is supported by this pipeline." >&2; exit 1 ;; +esac + +for tool in cargo git make pkg rsync; do + command -v "$tool" >/dev/null 2>&1 || { echo "Missing tool: $tool" >&2; exit 1; } +done + +rust_version="$(rustc --version | awk '{print $2}')" +echo "Using rustc $rust_version on $(freebsd-version -ku | tr '\n' ' ')" + +app_version="$(awk -F '"' '/^version = "/ {print $2; exit}' "$APP_DIR/Cargo.toml")" +if [ -z "$app_version" ]; then + echo "Cannot read app version from Cargo.toml" >&2 + exit 1 +fi + +mkdir -p "$CACHE_DIR" +if [ ! -d "$PLUGINS_DIR/.git" ]; then + git clone https://github.com/opnsense/plugins.git "$PLUGINS_DIR" +fi +git -C "$PLUGINS_DIR" fetch --prune origin "$OPNSENSE_PLUGINS_REF" +git -C "$PLUGINS_DIR" checkout -f -B rustproxy-build FETCH_HEAD +git -C "$PLUGINS_DIR" clean -ffd + +cd "$APP_DIR" +if [ "$RUN_TESTS" = "1" ]; then + cargo test --locked --release +fi +cargo build --locked --release + +plugin_dir="$PLUGINS_DIR/www/rustproxy" +rm -rf "$plugin_dir" +cp -R "$PLUGIN_SOURCE" "$plugin_dir" +mkdir -p "$plugin_dir/src/sbin" +install -m 0555 "$APP_DIR/target/release/rust-proxy" "$plugin_dir/src/sbin/rust-proxy" + +cd "$plugin_dir" +make PLUGIN_VERSION="$app_version" PLUGIN_REVISION="$SOURCE_REVISION" PLUGIN_ABI="$OPNSENSE_ABI" package + +package_file="$(find "$plugin_dir/work/pkg" -maxdepth 1 -type f -name 'os-rustproxy-*.pkg' | sort | tail -n 1)" +if [ -z "$package_file" ] || [ ! -f "$package_file" ]; then + echo "Package was not produced." >&2 + exit 1 +fi + +repo_dir="$DIST_DIR/repository/$OPNSENSE_ABI/amd64" +rm -rf "$DIST_DIR" +mkdir -p "$repo_dir" +cp "$package_file" "$repo_dir/" + +if [ -n "${PKG_REPO_SIGN_KEY:-}" ]; then + if [ ! -r "$PKG_REPO_SIGN_KEY" ]; then + echo "Repository signing key is not readable: $PKG_REPO_SIGN_KEY" >&2 + exit 1 + fi + pkg repo "$repo_dir" "$PKG_REPO_SIGN_KEY" +else + echo "WARNING: creating unsigned development repository" >&2 + pkg repo "$repo_dir" +fi + +if [ -n "${PKG_REPO_PUBLIC_KEY:-}" ]; then + if [ ! -r "$PKG_REPO_PUBLIC_KEY" ]; then + echo "Repository public key is not readable: $PKG_REPO_PUBLIC_KEY" >&2 + exit 1 + fi + cp "$PKG_REPO_PUBLIC_KEY" "$DIST_DIR/rustproxy-repo.pub" +fi + +cat > "$DIST_DIR/BUILD-INFO.txt" < "$DIST_DIR/SHA256" +echo "Release artifacts created in $DIST_DIR" diff --git a/builder/freebsd/create-repo-key.sh b/builder/freebsd/create-repo-key.sh new file mode 100755 index 0000000..d03f26a --- /dev/null +++ b/builder/freebsd/create-repo-key.sh @@ -0,0 +1,21 @@ +#!/bin/sh +set -eu + +KEY_DIR="${1:-/usr/local/rustproxy-build/keys}" +KEY="$KEY_DIR/rustproxy-repo.key" +PUB="$KEY_DIR/rustproxy-repo.pub" + +umask 077 +mkdir -p "$KEY_DIR" +if [ -e "$KEY" ] || [ -e "$PUB" ]; then + echo "Key already exists in $KEY_DIR" >&2 + exit 1 +fi + +openssl genrsa -out "$KEY" 2048 +chmod 0400 "$KEY" +openssl rsa -in "$KEY" -out "$PUB" -pubout +chmod 0444 "$PUB" + +echo "Private key: $KEY" +echo "Public key: $PUB" diff --git a/ci/build-release.sh b/ci/build-release.sh new file mode 100755 index 0000000..35b6881 --- /dev/null +++ b/ci/build-release.sh @@ -0,0 +1,120 @@ +#!/bin/bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +CONFIG_FILE="${RUSTPROXY_BUILDER_CONFIG:-/etc/rustproxy-opnsense-builder.env}" +if [[ -r "$CONFIG_FILE" ]]; then + # shellcheck disable=SC1090 + source "$CONFIG_FILE" +fi + +: "${BUILD_MODE:=native-freebsd}" +: "${STATE_DIR:=/var/lib/rustproxy-opnsense-builder}" +: "${OPNSENSE_PLUGINS_REF:=stable/26.7}" +: "${OPNSENSE_ABI:=26.7}" +: "${RUSTPROXY_SOURCE_DIR:?RUSTPROXY_SOURCE_DIR must point to a checked-out RustProxy repository}" +: "${RUSTPROXY_SOURCE_URL:=https://git.linuxiarz.pl/gru/RustProxy}" +: "${RUSTPROXY_SOURCE_REF:=HEAD}" +: "${RUSTPROXY_SOURCE_COMMIT:=$(git -C "$RUSTPROXY_SOURCE_DIR" rev-parse HEAD)}" +: "${RUSTPROXY_SOURCE_REVISION:=$(git -C "$RUSTPROXY_SOURCE_DIR" rev-list --count "$RUSTPROXY_SOURCE_COMMIT")}" + +if [[ ! -f "$RUSTPROXY_SOURCE_DIR/Cargo.toml" ]]; then + echo "RustProxy source directory is invalid: $RUSTPROXY_SOURCE_DIR" >&2 + exit 1 +fi + +revision="$RUSTPROXY_SOURCE_REVISION" + +safe_value() +{ + [[ "$1" =~ ^[A-Za-z0-9_./:@+-]*$ ]] +} + +publish_dist() +{ + if [[ -z "${PUBLISH_TARGET:-}" ]]; then + echo "PUBLISH_TARGET is empty; artifacts remain in $ROOT/dist" + return 0 + fi + local ssh_cmd=(ssh) + if [[ -n "${PUBLISH_SSH_KEY:-}" ]]; then + ssh_cmd+=( -i "$PUBLISH_SSH_KEY" ) + fi + rsync -az --delete-delay -e "${ssh_cmd[*]}" "$ROOT/dist/" "$PUBLISH_TARGET" +} + +auto_deploy_tests() +{ + [[ -n "${AUTO_DEPLOY_HOSTS:-}" ]] || return 0 + for host in $AUTO_DEPLOY_HOSTS; do + echo "Upgrading RustProxy on test firewall: $host" + ssh_args=( -o BatchMode=yes ) + if [[ -n "${AUTO_DEPLOY_SSH_KEY:-}" ]]; then + ssh_args+=( -i "$AUTO_DEPLOY_SSH_KEY" ) + fi + ssh "${ssh_args[@]}" "$host" \ + 'pkg update -f -r RustProxy && pkg install -y -r RustProxy os-rustproxy' + done +} + +case "$BUILD_MODE" in + native-freebsd) + : "${FREEBSD_HOST:?FREEBSD_HOST is required}" + : "${FREEBSD_USER:?FREEBSD_USER is required}" + : "${FREEBSD_REMOTE_DIR:?FREEBSD_REMOTE_DIR is required}" + for value in "$FREEBSD_HOST" "$FREEBSD_USER" "$FREEBSD_REMOTE_DIR" \ + "$OPNSENSE_PLUGINS_REF" "$OPNSENSE_ABI" \ + "${PKG_REPO_SIGN_KEY:-}" "${PKG_REPO_PUBLIC_KEY:-}" \ + "$RUSTPROXY_SOURCE_URL" "$RUSTPROXY_SOURCE_REF" \ + "$RUSTPROXY_SOURCE_COMMIT" "$revision"; do + if ! safe_value "$value"; then + echo "Unsafe character in builder configuration value: $value" >&2 + exit 1 + fi + done + + ssh_args=( -o BatchMode=yes ) + if [[ -n "${FREEBSD_SSH_KEY:-}" ]]; then + ssh_args+=( -i "$FREEBSD_SSH_KEY" ) + fi + remote="${FREEBSD_USER}@${FREEBSD_HOST}" + ssh "${ssh_args[@]}" "$remote" "mkdir -p '$FREEBSD_REMOTE_DIR' '$FREEBSD_REMOTE_DIR/app'" + + # Send packaging/plugin sources separately from the application source. + rsync -az --delete \ + --exclude .git/ --exclude app/ --exclude dist/ --exclude .cache/ \ + -e "ssh ${ssh_args[*]}" \ + "$ROOT/" "$remote:$FREEBSD_REMOTE_DIR/" + + rsync -az --delete \ + --exclude .git/ --exclude target/ \ + -e "ssh ${ssh_args[*]}" \ + "$RUSTPROXY_SOURCE_DIR/" "$remote:$FREEBSD_REMOTE_DIR/app/" + + remote_command="OPNSENSE_PLUGINS_REF=$OPNSENSE_PLUGINS_REF OPNSENSE_ABI=$OPNSENSE_ABI RUSTPROXY_SOURCE_URL=$RUSTPROXY_SOURCE_URL RUSTPROXY_SOURCE_REF=$RUSTPROXY_SOURCE_REF RUSTPROXY_SOURCE_COMMIT=$RUSTPROXY_SOURCE_COMMIT SOURCE_REVISION=$revision" + if [[ -n "${PKG_REPO_SIGN_KEY:-}" ]]; then + remote_command+=" PKG_REPO_SIGN_KEY=$PKG_REPO_SIGN_KEY" + fi + if [[ -n "${PKG_REPO_PUBLIC_KEY:-}" ]]; then + remote_command+=" PKG_REPO_PUBLIC_KEY=$PKG_REPO_PUBLIC_KEY" + fi + remote_command+=" sh '$FREEBSD_REMOTE_DIR/builder/freebsd/build-release.sh' '$FREEBSD_REMOTE_DIR'" + ssh "${ssh_args[@]}" "$remote" "$remote_command" + + rm -rf "$ROOT/dist" + mkdir -p "$ROOT/dist" + rsync -az -e "ssh ${ssh_args[*]}" "$remote:$FREEBSD_REMOTE_DIR/dist/" "$ROOT/dist/" + ;; + cross-binary) + RUSTPROXY_SOURCE_DIR="$RUSTPROXY_SOURCE_DIR" "$ROOT/builder/debian/build-freebsd-cross.sh" + echo "Cross mode builds a binary only and is not accepted as a release package pipeline." + exit 0 + ;; + *) + echo "Unknown BUILD_MODE: $BUILD_MODE" >&2 + exit 1 + ;; +esac + +publish_dist +auto_deploy_tests diff --git a/deploy/opnsense/install-repo.sh b/deploy/opnsense/install-repo.sh new file mode 100755 index 0000000..8a44df1 --- /dev/null +++ b/deploy/opnsense/install-repo.sh @@ -0,0 +1,36 @@ +#!/bin/sh +set -eu + +if [ "$(id -u)" -ne 0 ]; then + echo "Run as root on OPNsense" >&2 + exit 1 +fi + +REPO_URL="${1:-}" +PUBLIC_KEY_FILE="${2:-}" + +if [ -z "$REPO_URL" ] || [ -z "$PUBLIC_KEY_FILE" ]; then + echo "usage: $0 https://packages.example.net/rustproxy/26.7/amd64 /path/to/rustproxy-repo.pub" >&2 + exit 64 +fi +if [ ! -r "$PUBLIC_KEY_FILE" ]; then + echo "Public key not readable: $PUBLIC_KEY_FILE" >&2 + exit 1 +fi + +install -d -m 0755 /usr/local/etc/pkg/repos /usr/local/etc/pkg/keys +install -m 0444 "$PUBLIC_KEY_FILE" /usr/local/etc/pkg/keys/rustproxy-repo.pub + +cat > /usr/local/etc/pkg/repos/RustProxy.conf < RustProxy in OPNsense." diff --git a/deploy/opnsense/remove-repo.sh b/deploy/opnsense/remove-repo.sh new file mode 100755 index 0000000..833f686 --- /dev/null +++ b/deploy/opnsense/remove-repo.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu + +rm -f /usr/local/etc/pkg/repos/RustProxy.conf +rm -f /usr/local/etc/pkg/keys/rustproxy-repo.pub +pkg update -f diff --git a/deploy/opnsense/update.sh b/deploy/opnsense/update.sh new file mode 100755 index 0000000..8bb4687 --- /dev/null +++ b/deploy/opnsense/update.sh @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +pkg update -f -r RustProxy +pkg install -y -r RustProxy os-rustproxy diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..5332cb1 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,46 @@ +# Architecture + +## Release path + +1. Debian cron runs once per hour. +2. It refreshes the packaging repository containing this plugin/build logic. +3. Independently, it fetches `APP_REPO_REF` from `https://git.linuxiarz.pl/gru/RustProxy` into `APP_WORKDIR`. +4. It compares both current commits with the last successful build state. +5. If either commit changed, Debian rsyncs the packaging tree and the RustProxy checkout separately to a FreeBSD 15.1 amd64 builder. +6. FreeBSD reads the application version from the downloaded `Cargo.toml`, runs Cargo tests and a release build. +7. The binary is injected into `opnsense-plugin/www/rustproxy/src/sbin/rust-proxy` inside a clean checkout of the official OPNsense plugins tree. +8. `make package` creates `os-rustproxy-_.pkg`. +9. `pkg repo` creates a signed package repository. +10. Debian downloads `dist/` and optionally publishes it over rsync/SSH. +11. OPNsense consumes only the custom RustProxy package repository. + +The packaging repository deliberately contains no `app/` directory. This prevents stale application code from accidentally being packaged. + +## Upstream selection + +Default configuration: + +```text +APP_REPO_URL=https://git.linuxiarz.pl/gru/RustProxy +APP_REPO_REF=HEAD +``` + +`HEAD` follows the repository's default branch. To freeze releases, set `APP_REPO_REF` to a branch, tag or other fetchable ref. The exact commit used for each package is written to `dist/BUILD-INFO.txt`. + +## Why native FreeBSD for releases + +Rust can cross-compile to `x86_64-unknown-freebsd`, but a release also needs the target ABI and FreeBSD/OPNsense packaging behavior. A native FreeBSD builder makes both the Rust link step and `pkg` metadata/catalog generation match the target platform. The Docker/cross path is intended for fast developer validation only. + +## OPNsense plugin behavior + +The first UI version exposes only `general.enabled`. + +- Enabled: the service controller renders configuration and starts RustProxy through configd. +- Disabled: the service controller stops RustProxy. +- Boot: an OPNsense syshook regenerates configuration and starts the service only when enabled. + +RustProxy runs as the unprivileged `rustproxy` user. Its data is stored in `/var/db/rustproxy` and logs in `/var/log/rustproxy/rustproxy.log`. + +## Ports 80 and 443 + +The MVP does not run RustProxy as root. Keep initial listeners above port 1023. If public 80/443 is required later, prefer an explicit PF redirect from 80/443 to high RustProxy listener ports instead of running the whole proxy as root. diff --git a/docs/FIRST_RUN.md b/docs/FIRST_RUN.md new file mode 100644 index 0000000..c22e492 --- /dev/null +++ b/docs/FIRST_RUN.md @@ -0,0 +1,109 @@ +# First run + +## 1. Push this packaging repository + +Create a repository for the generated OPNsense plugin/build files and push this tree there. RustProxy itself must not be copied into this repository; it will be fetched dynamically from: + +```text +https://git.linuxiarz.pl/gru/RustProxy +``` + +After pushing, set that new repository address as `PACKAGING_REPO_URL` in the Debian configuration. + +## 2. Prepare the FreeBSD release builder + +Use FreeBSD 15.1 amd64. + +```sh +sudo sh builder/freebsd/bootstrap.sh builder +sudo -u builder sh builder/freebsd/create-repo-key.sh +``` + +The bootstrap creates a non-root `builder` account and gives it `/usr/local/rustproxy-build`. + +## 3. Prepare Debian + +From a checkout of this packaging repository: + +```sh +sudo bash builder/debian/install.sh +sudo editor /etc/rustproxy-opnsense-builder.env +``` + +At minimum configure: + +```text +PACKAGING_REPO_URL= +PACKAGING_REPO_REF=main + +APP_REPO_URL=https://git.linuxiarz.pl/gru/RustProxy +APP_REPO_REF=HEAD + +FREEBSD_HOST= +FREEBSD_USER=builder +FREEBSD_REMOTE_DIR=/usr/local/rustproxy-build/project +``` + +`APP_REPO_REF=HEAD` means "build the current default branch". If you want controlled releases, change it to a specific branch or tag. + +Run the first cycle manually: + +```sh +sudo /usr/local/sbin/rustproxy-opnsense-hourly +``` + +Cron runs at minute 17 of every hour. Each run fetches the RustProxy upstream. A new build starts when either the upstream RustProxy commit or this packaging repository commit changed. A failed build does not update `last-successful-build`, so it is retried on the next run. + +## 4. Build state + +Debian stores checkouts separately: + +```text +/srv/rustproxy-opnsense/packaging/ # this repository +/srv/rustproxy-opnsense/source/RustProxy # dynamically fetched app +/var/lib/rustproxy-opnsense-builder/ # lock + last successful commit pair +``` + +The FreeBSD build machine receives the two trees separately. Its temporary `$FREEBSD_REMOTE_DIR/app/` always comes from the upstream checkout, never from the packaging repository. + +## 5. Publish `dist/` + +Set `PUBLISH_TARGET`. The published layout is similar to: + +```text +/rustproxy/ + BUILD-INFO.txt + SHA256 + rustproxy-repo.pub + repository/ + 26.7/ + amd64/ + os-rustproxy-_.pkg + meta.conf + data.pkg + packagesite.pkg +``` + +`BUILD-INFO.txt` contains the RustProxy source URL, ref and exact commit used to create the package. + +## 6. Install on OPNsense + +Copy `rustproxy-repo.pub` to the firewall and run: + +```sh +sh deploy/opnsense/install-repo.sh \ + https://packages.example.net/rustproxy/repository/26.7/amd64 \ + /tmp/rustproxy-repo.pub +``` + +Then open `Services -> RustProxy`, enable it and click Apply. + +## 7. Update + +Publishing a new package does not automatically upgrade production firewalls. Run: + +```sh +sh deploy/opnsense/update.sh +``` + +`AUTO_DEPLOY_HOSTS` exists only for explicitly listed test firewalls and is empty by default. diff --git a/opnsense-plugin/www/rustproxy/+POST_INSTALL.pre b/opnsense-plugin/www/rustproxy/+POST_INSTALL.pre new file mode 100644 index 0000000..c26c8ca --- /dev/null +++ b/opnsense-plugin/www/rustproxy/+POST_INSTALL.pre @@ -0,0 +1,13 @@ +#!/bin/sh + +if ! /usr/sbin/pw groupshow rustproxy >/dev/null 2>&1; then + /usr/sbin/pw groupadd rustproxy +fi + +if ! /usr/sbin/pw usershow rustproxy >/dev/null 2>&1; then + /usr/sbin/pw useradd rustproxy -g rustproxy -c "RustProxy daemon" -d /var/db/rustproxy -s /usr/sbin/nologin +fi + +/usr/bin/install -d -o rustproxy -g rustproxy -m 0750 /var/db/rustproxy +/usr/bin/install -d -o rustproxy -g rustproxy -m 0750 /var/log/rustproxy +/usr/bin/install -d -o root -g wheel -m 0755 /usr/local/etc/rustproxy diff --git a/opnsense-plugin/www/rustproxy/+PRE_DEINSTALL.pre b/opnsense-plugin/www/rustproxy/+PRE_DEINSTALL.pre new file mode 100644 index 0000000..f41e117 --- /dev/null +++ b/opnsense-plugin/www/rustproxy/+PRE_DEINSTALL.pre @@ -0,0 +1,4 @@ +#!/bin/sh + +/usr/local/sbin/configctl rustproxy stop >/dev/null 2>&1 || true +exit 0 diff --git a/opnsense-plugin/www/rustproxy/Makefile b/opnsense-plugin/www/rustproxy/Makefile new file mode 100644 index 0000000..1c826f7 --- /dev/null +++ b/opnsense-plugin/www/rustproxy/Makefile @@ -0,0 +1,9 @@ +PLUGIN_NAME= rustproxy +PLUGIN_VERSION?= 0.20.0 +PLUGIN_REVISION?= 0 +PLUGIN_COMMENT= RustProxy reverse proxy service +PLUGIN_MAINTAINER?= local@localhost +PLUGIN_WWW?= https://example.invalid/rustproxy +PLUGIN_LICENSE= UNKNOWN + +.include "../../Mk/plugins.mk" diff --git a/opnsense-plugin/www/rustproxy/pkg-descr b/opnsense-plugin/www/rustproxy/pkg-descr new file mode 100644 index 0000000..e47c0e8 --- /dev/null +++ b/opnsense-plugin/www/rustproxy/pkg-descr @@ -0,0 +1,4 @@ +RustProxy packaged as an OPNsense plugin. + +The initial OPNsense UI intentionally exposes only one setting: Enabled. +Runtime proxy configuration remains managed by RustProxy itself. diff --git a/opnsense-plugin/www/rustproxy/src/etc/rc.d/rustproxy b/opnsense-plugin/www/rustproxy/src/etc/rc.d/rustproxy new file mode 100755 index 0000000..202f0d3 --- /dev/null +++ b/opnsense-plugin/www/rustproxy/src/etc/rc.d/rustproxy @@ -0,0 +1,45 @@ +#!/bin/sh + +# PROVIDE: rustproxy +# REQUIRE: LOGIN NETWORKING +# KEYWORD: shutdown + +. /etc/rc.subr + +name="rustproxy" +rcvar="${name}_enable" + +load_rc_config "$name" + +: ${rustproxy_enable:="NO"} +: ${rustproxy_user:="rustproxy"} +: ${rustproxy_group:="rustproxy"} +: ${rustproxy_data_dir:="/var/db/rustproxy"} +: ${rustproxy_log_dir:="/var/log/rustproxy"} +: ${rustproxy_log_file:="${rustproxy_log_dir}/rustproxy.log"} +: ${rustproxy_command:="/usr/local/sbin/rust-proxy"} +: ${rustproxy_restart_delay:="5"} + +pidfile="/var/run/rustproxy-daemon.pid" +child_pidfile="/var/run/rustproxy.pid" +command="/usr/sbin/daemon" +command_args="-r -R ${rustproxy_restart_delay} -P ${pidfile} -p ${child_pidfile} -u ${rustproxy_user} -o ${rustproxy_log_file} ${rustproxy_command} --data-dir ${rustproxy_data_dir} --update-check-interval-secs 0 serve" +required_files="${rustproxy_command}" +start_precmd="rustproxy_prestart" +stop_postcmd="rustproxy_poststop" + +rustproxy_prestart() +{ + /usr/bin/install -d -o "${rustproxy_user}" -g "${rustproxy_group}" -m 0750 "${rustproxy_data_dir}" + /usr/bin/install -d -o "${rustproxy_user}" -g "${rustproxy_group}" -m 0750 "${rustproxy_log_dir}" + /usr/bin/touch "${rustproxy_log_file}" + /usr/sbin/chown "${rustproxy_user}:${rustproxy_group}" "${rustproxy_log_file}" + /bin/chmod 0640 "${rustproxy_log_file}" +} + +rustproxy_poststop() +{ + /bin/rm -f "${child_pidfile}" +} + +run_rc_command "$1" diff --git a/opnsense-plugin/www/rustproxy/src/etc/rc.syshook.d/10-rustproxy.start b/opnsense-plugin/www/rustproxy/src/etc/rc.syshook.d/10-rustproxy.start new file mode 100755 index 0000000..a661a5f --- /dev/null +++ b/opnsense-plugin/www/rustproxy/src/etc/rc.syshook.d/10-rustproxy.start @@ -0,0 +1,4 @@ +#!/bin/sh + +/usr/local/sbin/configctl template reload OPNsense/RustProxy >/dev/null 2>&1 || true +/usr/local/sbin/configctl rustproxy boot >/dev/null 2>&1 || true diff --git a/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/controllers/OPNsense/RustProxy/Api/ServiceController.php b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/controllers/OPNsense/RustProxy/Api/ServiceController.php new file mode 100644 index 0000000..f96d74b --- /dev/null +++ b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/controllers/OPNsense/RustProxy/Api/ServiceController.php @@ -0,0 +1,18 @@ +view->generalForm = $this->getForm('general'); + $this->view->pick('OPNsense/RustProxy/index'); + } +} diff --git a/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/controllers/OPNsense/RustProxy/forms/general.xml b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/controllers/OPNsense/RustProxy/forms/general.xml new file mode 100644 index 0000000..de227d5 --- /dev/null +++ b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/controllers/OPNsense/RustProxy/forms/general.xml @@ -0,0 +1,8 @@ +
+ + rustproxy.general.enabled + + checkbox + Start RustProxy and keep it enabled after OPNsense restarts. + +
diff --git a/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/ACL/ACL.xml b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/ACL/ACL.xml new file mode 100644 index 0000000..bada5e3 --- /dev/null +++ b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/ACL/ACL.xml @@ -0,0 +1,10 @@ + + + Services: RustProxy + Allow access to RustProxy settings and service controls + + ui/rustproxy/* + api/rustproxy/* + + + diff --git a/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/Menu/Menu.xml b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/Menu/Menu.xml new file mode 100644 index 0000000..14374cc --- /dev/null +++ b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/Menu/Menu.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/RustProxy.php b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/RustProxy.php new file mode 100644 index 0000000..774b91c --- /dev/null +++ b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/RustProxy.php @@ -0,0 +1,9 @@ + + //OPNsense/rustproxy + 1.0.0 + RustProxy service settings + + + + 0 + Y + + + + diff --git a/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/views/OPNsense/RustProxy/index.volt b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/views/OPNsense/RustProxy/index.volt new file mode 100644 index 0000000..844a7d3 --- /dev/null +++ b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/views/OPNsense/RustProxy/index.volt @@ -0,0 +1,43 @@ + + +
+
+ {{ partial("layout_partials/base_form", ['fields': generalForm, 'id': 'frm_GeneralSettings']) }} +
+
+
+
+
+ +

+
+
+
diff --git a/opnsense-plugin/www/rustproxy/src/opnsense/scripts/rustproxy/service.sh b/opnsense-plugin/www/rustproxy/src/opnsense/scripts/rustproxy/service.sh new file mode 100755 index 0000000..d4f1c44 --- /dev/null +++ b/opnsense-plugin/www/rustproxy/src/opnsense/scripts/rustproxy/service.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +set -u + +CONFIG_FILE="/usr/local/etc/rustproxy/opnsense.conf" +RC_SCRIPT="/usr/local/etc/rc.d/rustproxy" +enabled="0" + +if [ -r "$CONFIG_FILE" ]; then + . "$CONFIG_FILE" +fi + +start_service() +{ + if [ "$enabled" != "1" ]; then + echo "RustProxy is disabled in OPNsense configuration" + return 0 + fi + "$RC_SCRIPT" onestart +} + +stop_service() +{ + "$RC_SCRIPT" onestop >/dev/null 2>&1 || true +} + +case "${1:-}" in + start|boot) + start_service + ;; + stop) + stop_service + ;; + restart|reload) + stop_service + start_service + ;; + status) + "$RC_SCRIPT" status + ;; + *) + echo "usage: $0 {start|stop|restart|reload|status|boot}" >&2 + exit 64 + ;; +esac diff --git a/opnsense-plugin/www/rustproxy/src/opnsense/service/conf/actions.d/actions_rustproxy.conf b/opnsense-plugin/www/rustproxy/src/opnsense/service/conf/actions.d/actions_rustproxy.conf new file mode 100644 index 0000000..f3754fd --- /dev/null +++ b/opnsense-plugin/www/rustproxy/src/opnsense/service/conf/actions.d/actions_rustproxy.conf @@ -0,0 +1,35 @@ +[start] +command:/usr/local/opnsense/scripts/rustproxy/service.sh start +parameters: +type:script +message:starting RustProxy + +[stop] +command:/usr/local/opnsense/scripts/rustproxy/service.sh stop +parameters: +type:script +message:stopping RustProxy + +[restart] +command:/usr/local/opnsense/scripts/rustproxy/service.sh restart +parameters: +type:script +message:restarting RustProxy + +[reload] +command:/usr/local/opnsense/scripts/rustproxy/service.sh restart +parameters: +type:script +message:reloading RustProxy + +[status] +command:/usr/local/opnsense/scripts/rustproxy/service.sh status +parameters: +type:script_output +message:RustProxy status + +[boot] +command:/usr/local/opnsense/scripts/rustproxy/service.sh boot +parameters: +type:script +message:starting RustProxy at boot diff --git a/opnsense-plugin/www/rustproxy/src/opnsense/service/templates/OPNsense/RustProxy/+TARGETS b/opnsense-plugin/www/rustproxy/src/opnsense/service/templates/OPNsense/RustProxy/+TARGETS new file mode 100644 index 0000000..5bb520e --- /dev/null +++ b/opnsense-plugin/www/rustproxy/src/opnsense/service/templates/OPNsense/RustProxy/+TARGETS @@ -0,0 +1 @@ +rustproxy.conf:/usr/local/etc/rustproxy/opnsense.conf diff --git a/opnsense-plugin/www/rustproxy/src/opnsense/service/templates/OPNsense/RustProxy/rustproxy.conf b/opnsense-plugin/www/rustproxy/src/opnsense/service/templates/OPNsense/RustProxy/rustproxy.conf new file mode 100644 index 0000000..fa00e89 --- /dev/null +++ b/opnsense-plugin/www/rustproxy/src/opnsense/service/templates/OPNsense/RustProxy/rustproxy.conf @@ -0,0 +1 @@ +enabled="{% if not helpers.empty('OPNsense.rustproxy.general.enabled') %}1{% else %}0{% endif %}" diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100755 index 0000000..b10dcf9 --- /dev/null +++ b/scripts/check.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +while IFS= read -r file; do + first_line="$(head -n 1 "$file")" + if [[ "$first_line" == *bash* ]]; then + bash -n "$file" + else + sh -n "$file" + fi +done < <(find "$ROOT" -type f \( -name '*.sh' -o -path '*/src/etc/rc.d/rustproxy' -o -path '*/src/etc/rc.syshook.d/*.start' \)) + +python3 - "$ROOT" <<'PY' +import pathlib +import sys +import xml.etree.ElementTree as ET +root = pathlib.Path(sys.argv[1]) +for path in root.glob('opnsense-plugin/**/*.xml'): + ET.parse(path) + print('xml ok:', path.relative_to(root)) +PY + +if command -v php >/dev/null 2>&1; then + while IFS= read -r file; do php -l "$file" >/dev/null; done < <(find "$ROOT/opnsense-plugin" -type f -name '*.php') +fi + +if [[ -n "${RUSTPROXY_SOURCE_DIR:-}" ]] && [[ -f "$RUSTPROXY_SOURCE_DIR/Cargo.toml" ]] && command -v cargo >/dev/null 2>&1; then + cargo metadata --locked --no-deps --format-version 1 --manifest-path "$RUSTPROXY_SOURCE_DIR/Cargo.toml" >/dev/null +fi + +if [[ -d "$ROOT/app" ]]; then + echo "RustProxy source must not be vendored in this packaging repository: $ROOT/app" >&2 + exit 1 +fi + +if [[ -e "$ROOT/opnsense-plugin/www/rustproxy/src/sbin/rust-proxy" ]]; then + echo "Generated binary must not be committed in plugin source." >&2 + exit 1 +fi + +echo "Repository checks passed."