v0.27.3
This commit is contained in:
1 parent
e84df445c6
commit
e87a382a7e
109 files changed
+1092
-2925
No files matched your search
@@ -0,0 +1,7 @@
|
||||
# External integrations
|
||||
|
||||
Platform-specific helpers that integrate RustProxy with software outside the application itself.
|
||||
They are not required by the RustProxy runtime.
|
||||
|
||||
- `opnsense-acme/` - OPNsense os-acme-client certificate deployment helper.
|
||||
- `routeros-oci-build/` - RouterOS-oriented OCI build/publish helper.
|
||||
@@ -0,0 +1,107 @@
|
||||
# OPNsense os-acme-client -> RustProxy
|
||||
|
||||
A simple mechanism for automatically sending a certificate generated by `os-acme-client` on OPNsense to RustProxy via API.
|
||||
|
||||
## Files
|
||||
|
||||
- `opnsense-rustproxy-cert-deploy.sh` - sends the certificate and private key to RustProxy.
|
||||
- `rustproxy-acme.conf` - connection settings, API token, and certificate file paths.
|
||||
- `actions_rustproxy_cert.conf` - optional `configd` action that allows the script to be executed from Cron in OPNsense.
|
||||
|
||||
## 1. Installation
|
||||
|
||||
```sh
|
||||
cp opnsense-rustproxy-cert-deploy.sh /usr/local/sbin/
|
||||
chmod 750 /usr/local/sbin/opnsense-rustproxy-cert-deploy.sh
|
||||
|
||||
cp rustproxy-acme.conf.example /usr/local/etc/rustproxy-acme.conf
|
||||
chmod 600 /usr/local/etc/rustproxy-acme.conf
|
||||
```
|
||||
|
||||
## 2. Configuration
|
||||
|
||||
Edit:
|
||||
|
||||
```sh
|
||||
vi /usr/local/etc/rustproxy-acme.conf
|
||||
```
|
||||
|
||||
Important parameters:
|
||||
|
||||
```sh
|
||||
RUSTPROXY_URL='https://127.0.0.1:9090'
|
||||
RUSTPROXY_KEY_ID='opnsense-acme'
|
||||
RUSTPROXY_TOKEN='RUSTPROXY_TOKEN'
|
||||
|
||||
CERTIFICATE_ID='shared-cert'
|
||||
|
||||
CERT_FILE='/var/etc/acme-client/certs/CERT_UUID/fullchain.pem'
|
||||
KEY_FILE='/var/etc/acme-client/keys/CERT_UUID/private.key'
|
||||
```
|
||||
|
||||
If `os-acme-client` exports the certificate to a custom directory, simply set the correct paths:
|
||||
|
||||
```sh
|
||||
CERT_FILE='/path/to/fullchain.pem'
|
||||
KEY_FILE='/path/to/private.key'
|
||||
```
|
||||
|
||||
`CERTIFICATE_ID` is the stable external certificate id. If it does not exist, RustProxy creates a manual certificate entry. If it already exists as a manual certificate, RustProxy rotates it in place. RustProxy-managed ACME ids are rejected to prevent two renewal owners for one lineage.
|
||||
|
||||
## 3. RustProxy API
|
||||
|
||||
Create a certificate API key in RustProxy and enter its values in the configuration:
|
||||
|
||||
```text
|
||||
RUSTPROXY_KEY_ID
|
||||
RUSTPROXY_TOKEN
|
||||
```
|
||||
|
||||
The script sends the certificate to:
|
||||
|
||||
```text
|
||||
POST /api/certificates/deploy
|
||||
```
|
||||
|
||||
The connection from OPNsense to RustProxy should use HTTPS.
|
||||
|
||||
## 4. Test
|
||||
|
||||
Run the certificate reconciliation:
|
||||
|
||||
```sh
|
||||
/usr/local/sbin/opnsense-rustproxy-cert-deploy.sh
|
||||
```
|
||||
|
||||
On success, you should see a message similar to:
|
||||
|
||||
```text
|
||||
RustProxy certificate reconcile succeeded (HTTP 200, id=shared-cert)
|
||||
```
|
||||
|
||||
## 5. OPNsense Automation
|
||||
|
||||
Optionally install the `configd` action:
|
||||
|
||||
```sh
|
||||
cp actions_rustproxy_cert.conf /usr/local/opnsense/service/conf/actions.d/
|
||||
service configd restart
|
||||
```
|
||||
|
||||
Then go to **System -> Settings -> Cron** and add a scheduled execution of the `Deploy ACME certificate to RustProxy` action.
|
||||
|
||||
The script deliberately reconciles on every run instead of trusting a local cache. The server is the source of truth: an identical RustProxy-owned certificate generation is a no-op, a missing id is created, and changed material is rotated atomically. This also repairs drift when a certificate entry or generation was removed outside OPNsense.
|
||||
|
||||
## Additional Options
|
||||
|
||||
If RustProxy uses a private CA:
|
||||
|
||||
```sh
|
||||
RUSTPROXY_CACERT='/usr/local/etc/ssl/rustproxy-ca.pem'
|
||||
```
|
||||
|
||||
If you want to connect to a specific LAN address while keeping proper TLS hostname verification:
|
||||
|
||||
```sh
|
||||
RUSTPROXY_RESOLVE='rustproxy.lan:8443:192.168.1.20'
|
||||
```
|
||||
@@ -0,0 +1,137 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# Deploy an externally issued OPNsense/os-acme-client certificate to RustProxy 0.23.x.
|
||||
# Configuration is kept separately so the API token does not need to live in this script.
|
||||
|
||||
CONFIG_FILE="${RUSTPROXY_ACME_CONFIG:-/usr/local/etc/rustproxy-acme.conf}"
|
||||
|
||||
log() {
|
||||
logger -t rustproxy-acme -- "$*" 2>/dev/null || true
|
||||
printf '%s\n' "$*"
|
||||
}
|
||||
|
||||
fail() {
|
||||
log "ERROR: $*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ -r "$CONFIG_FILE" ] || fail "Cannot read config: $CONFIG_FILE"
|
||||
# shellcheck disable=SC1090
|
||||
. "$CONFIG_FILE"
|
||||
|
||||
: "${RUSTPROXY_URL:?Set RUSTPROXY_URL in $CONFIG_FILE}"
|
||||
: "${RUSTPROXY_KEY_ID:?Set RUSTPROXY_KEY_ID in $CONFIG_FILE}"
|
||||
: "${RUSTPROXY_TOKEN:?Set RUSTPROXY_TOKEN in $CONFIG_FILE}"
|
||||
: "${CERTIFICATE_ID:?Set CERTIFICATE_ID in $CONFIG_FILE}"
|
||||
: "${CERT_FILE:?Set CERT_FILE in $CONFIG_FILE}"
|
||||
: "${KEY_FILE:?Set KEY_FILE in $CONFIG_FILE}"
|
||||
|
||||
CURL="${CURL:-$(command -v curl || true)}"
|
||||
OPENSSL="${OPENSSL:-$(command -v openssl || true)}"
|
||||
[ -n "$CURL" ] || fail "curl not found"
|
||||
[ -n "$OPENSSL" ] || fail "openssl not found"
|
||||
|
||||
case "$CERTIFICATE_ID" in
|
||||
''|*[!A-Za-z0-9_-]*) fail "CERTIFICATE_ID may contain only A-Z, a-z, 0-9, _ and -" ;;
|
||||
esac
|
||||
[ "${#CERTIFICATE_ID}" -le 64 ] || fail "CERTIFICATE_ID is longer than 64 characters"
|
||||
|
||||
[ -r "$CERT_FILE" ] || fail "Certificate not readable: $CERT_FILE"
|
||||
[ -r "$KEY_FILE" ] || fail "Private key not readable: $KEY_FILE"
|
||||
|
||||
# Validate certificate syntax and make sure the certificate and private key belong together.
|
||||
"$OPENSSL" x509 -in "$CERT_FILE" -noout >/dev/null 2>&1 \
|
||||
|| fail "Invalid certificate PEM: $CERT_FILE"
|
||||
"$OPENSSL" pkey -in "$KEY_FILE" -noout >/dev/null 2>&1 \
|
||||
|| fail "Invalid private key PEM: $KEY_FILE"
|
||||
|
||||
CERT_PUB=$(
|
||||
"$OPENSSL" x509 -in "$CERT_FILE" -pubkey -noout 2>/dev/null \
|
||||
| "$OPENSSL" pkey -pubin -outform DER 2>/dev/null \
|
||||
| "$OPENSSL" dgst -sha256 2>/dev/null
|
||||
)
|
||||
KEY_PUB=$(
|
||||
"$OPENSSL" pkey -in "$KEY_FILE" -pubout -outform DER 2>/dev/null \
|
||||
| "$OPENSSL" dgst -sha256 2>/dev/null
|
||||
)
|
||||
[ -n "$CERT_PUB" ] && [ "$CERT_PUB" = "$KEY_PUB" ] \
|
||||
|| fail "Certificate and private key do not match"
|
||||
|
||||
# Do not publish an already-expired certificate.
|
||||
"$OPENSSL" x509 -in "$CERT_FILE" -checkend 0 -noout >/dev/null 2>&1 \
|
||||
|| fail "Certificate is already expired"
|
||||
|
||||
# Always reconcile with RustProxy. The deploy endpoint is idempotent: an identical
|
||||
# RustProxy-owned generation is a server-side no-op, while a missing certificate id
|
||||
# is created and a changed manual certificate is rotated atomically. This avoids
|
||||
# trusting stale local state when the RustProxy configuration was changed separately.
|
||||
|
||||
umask 077
|
||||
TMP_JSON=$(mktemp "${TMPDIR:-/tmp}/rustproxy-cert.XXXXXX") || fail "mktemp failed"
|
||||
TMP_CURL=$(mktemp "${TMPDIR:-/tmp}/rustproxy-curl.XXXXXX") || { rm -f "$TMP_JSON"; fail "mktemp failed"; }
|
||||
TMP_RESPONSE=$(mktemp "${TMPDIR:-/tmp}/rustproxy-response.XXXXXX") || { rm -f "$TMP_JSON" "$TMP_CURL"; fail "mktemp failed"; }
|
||||
trap 'rm -f "$TMP_JSON" "$TMP_CURL" "$TMP_RESPONSE"' EXIT HUP INT TERM
|
||||
|
||||
# PEM is line-oriented Base64 plus BEGIN/END markers. Escape it as a JSON string
|
||||
# without needing jq/python on OPNsense.
|
||||
pem_json() {
|
||||
awk '{
|
||||
gsub(/\\/, "\\\\");
|
||||
gsub(/\"/, "\\\"");
|
||||
sub(/\r$/, "");
|
||||
printf "%s\\n", $0;
|
||||
}' "$1"
|
||||
}
|
||||
|
||||
{
|
||||
printf '{"certificate_id":"%s","cert_pem":"' "$CERTIFICATE_ID"
|
||||
pem_json "$CERT_FILE"
|
||||
printf '","key_pem":"'
|
||||
pem_json "$KEY_FILE"
|
||||
printf '"}\n'
|
||||
} > "$TMP_JSON"
|
||||
|
||||
# Keep the Bearer token out of curl's process arguments by using a mode-0600 config file.
|
||||
cat > "$TMP_CURL" <<EOF_CURL
|
||||
silent
|
||||
show-error
|
||||
connect-timeout = 10
|
||||
max-time = 45
|
||||
request = "POST"
|
||||
url = "${RUSTPROXY_URL%/}/api/certificates/deploy"
|
||||
header = "Authorization: Bearer ${RUSTPROXY_TOKEN}"
|
||||
header = "X-RustProxy-Certificate-Key: ${RUSTPROXY_KEY_ID}"
|
||||
header = "Content-Type: application/json"
|
||||
data-binary = "@${TMP_JSON}"
|
||||
output = "${TMP_RESPONSE}"
|
||||
write-out = "%{http_code}"
|
||||
EOF_CURL
|
||||
|
||||
# Optional trust anchor for an internal/self-signed administration certificate.
|
||||
if [ -n "${RUSTPROXY_CACERT:-}" ]; then
|
||||
[ -r "$RUSTPROXY_CACERT" ] || fail "CA file not readable: $RUSTPROXY_CACERT"
|
||||
printf 'cacert = "%s"\n' "$RUSTPROXY_CACERT" >> "$TMP_CURL"
|
||||
fi
|
||||
|
||||
# Optional curl --resolve equivalent, e.g. rustproxy.lan:8443:10.10.10.20.
|
||||
# This lets the URL use the certificate DNS name while traffic goes to a fixed LAN IP.
|
||||
if [ -n "${RUSTPROXY_RESOLVE:-}" ]; then
|
||||
printf 'resolve = "%s"\n' "$RUSTPROXY_RESOLVE" >> "$TMP_CURL"
|
||||
fi
|
||||
|
||||
HTTP_CODE=$("$CURL" --config "$TMP_CURL") || fail "HTTPS request to RustProxy failed"
|
||||
|
||||
case "$HTTP_CODE" in
|
||||
2??)
|
||||
log "RustProxy certificate reconcile succeeded (HTTP $HTTP_CODE, id=$CERTIFICATE_ID)"
|
||||
cat "$TMP_RESPONSE"
|
||||
printf '\n'
|
||||
;;
|
||||
*)
|
||||
log "RustProxy certificate deploy failed (HTTP $HTTP_CODE)"
|
||||
cat "$TMP_RESPONSE" >&2
|
||||
printf '\n' >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,40 @@
|
||||
# Zot / OCI registry. Do not include a path in REGISTRY.
|
||||
REGISTRY=zot.example.net
|
||||
IMAGE_NAME=network/rustproxy
|
||||
|
||||
# Alternative shorthand instead of REGISTRY + IMAGE_NAME:
|
||||
# OCI_IMAGE=zot.example.net/network/rustproxy
|
||||
|
||||
# Zot Basic Auth / htpasswd or token credentials.
|
||||
OCI_USERNAME=
|
||||
OCI_PASSWORD=
|
||||
# OCI_TOKEN=
|
||||
|
||||
# RouterOS targets used by this helper.
|
||||
BUILD_PLATFORMS=linux/amd64,linux/arm64
|
||||
BUILDER=rustproxy-routeros
|
||||
|
||||
# RustProxy currently requires Rust 1.88.
|
||||
RUST_VERSION=1.88
|
||||
DEBIAN_SUITE=trixie
|
||||
|
||||
# Empty OCI_TAG means the script uses Cargo.toml package version.
|
||||
OCI_TAG=
|
||||
OCI_LATEST=1
|
||||
OCI_EXTRA_TAGS=routeros
|
||||
|
||||
# Build behavior.
|
||||
OCI_PULL=1
|
||||
OCI_NO_CACHE=0
|
||||
OCI_VERIFY=1
|
||||
OCI_PROGRESS=plain
|
||||
|
||||
# 1 stores BuildKit cache in Zot as IMAGE_NAME:buildcache.
|
||||
# Recommended for CI/ephemeral buildx builders.
|
||||
OCI_REGISTRY_CACHE=0
|
||||
|
||||
# Optional BuildKit config, useful for an internal Zot with a custom CA.
|
||||
# BUILDER_CONFIG=/etc/buildkit/buildkitd.toml
|
||||
|
||||
# Optional manual repository root override.
|
||||
# RUSTPROXY_PROJECT_ROOT=/home/user/RustProxy
|
||||
@@ -0,0 +1,51 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM rust:1.88-trixie AS build
|
||||
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
|
||||
RUN if [ ! -f Cargo.lock ]; then cargo generate-lockfile; fi \
|
||||
&& cargo build --locked --release
|
||||
|
||||
|
||||
FROM debian:trixie-slim AS runtime-trixie
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
python3 \
|
||||
certbot \
|
||||
openssl \
|
||||
gosu \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& groupadd --system --gid 10001 rustproxy \
|
||||
&& useradd --system \
|
||||
--uid 10001 \
|
||||
--gid rustproxy \
|
||||
--home /var/lib/rustproxy \
|
||||
--shell /usr/sbin/nologin \
|
||||
rustproxy \
|
||||
&& install -d -o rustproxy -g rustproxy -m 0700 /var/lib/rustproxy \
|
||||
&& install -d -o rustproxy -g rustproxy -m 0750 /var/log/rustproxy
|
||||
|
||||
COPY --from=build /build/target/release/rust-proxy /usr/local/bin/rust-proxy
|
||||
COPY integrations/routeros-oci-build/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
RUN chmod 0755 \
|
||||
/usr/local/bin/rust-proxy \
|
||||
/usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
ENV RUST_LOG=rust_proxy=info,warn \
|
||||
RUST_PROXY_DATA_DIR=/var/lib/rustproxy \
|
||||
RUST_PROXY_ADMIN_LISTEN=0.0.0.0:9090 \
|
||||
RUST_PROXY_PROXY_LISTEN=0.0.0.0:8080 \
|
||||
RUST_PROXY_TLS_LISTEN=0.0.0.0:8443
|
||||
|
||||
VOLUME ["/var/lib/rustproxy"]
|
||||
|
||||
EXPOSE 8080/tcp
|
||||
EXPOSE 8443/tcp
|
||||
EXPOSE 9090/tcp
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
CMD ["serve", "--allow-remote-admin"]
|
||||
@@ -0,0 +1,25 @@
|
||||
.git
|
||||
.gitignore
|
||||
.github
|
||||
.idea
|
||||
.vscode
|
||||
target
|
||||
**/target
|
||||
node_modules
|
||||
**/node_modules
|
||||
*.zip
|
||||
*.tar
|
||||
*.tar.gz
|
||||
*.tgz
|
||||
*.pkg
|
||||
*.deb
|
||||
*.rpm
|
||||
.env
|
||||
**/.env
|
||||
*.pem
|
||||
*.key
|
||||
*.p12
|
||||
*.pfx
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
*.db
|
||||
@@ -0,0 +1,138 @@
|
||||
# RustProxy RouterOS OCI builder - Debian Trixie
|
||||
|
||||
Multi-arch build/push helper for RustProxy images intended for RouterOS Container.
|
||||
It publishes one OCI manifest for `linux/amd64` and `linux/arm64`.
|
||||
|
||||
## Why ARM64 builds are faster
|
||||
|
||||
The Rust build stage is pinned to Docker BuildKit's `BUILDPLATFORM`:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-trixie AS build
|
||||
```
|
||||
|
||||
On an amd64 build host:
|
||||
|
||||
- amd64 uses native `rustc` + `gcc`,
|
||||
- arm64 uses the same native amd64 `rustc` + `aarch64-linux-gnu-gcc`.
|
||||
|
||||
The expensive Cargo/Rust/C compilation is therefore not executed by an ARM CPU emulator.
|
||||
The final Debian arm64 runtime stage can still use binfmt/QEMU briefly for `apt/dpkg` if the build host is amd64; this is normally a small part of the total build and is cacheable.
|
||||
|
||||
## Repository layout
|
||||
|
||||
Copy this directory as:
|
||||
|
||||
```text
|
||||
RustProxy/
|
||||
Cargo.toml
|
||||
Cargo.lock
|
||||
integrations/
|
||||
routeros-oci-build/
|
||||
build-routeros-oci.sh
|
||||
Dockerfile.routeros
|
||||
Dockerfile.routeros.dockerignore
|
||||
.env.routeros.example
|
||||
.env
|
||||
```
|
||||
|
||||
Create `.env`:
|
||||
|
||||
```bash
|
||||
cp integrations/routeros-oci-build/.env.routeros.example \
|
||||
integrations/routeros-oci-build/.env
|
||||
```
|
||||
|
||||
Then edit the Zot address and credentials.
|
||||
|
||||
## Zot example
|
||||
|
||||
```env
|
||||
REGISTRY=zot.example.net
|
||||
IMAGE_NAME=network/rustproxy
|
||||
OCI_USERNAME=builder
|
||||
OCI_PASSWORD=secret
|
||||
|
||||
BUILD_PLATFORMS=linux/amd64,linux/arm64
|
||||
RUST_VERSION=1.88
|
||||
DEBIAN_SUITE=trixie
|
||||
```
|
||||
|
||||
`OCI_TOKEN` can be used instead of `OCI_PASSWORD`.
|
||||
Credentials are sent to `docker login` using `--password-stdin`.
|
||||
|
||||
## Check configuration
|
||||
|
||||
```bash
|
||||
ENV_FILE=integrations/routeros-oci-build/.env \
|
||||
bash integrations/routeros-oci-build/build-routeros-oci.sh config
|
||||
```
|
||||
|
||||
The repository root is detected using Git. If that is unavailable, the script walks upward until it finds `Cargo.toml`.
|
||||
|
||||
## Build and push multi-arch image
|
||||
|
||||
```bash
|
||||
ENV_FILE=integrations/routeros-oci-build/.env \
|
||||
bash integrations/routeros-oci-build/build-routeros-oci.sh push
|
||||
```
|
||||
|
||||
With RustProxy version `0.25.0` and the defaults this publishes:
|
||||
|
||||
```text
|
||||
zot.example.net/network/rustproxy:0.25.0
|
||||
zot.example.net/network/rustproxy:latest
|
||||
zot.example.net/network/rustproxy:routeros
|
||||
```
|
||||
|
||||
Each tag points to the same amd64 + arm64 OCI manifest.
|
||||
|
||||
## Local single-architecture build
|
||||
|
||||
```bash
|
||||
ENV_FILE=integrations/routeros-oci-build/.env \
|
||||
bash integrations/routeros-oci-build/build-routeros-oci.sh load amd64
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```bash
|
||||
ENV_FILE=integrations/routeros-oci-build/.env \
|
||||
bash integrations/routeros-oci-build/build-routeros-oci.sh load arm64
|
||||
```
|
||||
|
||||
`load arm64` loads an ARM64 image into Docker; it does not attempt to start the container.
|
||||
|
||||
## Cache
|
||||
|
||||
BuildKit cache mounts are used automatically for Cargo registry/git/target data.
|
||||
For short-lived CI builders, enable registry cache:
|
||||
|
||||
```env
|
||||
OCI_REGISTRY_CACHE=1
|
||||
```
|
||||
|
||||
It is stored in Zot as:
|
||||
|
||||
```text
|
||||
zot.example.net/network/rustproxy:buildcache
|
||||
```
|
||||
|
||||
## Custom/self-signed Zot CA
|
||||
|
||||
Point `BUILDER_CONFIG` to a BuildKit configuration that trusts the registry CA, for example:
|
||||
|
||||
```env
|
||||
BUILDER_CONFIG=/etc/buildkit/buildkitd.toml
|
||||
```
|
||||
|
||||
Docker itself must also trust the registry CA for `docker login` and manifest inspection.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Docker Engine
|
||||
- Docker Buildx
|
||||
- access to the Zot/OCI registry
|
||||
- an existing RustProxy `Cargo.lock`
|
||||
|
||||
Do not add an ARM-native Rust builder stage. Doing that would put `rustc` and Cargo back under QEMU on an amd64 host.
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# RustProxy multi-arch OCI builder for RouterOS.
|
||||
# Rust compilation runs on BUILDPLATFORM. Target binaries are cross-compiled,
|
||||
# so rustc/cargo do not run through QEMU for arm64 builds.
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
resolve_project_root() {
|
||||
if [[ -n "${RUSTPROXY_PROJECT_ROOT:-}" ]]; then
|
||||
(cd "$RUSTPROXY_PROJECT_ROOT" && pwd)
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v git >/dev/null 2>&1 && git -C "$SCRIPT_DIR" rev-parse --show-toplevel >/dev/null 2>&1; then
|
||||
git -C "$SCRIPT_DIR" rev-parse --show-toplevel
|
||||
return
|
||||
fi
|
||||
|
||||
local dir="$SCRIPT_DIR"
|
||||
while [[ "$dir" != "/" ]]; do
|
||||
if [[ -f "$dir/Cargo.toml" ]]; then
|
||||
printf '%s\n' "$dir"
|
||||
return
|
||||
fi
|
||||
dir="$(dirname "$dir")"
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
PROJECT_ROOT="$(resolve_project_root)" || {
|
||||
echo "Cannot find repository root (Cargo.toml)." >&2
|
||||
echo "Set RUSTPROXY_PROJECT_ROOT=/path/to/RustProxy." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ -f "$PROJECT_ROOT/Cargo.toml" ]] || {
|
||||
echo "Missing $PROJECT_ROOT/Cargo.toml" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Supports the invocation:
|
||||
# ENV_FILE=integrations/routeros-oci-build/.env bash integrations/routeros-oci-build/build-routeros-oci.sh push
|
||||
ENV_FILE="${ROUTEROS_OCI_ENV_FILE:-${ENV_FILE:-$SCRIPT_DIR/.env}}"
|
||||
if [[ "$ENV_FILE" != /* ]]; then
|
||||
ENV_FILE="$PROJECT_ROOT/$ENV_FILE"
|
||||
fi
|
||||
|
||||
[[ -f "$ENV_FILE" ]] || {
|
||||
echo "Missing environment file: $ENV_FILE" >&2
|
||||
echo "Copy $SCRIPT_DIR/.env.example to $SCRIPT_DIR/.env and edit it." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
|
||||
ACTION="${1:-push}"
|
||||
|
||||
VERSION="$(awk '
|
||||
/^\[package\][[:space:]]*$/ { in_package=1; next }
|
||||
/^\[/ && in_package { exit }
|
||||
in_package && /^[[:space:]]*version[[:space:]]*=/ {
|
||||
line=$0
|
||||
sub(/^[^=]*=[[:space:]]*"/, "", line)
|
||||
sub(/".*/, "", line)
|
||||
print line
|
||||
exit
|
||||
}
|
||||
' "$PROJECT_ROOT/Cargo.toml")"
|
||||
[[ -n "$VERSION" ]] || { echo "Cannot determine package version from Cargo.toml" >&2; exit 1; }
|
||||
|
||||
# Registry/image. Preferred format: REGISTRY + IMAGE_NAME.
|
||||
# OCI_IMAGE is kept as a compatible shorthand.
|
||||
if [[ -n "${OCI_IMAGE:-}" ]]; then
|
||||
IMAGE_REPO="${OCI_IMAGE%/}"
|
||||
REGISTRY="${OCI_REGISTRY:-${IMAGE_REPO%%/*}}"
|
||||
else
|
||||
: "${REGISTRY:?Set REGISTRY in $ENV_FILE}"
|
||||
: "${IMAGE_NAME:?Set IMAGE_NAME in $ENV_FILE}"
|
||||
REGISTRY="${REGISTRY#https://}"
|
||||
REGISTRY="${REGISTRY#http://}"
|
||||
REGISTRY="${REGISTRY%/}"
|
||||
IMAGE_REPO="${REGISTRY}/${IMAGE_NAME#/}"
|
||||
fi
|
||||
|
||||
REGISTRY="${REGISTRY#https://}"
|
||||
REGISTRY="${REGISTRY#http://}"
|
||||
REGISTRY="${REGISTRY%/}"
|
||||
IMAGE_REPO="${IMAGE_REPO#https://}"
|
||||
IMAGE_REPO="${IMAGE_REPO#http://}"
|
||||
IMAGE_REPO="${IMAGE_REPO%/}"
|
||||
|
||||
BUILD_PLATFORMS="${BUILD_PLATFORMS:-linux/amd64,linux/arm64}"
|
||||
BUILDER="${BUILDER:-rustproxy-routeros}"
|
||||
RUST_VERSION="${RUST_VERSION:-1.88}"
|
||||
DEBIAN_SUITE="${DEBIAN_SUITE:-trixie}"
|
||||
DOCKERFILE="${DOCKERFILE:-$SCRIPT_DIR/Dockerfile.routeros}"
|
||||
OCI_TAG="${OCI_TAG:-$VERSION}"
|
||||
OCI_LATEST="${OCI_LATEST:-1}"
|
||||
OCI_EXTRA_TAGS="${OCI_EXTRA_TAGS:-routeros}"
|
||||
OCI_PULL="${OCI_PULL:-1}"
|
||||
OCI_NO_CACHE="${OCI_NO_CACHE:-0}"
|
||||
OCI_VERIFY="${OCI_VERIFY:-1}"
|
||||
OCI_REGISTRY_CACHE="${OCI_REGISTRY_CACHE:-0}"
|
||||
OCI_PROGRESS="${OCI_PROGRESS:-plain}"
|
||||
|
||||
IMAGE_TAG="${IMAGE_REPO}:${OCI_TAG}"
|
||||
|
||||
[[ -f "$DOCKERFILE" ]] || { echo "Missing Dockerfile: $DOCKERFILE" >&2; exit 1; }
|
||||
|
||||
REGISTRY_USERNAME="${OCI_USERNAME:-${REGISTRY_USERNAME:-}}"
|
||||
REGISTRY_SECRET="${OCI_TOKEN:-${OCI_PASSWORD:-${REGISTRY_PASSWORD:-}}}"
|
||||
|
||||
registry_login() {
|
||||
if [[ -z "$REGISTRY_USERNAME" && -z "$REGISTRY_SECRET" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ -z "$REGISTRY_USERNAME" || -z "$REGISTRY_SECRET" ]]; then
|
||||
echo "Set both OCI_USERNAME and OCI_PASSWORD/OCI_TOKEN, or neither." >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '%s' "$REGISTRY_SECRET" | docker login "$REGISTRY" \
|
||||
--username "$REGISTRY_USERNAME" \
|
||||
--password-stdin
|
||||
}
|
||||
|
||||
ensure_builder() {
|
||||
command -v docker >/dev/null 2>&1 || { echo "docker is required" >&2; exit 127; }
|
||||
docker buildx version >/dev/null 2>&1 || { echo "docker buildx is required" >&2; exit 127; }
|
||||
if docker buildx inspect "$BUILDER" >/dev/null 2>&1; then
|
||||
docker buildx use "$BUILDER"
|
||||
else
|
||||
local args=(create --name "$BUILDER" --driver docker-container --use)
|
||||
[[ -z "${BUILDER_CONFIG:-}" ]] || args+=(--config "$BUILDER_CONFIG")
|
||||
docker buildx "${args[@]}" >/dev/null
|
||||
fi
|
||||
docker buildx inspect --bootstrap >/dev/null
|
||||
}
|
||||
|
||||
revision="unknown"
|
||||
if command -v git >/dev/null 2>&1 && git -C "$PROJECT_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
revision="$(git -C "$PROJECT_ROOT" rev-parse --short=12 HEAD 2>/dev/null || printf 'unknown')"
|
||||
fi
|
||||
created="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
|
||||
|
||||
common_build_args() {
|
||||
BUILD_ARGS=(
|
||||
build
|
||||
--builder "$BUILDER"
|
||||
--file "$DOCKERFILE"
|
||||
--target runtime-trixie
|
||||
--build-arg "BUILD_VERSION=$VERSION"
|
||||
--build-arg "RUST_VERSION=$RUST_VERSION"
|
||||
--build-arg "DEBIAN_SUITE=$DEBIAN_SUITE"
|
||||
--provenance=false
|
||||
--sbom=false
|
||||
--progress "$OCI_PROGRESS"
|
||||
--label "org.opencontainers.image.title=RustProxy"
|
||||
--label "org.opencontainers.image.version=$VERSION"
|
||||
--label "org.opencontainers.image.revision=$revision"
|
||||
--label "org.opencontainers.image.created=$created"
|
||||
)
|
||||
|
||||
[[ "$OCI_PULL" == "1" ]] && BUILD_ARGS+=(--pull)
|
||||
[[ "$OCI_NO_CACHE" == "1" ]] && BUILD_ARGS+=(--no-cache)
|
||||
|
||||
if [[ "$OCI_REGISTRY_CACHE" == "1" ]]; then
|
||||
BUILD_ARGS+=(
|
||||
--cache-from "type=registry,ref=${IMAGE_REPO}:buildcache"
|
||||
--cache-to "type=registry,ref=${IMAGE_REPO}:buildcache,mode=max,image-manifest=true,oci-mediatypes=true"
|
||||
)
|
||||
fi
|
||||
}
|
||||
|
||||
build_push() {
|
||||
registry_login
|
||||
ensure_builder
|
||||
common_build_args
|
||||
|
||||
BUILD_ARGS+=(
|
||||
--platform "$BUILD_PLATFORMS"
|
||||
--tag "$IMAGE_TAG"
|
||||
)
|
||||
|
||||
if [[ "$OCI_LATEST" == "1" && "$OCI_TAG" != "latest" ]]; then
|
||||
BUILD_ARGS+=(--tag "${IMAGE_REPO}:latest")
|
||||
fi
|
||||
|
||||
if [[ -n "$OCI_EXTRA_TAGS" ]]; then
|
||||
local tag
|
||||
IFS=',' read -r -a extra_tags <<< "$OCI_EXTRA_TAGS"
|
||||
for tag in "${extra_tags[@]}"; do
|
||||
tag="${tag//[[:space:]]/}"
|
||||
[[ -n "$tag" ]] && BUILD_ARGS+=(--tag "${IMAGE_REPO}:${tag}")
|
||||
done
|
||||
fi
|
||||
|
||||
BUILD_ARGS+=(--push "$PROJECT_ROOT")
|
||||
|
||||
cat <<INFO
|
||||
==> RustProxy RouterOS OCI build
|
||||
image: $IMAGE_TAG
|
||||
platforms: $BUILD_PLATFORMS
|
||||
Rust: $RUST_VERSION
|
||||
runtime: debian:$DEBIAN_SUITE-slim
|
||||
builder: $BUILDER
|
||||
project: $PROJECT_ROOT
|
||||
Dockerfile: $DOCKERFILE
|
||||
|
||||
Rust compilation runs on BUILDPLATFORM; arm64 is cross-compiled.
|
||||
INFO
|
||||
|
||||
docker buildx "${BUILD_ARGS[@]}"
|
||||
|
||||
if [[ "$OCI_VERIFY" == "1" ]]; then
|
||||
echo
|
||||
echo "==> Published manifest"
|
||||
docker buildx imagetools inspect "$IMAGE_TAG"
|
||||
fi
|
||||
}
|
||||
|
||||
build_load() {
|
||||
local arch="${2:-amd64}" platform
|
||||
case "$arch" in
|
||||
amd64|x86_64) platform="linux/amd64"; arch="amd64" ;;
|
||||
arm64|aarch64) platform="linux/arm64"; arch="arm64" ;;
|
||||
*) echo "Usage: $0 load [amd64|arm64]" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
ensure_builder
|
||||
common_build_args
|
||||
BUILD_ARGS+=(
|
||||
--platform "$platform"
|
||||
--tag "${IMAGE_REPO}:local-${arch}"
|
||||
--load
|
||||
"$PROJECT_ROOT"
|
||||
)
|
||||
|
||||
docker buildx "${BUILD_ARGS[@]}"
|
||||
printf 'Loaded %s:local-%s (%s)\n' "$IMAGE_REPO" "$arch" "$platform"
|
||||
}
|
||||
|
||||
show_config() {
|
||||
cat <<CFG
|
||||
project_root=$PROJECT_ROOT
|
||||
env_file=$ENV_FILE
|
||||
version=$VERSION
|
||||
image=$IMAGE_TAG
|
||||
registry=$REGISTRY
|
||||
platforms=$BUILD_PLATFORMS
|
||||
builder=$BUILDER
|
||||
rust=$RUST_VERSION
|
||||
debian_suite=$DEBIAN_SUITE
|
||||
dockerfile=$DOCKERFILE
|
||||
registry_cache=$OCI_REGISTRY_CACHE
|
||||
CFG
|
||||
}
|
||||
|
||||
case "$ACTION" in
|
||||
push) build_push ;;
|
||||
load) build_load "${2:-amd64}" ;;
|
||||
config) show_config ;;
|
||||
*)
|
||||
echo "Usage: $0 [push|load [amd64|arm64]|config]" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
DATA_DIR="${RUST_PROXY_DATA_DIR:-/var/lib/rustproxy}"
|
||||
RUSTPROXY_UID="${RUSTPROXY_UID:-10001}"
|
||||
RUSTPROXY_GID="${RUSTPROXY_GID:-10001}"
|
||||
|
||||
if [ "$(id -u)" = "0" ]; then
|
||||
mkdir -p "$DATA_DIR"
|
||||
|
||||
chown "$RUSTPROXY_UID:$RUSTPROXY_GID" "$DATA_DIR"
|
||||
chmod 0700 "$DATA_DIR"
|
||||
|
||||
exec gosu "$RUSTPROXY_UID:$RUSTPROXY_GID" \
|
||||
/usr/local/bin/rust-proxy "$@"
|
||||
fi
|
||||
|
||||
exec /usr/local/bin/rust-proxy "$@"
|
||||
Reference in new issue
Block a user