opn
This commit is contained in:
1 parent
cce1081f75
commit
2b8f11ef8c
36 files changed
+1868
-309
No files matched your search
+42
-5
@@ -1,15 +1,36 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# FreeBSD headers and libraries used only as a cross-compilation sysroot.
|
||||||
|
FROM rockylinux:9 AS freebsd-sysroot
|
||||||
|
ARG FREEBSD_RELEASE=15.1-RELEASE
|
||||||
|
ARG FREEBSD_MIRROR=https://download.freebsd.org/releases/amd64/amd64
|
||||||
|
RUN dnf -y install --allowerasing ca-certificates coreutils curl tar xz \
|
||||||
|
&& dnf clean all \
|
||||||
|
&& rm -rf /var/cache/dnf
|
||||||
|
WORKDIR /tmp/freebsd
|
||||||
|
RUN set -eux; \
|
||||||
|
url="${FREEBSD_MIRROR}/${FREEBSD_RELEASE}"; \
|
||||||
|
curl -fsSL "${url}/base.txz" -o base.txz; \
|
||||||
|
curl -fsSL "${url}/MANIFEST" -o MANIFEST; \
|
||||||
|
expected="$(awk '$1 == "base.txz" { print $2; exit }' MANIFEST)"; \
|
||||||
|
test -n "$expected"; \
|
||||||
|
echo "$expected base.txz" | sha256sum -c -; \
|
||||||
|
mkdir -p /freebsd-sysroot; \
|
||||||
|
tar -xJf base.txz -C /freebsd-sysroot ./lib ./usr/include ./usr/lib
|
||||||
|
|
||||||
FROM rockylinux:9
|
FROM rockylinux:9
|
||||||
|
|
||||||
ARG RUST_TOOLCHAIN=stable
|
ARG RUST_TOOLCHAIN=stable
|
||||||
|
ARG FREEBSD_RUST_TOOLCHAIN=1.88.0
|
||||||
ARG NFPM_VERSION=2.47.0
|
ARG NFPM_VERSION=2.47.0
|
||||||
ARG NFPM_RPM_SHA256=5ad3287a0b1d0c068e556bd84965a9866ab6451ef861506a3c09b8971ca5326f
|
ARG NFPM_RPM_SHA256=5ad3287a0b1d0c068e556bd84965a9866ab6451ef861506a3c09b8971ca5326f
|
||||||
ARG APTLY_VERSION=1.6.3
|
ARG APTLY_VERSION=1.6.3
|
||||||
ARG APTLY_ZIP_SHA256=535ed24eb706bc25a316b59d709a7e74e10165f2ffab8552097ba7e4816a3e7e
|
ARG APTLY_ZIP_SHA256=535ed24eb706bc25a316b59d709a7e74e10165f2ffab8552097ba7e4816a3e7e
|
||||||
|
|
||||||
RUN dnf -y install --allowerasing \
|
RUN dnf -y install --allowerasing \
|
||||||
bash binutils ca-certificates cmake coreutils createrepo_c curl findutils \
|
bash binutils ca-certificates clang cmake coreutils createrepo_c curl file findutils \
|
||||||
gcc gcc-c++ git gnupg2 gzip make openssl pkgconf-pkg-config python3 python3.11 \
|
gcc gcc-c++ git gnupg2 gzip libarchive lld llvm make openssl pkgconf-pkg-config \
|
||||||
rpm rsync shadow-utils sqlite-devel tar unzip xz bzip2 \
|
python3 python3.11 rpm rsync shadow-utils sqlite-devel tar unzip xz bzip2 zstd \
|
||||||
&& dnf clean all \
|
&& dnf clean all \
|
||||||
&& rm -rf /var/cache/dnf
|
&& rm -rf /var/cache/dnf
|
||||||
|
|
||||||
@@ -40,14 +61,30 @@ RUN set -eux; \
|
|||||||
curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs -o /tmp/rustup-init.sh; \
|
curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs -o /tmp/rustup-init.sh; \
|
||||||
sh /tmp/rustup-init.sh -y --no-modify-path --profile minimal --default-toolchain "${RUST_TOOLCHAIN}" --component rustfmt --component clippy; \
|
sh /tmp/rustup-init.sh -y --no-modify-path --profile minimal --default-toolchain "${RUST_TOOLCHAIN}" --component rustfmt --component clippy; \
|
||||||
rm -f /tmp/rustup-init.sh; \
|
rm -f /tmp/rustup-init.sh; \
|
||||||
|
rustup toolchain install "${FREEBSD_RUST_TOOLCHAIN}" --profile minimal; \
|
||||||
|
rustup target add --toolchain "${FREEBSD_RUST_TOOLCHAIN}" x86_64-unknown-freebsd; \
|
||||||
rustc --version; \
|
rustc --version; \
|
||||||
cargo --version
|
cargo --version; \
|
||||||
|
rustc "+${FREEBSD_RUST_TOOLCHAIN}" --version
|
||||||
|
|
||||||
|
COPY --from=freebsd-sysroot /freebsd-sysroot /opt/freebsd-sysroot
|
||||||
|
|
||||||
|
ENV FREEBSD_RUST_TOOLCHAIN=${FREEBSD_RUST_TOOLCHAIN} \
|
||||||
|
CARGO_TARGET_X86_64_UNKNOWN_FREEBSD_LINKER=clang \
|
||||||
|
CC_x86_64_unknown_freebsd=clang \
|
||||||
|
CXX_x86_64_unknown_freebsd=clang++ \
|
||||||
|
AR_x86_64_unknown_freebsd=llvm-ar \
|
||||||
|
RANLIB_x86_64_unknown_freebsd=llvm-ranlib \
|
||||||
|
CFLAGS_x86_64_unknown_freebsd="--target=x86_64-unknown-freebsd15.1 --sysroot=/opt/freebsd-sysroot" \
|
||||||
|
CXXFLAGS_x86_64_unknown_freebsd="--target=x86_64-unknown-freebsd15.1 --sysroot=/opt/freebsd-sysroot" \
|
||||||
|
CARGO_TARGET_X86_64_UNKNOWN_FREEBSD_RUSTFLAGS="-C link-arg=--target=x86_64-unknown-freebsd15.1 -C link-arg=--sysroot=/opt/freebsd-sysroot -C link-arg=-fuse-ld=lld"
|
||||||
|
|
||||||
# Registry/git/target cache is persistent, while rustup/toolchains stay in the image.
|
# Registry/git/target cache is persistent, while rustup/toolchains stay in the image.
|
||||||
ENV CARGO_HOME=/var/lib/package-builder/cargo
|
ENV CARGO_HOME=/var/lib/package-builder/cargo
|
||||||
|
|
||||||
COPY scripts/ /opt/rust-package-builder/scripts/
|
COPY scripts/ /opt/rust-package-builder/scripts/
|
||||||
RUN chmod 0755 /opt/rust-package-builder/scripts/*.sh
|
COPY opnsense/ /opt/rust-package-builder/opnsense/
|
||||||
|
RUN chmod 0755 /opt/rust-package-builder/scripts/*.sh /opt/rust-package-builder/scripts/*.py
|
||||||
|
|
||||||
VOLUME ["/var/lib/package-builder"]
|
VOLUME ["/var/lib/package-builder"]
|
||||||
ENTRYPOINT ["/opt/rust-package-builder/scripts/entrypoint.sh"]
|
ENTRYPOINT ["/opt/rust-package-builder/scripts/entrypoint.sh"]
|
||||||
@@ -1,160 +1,159 @@
|
|||||||
# Rust Package Builder
|
# RustProxy unified package builder
|
||||||
|
|
||||||
Minimalny automat Docker, który cyklicznie sprawdza branch Git, buduje projekt Rust i publikuje podpisane repozytoria APT oraz RPM/DNF. Projekt zawiera również mały, utwardzony serwer HTTP udostępniający gotowe repozytorium i stronę z instrukcją instalacji.
|
Jeden kontener buildera buduje z jednego commita RustProxy:
|
||||||
|
|
||||||
## Obsługiwane cele
|
- Linux x86_64: DEB + podpisane repo APT,
|
||||||
|
- Linux x86_64: RPM + podpisane repo DNF/YUM,
|
||||||
|
- FreeBSD x86_64: cross-build bez VM FreeBSD,
|
||||||
|
- OPNsense: `os-rustproxy-<version>_<revision>.pkg` + statyczne repo `pkg`.
|
||||||
|
|
||||||
- architektura: amd64 / x86_64
|
Nie ma Docker-in-Docker, SCP ani osobnego watchera repo OPNsense. FreeBSD sysroot, clang/lld i Rust target są częścią głównego obrazu buildera. OPNsense dostaje osobny publiczny adres, ale wszystkie artefakty są publikowane jednym atomowym przełączeniem `repository/current`.
|
||||||
- Debian 12, Debian 13
|
|
||||||
- Ubuntu 24.04, Ubuntu 26.04
|
|
||||||
- RHEL/Rocky/Alma 9 i 10
|
|
||||||
|
|
||||||
Obraz buildera bazuje na Rocky Linux 9. Dzięki temu binarka jest budowana na glibc 2.34; builder dodatkowo odrzuca wynik wymagający nowszego symbolu glibc. Ta sama binarka może być pakowana do DEB i RPM dla wymienionych, nowszych systemów x86_64.
|
## Szybki start
|
||||||
|
|
||||||
## RustProxy
|
|
||||||
|
|
||||||
Dla dostarczonego RustProxy builder automatycznie używa plików znajdujących się w repozytorium źródłowym:
|
|
||||||
|
|
||||||
- `debian/rustproxy.service`
|
|
||||||
- `debian/rustproxy.environment`
|
|
||||||
- `debian/rustproxy.logrotate`
|
|
||||||
- `debian/rustproxy.tmpfiles`
|
|
||||||
- logiki `debian/rustproxy.postinst`
|
|
||||||
- `grafana/rustproxy-overview.json`
|
|
||||||
|
|
||||||
Nie utrzymuje drugiej kopii konfiguracji systemd RustProxy. Wersja i nazwa binarki pochodzą z metadanych Cargo, a nazwa pakietu i maintainer są przejmowane z `debian/control`, jeśli plik istnieje.
|
|
||||||
|
|
||||||
## Uruchomienie
|
|
||||||
|
|
||||||
Ustawiasz ręcznie tylko dwie wartości:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
nano .env
|
nano .env
|
||||||
|
./start.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Przykład dla serwera `192.168.1.100`:
|
Minimalnie ustaw:
|
||||||
|
|
||||||
```env
|
```env
|
||||||
SOURCE_REPO=https://git.linuxiarz.pl/gru/RustProxy.git
|
SOURCE_REPO=https://git.linuxiarz.pl/gru/RustProxy.git
|
||||||
REPOSITORY_URL=http://192.168.1.100
|
REPOSITORY_URL=http://192.168.1.100
|
||||||
|
OPNSENSE_REPOSITORY_URL=https://opn-rustproxy.linuxiarz.pl
|
||||||
```
|
```
|
||||||
|
|
||||||
Następnie:
|
Dla `http://` bez portu `start.sh` dopisze domyślny port. Dla `https://` zakładany jest zewnętrzny reverse proxy/TLS.
|
||||||
|
|
||||||
```bash
|
Domyślne lokalne porty:
|
||||||
./start.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Jeżeli w `REPOSITORY_URL` nie podasz portu, `start.sh` automatycznie ustawi `18765` i zapisze efektywny adres, np.:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
http://192.168.1.100:18765
|
APT/RPM: 18765 -> nginx :8080
|
||||||
|
OPNsense: 18766 -> nginx :8081
|
||||||
```
|
```
|
||||||
|
|
||||||
Jeżeli chcesz inny port, wystarczy wpisać go bezpośrednio w adresie:
|
Przykładowo reverse proxy może kierować:
|
||||||
|
|
||||||
```env
|
|
||||||
REPOSITORY_URL=http://192.168.1.100:23456
|
|
||||||
```
|
|
||||||
|
|
||||||
Nie trzeba ustawiać osobnego `HTTP_PORT` ręcznie — skrypt pobierze go z adresu. `HTTP_PORT` jest zapisywany pomocniczo do `.env`, aby Compose zachował tę samą konfigurację.
|
|
||||||
|
|
||||||
`start.sh` automatycznie:
|
|
||||||
|
|
||||||
- sprawdza Docker i Docker Compose,
|
|
||||||
- tworzy katalogi `data/` i `gpg/`,
|
|
||||||
- buduje obraz buildera,
|
|
||||||
- przy pierwszym uruchomieniu generuje trwały klucz GPG RSA 3072 bez hasła,
|
|
||||||
- zapisuje klucz prywatny jako `gpg/private-key.asc`, publiczny jako `gpg/public-key.asc` oraz fingerprint jako `gpg/fingerprint`,
|
|
||||||
- przy kolejnych uruchomieniach używa tego samego klucza,
|
|
||||||
- uruchamia builder i mikroserwer HTTP,
|
|
||||||
- builder natychmiast sprawdza repozytorium Git i po udanym buildzie publikuje APT oraz RPM/DNF,
|
|
||||||
- strona główna HTTP automatycznie pokazuje instrukcję instalacji i linki do klucza/metadanych.
|
|
||||||
|
|
||||||
Można też uruchomić bez wcześniejszego tworzenia `.env`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
SOURCE_REPO=https://git.linuxiarz.pl/gru/RustProxy.git \
|
|
||||||
REPOSITORY_URL=http://192.168.1.100 \
|
|
||||||
./start.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Mikro HTTP
|
|
||||||
|
|
||||||
Repozytorium jest wystawiane przez osobny kontener `repository-http` oparty o `nginxinc/nginx-unprivileged:stable-alpine-slim`.
|
|
||||||
|
|
||||||
Domyślnie:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
http://ADRES_SERWERA:18765/
|
packages.example.net -> http://builder:18765
|
||||||
|
opn-rustproxy.linuxiarz.pl -> http://builder:18766
|
||||||
```
|
```
|
||||||
|
|
||||||
Serwer HTTP jest celowo bardzo prosty:
|
## Pipeline
|
||||||
|
|
||||||
- działa jako użytkownik nieuprzywilejowany,
|
|
||||||
- kontener ma `cap_drop: ALL`,
|
|
||||||
- ma `no-new-privileges`,
|
|
||||||
- root filesystem jest read-only,
|
|
||||||
- repozytorium jest montowane read-only,
|
|
||||||
- obsługuje wyłącznie `GET` i `HEAD`,
|
|
||||||
- nie pozwala na upload,
|
|
||||||
- nie pokazuje listingów katalogów,
|
|
||||||
- blokuje pliki ukryte,
|
|
||||||
- dodaje podstawowe nagłówki bezpieczeństwa,
|
|
||||||
- posiada `/healthz`,
|
|
||||||
- logi idą do `docker compose logs`.
|
|
||||||
|
|
||||||
Przed pierwszym udanym buildem `/` wyświetla prostą informację, że repozytorium jest inicjalizowane. Po publikacji builder atomowo podmienia stronę na właściwą instrukcję instalacji.
|
|
||||||
|
|
||||||
### HTTPS
|
|
||||||
|
|
||||||
Wbudowany mikroserwer świadomie obsługuje tylko HTTP. Jeżeli `REPOSITORY_URL` zaczyna się od `https://`, zakładane jest, że przed nim stoi zewnętrzny reverse proxy/TLS (np. RustProxy/nginx/Caddy). Sam builder nadal wystawia lokalny HTTP na porcie `18765` lub `HTTP_PORT`.
|
|
||||||
|
|
||||||
Dla kompletnego uruchomienia bez reverse proxy użyj `http://IP_LUB_HOST`.
|
|
||||||
|
|
||||||
## Klucz GPG
|
|
||||||
|
|
||||||
Klucz jest generowany tylko wtedy, gdy `gpg/private-key.asc` nie istnieje. Nie usuwaj katalogu `gpg/` po publikacji repozytorium: zmiana klucza spowoduje, że klienci z wcześniej zainstalowanym kluczem przestaną ufać nowym podpisom.
|
|
||||||
|
|
||||||
Do backupu zachowaj co najmniej:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
gpg/private-key.asc
|
Git RustProxy
|
||||||
gpg/public-key.asc
|
|
|
||||||
gpg/fingerprint
|
+-> cargo test
|
||||||
data/
|
+-> Linux cargo build
|
||||||
|
| +-> DEB
|
||||||
|
| +-> RPM
|
||||||
|
|
|
||||||
|
+-> izolowana kopia źródła
|
||||||
|
+-> poprawki FreeBSD/OPNsense
|
||||||
|
+-> x86_64-unknown-freebsd
|
||||||
|
+-> os-rustproxy.pkg
|
||||||
|
|
|
||||||
|
+-> APT metadata
|
||||||
|
+-> RPM metadata
|
||||||
|
+-> OPNsense pkg metadata
|
||||||
|
+-> walidacja
|
||||||
|
+-> atomic switch repository/current
|
||||||
|
```
|
||||||
|
|
||||||
|
Jeżeli którykolwiek etap się nie powiedzie, publiczne repozytoria nie są przełączane.
|
||||||
|
|
||||||
|
## OPNsense repo
|
||||||
|
|
||||||
|
Publiczny root `OPNSENSE_REPOSITORY_URL` zawiera m.in.:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/
|
||||||
|
├── meta.conf
|
||||||
|
├── packagesite.pkg
|
||||||
|
├── data.pkg
|
||||||
|
├── RustProxy.conf
|
||||||
|
├── BUILD-INFO.txt
|
||||||
|
├── SHA256
|
||||||
|
├── os-rustproxy-latest.pkg
|
||||||
|
└── 26.7/
|
||||||
|
└── amd64/
|
||||||
|
├── os-rustproxy-0.20.0_123.pkg
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`RustProxy.conf` jest generowany automatycznie i wskazuje na `OPNSENSE_REPOSITORY_URL`. Repo OPNsense jest obecnie niesygnowane, zgodnie z wcześniejszym builderem/repo (`signature_type: "none"`). Repo APT/RPM pozostaje podpisane GPG.
|
||||||
|
|
||||||
|
Ręczna konfiguracja na OPNsense:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p /usr/local/etc/pkg/repos
|
||||||
|
fetch -o /usr/local/etc/pkg/repos/RustProxy.conf \
|
||||||
|
https://opn-rustproxy.linuxiarz.pl/RustProxy.conf
|
||||||
|
pkg update -f
|
||||||
|
pkg install os-rustproxy
|
||||||
```
|
```
|
||||||
|
|
||||||
## Konfiguracja
|
## Konfiguracja
|
||||||
|
|
||||||
Wymagane:
|
Najważniejsze zmienne:
|
||||||
|
|
||||||
```text
|
```env
|
||||||
SOURCE_REPO
|
SOURCE_REPO=https://git.linuxiarz.pl/gru/RustProxy.git
|
||||||
REPOSITORY_URL
|
|
||||||
```
|
|
||||||
|
|
||||||
Domyślne / automatyczne:
|
|
||||||
|
|
||||||
```text
|
|
||||||
SOURCE_BRANCH=master
|
SOURCE_BRANCH=master
|
||||||
POLL_INTERVAL=300
|
|
||||||
|
REPOSITORY_URL=http://192.168.1.100
|
||||||
HTTP_PORT=18765
|
HTTP_PORT=18765
|
||||||
REPO_DISTRIBUTION=stable
|
|
||||||
REPO_COMPONENT=main
|
OPNSENSE_REPOSITORY_URL=https://opn-rustproxy.linuxiarz.pl
|
||||||
|
OPNSENSE_HTTP_PORT=18766
|
||||||
|
|
||||||
|
POLL_INTERVAL=300
|
||||||
PACKAGE_RELEASE=auto
|
PACKAGE_RELEASE=auto
|
||||||
MAX_GLIBC_VERSION=2.34
|
MAX_GLIBC_VERSION=2.34
|
||||||
GPG_PRIVATE_KEY_FILE=/run/secrets/gpg/private-key.asc
|
RUN_LINUX_TESTS=1
|
||||||
GPG_PASSPHRASE_FILE=/run/secrets/gpg/passphrase
|
|
||||||
|
OPNSENSE_ABI=26.7
|
||||||
|
OPNSENSE_RETENTION=10
|
||||||
|
FREEBSD_RELEASE=15.1-RELEASE
|
||||||
|
FREEBSD_ABI=FreeBSD:15:amd64
|
||||||
|
FREEBSD_ARCH=freebsd:15:x86:64
|
||||||
|
FREEBSD_RUST_TOOLCHAIN=1.88.0
|
||||||
|
APPLY_FREEBSD_PATCHES=1
|
||||||
```
|
```
|
||||||
|
|
||||||
`GPG_PASSPHRASE_FILE` nie musi istnieć dla automatycznie generowanego klucza bez hasła. `GPG_KEY_ID` może być podany przez ENV; jeśli go brak, builder wybiera pierwszy zaimportowany klucz prywatny.
|
`OPNSENSE_RETENTION=10` zachowuje ostatnie 10 wersji pakietu dla danej serii/architektury OPNsense. `0` wyłącza usuwanie starych wersji.
|
||||||
|
|
||||||
## Dane trwałe
|
## FreeBSD cross-build
|
||||||
|
|
||||||
|
Obraz buildera pobiera i weryfikuje `base.txz` dla `FREEBSD_RELEASE`, a następnie używa:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/var/lib/package-builder/
|
/opt/freebsd-sysroot
|
||||||
|
clang / lld / llvm
|
||||||
|
Rust target x86_64-unknown-freebsd
|
||||||
|
```
|
||||||
|
|
||||||
|
Poprawki tylko dla builda OPNsense są w:
|
||||||
|
|
||||||
|
```text
|
||||||
|
opnsense/patches/<wersja-RustProxy>/*.patch
|
||||||
|
```
|
||||||
|
|
||||||
|
Są nakładane na izolowaną kopię źródła. Build Linux nie jest modyfikowany.
|
||||||
|
|
||||||
|
Kod pluginu OPNsense znajduje się w:
|
||||||
|
|
||||||
|
```text
|
||||||
|
opnsense/plugin/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dane
|
||||||
|
|
||||||
|
```text
|
||||||
|
data/
|
||||||
├── aptly/
|
├── aptly/
|
||||||
├── cargo/
|
├── cargo/
|
||||||
├── packages/
|
├── packages/
|
||||||
@@ -164,69 +163,33 @@ GPG_PASSPHRASE_FILE=/run/secrets/gpg/passphrase
|
|||||||
│ ├── current -> releases/<build>
|
│ ├── current -> releases/<build>
|
||||||
│ ├── apt -> current/apt
|
│ ├── apt -> current/apt
|
||||||
│ ├── rpm -> current/rpm
|
│ ├── rpm -> current/rpm
|
||||||
│ ├── repo-key.gpg -> current/repo-key.gpg
|
│ ├── opnsense -> current/opnsense
|
||||||
│ ├── index.html -> current/index.html
|
|
||||||
│ └── releases/
|
│ └── releases/
|
||||||
├── state/
|
├── state/
|
||||||
│ ├── last_commit
|
|
||||||
│ ├── last_version
|
|
||||||
│ └── last_package_release
|
|
||||||
└── work/
|
└── work/
|
||||||
```
|
```
|
||||||
|
|
||||||
Poprzednie wersje pakietów nie są usuwane. Aktualne repozytoria APT i RPM zawierają historię pakietów. Przy `PACKAGE_RELEASE=auto` nowy commit z niezmienionym `Cargo.toml version` dostaje kolejną rewizję paczki (`-2`, `-3`, ...), więc nie nadpisuje wcześniej opublikowanego artefaktu.
|
Jeden release zawiera jednocześnie `apt/`, `rpm/` i `opnsense/`. Dzięki temu oba publiczne adresy przechodzą na nową wersję jednocześnie.
|
||||||
|
|
||||||
## Publikacja bez uszkodzenia działającego repo
|
## HTTP i logi
|
||||||
|
|
||||||
Nowy commit przechodzi kolejno:
|
Jeden kontener nginx obsługuje oba repozytoria na dwóch lokalnych portach:
|
||||||
|
|
||||||
```text
|
|
||||||
clone -> cargo build --release -> nFPM DEB/RPM -> podpisy -> aptly -> createrepo_c -> walidacja -> atomic switch
|
|
||||||
```
|
|
||||||
|
|
||||||
Publiczny symlink `repository/current` jest podmieniany dopiero na końcu. Błąd kompilacji, pakowania, podpisu lub generowania metadata nie przełącza publicznego repozytorium. `last_commit` jest zapisywany dopiero po udanej publikacji, więc nieudany commit zostanie ponowiony w następnym cyklu.
|
|
||||||
|
|
||||||
Kolejne snapshoty w `repository/releases/` używają hardlinków do niezmienionych plików poprzedniego snapshotu.
|
|
||||||
|
|
||||||
## Podpisy
|
|
||||||
|
|
||||||
- DEB: podpis GPG nFPM (`_gpgorigin`) oraz podpisane `Release.gpg`/`InRelease` przez aptly.
|
|
||||||
- RPM: podpis GPG pakietu nFPM.
|
|
||||||
- RPM metadata: `repodata/repomd.xml.asc`; instrukcja używa `repo_gpgcheck=1` i `gpgcheck=1`.
|
|
||||||
- publiczny klucz: `http(s)://.../repo-key.gpg` jako ASCII-armored OpenPGP, zgodny z `gpg --dearmor` i `rpm --import`.
|
|
||||||
|
|
||||||
## Logi i kontrola
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose logs -f rust-package-builder
|
docker compose logs -f rust-package-builder
|
||||||
docker compose logs -f repository-http
|
docker compose logs -f repository-http
|
||||||
|
|
||||||
curl -f http://127.0.0.1:18765/healthz
|
curl -f http://127.0.0.1:18765/healthz
|
||||||
|
curl -f http://127.0.0.1:18766/healthz
|
||||||
```
|
```
|
||||||
|
|
||||||
## Test plików dostarczonego RustProxy
|
## Testy statyczne
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./tests/static.sh
|
./tests/static.sh
|
||||||
./tests/check-rustproxy-source.sh /sciezka/do/RustProxy
|
./tests/check-rustproxy-source.sh /sciezka/do/RustProxy
|
||||||
```
|
```
|
||||||
|
|
||||||
## Własny klucz GPG (opcjonalnie)
|
## Klucz GPG
|
||||||
|
|
||||||
Jeżeli zamiast automatycznie generowanego klucza chcesz użyć własnego, umieść go przed pierwszym `./start.sh` jako:
|
`./start.sh` generuje trwały klucz GPG przy pierwszym uruchomieniu. Katalogu `gpg/` nie należy usuwać po publikacji APT/RPM.
|
||||||
|
|
||||||
```text
|
|
||||||
gpg/private-key.asc
|
|
||||||
```
|
|
||||||
|
|
||||||
Przykładowy eksport:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gpg --armor --export-secret-keys YOUR_KEY_ID > gpg/private-key.asc
|
|
||||||
chmod 600 gpg/private-key.asc
|
|
||||||
```
|
|
||||||
|
|
||||||
`start.sh` wykryje istniejący klucz i nie wygeneruje nowego. Klucz prywatny nie jest kopiowany do obrazu. Kontener otrzymuje katalog `gpg/` wyłącznie jako read-only mount `/run/secrets/gpg`.
|
|
||||||
|
|
||||||
## Rocky Linux 9 / pakiety bazowe
|
|
||||||
|
|
||||||
Dockerfile używa `dnf --allowerasing`, ponieważ obrazy Rocky Linux 9 mogą zawierać `coreutils-single` i `curl-minimal`. Podczas budowy są one zastępowane pełnymi pakietami `coreutils` i `curl`, wymaganymi przez builder.
|
|
||||||
@@ -2,12 +2,29 @@ services:
|
|||||||
rust-package-builder:
|
rust-package-builder:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
|
args:
|
||||||
|
FREEBSD_RELEASE: "${FREEBSD_RELEASE:-15.1-RELEASE}"
|
||||||
|
FREEBSD_RUST_TOOLCHAIN: "${FREEBSD_RUST_TOOLCHAIN:-1.88.0}"
|
||||||
image: linuxiarz/rust-package-builder:latest
|
image: linuxiarz/rust-package-builder:latest
|
||||||
environment:
|
environment:
|
||||||
SOURCE_REPO: "${SOURCE_REPO:?SOURCE_REPO is required}"
|
SOURCE_REPO: "${SOURCE_REPO:?SOURCE_REPO is required}"
|
||||||
SOURCE_BRANCH: "${SOURCE_BRANCH:-master}"
|
SOURCE_BRANCH: "${SOURCE_BRANCH:-master}"
|
||||||
REPOSITORY_URL: "${REPOSITORY_URL:?REPOSITORY_URL is required}"
|
REPOSITORY_URL: "${REPOSITORY_URL:?REPOSITORY_URL is required}"
|
||||||
|
OPNSENSE_REPOSITORY_URL: "${OPNSENSE_REPOSITORY_URL:?OPNSENSE_REPOSITORY_URL is required}"
|
||||||
POLL_INTERVAL: "${POLL_INTERVAL:-300}"
|
POLL_INTERVAL: "${POLL_INTERVAL:-300}"
|
||||||
|
REPO_DISTRIBUTION: "${REPO_DISTRIBUTION:-stable}"
|
||||||
|
REPO_COMPONENT: "${REPO_COMPONENT:-main}"
|
||||||
|
PACKAGE_RELEASE: "${PACKAGE_RELEASE:-auto}"
|
||||||
|
MAX_GLIBC_VERSION: "${MAX_GLIBC_VERSION:-2.34}"
|
||||||
|
KEEP_WORKSPACE: "${KEEP_WORKSPACE:-0}"
|
||||||
|
OPNSENSE_ABI: "${OPNSENSE_ABI:-26.7}"
|
||||||
|
OPNSENSE_RETENTION: "${OPNSENSE_RETENTION:-10}"
|
||||||
|
FREEBSD_RELEASE: "${FREEBSD_RELEASE:-15.1-RELEASE}"
|
||||||
|
FREEBSD_ABI: "${FREEBSD_ABI:-FreeBSD:15:amd64}"
|
||||||
|
FREEBSD_ARCH: "${FREEBSD_ARCH:-freebsd:15:x86:64}"
|
||||||
|
FREEBSD_RUST_TOOLCHAIN: "${FREEBSD_RUST_TOOLCHAIN:-1.88.0}"
|
||||||
|
APPLY_FREEBSD_PATCHES: "${APPLY_FREEBSD_PATCHES:-1}"
|
||||||
|
RUN_LINUX_TESTS: "${RUN_LINUX_TESTS:-1}"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/var/lib/package-builder
|
- ./data:/var/lib/package-builder
|
||||||
- ./gpg:/run/secrets/gpg:ro
|
- ./gpg:/run/secrets/gpg:ro
|
||||||
@@ -17,6 +34,7 @@ services:
|
|||||||
image: nginxinc/nginx-unprivileged:stable-alpine-slim
|
image: nginxinc/nginx-unprivileged:stable-alpine-slim
|
||||||
ports:
|
ports:
|
||||||
- "0.0.0.0:${HTTP_PORT:-18765}:8080"
|
- "0.0.0.0:${HTTP_PORT:-18765}:8080"
|
||||||
|
- "0.0.0.0:${OPNSENSE_HTTP_PORT:-18766}:8081"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/repository:/srv/repository:ro
|
- ./data/repository:/srv/repository:ro
|
||||||
- ./http/nginx.conf:/etc/nginx/nginx.conf:ro
|
- ./http/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
|||||||
+46
-5
@@ -23,6 +23,7 @@ http {
|
|||||||
uwsgi_temp_path /tmp/uwsgi_temp;
|
uwsgi_temp_path /tmp/uwsgi_temp;
|
||||||
scgi_temp_path /tmp/scgi_temp;
|
scgi_temp_path /tmp/scgi_temp;
|
||||||
|
|
||||||
|
# Linux APT/RPM repository.
|
||||||
server {
|
server {
|
||||||
listen 8080 default_server;
|
listen 8080 default_server;
|
||||||
server_name _;
|
server_name _;
|
||||||
@@ -34,8 +35,6 @@ http {
|
|||||||
add_header X-Content-Type-Options "nosniff" always;
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
add_header X-Frame-Options "DENY" always;
|
add_header X-Frame-Options "DENY" always;
|
||||||
add_header Referrer-Policy "no-referrer" always;
|
add_header Referrer-Policy "no-referrer" always;
|
||||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
|
||||||
add_header Content-Security-Policy "default-src 'none'; style-src 'unsafe-inline'; img-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'" always;
|
|
||||||
|
|
||||||
if ($request_method !~ ^(GET|HEAD)$) {
|
if ($request_method !~ ^(GET|HEAD)$) {
|
||||||
return 405;
|
return 405;
|
||||||
@@ -47,12 +46,54 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location = / {
|
location = / {
|
||||||
try_files /index.html @initializing;
|
try_files /index.html @linux_initializing;
|
||||||
}
|
}
|
||||||
|
|
||||||
location @initializing {
|
location @linux_initializing {
|
||||||
default_type text/html;
|
default_type text/html;
|
||||||
return 200 '<!doctype html><html lang="pl"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Rust Package Repository</title><style>body{font:16px system-ui,sans-serif;max-width:760px;margin:64px auto;padding:0 20px;color:#1f2937}code{background:#f3f4f6;padding:.15rem .35rem;border-radius:.3rem}</style></head><body><h1>Rust Package Repository</h1><p>Repozytorium HTTP działa. Pierwszy build pakietu jeszcze trwa albo nie został zakończony.</p><p>Stan buildera sprawdzisz przez <code>docker compose logs -f rust-package-builder</code>.</p></body></html>';
|
return 200 '<!doctype html><html><head><meta charset="utf-8"><title>RustProxy repository</title></head><body><h1>RustProxy Linux repository</h1><p>Repository is being initialized.</p></body></html>';
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ (^|/)\. {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# OPNsense/FreeBSD pkg repository. It has a separate public URL and local port,
|
||||||
|
# but follows the same atomic repository/current switch as the Linux repo.
|
||||||
|
server {
|
||||||
|
listen 8081;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /srv/repository/opnsense;
|
||||||
|
index index.html;
|
||||||
|
autoindex off;
|
||||||
|
|
||||||
|
#add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
#add_header X-Frame-Options "DENY" always;
|
||||||
|
#add_header Referrer-Policy "no-referrer" always;
|
||||||
|
#add_header Cache-Control "no-cache, no-store, must-revalidate" always;
|
||||||
|
|
||||||
|
if ($request_method !~ ^(GET|HEAD)$) {
|
||||||
|
return 405;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /healthz {
|
||||||
|
default_type text/plain;
|
||||||
|
return 200 "ok\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
location = / {
|
||||||
|
try_files /index.html @opnsense_initializing;
|
||||||
|
}
|
||||||
|
|
||||||
|
location @opnsense_initializing {
|
||||||
|
default_type text/html;
|
||||||
|
return 200 '<!doctype html><html><head><meta charset="utf-8"><title>RustProxy OPNsense repository</title></head><body><h1>RustProxy OPNsense repository</h1><p>Repository is being initialized.</p></body></html>';
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ (^|/)\. {
|
location ~ (^|/)\. {
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
|
||||||
|
index 05e6ca1..fdda5b9 100644
|
||||||
|
--- a/rust-toolchain.toml
|
||||||
|
+++ b/rust-toolchain.toml
|
||||||
|
@@ -1,4 +1,4 @@
|
||||||
|
[toolchain]
|
||||||
|
-channel = "stable"
|
||||||
|
+channel = "1.88.0"
|
||||||
|
profile = "minimal"
|
||||||
|
components = ["rustfmt", "clippy"]
|
||||||
|
diff --git a/src/cache.rs b/src/cache.rs
|
||||||
|
index fcd6f36..82332e5 100644
|
||||||
|
--- a/src/cache.rs
|
||||||
|
+++ b/src/cache.rs
|
||||||
|
@@ -3774,9 +3774,10 @@ mod tests {
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
h.insert("age", HeaderValue::from_static("3"));
|
||||||
|
- let plan = plan(StatusCode::OK, &h, &settings, Duration::from_millis(250)).unwrap();
|
||||||
|
- assert!(plan.initial_age >= Duration::from_secs(10));
|
||||||
|
- assert!(plan.initial_age < Duration::from_secs(12));
|
||||||
|
+ let cache_plan =
|
||||||
|
+ plan(StatusCode::OK, &h, &settings, Duration::from_millis(250)).unwrap();
|
||||||
|
+ assert!(cache_plan.initial_age >= Duration::from_secs(10));
|
||||||
|
+ assert!(cache_plan.initial_age < Duration::from_secs(12));
|
||||||
|
let fresh = plan(
|
||||||
|
StatusCode::OK,
|
||||||
|
&headers("max-age=60"),
|
||||||
|
diff --git a/src/proxy/websocket.rs b/src/proxy/websocket.rs
|
||||||
|
index a25776e..c1d3519 100644
|
||||||
|
--- a/src/proxy/websocket.rs
|
||||||
|
+++ b/src/proxy/websocket.rs
|
||||||
|
@@ -653,7 +653,11 @@ mod tests {
|
||||||
|
.unwrap();
|
||||||
|
request
|
||||||
|
.extensions_mut()
|
||||||
|
- .insert("websocket".parse::<h3::ext::Protocol>().unwrap());
|
||||||
|
+ .insert(
|
||||||
|
+ "websocket"
|
||||||
|
+ .parse::<h3::ext::Protocol>()
|
||||||
|
+ .unwrap_or_else(|_| panic!("websocket is a valid HTTP/3 protocol token")),
|
||||||
|
+ );
|
||||||
|
request
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
@@ -702,7 +706,11 @@ mod tests {
|
||||||
|
let mut request = h3_request();
|
||||||
|
request
|
||||||
|
.extensions_mut()
|
||||||
|
- .insert("connect-udp".parse::<h3::ext::Protocol>().unwrap());
|
||||||
|
+ .insert(
|
||||||
|
+ "connect-udp"
|
||||||
|
+ .parse::<h3::ext::Protocol>()
|
||||||
|
+ .unwrap_or_else(|_| panic!("connect-udp is a valid HTTP/3 protocol token")),
|
||||||
|
+ );
|
||||||
|
assert!(validate_h3(&request).is_err());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
diff --git a/tests/http3.rs b/tests/http3.rs
|
||||||
|
index c5990bc..6f775ea 100644
|
||||||
|
--- a/tests/http3.rs
|
||||||
|
+++ b/tests/http3.rs
|
||||||
|
@@ -14,7 +14,11 @@ fn rfc9220_extended_connect_is_recognized() {
|
||||||
|
.unwrap();
|
||||||
|
request
|
||||||
|
.extensions_mut()
|
||||||
|
- .insert("websocket".parse::<h3::ext::Protocol>().unwrap());
|
||||||
|
+ .insert(
|
||||||
|
+ "websocket"
|
||||||
|
+ .parse::<h3::ext::Protocol>()
|
||||||
|
+ .unwrap_or_else(|_| panic!("websocket is a valid HTTP/3 protocol token")),
|
||||||
|
+ );
|
||||||
|
|
||||||
|
assert_eq!(h3_protocol(&request), Some("websocket"));
|
||||||
|
let handshake = validate_h3(&request).expect("valid RFC 9220 bootstrap");
|
||||||
Executable
+47
@@ -0,0 +1,47 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Keep the historical service account so package downgrades remain possible,
|
||||||
|
# but current OPNsense runtime executes RustProxy as root (see rc.d script).
|
||||||
|
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 root -g wheel -m 0700 /var/db/rustproxy
|
||||||
|
/usr/sbin/chown -R root:wheel /var/db/rustproxy
|
||||||
|
/bin/chmod 0700 /var/db/rustproxy
|
||||||
|
/usr/bin/install -d -o root -g wheel -m 0750 /var/log/rustproxy
|
||||||
|
/usr/bin/install -d -o root -g wheel -m 0750 /var/run/rustproxy
|
||||||
|
/usr/bin/install -d -o root -g wheel -m 0755 /usr/local/etc/rustproxy
|
||||||
|
|
||||||
|
# configd discovers actions only on startup/reload. A direct `pkg add` must
|
||||||
|
# therefore refresh configd before the MVC service controller can call them.
|
||||||
|
/usr/sbin/service configd restart >/dev/null 2>&1 || true
|
||||||
|
/usr/local/sbin/pluginctl -cq cache_flush >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# Apply model migrations before rendering the service template. In particular
|
||||||
|
# 1.4.0 copies the old combined listener values into the split address/port
|
||||||
|
# fields so upgrades keep their existing ports instead of falling back.
|
||||||
|
if [ -x /usr/local/opnsense/mvc/script/run_migrations.php ]; then
|
||||||
|
/usr/local/opnsense/mvc/script/run_migrations.php >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
/usr/local/sbin/configctl template reload OPNsense/RustProxy >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# A package upgrade invokes the previous package pre-deinstall hook, which stops
|
||||||
|
# RustProxy before replacing the binary. Start it again after migrations and
|
||||||
|
# template rendering. Use the installed service helper directly so this does
|
||||||
|
# not depend on configd having finished reloading its action registry. The
|
||||||
|
# helper itself checks enabled=1; on a fresh/default-disabled install this is a
|
||||||
|
# no-op. restart is deliberate: if an old process survived a forced pkg add,
|
||||||
|
# it is stopped before the new binary starts.
|
||||||
|
if [ -x /usr/local/opnsense/scripts/rustproxy/service.sh ]; then
|
||||||
|
if ! /usr/local/opnsense/scripts/rustproxy/service.sh restart; then
|
||||||
|
echo "WARNING: RustProxy package installed but the service could not be started." >&2
|
||||||
|
echo "Check /var/log/rustproxy/rustproxy.log and run: configctl rustproxy start" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# pkg upgrade runs the old package pre-deinstall hook before installing the new
|
||||||
|
# binary. Stop the supervised child first so the upgrade never leaves an old
|
||||||
|
# RustProxy process running from an unlinked executable. configctl is preferred
|
||||||
|
# on OPNsense; the rc.d fallback also works while configd is restarting.
|
||||||
|
if ! /usr/local/sbin/configctl rustproxy stop >/dev/null 2>&1; then
|
||||||
|
if [ -x /usr/local/etc/rc.d/rustproxy ]; then
|
||||||
|
/usr/local/etc/rc.d/rustproxy onestop >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
PLUGIN_NAME= rustproxy
|
||||||
|
PLUGIN_VERSION?= 0.23.3
|
||||||
|
PLUGIN_REVISION?= 1
|
||||||
|
PLUGIN_COMMENT= RustProxy reverse proxy service
|
||||||
|
PLUGIN_MAINTAINER?= local@localhost
|
||||||
|
PLUGIN_WWW?= https://git.linuxiarz.pl/gru/RustProxy
|
||||||
|
PLUGIN_LICENSE= UNKNOWN
|
||||||
|
|
||||||
|
.include "../../Mk/plugins.mk"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
RustProxy packaged as an OPNsense service plugin.
|
||||||
|
|
||||||
|
The OPNsense UI exposes service enable/disable, separate bind-address and
|
||||||
|
port fields for HTTP, HTTPS/TLS and administration, plus remote-admin opt-in.
|
||||||
Executable
+276
@@ -0,0 +1,276 @@
|
|||||||
|
#!/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 on OPNsense/FreeBSD runs as root. In practice the application
|
||||||
|
# needs access to its complete data/certificate state and service startup was
|
||||||
|
# unreliable after daemon(8) dropped privileges to the dedicated account.
|
||||||
|
: ${rustproxy_user:="root"}
|
||||||
|
: ${rustproxy_group:="wheel"}
|
||||||
|
: ${rustproxy_data_dir:="/var/db/rustproxy"}
|
||||||
|
: ${rustproxy_log_dir:="/var/log/rustproxy"}
|
||||||
|
: ${rustproxy_log_file:="${rustproxy_log_dir}/rustproxy.log"}
|
||||||
|
: ${rustproxy_run_dir:="/var/run/rustproxy"}
|
||||||
|
: ${rustproxy_command:="/usr/local/sbin/rust-proxy"}
|
||||||
|
: ${rustproxy_restart_delay:="5"}
|
||||||
|
: ${rustproxy_allow_remote_admin:="1"}
|
||||||
|
# FreeBSD uses jemalloc rather than glibc. Keep the same low-arena intent as
|
||||||
|
# Linux MALLOC_ARENA_MAX=2 with the native jemalloc knob. rc.conf can override
|
||||||
|
# rustproxy_malloc_conf or set it to an empty value to disable the tuning.
|
||||||
|
if [ "${rustproxy_malloc_conf+x}" != "x" ]; then
|
||||||
|
rustproxy_malloc_conf="${MALLOC_CONF:-narenas:2}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Split listener settings written by the current OPNsense model.
|
||||||
|
: ${rustproxy_proxy_address:=""}
|
||||||
|
: ${rustproxy_proxy_port:=""}
|
||||||
|
: ${rustproxy_tls_enabled:="1"}
|
||||||
|
: ${rustproxy_tls_address:=""}
|
||||||
|
: ${rustproxy_tls_port:=""}
|
||||||
|
: ${rustproxy_admin_address:=""}
|
||||||
|
: ${rustproxy_admin_port:=""}
|
||||||
|
|
||||||
|
# Combined listener settings used by plugin model 1.2.x and earlier.
|
||||||
|
: ${rustproxy_proxy_listen_legacy:=""}
|
||||||
|
: ${rustproxy_tls_listen_legacy:=""}
|
||||||
|
: ${rustproxy_admin_listen_legacy:=""}
|
||||||
|
|
||||||
|
# Old rc.conf-style variable names are also accepted for hand-written configs.
|
||||||
|
: ${rustproxy_proxy_listen:=""}
|
||||||
|
: ${rustproxy_tls_listen:=""}
|
||||||
|
: ${rustproxy_admin_listen:=""}
|
||||||
|
|
||||||
|
enabled="0"
|
||||||
|
config_file="/usr/local/etc/rustproxy/opnsense.conf"
|
||||||
|
if [ -r "$config_file" ]; then
|
||||||
|
. "$config_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$enabled" = "1" ]; then
|
||||||
|
rustproxy_enable="YES"
|
||||||
|
else
|
||||||
|
rustproxy_enable="NO"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rustproxy_valid_port()
|
||||||
|
{
|
||||||
|
case "$1" in
|
||||||
|
''|*[!0-9]*) return 1 ;;
|
||||||
|
esac
|
||||||
|
[ "$1" -ge 1 ] 2>/dev/null && [ "$1" -le 65535 ] 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
rustproxy_valid_listener()
|
||||||
|
{
|
||||||
|
_rp_listener="$1"
|
||||||
|
case "$_rp_listener" in
|
||||||
|
\[*\]:*) _rp_port="${_rp_listener##*:}" ;;
|
||||||
|
*:*) _rp_port="${_rp_listener##*:}" ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
rustproxy_valid_port "$_rp_port"
|
||||||
|
}
|
||||||
|
|
||||||
|
rustproxy_format_listener()
|
||||||
|
{
|
||||||
|
_rp_address="$1"
|
||||||
|
_rp_port="$2"
|
||||||
|
case "$_rp_address" in
|
||||||
|
\[*\]) printf '%s:%s\n' "$_rp_address" "$_rp_port" ;;
|
||||||
|
*:*) printf '[%s]:%s\n' "$_rp_address" "$_rp_port" ;;
|
||||||
|
*) printf '%s:%s\n' "$_rp_address" "$_rp_port" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
rustproxy_resolve_listener()
|
||||||
|
{
|
||||||
|
_rp_address="$1"
|
||||||
|
_rp_port="$2"
|
||||||
|
_rp_legacy="$3"
|
||||||
|
_rp_old_rc="$4"
|
||||||
|
_rp_default_address="$5"
|
||||||
|
_rp_default_port="$6"
|
||||||
|
_rp_name="$7"
|
||||||
|
|
||||||
|
# New split settings are authoritative as soon as either half is set.
|
||||||
|
if [ -n "$_rp_address" ] || [ -n "$_rp_port" ]; then
|
||||||
|
[ -n "$_rp_address" ] || _rp_address="$_rp_default_address"
|
||||||
|
[ -n "$_rp_port" ] || _rp_port="$_rp_default_port"
|
||||||
|
if ! rustproxy_valid_port "$_rp_port"; then
|
||||||
|
echo "Invalid RustProxy ${_rp_name} port: ${_rp_port}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
rustproxy_format_listener "$_rp_address" "$_rp_port"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Preserve a valid listener from plugin 1.2.x during upgrade.
|
||||||
|
if [ -n "$_rp_legacy" ] && rustproxy_valid_listener "$_rp_legacy"; then
|
||||||
|
printf '%s\n' "$_rp_legacy"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Preserve hand-written rc.conf settings as another compatibility path.
|
||||||
|
if [ -n "$_rp_old_rc" ] && rustproxy_valid_listener "$_rp_old_rc"; then
|
||||||
|
printf '%s\n' "$_rp_old_rc"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Malformed legacy values such as "0.0.0.0:" must never reach RustProxy.
|
||||||
|
# Fall back to a known-good listener and let the user set the separate port
|
||||||
|
# field in Services -> RustProxy.
|
||||||
|
rustproxy_format_listener "$_rp_default_address" "$_rp_default_port"
|
||||||
|
}
|
||||||
|
|
||||||
|
rustproxy_proxy_listen_resolved="$(rustproxy_resolve_listener \
|
||||||
|
"$rustproxy_proxy_address" "$rustproxy_proxy_port" \
|
||||||
|
"$rustproxy_proxy_listen_legacy" "$rustproxy_proxy_listen" \
|
||||||
|
"0.0.0.0" "8080" "HTTP")" || exit 1
|
||||||
|
|
||||||
|
rustproxy_tls_listen_resolved="$(rustproxy_resolve_listener \
|
||||||
|
"$rustproxy_tls_address" "$rustproxy_tls_port" \
|
||||||
|
"$rustproxy_tls_listen_legacy" "$rustproxy_tls_listen" \
|
||||||
|
"0.0.0.0" "8181" "HTTPS/TLS")" || exit 1
|
||||||
|
|
||||||
|
rustproxy_admin_listen_resolved="$(rustproxy_resolve_listener \
|
||||||
|
"$rustproxy_admin_address" "$rustproxy_admin_port" \
|
||||||
|
"$rustproxy_admin_listen_legacy" "$rustproxy_admin_listen" \
|
||||||
|
"0.0.0.0" "9090" "admin")" || exit 1
|
||||||
|
|
||||||
|
rustproxy_runtime_args="--data-dir ${rustproxy_data_dir} --admin-listen ${rustproxy_admin_listen_resolved} --update-check-interval-secs 0 serve --proxy-listen ${rustproxy_proxy_listen_resolved}"
|
||||||
|
if [ "$rustproxy_tls_enabled" = "1" ]; then
|
||||||
|
rustproxy_runtime_args="${rustproxy_runtime_args} --tls-listen ${rustproxy_tls_listen_resolved}"
|
||||||
|
fi
|
||||||
|
if [ "$rustproxy_allow_remote_admin" = "1" ]; then
|
||||||
|
rustproxy_runtime_args="${rustproxy_runtime_args} --allow-remote-admin"
|
||||||
|
fi
|
||||||
|
|
||||||
|
pidfile="${rustproxy_run_dir}/daemon.pid"
|
||||||
|
child_pidfile="${rustproxy_run_dir}/rustproxy.pid"
|
||||||
|
command="/usr/sbin/daemon"
|
||||||
|
|
||||||
|
rustproxy_daemon_user_arg=""
|
||||||
|
if [ "$rustproxy_user" != "root" ]; then
|
||||||
|
rustproxy_daemon_user_arg="-u ${rustproxy_user}"
|
||||||
|
fi
|
||||||
|
command_args="-r -R ${rustproxy_restart_delay} -P ${pidfile} -p ${child_pidfile} ${rustproxy_daemon_user_arg} -o ${rustproxy_log_file} ${rustproxy_command} ${rustproxy_runtime_args}"
|
||||||
|
required_files="${rustproxy_command}"
|
||||||
|
start_precmd="rustproxy_prestart"
|
||||||
|
start_postcmd="rustproxy_poststart"
|
||||||
|
stop_postcmd="rustproxy_poststop"
|
||||||
|
status_cmd="rustproxy_status"
|
||||||
|
|
||||||
|
rustproxy_prestart()
|
||||||
|
{
|
||||||
|
# RustProxy on OPNsense/FreeBSD uses jemalloc. MALLOC_ARENA_MAX is a
|
||||||
|
# glibc/Linux setting and has no useful effect here, so export MALLOC_CONF
|
||||||
|
# instead. daemon(8) inherits this environment and passes it to RustProxy.
|
||||||
|
if [ -n "${rustproxy_malloc_conf}" ]; then
|
||||||
|
MALLOC_CONF="${rustproxy_malloc_conf}"
|
||||||
|
export MALLOC_CONF
|
||||||
|
else
|
||||||
|
unset MALLOC_CONF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# /var/run is ephemeral and recreated on boot.
|
||||||
|
/usr/bin/install -d -o "${rustproxy_user}" -g "${rustproxy_group}" -m 0750 "${rustproxy_run_dir}"
|
||||||
|
/usr/sbin/chown "${rustproxy_user}:${rustproxy_group}" "${rustproxy_run_dir}"
|
||||||
|
/bin/chmod 0750 "${rustproxy_run_dir}"
|
||||||
|
|
||||||
|
/usr/bin/install -d -o "${rustproxy_user}" -g "${rustproxy_group}" -m 0700 "${rustproxy_data_dir}"
|
||||||
|
/usr/sbin/chown "${rustproxy_user}:${rustproxy_group}" "${rustproxy_data_dir}"
|
||||||
|
/bin/chmod 0700 "${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}"
|
||||||
|
|
||||||
|
echo "RustProxy listeners: HTTP=${rustproxy_proxy_listen_resolved} TLS=${rustproxy_tls_listen_resolved} ADMIN=${rustproxy_admin_listen_resolved}"
|
||||||
|
if [ -n "${rustproxy_malloc_conf}" ]; then
|
||||||
|
echo "RustProxy allocator: MALLOC_CONF=${rustproxy_malloc_conf}"
|
||||||
|
fi
|
||||||
|
echo "RustProxy exec: ${rustproxy_command} ${rustproxy_runtime_args}"
|
||||||
|
printf '%s\n' "${rustproxy_command} ${rustproxy_runtime_args}" > "${rustproxy_run_dir}/command.txt"
|
||||||
|
/bin/chmod 0640 "${rustproxy_run_dir}/command.txt"
|
||||||
|
}
|
||||||
|
|
||||||
|
rustproxy_child_pid()
|
||||||
|
{
|
||||||
|
[ -r "${child_pidfile}" ] || return 1
|
||||||
|
_rp_pid="$(/bin/cat "${child_pidfile}" 2>/dev/null)"
|
||||||
|
case "${_rp_pid}" in
|
||||||
|
''|*[!0-9]*) return 1 ;;
|
||||||
|
esac
|
||||||
|
/bin/kill -0 "${_rp_pid}" 2>/dev/null || return 1
|
||||||
|
printf '%s\n' "${_rp_pid}"
|
||||||
|
}
|
||||||
|
|
||||||
|
rustproxy_poststart()
|
||||||
|
{
|
||||||
|
# daemon(8) may stay alive with -r even when rust-proxy exits immediately.
|
||||||
|
# Verify the supervised child so service/configd cannot report a false OK.
|
||||||
|
/bin/sleep 1
|
||||||
|
if _rp_pid="$(rustproxy_child_pid)"; then
|
||||||
|
echo "RustProxy child started as PID ${_rp_pid}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "RustProxy failed to stay running; stopping the restart supervisor." >&2
|
||||||
|
if [ -s "${rustproxy_log_file}" ]; then
|
||||||
|
echo "Last RustProxy log lines:" >&2
|
||||||
|
/usr/bin/tail -n 40 "${rustproxy_log_file}" >&2
|
||||||
|
fi
|
||||||
|
if [ -r "${pidfile}" ]; then
|
||||||
|
_rp_supervisor_pid="$(/bin/cat "${pidfile}" 2>/dev/null)"
|
||||||
|
case "${_rp_supervisor_pid}" in
|
||||||
|
''|*[!0-9]*) ;;
|
||||||
|
*) /bin/kill -TERM "${_rp_supervisor_pid}" 2>/dev/null || true ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
/bin/sleep 1
|
||||||
|
/bin/rm -f "${child_pidfile}" "${pidfile}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
rustproxy_status()
|
||||||
|
{
|
||||||
|
if _rp_pid="$(rustproxy_child_pid)"; then
|
||||||
|
echo "rustproxy is running as pid ${_rp_pid}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
_rp_supervisor_pid=""
|
||||||
|
if [ -r "${pidfile}" ]; then
|
||||||
|
_rp_supervisor_pid="$(/bin/cat "${pidfile}" 2>/dev/null)"
|
||||||
|
fi
|
||||||
|
case "${_rp_supervisor_pid}" in
|
||||||
|
''|*[!0-9]*) ;;
|
||||||
|
*)
|
||||||
|
if /bin/kill -0 "${_rp_supervisor_pid}" 2>/dev/null; then
|
||||||
|
echo "rustproxy is not running (daemon supervisor ${_rp_supervisor_pid} is waiting/restarting)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "rustproxy is not running"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
rustproxy_poststop()
|
||||||
|
{
|
||||||
|
/bin/rm -f "${child_pidfile}" "${pidfile}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
@@ -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
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OPNsense\RustProxy\Api;
|
||||||
|
|
||||||
|
use OPNsense\Base\ApiMutableServiceControllerBase;
|
||||||
|
|
||||||
|
class ServiceController extends ApiMutableServiceControllerBase
|
||||||
|
{
|
||||||
|
protected static $internalServiceClass = '\\OPNsense\\RustProxy\\RustProxy';
|
||||||
|
protected static $internalServiceTemplate = 'OPNsense/RustProxy';
|
||||||
|
protected static $internalServiceEnabled = 'general.enabled';
|
||||||
|
protected static $internalServiceName = 'rustproxy';
|
||||||
|
|
||||||
|
protected function reconfigureForceRestart()
|
||||||
|
{
|
||||||
|
// Listener addresses are process startup arguments, so Apply must
|
||||||
|
// always stop and start the service after regenerating the template.
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OPNsense\RustProxy\Api;
|
||||||
|
|
||||||
|
use OPNsense\Base\ApiMutableModelControllerBase;
|
||||||
|
|
||||||
|
class SettingsController extends ApiMutableModelControllerBase
|
||||||
|
{
|
||||||
|
protected static $internalModelClass = 'OPNsense\RustProxy\RustProxy';
|
||||||
|
protected static $internalModelName = 'rustproxy';
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OPNsense\RustProxy;
|
||||||
|
|
||||||
|
class IndexController extends \OPNsense\Base\IndexController
|
||||||
|
{
|
||||||
|
public function indexAction()
|
||||||
|
{
|
||||||
|
$this->view->generalForm = $this->getForm('general');
|
||||||
|
$this->view->pick('OPNsense/RustProxy/index');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<form>
|
||||||
|
<field>
|
||||||
|
<label>General</label>
|
||||||
|
<type>header</type>
|
||||||
|
<static>true</static>
|
||||||
|
</field>
|
||||||
|
<field>
|
||||||
|
<id>rustproxy.general.enabled</id>
|
||||||
|
<label>Enabled</label>
|
||||||
|
<type>checkbox</type>
|
||||||
|
<help>Start RustProxy and keep it enabled after OPNsense restarts.</help>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<label>HTTP listener</label>
|
||||||
|
<type>header</type>
|
||||||
|
<static>true</static>
|
||||||
|
</field>
|
||||||
|
<field>
|
||||||
|
<id>rustproxy.general.proxyAddress</id>
|
||||||
|
<label>Bind address</label>
|
||||||
|
<type>text</type>
|
||||||
|
<help>HTTP bind address. Default: 0.0.0.0. Existing installations are migrated from the legacy combined listener automatically.</help>
|
||||||
|
</field>
|
||||||
|
<field>
|
||||||
|
<id>rustproxy.general.proxyPort</id>
|
||||||
|
<label>Port</label>
|
||||||
|
<type>text</type>
|
||||||
|
<help>HTTP port passed as --proxy-listen. Default: 8080.</help>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<label>HTTPS/TLS listener</label>
|
||||||
|
<type>header</type>
|
||||||
|
<static>true</static>
|
||||||
|
</field>
|
||||||
|
<field>
|
||||||
|
<id>rustproxy.general.tlsEnabled</id>
|
||||||
|
<label>Enable HTTPS/TLS listener</label>
|
||||||
|
<type>checkbox</type>
|
||||||
|
<help>When enabled, pass --tls-listen to RustProxy.</help>
|
||||||
|
</field>
|
||||||
|
<field>
|
||||||
|
<id>rustproxy.general.tlsAddress</id>
|
||||||
|
<label>Bind address</label>
|
||||||
|
<type>text</type>
|
||||||
|
<help>HTTPS/TLS bind address. Default: 0.0.0.0. Existing installations are migrated automatically.</help>
|
||||||
|
</field>
|
||||||
|
<field>
|
||||||
|
<id>rustproxy.general.tlsPort</id>
|
||||||
|
<label>Port</label>
|
||||||
|
<type>text</type>
|
||||||
|
<help>HTTPS/TLS port passed as --tls-listen. Default: 8181.</help>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<label>Administration</label>
|
||||||
|
<type>header</type>
|
||||||
|
<static>true</static>
|
||||||
|
</field>
|
||||||
|
<field>
|
||||||
|
<id>rustproxy.general.adminAddress</id>
|
||||||
|
<label>Bind address</label>
|
||||||
|
<type>text</type>
|
||||||
|
<help>Administration bind address. Default: 0.0.0.0. Existing installations are migrated automatically.</help>
|
||||||
|
</field>
|
||||||
|
<field>
|
||||||
|
<id>rustproxy.general.adminPort</id>
|
||||||
|
<label>Port</label>
|
||||||
|
<type>text</type>
|
||||||
|
<help>Administration port passed as --admin-listen. Default: 9090.</help>
|
||||||
|
</field>
|
||||||
|
<field>
|
||||||
|
<id>rustproxy.general.allowRemoteAdmin</id>
|
||||||
|
<label>Allow remote admin</label>
|
||||||
|
<type>checkbox</type>
|
||||||
|
<help>Pass --allow-remote-admin so the RustProxy panel accepts non-loopback clients. Keep firewall access restricted to trusted networks; do not expose the admin port to WAN unless this is intentional.</help>
|
||||||
|
</field>
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<acl>
|
||||||
|
<page-services-rustproxy>
|
||||||
|
<name>Services: RustProxy</name>
|
||||||
|
<description>Allow access to RustProxy settings and service controls</description>
|
||||||
|
<patterns>
|
||||||
|
<pattern>ui/rustproxy/*</pattern>
|
||||||
|
<pattern>api/rustproxy/*</pattern>
|
||||||
|
</patterns>
|
||||||
|
</page-services-rustproxy>
|
||||||
|
</acl>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<menu>
|
||||||
|
<Services>
|
||||||
|
<RustProxy cssClass="fa fa-exchange fa-fw">
|
||||||
|
<General VisibleName="General" order="10" url="/ui/rustproxy/"/>
|
||||||
|
</RustProxy>
|
||||||
|
</Services>
|
||||||
|
</menu>
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OPNsense\RustProxy\Migrations;
|
||||||
|
|
||||||
|
use OPNsense\Base\BaseModelMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repair the 1.3.x split-listener transition.
|
||||||
|
*
|
||||||
|
* 1.2.x stored listeners as a single IP:port field. The first 1.3.x package
|
||||||
|
* introduced address/port fields without model defaults, so an upgrade could
|
||||||
|
* save fallback/new values while the still-valid legacy values remained in
|
||||||
|
* config.xml. Copy the legacy values once into the split fields so existing
|
||||||
|
* installations keep the exact ports they used before the upgrade.
|
||||||
|
*/
|
||||||
|
class M1_4_0 extends BaseModelMigration
|
||||||
|
{
|
||||||
|
private function splitListener($value)
|
||||||
|
{
|
||||||
|
$value = trim((string)$value);
|
||||||
|
if ($value === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/^\[([^\]]+)\]:(\d+)$/', $value, $matches)) {
|
||||||
|
$address = $matches[1];
|
||||||
|
$port = $matches[2];
|
||||||
|
} else {
|
||||||
|
$separator = strrpos($value, ':');
|
||||||
|
if ($separator === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$address = substr($value, 0, $separator);
|
||||||
|
$port = substr($value, $separator + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($address === '' || !ctype_digit($port)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$number = (int)$port;
|
||||||
|
if ($number < 1 || $number > 65535) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$address, (string)$number];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function migrateListener($general, $legacyField, $addressField, $portField)
|
||||||
|
{
|
||||||
|
$parts = $this->splitListener((string)$general->$legacyField);
|
||||||
|
if ($parts === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$general->$addressField = $parts[0];
|
||||||
|
$general->$portField = $parts[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run($model)
|
||||||
|
{
|
||||||
|
parent::run($model);
|
||||||
|
|
||||||
|
// Deliberately prefer valid 1.2.x values during this one migration.
|
||||||
|
// This also repairs hosts that already installed the broken 1.3.x model.
|
||||||
|
$this->migrateListener($model->general, 'proxyListen', 'proxyAddress', 'proxyPort');
|
||||||
|
$this->migrateListener($model->general, 'tlsListen', 'tlsAddress', 'tlsPort');
|
||||||
|
$this->migrateListener($model->general, 'adminListen', 'adminAddress', 'adminPort');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OPNsense\RustProxy;
|
||||||
|
|
||||||
|
use OPNsense\Base\BaseModel;
|
||||||
|
|
||||||
|
class RustProxy extends BaseModel
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<model>
|
||||||
|
<mount>//OPNsense/rustproxy</mount>
|
||||||
|
<version>1.4.0</version>
|
||||||
|
<description>RustProxy service settings</description>
|
||||||
|
<items>
|
||||||
|
<general>
|
||||||
|
<enabled type="BooleanField">
|
||||||
|
<Default>0</Default>
|
||||||
|
<Required>Y</Required>
|
||||||
|
</enabled>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Listener address/port are intentionally stored as separate fields.
|
||||||
|
OPNsense/FreeBSD installations in the field have produced malformed
|
||||||
|
combined IP:port values (for example 0.0.0.0:) when IPPortField is
|
||||||
|
round-tripped through config.xml/templates. Keep the old combined
|
||||||
|
fields below for upgrade compatibility, but new UI writes these.
|
||||||
|
-->
|
||||||
|
<proxyAddress type="NetworkField">
|
||||||
|
<Default>0.0.0.0</Default>
|
||||||
|
<Required>Y</Required>
|
||||||
|
<NetMaskAllowed>N</NetMaskAllowed>
|
||||||
|
<ValidationMessage>Enter a valid HTTP bind address, for example 0.0.0.0.</ValidationMessage>
|
||||||
|
</proxyAddress>
|
||||||
|
<proxyPort type="PortField">
|
||||||
|
<Default>8080</Default>
|
||||||
|
<Required>Y</Required>
|
||||||
|
<ValidationMessage>Enter a valid HTTP port between 1 and 65535.</ValidationMessage>
|
||||||
|
</proxyPort>
|
||||||
|
<tlsEnabled type="BooleanField">
|
||||||
|
<Default>1</Default>
|
||||||
|
<Required>Y</Required>
|
||||||
|
</tlsEnabled>
|
||||||
|
<tlsAddress type="NetworkField">
|
||||||
|
<Default>0.0.0.0</Default>
|
||||||
|
<Required>Y</Required>
|
||||||
|
<NetMaskAllowed>N</NetMaskAllowed>
|
||||||
|
<ValidationMessage>Enter a valid HTTPS/TLS bind address, for example 0.0.0.0.</ValidationMessage>
|
||||||
|
</tlsAddress>
|
||||||
|
<tlsPort type="PortField">
|
||||||
|
<Default>8181</Default>
|
||||||
|
<Required>Y</Required>
|
||||||
|
<ValidationMessage>Enter a valid HTTPS/TLS port between 1 and 65535.</ValidationMessage>
|
||||||
|
</tlsPort>
|
||||||
|
<adminAddress type="NetworkField">
|
||||||
|
<Default>0.0.0.0</Default>
|
||||||
|
<Required>Y</Required>
|
||||||
|
<NetMaskAllowed>N</NetMaskAllowed>
|
||||||
|
<ValidationMessage>Enter a valid administration bind address, for example 0.0.0.0.</ValidationMessage>
|
||||||
|
</adminAddress>
|
||||||
|
<adminPort type="PortField">
|
||||||
|
<Default>9090</Default>
|
||||||
|
<Required>Y</Required>
|
||||||
|
<ValidationMessage>Enter a valid administration port between 1 and 65535.</ValidationMessage>
|
||||||
|
</adminPort>
|
||||||
|
<allowRemoteAdmin type="BooleanField">
|
||||||
|
<Default>1</Default>
|
||||||
|
<Required>Y</Required>
|
||||||
|
</allowRemoteAdmin>
|
||||||
|
|
||||||
|
<!-- Legacy combined listener values retained for seamless upgrades. -->
|
||||||
|
<proxyListen type="IPPortField"/>
|
||||||
|
<tlsListen type="IPPortField"/>
|
||||||
|
<adminListen type="IPPortField"/>
|
||||||
|
</general>
|
||||||
|
</items>
|
||||||
|
</model>
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
function parseAdminListen(value) {
|
||||||
|
value = (value || '').trim();
|
||||||
|
if (value.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bracketed IPv6, for example [2001:db8::1]:9090.
|
||||||
|
if (value.charAt(0) === '[') {
|
||||||
|
const end = value.indexOf(']');
|
||||||
|
if (end === -1 || value.charAt(end + 1) !== ':') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
host: value.substring(1, end),
|
||||||
|
port: value.substring(end + 2)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const separator = value.lastIndexOf(':');
|
||||||
|
if (separator <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
host: value.substring(0, separator),
|
||||||
|
port: value.substring(separator + 1)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatUrlHost(host) {
|
||||||
|
return host.indexOf(':') !== -1 ? '[' + host + ']' : host;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openRustProxyAdmin() {
|
||||||
|
$.getJSON('/api/rustproxy/settings/get')
|
||||||
|
.done(function(data) {
|
||||||
|
const general = data && data.rustproxy && data.rustproxy.general
|
||||||
|
? data.rustproxy.general
|
||||||
|
: {};
|
||||||
|
let listen = null;
|
||||||
|
const adminAddress = String(general.adminAddress || '').trim();
|
||||||
|
const adminPort = String(general.adminPort || '').trim();
|
||||||
|
if (adminAddress || adminPort) {
|
||||||
|
listen = {
|
||||||
|
host: adminAddress || '0.0.0.0',
|
||||||
|
port: adminPort || '9090'
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Upgrade compatibility with plugin model 1.2.x.
|
||||||
|
listen = parseAdminListen(general.adminListen || '0.0.0.0:9090');
|
||||||
|
}
|
||||||
|
const remoteValue = general.allowRemoteAdmin;
|
||||||
|
const allowRemote = remoteValue === true || remoteValue === 1 ||
|
||||||
|
['1', 'true', 'yes', 'on'].indexOf(String(remoteValue || '').toLowerCase()) !== -1;
|
||||||
|
|
||||||
|
if (!listen || !listen.port) {
|
||||||
|
BootstrapDialog.alert('{{ lang._("Invalid RustProxy admin listener configuration.") }}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allowRemote) {
|
||||||
|
BootstrapDialog.alert('{{ lang._("Enable Allow remote admin and Apply the configuration before opening the RustProxy panel from another host.") }}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetHost = listen.host;
|
||||||
|
if (targetHost === '0.0.0.0' || targetHost === '::' || targetHost === '*') {
|
||||||
|
targetHost = window.location.hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetHost === '127.0.0.1' || targetHost === 'localhost' || targetHost === '::1') {
|
||||||
|
BootstrapDialog.alert('{{ lang._("The RustProxy admin listener is bound to loopback only. Set Admin listen to an address reachable from your network, for example 0.0.0.0:9090, then Apply.") }}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = 'http://' + formatUrlHost(targetHost) + ':' + listen.port + '/';
|
||||||
|
const adminWindow = window.open(url, '_blank');
|
||||||
|
if (adminWindow) {
|
||||||
|
adminWindow.opener = null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fail(function() {
|
||||||
|
BootstrapDialog.alert('{{ lang._("Unable to read RustProxy settings.") }}');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mapDataToFormUI({'frm_GeneralSettings': '/api/rustproxy/settings/get'}).done(function() {
|
||||||
|
updateServiceControlUI('rustproxy');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#reconfigureAct').SimpleActionButton({
|
||||||
|
onPreAction: function() {
|
||||||
|
const deferred = new $.Deferred();
|
||||||
|
saveFormToEndpoint(
|
||||||
|
'/api/rustproxy/settings/set',
|
||||||
|
'frm_GeneralSettings',
|
||||||
|
function() { deferred.resolve(); },
|
||||||
|
true,
|
||||||
|
function() { deferred.reject(); }
|
||||||
|
);
|
||||||
|
return deferred;
|
||||||
|
},
|
||||||
|
onAction: function() {
|
||||||
|
updateServiceControlUI('rustproxy');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#openAdminAct').click(function() {
|
||||||
|
openRustProxyAdmin();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="page-content-main">
|
||||||
|
<div class="content-box">
|
||||||
|
{{ partial("layout_partials/base_form", ['fields': generalForm, 'id': 'frm_GeneralSettings']) }}
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div class="content-box">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<br/>
|
||||||
|
<button class="btn btn-primary"
|
||||||
|
id="reconfigureAct"
|
||||||
|
data-endpoint="/api/rustproxy/service/reconfigure"
|
||||||
|
data-label="{{ lang._('Apply') }}"
|
||||||
|
data-error-title="{{ lang._('Error reconfiguring RustProxy') }}"
|
||||||
|
type="button"></button>
|
||||||
|
<button class="btn btn-default"
|
||||||
|
id="openAdminAct"
|
||||||
|
type="button">
|
||||||
|
<span class="fa fa-external-link"></span>
|
||||||
|
{{ lang._('Open RustProxy Admin') }}
|
||||||
|
</button>
|
||||||
|
<br/><br/>
|
||||||
|
<p class="help-block">
|
||||||
|
{{ lang._('Remote administration is controlled by the RustProxy listener and OPNsense firewall rules. The plugin does not automatically create a WAN firewall rule for the admin port.') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
[start]
|
||||||
|
command:/usr/local/opnsense/scripts/rustproxy/service.sh start
|
||||||
|
parameters:
|
||||||
|
type:script
|
||||||
|
message:starting RustProxy
|
||||||
|
allowed_groups:wheel,wwwonly
|
||||||
|
|
||||||
|
[stop]
|
||||||
|
command:/usr/local/opnsense/scripts/rustproxy/service.sh stop
|
||||||
|
parameters:
|
||||||
|
type:script
|
||||||
|
message:stopping RustProxy
|
||||||
|
allowed_groups:wheel,wwwonly
|
||||||
|
|
||||||
|
[restart]
|
||||||
|
command:/usr/local/opnsense/scripts/rustproxy/service.sh restart
|
||||||
|
parameters:
|
||||||
|
type:script
|
||||||
|
message:restarting RustProxy
|
||||||
|
allowed_groups:wheel,wwwonly
|
||||||
|
|
||||||
|
[reload]
|
||||||
|
command:/usr/local/opnsense/scripts/rustproxy/service.sh restart
|
||||||
|
parameters:
|
||||||
|
type:script
|
||||||
|
message:reloading RustProxy
|
||||||
|
allowed_groups:wheel,wwwonly
|
||||||
|
|
||||||
|
[status]
|
||||||
|
command:/usr/local/opnsense/scripts/rustproxy/service.sh status
|
||||||
|
parameters:
|
||||||
|
type:script_output
|
||||||
|
errors:no
|
||||||
|
message:RustProxy status
|
||||||
|
allowed_groups:wheel,wwwonly
|
||||||
|
|
||||||
|
[boot]
|
||||||
|
command:/usr/local/opnsense/scripts/rustproxy/service.sh boot
|
||||||
|
parameters:
|
||||||
|
type:script
|
||||||
|
message:starting RustProxy at boot
|
||||||
|
allowed_groups:wheel,wwwonly
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rustproxy.conf:/usr/local/etc/rustproxy/opnsense.conf
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
enabled="{% if not helpers.empty('OPNsense.rustproxy.general.enabled') %}1{% else %}0{% endif %}"
|
||||||
|
|
||||||
|
# New split listener fields. Empty values are resolved by rc.d using the
|
||||||
|
# legacy combined field (upgrade path) and finally the built-in defaults.
|
||||||
|
rustproxy_proxy_address="{% if not helpers.empty('OPNsense.rustproxy.general.proxyAddress') %}{{ OPNsense.rustproxy.general.proxyAddress }}{% endif %}"
|
||||||
|
rustproxy_proxy_port="{% if not helpers.empty('OPNsense.rustproxy.general.proxyPort') %}{{ OPNsense.rustproxy.general.proxyPort }}{% endif %}"
|
||||||
|
rustproxy_tls_enabled="{% if not helpers.empty('OPNsense.rustproxy.general.tlsEnabled') %}1{% else %}0{% endif %}"
|
||||||
|
rustproxy_tls_address="{% if not helpers.empty('OPNsense.rustproxy.general.tlsAddress') %}{{ OPNsense.rustproxy.general.tlsAddress }}{% endif %}"
|
||||||
|
rustproxy_tls_port="{% if not helpers.empty('OPNsense.rustproxy.general.tlsPort') %}{{ OPNsense.rustproxy.general.tlsPort }}{% endif %}"
|
||||||
|
rustproxy_admin_address="{% if not helpers.empty('OPNsense.rustproxy.general.adminAddress') %}{{ OPNsense.rustproxy.general.adminAddress }}{% endif %}"
|
||||||
|
rustproxy_admin_port="{% if not helpers.empty('OPNsense.rustproxy.general.adminPort') %}{{ OPNsense.rustproxy.general.adminPort }}{% endif %}"
|
||||||
|
rustproxy_allow_remote_admin="{% if not helpers.empty('OPNsense.rustproxy.general.allowRemoteAdmin') %}1{% else %}0{% endif %}"
|
||||||
|
|
||||||
|
# Legacy 1.2.x combined settings. They are only used when the corresponding
|
||||||
|
# new split address/port fields are both empty.
|
||||||
|
rustproxy_proxy_listen_legacy="{% if not helpers.empty('OPNsense.rustproxy.general.proxyListen') %}{{ OPNsense.rustproxy.general.proxyListen }}{% endif %}"
|
||||||
|
rustproxy_tls_listen_legacy="{% if not helpers.empty('OPNsense.rustproxy.general.tlsListen') %}{{ OPNsense.rustproxy.general.tlsListen }}{% endif %}"
|
||||||
|
rustproxy_admin_listen_legacy="{% if not helpers.empty('OPNsense.rustproxy.general.adminListen') %}{{ OPNsense.rustproxy.general.adminListen }}{% endif %}"
|
||||||
Executable
+39
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
source /opt/rust-package-builder/scripts/lib.sh
|
||||||
|
|
||||||
|
SOURCE_DIR=${1:?usage: apply-freebsd-patches.sh /path/to/source}
|
||||||
|
PATCH_ROOT=/opt/rust-package-builder/opnsense/patches
|
||||||
|
|
||||||
|
[[ -f "$SOURCE_DIR/Cargo.toml" ]] || fatal "missing Cargo.toml in FreeBSD build source: $SOURCE_DIR"
|
||||||
|
|
||||||
|
VERSION=$(python3 - "$SOURCE_DIR/Cargo.toml" <<'PY'
|
||||||
|
import sys, tomllib
|
||||||
|
from pathlib import Path
|
||||||
|
with Path(sys.argv[1]).open('rb') as f:
|
||||||
|
print(tomllib.load(f)['package']['version'])
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
|
||||||
|
PATCH_DIR="$PATCH_ROOT/$VERSION"
|
||||||
|
if [[ ! -d "$PATCH_DIR" ]]; then
|
||||||
|
log "no FreeBSD/OPNsense patch set for RustProxy $VERSION; using upstream source"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
patches=("$PATCH_DIR"/*.patch)
|
||||||
|
(( ${#patches[@]} > 0 )) || fatal "FreeBSD patch directory is empty: $PATCH_DIR"
|
||||||
|
|
||||||
|
log "applying FreeBSD/OPNsense patches for RustProxy $VERSION"
|
||||||
|
for patch_file in "${patches[@]}"; do
|
||||||
|
patch_name=$(basename "$patch_file")
|
||||||
|
if git -C "$SOURCE_DIR" apply --check "$patch_file" 2>/dev/null; then
|
||||||
|
git -C "$SOURCE_DIR" apply "$patch_file"
|
||||||
|
log "applied patch: $patch_name"
|
||||||
|
elif git -C "$SOURCE_DIR" apply --reverse --check "$patch_file" 2>/dev/null; then
|
||||||
|
log "patch already present: $patch_name"
|
||||||
|
else
|
||||||
|
fatal "patch does not apply cleanly: $patch_name (RustProxy $VERSION)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
Executable
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
source /opt/rust-package-builder/scripts/lib.sh
|
||||||
|
|
||||||
|
SOURCE_DIR=${1:?source path required}
|
||||||
|
OUTPUT_BINARY=${2:?output binary required}
|
||||||
|
BINARY_NAME=${3:?binary name required}
|
||||||
|
WORK_DIR=${4:?work directory required}
|
||||||
|
|
||||||
|
BUILD_SOURCE="$WORK_DIR/freebsd-source"
|
||||||
|
rm -rf "$BUILD_SOURCE"
|
||||||
|
mkdir -p "$BUILD_SOURCE" "$(dirname "$OUTPUT_BINARY")"
|
||||||
|
|
||||||
|
rsync -a --delete \
|
||||||
|
--exclude='.git/' \
|
||||||
|
--exclude='target/' \
|
||||||
|
"$SOURCE_DIR/" "$BUILD_SOURCE/"
|
||||||
|
|
||||||
|
if [[ "${APPLY_FREEBSD_PATCHES:-1}" == "1" ]]; then
|
||||||
|
/opt/rust-package-builder/scripts/apply-freebsd-patches.sh "$BUILD_SOURCE"
|
||||||
|
else
|
||||||
|
log "FreeBSD/OPNsense source patches disabled"
|
||||||
|
fi
|
||||||
|
|
||||||
|
FREEBSD_TARGET_VERSION=${FREEBSD_RELEASE%-RELEASE}
|
||||||
|
export CFLAGS_x86_64_unknown_freebsd="--target=x86_64-unknown-freebsd${FREEBSD_TARGET_VERSION} --sysroot=/opt/freebsd-sysroot"
|
||||||
|
export CXXFLAGS_x86_64_unknown_freebsd="--target=x86_64-unknown-freebsd${FREEBSD_TARGET_VERSION} --sysroot=/opt/freebsd-sysroot"
|
||||||
|
export CARGO_TARGET_X86_64_UNKNOWN_FREEBSD_RUSTFLAGS="-C link-arg=--target=x86_64-unknown-freebsd${FREEBSD_TARGET_VERSION} -C link-arg=--sysroot=/opt/freebsd-sysroot -C link-arg=-fuse-ld=lld"
|
||||||
|
|
||||||
|
log "cross-compiling $BINARY_NAME for x86_64-unknown-freebsd${FREEBSD_TARGET_VERSION}"
|
||||||
|
cd "$BUILD_SOURCE"
|
||||||
|
if [[ -f Cargo.lock ]]; then
|
||||||
|
cargo "+$FREEBSD_RUST_TOOLCHAIN" build --release --locked --target x86_64-unknown-freebsd
|
||||||
|
else
|
||||||
|
cargo "+$FREEBSD_RUST_TOOLCHAIN" build --release --target x86_64-unknown-freebsd
|
||||||
|
fi
|
||||||
|
|
||||||
|
BUILT="$CARGO_TARGET_DIR/x86_64-unknown-freebsd/release/$BINARY_NAME"
|
||||||
|
[[ -x "$BUILT" ]] || fatal "FreeBSD binary not found: $BUILT"
|
||||||
|
install -m 0555 "$BUILT" "$OUTPUT_BINARY"
|
||||||
|
|
||||||
|
desc=$(file -b "$OUTPUT_BINARY")
|
||||||
|
log "FreeBSD binary: $desc"
|
||||||
|
[[ "$desc" == *FreeBSD* ]] || fatal "cross-built ELF is not identified as FreeBSD"
|
||||||
|
|
||||||
|
header=$(llvm-readelf -h "$OUTPUT_BINARY")
|
||||||
|
printf '%s\n' "$header" | grep -qi FreeBSD || fatal "cross-built ELF header is not marked for FreeBSD"
|
||||||
|
dynamic=$(llvm-readelf -d "$OUTPUT_BINARY" || true)
|
||||||
|
if printf '%s\n' "$dynamic" | grep -q 'libc\.so\.6'; then
|
||||||
|
fatal "Linux libc detected in FreeBSD target binary"
|
||||||
|
fi
|
||||||
@@ -11,8 +11,13 @@ PKG="$WORK/package"
|
|||||||
OUT="$WORK/out"
|
OUT="$WORK/out"
|
||||||
REPOSITORY="$DATA_DIR/repository"
|
REPOSITORY="$DATA_DIR/repository"
|
||||||
APTLY_CONF="$DATA_DIR/state/aptly.conf"
|
APTLY_CONF="$DATA_DIR/state/aptly.conf"
|
||||||
|
RELEASE_DIR=""
|
||||||
|
PUBLISHED=0
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
|
if [[ "$PUBLISHED" != "1" && -n "$RELEASE_DIR" && -d "$RELEASE_DIR" ]]; then
|
||||||
|
rm -rf "$RELEASE_DIR"
|
||||||
|
fi
|
||||||
if [[ "${KEEP_WORKSPACE:-0}" != "1" ]]; then
|
if [[ "${KEEP_WORKSPACE:-0}" != "1" ]]; then
|
||||||
rm -rf "$WORK"
|
rm -rf "$WORK"
|
||||||
else
|
else
|
||||||
@@ -28,6 +33,7 @@ git clone --quiet --no-tags --single-branch --branch "$SOURCE_BRANCH" "$SOURCE_R
|
|||||||
git -C "$SRC" checkout --quiet --detach "$SHA"
|
git -C "$SRC" checkout --quiet --detach "$SHA"
|
||||||
ACTUAL_SHA=$(git -C "$SRC" rev-parse HEAD)
|
ACTUAL_SHA=$(git -C "$SRC" rev-parse HEAD)
|
||||||
[[ "$ACTUAL_SHA" == "$SHA" ]] || fatal "checkout SHA mismatch"
|
[[ "$ACTUAL_SHA" == "$SHA" ]] || fatal "checkout SHA mismatch"
|
||||||
|
SOURCE_REVISION=$(git -C "$SRC" rev-list --count "$SHA")
|
||||||
|
|
||||||
cd "$SRC"
|
cd "$SRC"
|
||||||
META_JSON=$(cargo metadata --format-version 1 --no-deps)
|
META_JSON=$(cargo metadata --format-version 1 --no-deps)
|
||||||
@@ -109,13 +115,24 @@ fi
|
|||||||
|
|
||||||
export PACKAGE_NAME VERSION BINARY_NAME DESCRIPTION LICENSE HOMEPAGE MAINTAINER PACKAGE_RELEASE
|
export PACKAGE_NAME VERSION BINARY_NAME DESCRIPTION LICENSE HOMEPAGE MAINTAINER PACKAGE_RELEASE
|
||||||
log "building version $VERSION ($PACKAGE_NAME / $BINARY_NAME)"
|
log "building version $VERSION ($PACKAGE_NAME / $BINARY_NAME)"
|
||||||
log "cargo build started"
|
|
||||||
|
if [[ "${RUN_LINUX_TESTS:-1}" == "1" ]]; then
|
||||||
|
log "cargo tests started"
|
||||||
|
if [[ -f Cargo.lock ]]; then
|
||||||
|
cargo test --release --locked
|
||||||
|
else
|
||||||
|
cargo test --release
|
||||||
|
fi
|
||||||
|
log "cargo tests completed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Linux cargo build started"
|
||||||
if [[ -f Cargo.lock ]]; then
|
if [[ -f Cargo.lock ]]; then
|
||||||
cargo build --release --locked
|
cargo build --release --locked
|
||||||
else
|
else
|
||||||
cargo build --release
|
cargo build --release
|
||||||
fi
|
fi
|
||||||
log "cargo build completed"
|
log "Linux cargo build completed"
|
||||||
|
|
||||||
BINARY_PATH="$CARGO_TARGET_DIR/release/$BINARY_NAME"
|
BINARY_PATH="$CARGO_TARGET_DIR/release/$BINARY_NAME"
|
||||||
[[ -x "$BINARY_PATH" ]] || fatal "built binary not found: $BINARY_PATH"
|
[[ -x "$BINARY_PATH" ]] || fatal "built binary not found: $BINARY_PATH"
|
||||||
@@ -140,8 +157,24 @@ nfpm package --config "$PKG/nfpm-rpm.yaml" --packager rpm --target "$RPM_FILE"
|
|||||||
|
|
||||||
ar t "$DEB_FILE" | grep '^_gpgorigin$' >/dev/null || fatal "DEB package does not contain nFPM GPG origin signature"
|
ar t "$DEB_FILE" | grep '^_gpgorigin$' >/dev/null || fatal "DEB package does not contain nFPM GPG origin signature"
|
||||||
rpm -qpi "$RPM_FILE" | awk -F: '/^Signature/ && $0 !~ /\(none\)/ {ok=1} END{exit !ok}' || fatal "RPM package is not signed"
|
rpm -qpi "$RPM_FILE" | awk -F: '/^Signature/ && $0 !~ /\(none\)/ {ok=1} END{exit !ok}' || fatal "RPM package is not signed"
|
||||||
log "package signatures verified"
|
log "Linux package signatures verified"
|
||||||
|
|
||||||
|
# OPNsense is built from an isolated source copy. FreeBSD-specific patches never
|
||||||
|
# touch the Linux source checkout used above.
|
||||||
|
FREEBSD_BINARY="$OUT/rust-proxy-freebsd"
|
||||||
|
/opt/rust-package-builder/scripts/build-opnsense.sh "$SRC" "$FREEBSD_BINARY" "$BINARY_NAME" "$WORK"
|
||||||
|
|
||||||
|
if [[ "$SOURCE_REVISION" == "0" ]]; then
|
||||||
|
OPNSENSE_PACKAGE_VERSION="$VERSION"
|
||||||
|
else
|
||||||
|
OPNSENSE_PACKAGE_VERSION="${VERSION}_${SOURCE_REVISION}"
|
||||||
|
fi
|
||||||
|
OPNSENSE_PACKAGE_FILE="$OUT/os-rustproxy-${OPNSENSE_PACKAGE_VERSION}.pkg"
|
||||||
|
/opt/rust-package-builder/scripts/package-opnsense.sh \
|
||||||
|
"$SRC" "$FREEBSD_BINARY" "$OPNSENSE_PACKAGE_FILE" "$OPNSENSE_PACKAGE_VERSION"
|
||||||
|
|
||||||
|
# Only after all package formats have been built successfully do we update the
|
||||||
|
# persistent Linux package stores and the internal APT repository.
|
||||||
cp -f "$DEB_FILE" "$DATA_DIR/packages/deb/"
|
cp -f "$DEB_FILE" "$DATA_DIR/packages/deb/"
|
||||||
cp -f "$RPM_FILE" "$DATA_DIR/packages/rpm/x86_64/"
|
cp -f "$RPM_FILE" "$DATA_DIR/packages/rpm/x86_64/"
|
||||||
|
|
||||||
@@ -178,7 +211,7 @@ gpg --batch --verify \
|
|||||||
"$APT_PUBLIC/dists/$REPO_DISTRIBUTION/Release" >/dev/null 2>&1 || fatal "APT Release signature verification failed"
|
"$APT_PUBLIC/dists/$REPO_DISTRIBUTION/Release" >/dev/null 2>&1 || fatal "APT Release signature verification failed"
|
||||||
|
|
||||||
RELEASE_DIR="$REPOSITORY/releases/$BUILD_ID"
|
RELEASE_DIR="$REPOSITORY/releases/$BUILD_ID"
|
||||||
mkdir -p "$RELEASE_DIR/apt" "$RELEASE_DIR/rpm/x86_64"
|
mkdir -p "$RELEASE_DIR/apt" "$RELEASE_DIR/rpm/x86_64" "$RELEASE_DIR/opnsense"
|
||||||
|
|
||||||
CURRENT_TARGET=""
|
CURRENT_TARGET=""
|
||||||
if [[ -L "$REPOSITORY/current" ]]; then
|
if [[ -L "$REPOSITORY/current" ]]; then
|
||||||
@@ -214,24 +247,76 @@ gpg --batch --verify \
|
|||||||
"$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml.asc" \
|
"$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml.asc" \
|
||||||
"$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml" >/dev/null 2>&1 || fatal "RPM metadata signature verification failed"
|
"$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml" >/dev/null 2>&1 || fatal "RPM metadata signature verification failed"
|
||||||
|
|
||||||
|
log "staging OPNsense pkg repository"
|
||||||
|
if [[ -n "$CURRENT_TARGET" && -d "$CURRENT_TARGET/opnsense" ]]; then
|
||||||
|
rsync -a --delete --link-dest="$CURRENT_TARGET/opnsense"/ "$CURRENT_TARGET/opnsense"/ "$RELEASE_DIR/opnsense"/
|
||||||
|
fi
|
||||||
|
OPNSENSE_PACKAGE_DIR="$RELEASE_DIR/opnsense/$OPNSENSE_ABI/amd64"
|
||||||
|
mkdir -p "$OPNSENSE_PACKAGE_DIR"
|
||||||
|
# The previous OPNsense snapshot is hard-linked into this one. Unlink every
|
||||||
|
# file we are about to replace so the currently published snapshot stays immutable.
|
||||||
|
rm -f \
|
||||||
|
"$OPNSENSE_PACKAGE_DIR/$(basename "$OPNSENSE_PACKAGE_FILE")" \
|
||||||
|
"$RELEASE_DIR/opnsense/RustProxy.conf" \
|
||||||
|
"$RELEASE_DIR/opnsense/os-rustproxy-latest.pkg" \
|
||||||
|
"$RELEASE_DIR/opnsense/SHA256" \
|
||||||
|
"$RELEASE_DIR/opnsense/BUILD-INFO.txt" \
|
||||||
|
"$RELEASE_DIR/opnsense/index.html"
|
||||||
|
cp -f "$OPNSENSE_PACKAGE_FILE" "$OPNSENSE_PACKAGE_DIR/"
|
||||||
|
/opt/rust-package-builder/scripts/generate-opnsense-repo.py \
|
||||||
|
"$RELEASE_DIR/opnsense" --retention "$OPNSENSE_RETENTION"
|
||||||
|
cat > "$RELEASE_DIR/opnsense/RustProxy.conf" <<REPOCONF
|
||||||
|
RustProxy: {
|
||||||
|
url: "${OPNSENSE_REPOSITORY_URL%/}",
|
||||||
|
enabled: yes,
|
||||||
|
mirror_type: "none",
|
||||||
|
signature_type: "none"
|
||||||
|
}
|
||||||
|
REPOCONF
|
||||||
|
cp -f "$OPNSENSE_PACKAGE_FILE" "$RELEASE_DIR/opnsense/os-rustproxy-latest.pkg"
|
||||||
|
(cd "$RELEASE_DIR/opnsense" && sha256sum "$OPNSENSE_ABI/amd64/$(basename "$OPNSENSE_PACKAGE_FILE")") > "$RELEASE_DIR/opnsense/SHA256"
|
||||||
|
cat > "$RELEASE_DIR/opnsense/BUILD-INFO.txt" <<INFO
|
||||||
|
app_version=$VERSION
|
||||||
|
package_version=$OPNSENSE_PACKAGE_VERSION
|
||||||
|
source_url=$SOURCE_REPO
|
||||||
|
source_ref=$SOURCE_BRANCH
|
||||||
|
source_commit=$SHA
|
||||||
|
source_revision=$SOURCE_REVISION
|
||||||
|
opnsense_series=$OPNSENSE_ABI
|
||||||
|
freebsd_release=$FREEBSD_RELEASE
|
||||||
|
freebsd_abi=$FREEBSD_ABI
|
||||||
|
freebsd_arch=$FREEBSD_ARCH
|
||||||
|
package=$OPNSENSE_ABI/amd64/$(basename "$OPNSENSE_PACKAGE_FILE")
|
||||||
|
INFO
|
||||||
|
/opt/rust-package-builder/scripts/generate-opnsense-index.sh "$RELEASE_DIR/opnsense/index.html"
|
||||||
|
|
||||||
gpg --batch --armor --export "$GPG_KEY_ID" > "$RELEASE_DIR/repo-key.gpg"
|
gpg --batch --armor --export "$GPG_KEY_ID" > "$RELEASE_DIR/repo-key.gpg"
|
||||||
[[ -s "$RELEASE_DIR/repo-key.gpg" ]] || fatal "public GPG key export failed"
|
[[ -s "$RELEASE_DIR/repo-key.gpg" ]] || fatal "public GPG key export failed"
|
||||||
|
|
||||||
/opt/rust-package-builder/scripts/generate-index.sh "$RELEASE_DIR/index.html" "$PACKAGE_NAME"
|
/opt/rust-package-builder/scripts/generate-index.sh "$RELEASE_DIR/index.html" "$PACKAGE_NAME"
|
||||||
|
|
||||||
# Validate all externally visible endpoints before the atomic switch.
|
# Validate both public repositories before one atomic switch exposes either one.
|
||||||
[[ -s "$RELEASE_DIR/index.html" ]]
|
[[ -s "$RELEASE_DIR/index.html" ]]
|
||||||
[[ -f "$RELEASE_DIR/apt/dists/$REPO_DISTRIBUTION/InRelease" ]]
|
[[ -f "$RELEASE_DIR/apt/dists/$REPO_DISTRIBUTION/InRelease" ]]
|
||||||
[[ -f "$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml" ]]
|
[[ -f "$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml" ]]
|
||||||
|
[[ -s "$RELEASE_DIR/opnsense/index.html" ]]
|
||||||
|
[[ -f "$RELEASE_DIR/opnsense/meta.conf" ]]
|
||||||
|
[[ -f "$RELEASE_DIR/opnsense/packagesite.pkg" ]]
|
||||||
|
[[ -f "$RELEASE_DIR/opnsense/data.pkg" ]]
|
||||||
|
[[ -f "$RELEASE_DIR/opnsense/os-rustproxy-latest.pkg" ]]
|
||||||
|
bsdtar -tf "$RELEASE_DIR/opnsense/packagesite.pkg" | grep -Fx 'packagesite.yaml' >/dev/null || fatal "invalid OPNsense packagesite.pkg"
|
||||||
|
bsdtar -xOf "$OPNSENSE_PACKAGE_DIR/$(basename "$OPNSENSE_PACKAGE_FILE")" +COMPACT_MANIFEST >/dev/null || fatal "invalid OPNsense package manifest"
|
||||||
|
|
||||||
log "publishing repository atomically"
|
log "publishing Linux and OPNsense repositories atomically"
|
||||||
rm -f "$REPOSITORY/current.new"
|
rm -f "$REPOSITORY/current.new"
|
||||||
ln -s "releases/$BUILD_ID" "$REPOSITORY/current.new"
|
ln -s "releases/$BUILD_ID" "$REPOSITORY/current.new"
|
||||||
mv -Tf "$REPOSITORY/current.new" "$REPOSITORY/current"
|
mv -Tf "$REPOSITORY/current.new" "$REPOSITORY/current"
|
||||||
|
PUBLISHED=1
|
||||||
|
|
||||||
printf '%s\n' "$SHA" | atomic_write "$DATA_DIR/state/last_commit"
|
printf '%s\n' "$SHA" | atomic_write "$DATA_DIR/state/last_commit"
|
||||||
printf '%s\n' "$VERSION" | atomic_write "$DATA_DIR/state/last_version"
|
printf '%s\n' "$VERSION" | atomic_write "$DATA_DIR/state/last_version"
|
||||||
printf '%s\n' "$PACKAGE_RELEASE" | atomic_write "$DATA_DIR/state/last_package_release"
|
printf '%s\n' "$PACKAGE_RELEASE" | atomic_write "$DATA_DIR/state/last_package_release"
|
||||||
|
printf '%s\n' "$OPNSENSE_PACKAGE_VERSION" | atomic_write "$DATA_DIR/state/last_opnsense_package_version"
|
||||||
printf '%s\n' "$BUILD_ID" | atomic_write "$DATA_DIR/state/last_release"
|
printf '%s\n' "$BUILD_ID" | atomic_write "$DATA_DIR/state/last_release"
|
||||||
|
|
||||||
log "build completed: version=$VERSION package_release=$PACKAGE_RELEASE commit=$SHA release=$BUILD_ID"
|
log "build completed: version=$VERSION linux_release=$PACKAGE_RELEASE opnsense=$OPNSENSE_PACKAGE_VERSION commit=$SHA release=$BUILD_ID"
|
||||||
+24
-4
@@ -5,18 +5,33 @@ source /opt/rust-package-builder/scripts/lib.sh
|
|||||||
: "${SOURCE_REPO:?SOURCE_REPO is required}"
|
: "${SOURCE_REPO:?SOURCE_REPO is required}"
|
||||||
SOURCE_BRANCH=${SOURCE_BRANCH:-master}
|
SOURCE_BRANCH=${SOURCE_BRANCH:-master}
|
||||||
: "${REPOSITORY_URL:?REPOSITORY_URL is required}"
|
: "${REPOSITORY_URL:?REPOSITORY_URL is required}"
|
||||||
|
: "${OPNSENSE_REPOSITORY_URL:?OPNSENSE_REPOSITORY_URL is required}"
|
||||||
POLL_INTERVAL=${POLL_INTERVAL:-300}
|
POLL_INTERVAL=${POLL_INTERVAL:-300}
|
||||||
PACKAGE_RELEASE=${PACKAGE_RELEASE:-auto}
|
PACKAGE_RELEASE=${PACKAGE_RELEASE:-auto}
|
||||||
|
OPNSENSE_ABI=${OPNSENSE_ABI:-26.7}
|
||||||
|
OPNSENSE_RETENTION=${OPNSENSE_RETENTION:-10}
|
||||||
|
FREEBSD_RELEASE=${FREEBSD_RELEASE:-15.1-RELEASE}
|
||||||
|
FREEBSD_ABI=${FREEBSD_ABI:-FreeBSD:15:amd64}
|
||||||
|
FREEBSD_ARCH=${FREEBSD_ARCH:-freebsd:15:x86:64}
|
||||||
|
FREEBSD_RUST_TOOLCHAIN=${FREEBSD_RUST_TOOLCHAIN:-1.88.0}
|
||||||
|
APPLY_FREEBSD_PATCHES=${APPLY_FREEBSD_PATCHES:-1}
|
||||||
|
RUN_LINUX_TESTS=${RUN_LINUX_TESTS:-1}
|
||||||
|
|
||||||
[[ "$POLL_INTERVAL" =~ ^[0-9]+$ ]] || fatal "POLL_INTERVAL must be an integer"
|
[[ "$POLL_INTERVAL" =~ ^[0-9]+$ ]] || fatal "POLL_INTERVAL must be an integer"
|
||||||
(( POLL_INTERVAL >= 1 )) || fatal "POLL_INTERVAL must be >= 1"
|
(( POLL_INTERVAL >= 1 )) || fatal "POLL_INTERVAL must be >= 1"
|
||||||
[[ "$PACKAGE_RELEASE" == "auto" || "$PACKAGE_RELEASE" =~ ^[1-9][0-9]*$ ]] || fatal "PACKAGE_RELEASE must be auto or a positive integer"
|
[[ "$PACKAGE_RELEASE" == "auto" || "$PACKAGE_RELEASE" =~ ^[1-9][0-9]*$ ]] || fatal "PACKAGE_RELEASE must be auto or a positive integer"
|
||||||
|
[[ "$OPNSENSE_RETENTION" =~ ^[0-9]+$ ]] || fatal "OPNSENSE_RETENTION must be >= 0"
|
||||||
|
[[ "$APPLY_FREEBSD_PATCHES" =~ ^[01]$ ]] || fatal "APPLY_FREEBSD_PATCHES must be 0 or 1"
|
||||||
|
[[ "$RUN_LINUX_TESTS" =~ ^[01]$ ]] || fatal "RUN_LINUX_TESTS must be 0 or 1"
|
||||||
|
|
||||||
export DATA_DIR=${DATA_DIR:-/var/lib/package-builder}
|
export DATA_DIR=${DATA_DIR:-/var/lib/package-builder}
|
||||||
export SOURCE_REPO SOURCE_BRANCH REPOSITORY_URL POLL_INTERVAL
|
export SOURCE_REPO SOURCE_BRANCH REPOSITORY_URL OPNSENSE_REPOSITORY_URL POLL_INTERVAL
|
||||||
export REPO_DISTRIBUTION=${REPO_DISTRIBUTION:-stable}
|
export REPO_DISTRIBUTION=${REPO_DISTRIBUTION:-stable}
|
||||||
export REPO_COMPONENT=${REPO_COMPONENT:-main}
|
export REPO_COMPONENT=${REPO_COMPONENT:-main}
|
||||||
export PACKAGE_RELEASE
|
export PACKAGE_RELEASE
|
||||||
|
export OPNSENSE_ABI OPNSENSE_RETENTION
|
||||||
|
export FREEBSD_RELEASE FREEBSD_ABI FREEBSD_ARCH FREEBSD_RUST_TOOLCHAIN
|
||||||
|
export APPLY_FREEBSD_PATCHES RUN_LINUX_TESTS
|
||||||
export MAX_GLIBC_VERSION=${MAX_GLIBC_VERSION:-2.34}
|
export MAX_GLIBC_VERSION=${MAX_GLIBC_VERSION:-2.34}
|
||||||
export GPG_PRIVATE_KEY_FILE=${GPG_PRIVATE_KEY_FILE:-/run/secrets/gpg/private-key.asc}
|
export GPG_PRIVATE_KEY_FILE=${GPG_PRIVATE_KEY_FILE:-/run/secrets/gpg/private-key.asc}
|
||||||
export GPG_PASSPHRASE_FILE=${GPG_PASSPHRASE_FILE:-/run/secrets/gpg/passphrase}
|
export GPG_PASSPHRASE_FILE=${GPG_PASSPHRASE_FILE:-/run/secrets/gpg/passphrase}
|
||||||
@@ -24,10 +39,15 @@ export GNUPGHOME=${GNUPGHOME:-/run/package-builder/gnupg}
|
|||||||
export CARGO_HOME="$DATA_DIR/cargo"
|
export CARGO_HOME="$DATA_DIR/cargo"
|
||||||
export CARGO_TARGET_DIR="$DATA_DIR/cargo/target"
|
export CARGO_TARGET_DIR="$DATA_DIR/cargo/target"
|
||||||
|
|
||||||
for cmd in git cargo rustc nfpm aptly createrepo_c gpg rsync python3 python3.11 objdump ar rpm; do
|
for cmd in git cargo rustc rustup nfpm aptly createrepo_c gpg rsync python3 python3.11 \
|
||||||
|
objdump ar rpm clang llvm-readelf llvm-ar llvm-ranlib file tar zstd bsdtar sha256sum; do
|
||||||
require_cmd "$cmd"
|
require_cmd "$cmd"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
rustc "+$FREEBSD_RUST_TOOLCHAIN" --version >/dev/null 2>&1 || fatal "FreeBSD Rust toolchain is unavailable: $FREEBSD_RUST_TOOLCHAIN"
|
||||||
|
rustup target list --toolchain "$FREEBSD_RUST_TOOLCHAIN" --installed | grep -Fx x86_64-unknown-freebsd >/dev/null \
|
||||||
|
|| fatal "Rust target x86_64-unknown-freebsd is not installed for $FREEBSD_RUST_TOOLCHAIN"
|
||||||
|
|
||||||
mkdir -p \
|
mkdir -p \
|
||||||
"$DATA_DIR/repository/releases" \
|
"$DATA_DIR/repository/releases" \
|
||||||
"$DATA_DIR/state" \
|
"$DATA_DIR/state" \
|
||||||
@@ -57,10 +77,10 @@ fi
|
|||||||
|
|
||||||
/opt/rust-package-builder/scripts/init-storage.sh
|
/opt/rust-package-builder/scripts/init-storage.sh
|
||||||
|
|
||||||
log "builder started: branch=$SOURCE_BRANCH interval=${POLL_INTERVAL}s"
|
log "builder started: branch=$SOURCE_BRANCH interval=${POLL_INTERVAL}s opnsense=$OPNSENSE_ABI freebsd=$FREEBSD_RELEASE"
|
||||||
while true; do
|
while true; do
|
||||||
if ! /opt/rust-package-builder/scripts/check-and-build.sh; then
|
if ! /opt/rust-package-builder/scripts/check-and-build.sh; then
|
||||||
log "ERROR: build cycle failed; published repository was not switched"
|
log "ERROR: build cycle failed; published repositories were not switched"
|
||||||
fi
|
fi
|
||||||
sleep "$POLL_INTERVAL"
|
sleep "$POLL_INTERVAL"
|
||||||
done
|
done
|
||||||
+17
-10
@@ -6,7 +6,7 @@ OUT=${1:?output file required}
|
|||||||
PACKAGE=${2:?package name required}
|
PACKAGE=${2:?package name required}
|
||||||
URL=$(trim_trailing_slash "$REPOSITORY_URL")
|
URL=$(trim_trailing_slash "$REPOSITORY_URL")
|
||||||
|
|
||||||
export OUT PACKAGE URL REPO_DISTRIBUTION REPO_COMPONENT
|
export OUT PACKAGE URL OPNSENSE_REPOSITORY_URL REPO_DISTRIBUTION REPO_COMPONENT
|
||||||
python3 <<'PY_END'
|
python3 <<'PY_END'
|
||||||
import html, os
|
import html, os
|
||||||
out=os.environ['OUT']
|
out=os.environ['OUT']
|
||||||
@@ -14,14 +14,15 @@ package=os.environ['PACKAGE']
|
|||||||
url=os.environ['URL']
|
url=os.environ['URL']
|
||||||
dist=os.environ['REPO_DISTRIBUTION']
|
dist=os.environ['REPO_DISTRIBUTION']
|
||||||
component=os.environ['REPO_COMPONENT']
|
component=os.environ['REPO_COMPONENT']
|
||||||
|
opn_url=os.environ['OPNSENSE_REPOSITORY_URL'].rstrip('/')
|
||||||
h=html.escape
|
h=html.escape
|
||||||
content=f'''<!doctype html>
|
content=f'''<!doctype html>
|
||||||
<html lang="pl">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<meta name="referrer" content="no-referrer">
|
<meta name="referrer" content="no-referrer">
|
||||||
<title>{h(package)} — repozytorium pakietów</title>
|
<title>{h(package)} — package repository</title>
|
||||||
<style>
|
<style>
|
||||||
:root{{color-scheme:light dark;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}}
|
:root{{color-scheme:light dark;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}}
|
||||||
body{{max-width:920px;margin:0 auto;padding:42px 20px 70px;line-height:1.55}}
|
body{{max-width:920px;margin:0 auto;padding:42px 20px 70px;line-height:1.55}}
|
||||||
@@ -34,9 +35,9 @@ a{{color:inherit}} .meta{{display:flex;gap:16px;flex-wrap:wrap;opacity:.82;font-
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>{h(package)} — repozytorium pakietów</h1>
|
<h1>{h(package)} — package repository</h1>
|
||||||
<p class="lead">Podpisane pakiety DEB i RPM dla x86_64/amd64.</p>
|
<p class="lead">Signed DEB and RPM packages for x86_64/amd64.</p>
|
||||||
<div class="meta"><span class="ok">Repozytorium aktywne</span><span>{h(url)}</span><span>APT + RPM/DNF</span></div>
|
<div class="meta"><span class="ok">Repository active</span><span>{h(url)}</span><span>APT + RPM/DNF</span></div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Debian / Ubuntu</h2>
|
<h2>Debian / Ubuntu</h2>
|
||||||
@@ -68,17 +69,23 @@ sudo dnf install {h(package)}</code></pre>
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Pliki repozytorium</h2>
|
<h2>OPNsense / FreeBSD pkg</h2>
|
||||||
|
<p>The OPNsense plugin is published separately because it uses the FreeBSD pkg format.</p>
|
||||||
|
<p><a href="{h(opn_url)}/">{h(opn_url)}/</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>Repository files</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{h(url)}/repo-key.gpg">Publiczny klucz GPG</a></li>
|
<li><a href="{h(url)}/repo-key.gpg">Public GPG key</a></li>
|
||||||
<li><a href="{h(url)}/apt/dists/{h(dist)}/InRelease">APT InRelease</a></li>
|
<li><a href="{h(url)}/apt/dists/{h(dist)}/InRelease">APT InRelease</a></li>
|
||||||
<li><a href="{h(url)}/rpm/x86_64/repodata/repomd.xml">RPM repomd.xml</a></li>
|
<li><a href="{h(url)}/rpm/x86_64/repodata/repomd.xml">RPM repomd.xml</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>Obsługiwane cele: Debian 12/13, Ubuntu 24.04/26.04, RHEL 9/10; architektura amd64/x86_64.</p>
|
<p>Supported targets: Debian 12/13, Ubuntu 24.04/26.04, RHEL 9/10; architecture: amd64/x86_64.</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
'''
|
'''
|
||||||
with open(out, 'w', encoding='utf-8') as f:
|
with open(out, 'w', encoding='utf-8') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
PY_END
|
PY_END
|
||||||
Executable
+71
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
source /opt/rust-package-builder/scripts/lib.sh
|
||||||
|
|
||||||
|
OUT=${1:?output file required}
|
||||||
|
URL=$(trim_trailing_slash "$OPNSENSE_REPOSITORY_URL")
|
||||||
|
PACKAGE=${PACKAGE_NAME:-rustproxy}
|
||||||
|
|
||||||
|
export OUT URL PACKAGE OPNSENSE_ABI FREEBSD_ABI
|
||||||
|
python3 <<'PY'
|
||||||
|
import html
|
||||||
|
import os
|
||||||
|
|
||||||
|
out = os.environ["OUT"]
|
||||||
|
url = os.environ["URL"]
|
||||||
|
package = os.environ["PACKAGE"]
|
||||||
|
series = os.environ["OPNSENSE_ABI"]
|
||||||
|
abi = os.environ["FREEBSD_ABI"]
|
||||||
|
h = html.escape
|
||||||
|
content = f'''<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>RustProxy OPNsense repository</title>
|
||||||
|
<style>
|
||||||
|
:root{{color-scheme:light dark;font-family:system-ui,-apple-system,"Segoe UI",sans-serif}}
|
||||||
|
body{{max-width:900px;margin:0 auto;padding:42px 20px 70px;line-height:1.55}}
|
||||||
|
.card{{border:1px solid #8885;border-radius:12px;padding:18px;margin:18px 0}}
|
||||||
|
pre{{overflow:auto;padding:16px;border-radius:9px;background:#111;color:#eee}}
|
||||||
|
code{{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>RustProxy OPNsense repository</h1>
|
||||||
|
<p>FreeBSD pkg repository for the <code>os-rustproxy</code> OPNsense plugin.</p>
|
||||||
|
<div class="card">
|
||||||
|
<p><strong>Repository:</strong> {h(url)}</p>
|
||||||
|
<p><strong>OPNsense series:</strong> {h(series)} <strong>ABI:</strong> {h(abi)}</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h2>pkg repository configuration</h2>
|
||||||
|
<pre><code>mkdir -p /usr/local/etc/pkg/repos
|
||||||
|
cat > /usr/local/etc/pkg/repos/RustProxy.conf <<'REPO'
|
||||||
|
RustProxy: {{
|
||||||
|
url: "{h(url)}",
|
||||||
|
enabled: yes,
|
||||||
|
mirror_type: "none",
|
||||||
|
signature_type: "none"
|
||||||
|
}}
|
||||||
|
REPO
|
||||||
|
|
||||||
|
pkg update -f
|
||||||
|
pkg install os-rustproxy</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h2>Repository metadata</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="{h(url)}/RustProxy.conf">RustProxy.conf</a> - ready pkg repo configuration</li>
|
||||||
|
<li><a href="{h(url)}/meta.conf">meta.conf</a></li>
|
||||||
|
<li><a href="{h(url)}/packagesite.pkg">packagesite.pkg</a></li>
|
||||||
|
<li><a href="{h(url)}/data.pkg">data.pkg</a></li>
|
||||||
|
<li><a href="{h(url)}/os-rustproxy-latest.pkg">latest package</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
with open(out, "w", encoding="utf-8") as f:
|
||||||
|
f.write(content)
|
||||||
|
PY
|
||||||
Executable
+141
@@ -0,0 +1,141 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tarfile
|
||||||
|
import time
|
||||||
|
from collections import defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
CATALOG_FILES = {"data.pkg", "packagesite.pkg"}
|
||||||
|
LATEST_NAMES = {"latest.pkg", "os-rustproxy-latest.pkg"}
|
||||||
|
|
||||||
|
|
||||||
|
def sha256(path: Path) -> str:
|
||||||
|
h = hashlib.sha256()
|
||||||
|
with path.open("rb") as f:
|
||||||
|
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
||||||
|
h.update(chunk)
|
||||||
|
return h.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def read_manifest(pkg: Path) -> dict:
|
||||||
|
for member in ("+COMPACT_MANIFEST", "+MANIFEST"):
|
||||||
|
p = subprocess.run(
|
||||||
|
["bsdtar", "-xOf", str(pkg), member],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
if p.returncode == 0 and p.stdout:
|
||||||
|
return json.loads(p.stdout.decode("utf-8"))
|
||||||
|
raise RuntimeError(f"{pkg.name}: missing +COMPACT_MANIFEST / +MANIFEST")
|
||||||
|
|
||||||
|
|
||||||
|
def is_payload_pkg(path: Path) -> bool:
|
||||||
|
return (
|
||||||
|
path.is_file()
|
||||||
|
and path.suffix == ".pkg"
|
||||||
|
and path.name not in CATALOG_FILES
|
||||||
|
and path.name not in LATEST_NAMES
|
||||||
|
and not path.name.endswith(".tmp.pkg")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def collect_packages(repo: Path):
|
||||||
|
packages = []
|
||||||
|
for path in sorted(repo.rglob("*.pkg")):
|
||||||
|
if not is_payload_pkg(path):
|
||||||
|
continue
|
||||||
|
manifest = read_manifest(path)
|
||||||
|
manifest["path"] = path.relative_to(repo).as_posix()
|
||||||
|
manifest["pkgsize"] = path.stat().st_size
|
||||||
|
manifest["sum"] = sha256(path)
|
||||||
|
packages.append((path, manifest))
|
||||||
|
return packages
|
||||||
|
|
||||||
|
|
||||||
|
def enforce_retention(packages, retention: int) -> bool:
|
||||||
|
if retention <= 0:
|
||||||
|
return False
|
||||||
|
groups = defaultdict(list)
|
||||||
|
for path, manifest in packages:
|
||||||
|
# Keep the requested number per repository subdirectory/ABI and package name.
|
||||||
|
key = (path.parent, manifest.get("name", path.stem))
|
||||||
|
groups[key].append((path, manifest))
|
||||||
|
|
||||||
|
removed = False
|
||||||
|
for (_parent, _name), entries in groups.items():
|
||||||
|
entries.sort(key=lambda item: item[0].stat().st_mtime_ns, reverse=True)
|
||||||
|
for path, _manifest in entries[retention:]:
|
||||||
|
try:
|
||||||
|
path.unlink()
|
||||||
|
removed = True
|
||||||
|
print(f"[opnsense-repo] retention: removed {path}", flush=True)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
return removed
|
||||||
|
|
||||||
|
|
||||||
|
def make_tar(dst: Path, member_name: str, payload: bytes):
|
||||||
|
tmp = dst.with_name(dst.name + ".tmp")
|
||||||
|
with tarfile.open(tmp, "w", format=tarfile.PAX_FORMAT) as tf:
|
||||||
|
info = tarfile.TarInfo(member_name)
|
||||||
|
info.size = len(payload)
|
||||||
|
info.mode = 0o644
|
||||||
|
info.mtime = int(time.time())
|
||||||
|
tf.addfile(info, io.BytesIO(payload))
|
||||||
|
os.replace(tmp, dst)
|
||||||
|
|
||||||
|
|
||||||
|
def rebuild(repo: Path, retention: int):
|
||||||
|
packages = collect_packages(repo)
|
||||||
|
if enforce_retention(packages, retention):
|
||||||
|
packages = collect_packages(repo)
|
||||||
|
|
||||||
|
manifests = [manifest for _, manifest in packages]
|
||||||
|
packagesite = b"".join(
|
||||||
|
json.dumps(m, separators=(",", ":"), ensure_ascii=False).encode("utf-8") + b"\n"
|
||||||
|
for m in manifests
|
||||||
|
)
|
||||||
|
data = json.dumps(
|
||||||
|
{"groups": [], "expired_packages": [], "packages": manifests},
|
||||||
|
separators=(",", ":"),
|
||||||
|
ensure_ascii=False,
|
||||||
|
).encode("utf-8")
|
||||||
|
|
||||||
|
make_tar(repo / "packagesite.pkg", "packagesite.yaml", packagesite)
|
||||||
|
make_tar(repo / "data.pkg", "data", data)
|
||||||
|
|
||||||
|
meta = (
|
||||||
|
'version: 1;\n'
|
||||||
|
'packing_format: "tar";\n'
|
||||||
|
'manifests: "packagesite.yaml";\n'
|
||||||
|
'manifests_archive: "packagesite";\n'
|
||||||
|
'data: "data";\n'
|
||||||
|
'data_archive: "data";\n'
|
||||||
|
'filesite: "files";\n'
|
||||||
|
'filesite_archive: "files";\n'
|
||||||
|
)
|
||||||
|
tmp = repo / "meta.conf.tmp"
|
||||||
|
tmp.write_text(meta, encoding="utf-8")
|
||||||
|
os.replace(tmp, repo / "meta.conf")
|
||||||
|
print(f"[opnsense-repo] rebuilt: {len(manifests)} package(s)", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Build a static FreeBSD pkg repository catalogue")
|
||||||
|
parser.add_argument("repo", type=Path, help="repository root")
|
||||||
|
parser.add_argument("--retention", type=int, default=10, help="versions kept per package/ABI; 0 disables pruning")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
repo = args.repo.resolve()
|
||||||
|
repo.mkdir(parents=True, exist_ok=True)
|
||||||
|
rebuild(repo, args.retention)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -34,7 +34,7 @@ if ! aptly -config="$APTLY_CONF" repo list -raw | grep -Fx package-builder >/dev
|
|||||||
package-builder >/dev/null
|
package-builder >/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for item in apt rpm repo-key.gpg index.html; do
|
for item in apt rpm opnsense repo-key.gpg index.html; do
|
||||||
if [[ -e "$REPOSITORY/$item" && ! -L "$REPOSITORY/$item" ]]; then
|
if [[ -e "$REPOSITORY/$item" && ! -L "$REPOSITORY/$item" ]]; then
|
||||||
fatal "$REPOSITORY/$item exists and is not a symlink; refusing to overwrite"
|
fatal "$REPOSITORY/$item exists and is not a symlink; refusing to overwrite"
|
||||||
fi
|
fi
|
||||||
@@ -42,5 +42,6 @@ done
|
|||||||
|
|
||||||
ln -sfn current/apt "$REPOSITORY/apt"
|
ln -sfn current/apt "$REPOSITORY/apt"
|
||||||
ln -sfn current/rpm "$REPOSITORY/rpm"
|
ln -sfn current/rpm "$REPOSITORY/rpm"
|
||||||
|
ln -sfn current/opnsense "$REPOSITORY/opnsense"
|
||||||
ln -sfn current/repo-key.gpg "$REPOSITORY/repo-key.gpg"
|
ln -sfn current/repo-key.gpg "$REPOSITORY/repo-key.gpg"
|
||||||
ln -sfn current/index.html "$REPOSITORY/index.html"
|
ln -sfn current/index.html "$REPOSITORY/index.html"
|
||||||
Executable
+143
@@ -0,0 +1,143 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
source /opt/rust-package-builder/scripts/lib.sh
|
||||||
|
|
||||||
|
SOURCE_DIR=${1:?source path required}
|
||||||
|
BINARY=${2:?FreeBSD binary required}
|
||||||
|
PACKAGE_FILE=${3:?output package file required}
|
||||||
|
PACKAGE_VERSION=${4:?OPNsense package version required}
|
||||||
|
|
||||||
|
PLUGIN_ROOT=/opt/rust-package-builder/opnsense/plugin
|
||||||
|
FREEBSD_ABI=${FREEBSD_ABI:-FreeBSD:15:amd64}
|
||||||
|
FREEBSD_ARCH=${FREEBSD_ARCH:-freebsd:15:x86:64}
|
||||||
|
|
||||||
|
for tool in python3 sha256sum tar zstd file; do
|
||||||
|
require_cmd "$tool"
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -f "$SOURCE_DIR/Cargo.toml" ]] || fatal "missing Cargo.toml in $SOURCE_DIR"
|
||||||
|
[[ -f "$BINARY" ]] || fatal "missing cross-built binary: $BINARY"
|
||||||
|
[[ -d "$PLUGIN_ROOT/src" ]] || fatal "missing embedded OPNsense plugin source: $PLUGIN_ROOT/src"
|
||||||
|
|
||||||
|
binary_desc=$(file -b "$BINARY")
|
||||||
|
[[ "$binary_desc" == *FreeBSD* ]] || fatal "refusing to package a non-FreeBSD binary: $binary_desc"
|
||||||
|
|
||||||
|
WORK=$(mktemp -d "${PACKAGE_FILE}.work.XXXXXX")
|
||||||
|
trap 'rm -rf "$WORK"' EXIT
|
||||||
|
STAGE="$WORK/stage"
|
||||||
|
META="$WORK/meta"
|
||||||
|
ARCHIVE="$WORK/os-rustproxy.tar"
|
||||||
|
PAYLOAD_LIST="$WORK/payload.list"
|
||||||
|
mkdir -p "$STAGE/usr/local" "$META" "$(dirname "$PACKAGE_FILE")"
|
||||||
|
|
||||||
|
cp -a "$PLUGIN_ROOT/src/." "$STAGE/usr/local/"
|
||||||
|
mkdir -p "$STAGE/usr/local/sbin"
|
||||||
|
install -m 0555 "$BINARY" "$STAGE/usr/local/sbin/rust-proxy"
|
||||||
|
|
||||||
|
chmod 0555 "$STAGE/usr/local/etc/rc.d/rustproxy"
|
||||||
|
chmod 0555 "$STAGE/usr/local/etc/rc.syshook.d/10-rustproxy.start"
|
||||||
|
chmod 0555 "$STAGE/usr/local/opnsense/scripts/rustproxy/service.sh"
|
||||||
|
|
||||||
|
POST_INSTALL_FILE="$PLUGIN_ROOT/+POST_INSTALL.pre"
|
||||||
|
PRE_DEINSTALL_FILE="$PLUGIN_ROOT/+PRE_DEINSTALL.pre"
|
||||||
|
|
||||||
|
python3 - "$STAGE" "$META" "$PACKAGE_VERSION" "$FREEBSD_ABI" "$FREEBSD_ARCH" \
|
||||||
|
"$POST_INSTALL_FILE" "$PRE_DEINSTALL_FILE" <<'PY'
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
stage = Path(sys.argv[1])
|
||||||
|
meta_dir = Path(sys.argv[2])
|
||||||
|
version = sys.argv[3]
|
||||||
|
abi = sys.argv[4]
|
||||||
|
arch = sys.argv[5]
|
||||||
|
post_install = Path(sys.argv[6]).read_text(encoding="utf-8")
|
||||||
|
pre_deinstall = Path(sys.argv[7]).read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
files = {}
|
||||||
|
flatsize = 0
|
||||||
|
for p in sorted(stage.rglob("*")):
|
||||||
|
if p.is_symlink():
|
||||||
|
files["/" + p.relative_to(stage).as_posix()] = "-"
|
||||||
|
elif p.is_file():
|
||||||
|
files["/" + p.relative_to(stage).as_posix()] = hashlib.sha256(p.read_bytes()).hexdigest()
|
||||||
|
flatsize += p.stat().st_size
|
||||||
|
|
||||||
|
common = {
|
||||||
|
"name": "os-rustproxy",
|
||||||
|
"version": version,
|
||||||
|
"origin": "opnsense/os-rustproxy",
|
||||||
|
"comment": "RustProxy reverse proxy service",
|
||||||
|
"desc": "RustProxy packaged as an OPNsense plugin with configurable HTTP, HTTPS/TLS and administration listeners.",
|
||||||
|
"maintainer": "local@localhost",
|
||||||
|
"www": "https://git.linuxiarz.pl/gru/RustProxy",
|
||||||
|
"prefix": "/usr/local",
|
||||||
|
"abi": abi,
|
||||||
|
"arch": arch,
|
||||||
|
"flatsize": flatsize,
|
||||||
|
"licenselogic": "single",
|
||||||
|
"licenses": ["UNKNOWN"],
|
||||||
|
"categories": ["www"],
|
||||||
|
"deps": {},
|
||||||
|
"options": {},
|
||||||
|
}
|
||||||
|
manifest = dict(common)
|
||||||
|
manifest["files"] = files
|
||||||
|
manifest["scripts"] = {
|
||||||
|
"post-install": post_install,
|
||||||
|
"pre-deinstall": pre_deinstall,
|
||||||
|
}
|
||||||
|
|
||||||
|
(meta_dir / "+COMPACT_MANIFEST").write_text(
|
||||||
|
json.dumps(common, separators=(",", ":"), ensure_ascii=False) + "\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
(meta_dir / "+MANIFEST").write_text(
|
||||||
|
json.dumps(manifest, separators=(",", ":"), ensure_ascii=False) + "\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
PY
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$STAGE"
|
||||||
|
find . \( -type f -o -type l \) -print | LC_ALL=C sort | sed 's#^\./##' > "$PAYLOAD_LIST"
|
||||||
|
)
|
||||||
|
[[ -s "$PAYLOAD_LIST" ]] || fatal "OPNsense package payload is empty"
|
||||||
|
|
||||||
|
tar --format=ustar --owner=0 --group=0 --numeric-owner \
|
||||||
|
-cf "$ARCHIVE" -C "$META" +COMPACT_MANIFEST +MANIFEST
|
||||||
|
tar -P --format=ustar --owner=0 --group=0 --numeric-owner --no-recursion \
|
||||||
|
--transform='s,^,/,S' \
|
||||||
|
-rf "$ARCHIVE" -C "$STAGE" -T "$PAYLOAD_LIST"
|
||||||
|
|
||||||
|
python3 - "$ARCHIVE" "$META/+MANIFEST" <<'PY'
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import tarfile
|
||||||
|
|
||||||
|
archive = Path(sys.argv[1])
|
||||||
|
manifest = json.loads(Path(sys.argv[2]).read_text(encoding="utf-8"))
|
||||||
|
expected = set(manifest.get("files", {}))
|
||||||
|
with tarfile.open(archive, "r:") as tf:
|
||||||
|
members = tf.getmembers()
|
||||||
|
if [m.name for m in members[:2]] != ["+COMPACT_MANIFEST", "+MANIFEST"]:
|
||||||
|
raise SystemExit("package metadata must be the first two tar members")
|
||||||
|
payload = members[2:]
|
||||||
|
if any(m.isdir() for m in payload):
|
||||||
|
raise SystemExit("directory entries are not allowed in OPNsense package payload")
|
||||||
|
actual = {m.name for m in payload if m.isfile() or m.issym() or m.islnk()}
|
||||||
|
relative = sorted(name for name in actual if not name.startswith("/"))
|
||||||
|
if relative:
|
||||||
|
raise SystemExit(f"payload contains non-absolute paths: {relative[:5]}")
|
||||||
|
missing = sorted(expected - actual)
|
||||||
|
extra = sorted(actual - expected)
|
||||||
|
if missing or extra:
|
||||||
|
raise SystemExit(f"package payload does not match +MANIFEST; missing={missing[:5]} extra={extra[:5]}")
|
||||||
|
PY
|
||||||
|
|
||||||
|
zstd -q -f -19 -T0 "$ARCHIVE" -o "$PACKAGE_FILE"
|
||||||
|
zstd -dc "$PACKAGE_FILE" | tar -tf - >/dev/null
|
||||||
|
log "created OPNsense package: $PACKAGE_FILE"
|
||||||
@@ -10,40 +10,24 @@ DATA_DIR="$ROOT/data"
|
|||||||
SERVICE="rust-package-builder"
|
SERVICE="rust-package-builder"
|
||||||
HTTP_SERVICE="repository-http"
|
HTTP_SERVICE="repository-http"
|
||||||
DEFAULT_HTTP_PORT=18765
|
DEFAULT_HTTP_PORT=18765
|
||||||
|
DEFAULT_OPNSENSE_HTTP_PORT=18766
|
||||||
|
|
||||||
log() {
|
log() { printf '[start] %s\n' "$*"; }
|
||||||
printf '[start] %s\n' "$*"
|
warn() { printf '[start] WARN: %s\n' "$*" >&2; }
|
||||||
}
|
die() { printf '[start] ERROR: %s\n' "$*" >&2; exit 1; }
|
||||||
|
require_cmd() { command -v "$1" >/dev/null 2>&1 || die "brak wymaganego polecenia: $1"; }
|
||||||
warn() {
|
|
||||||
printf '[start] WARN: %s\n' "$*" >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
die() {
|
|
||||||
printf '[start] ERROR: %s\n' "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
require_cmd() {
|
|
||||||
command -v "$1" >/dev/null 2>&1 || die "brak wymaganego polecenia: $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
write_env_file() {
|
write_env_file() {
|
||||||
local source_repo=${SOURCE_REPO:-}
|
|
||||||
local repository_url=${REPOSITORY_URL:-}
|
|
||||||
|
|
||||||
cat > "$ENV_FILE" <<ENV
|
cat > "$ENV_FILE" <<ENV
|
||||||
SOURCE_REPO=$source_repo
|
SOURCE_REPO=${SOURCE_REPO:-}
|
||||||
REPOSITORY_URL=$repository_url
|
REPOSITORY_URL=${REPOSITORY_URL:-}
|
||||||
|
OPNSENSE_REPOSITORY_URL=${OPNSENSE_REPOSITORY_URL:-}
|
||||||
ENV
|
ENV
|
||||||
chmod 0600 "$ENV_FILE" 2>/dev/null || true
|
chmod 0600 "$ENV_FILE" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
update_env_var() {
|
update_env_var() {
|
||||||
local key=$1
|
local key=$1 value=$2 tmp="$ENV_FILE.tmp.$$"
|
||||||
local value=$2
|
|
||||||
local tmp="$ENV_FILE.tmp.$$"
|
|
||||||
|
|
||||||
awk -v key="$key" -v value="$value" '
|
awk -v key="$key" -v value="$value" '
|
||||||
BEGIN { found=0 }
|
BEGIN { found=0 }
|
||||||
index($0, key "=") == 1 { print key "=" value; found=1; next }
|
index($0, key "=") == 1 { print key "=" value; found=1; next }
|
||||||
@@ -55,58 +39,53 @@ update_env_var() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
load_env_file() {
|
load_env_file() {
|
||||||
# .env is intentionally limited to simple KEY=value entries used by Compose.
|
|
||||||
# shellcheck disable=SC1090
|
|
||||||
set -a
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
source "$ENV_FILE"
|
source "$ENV_FILE"
|
||||||
set +a
|
set +a
|
||||||
}
|
}
|
||||||
|
|
||||||
normalize_http_settings() {
|
normalize_repo_url() {
|
||||||
local url=${REPOSITORY_URL%/}
|
local url_var=$1 port_var=$2 default_port=$3 label=$4
|
||||||
local rest hostpart explicit_port=""
|
local url=${!url_var:-} port=${!port_var:-} rest hostpart explicit_port=""
|
||||||
|
|
||||||
HTTP_PORT=${HTTP_PORT:-}
|
url=${url%/}
|
||||||
|
[[ -n "$url" ]] || die "$url_var jest wymagane"
|
||||||
|
|
||||||
case "$url" in
|
case "$url" in
|
||||||
http://*)
|
http://*) rest=${url#http://} ;;
|
||||||
rest=${url#http://}
|
https://*) rest=${url#https://} ;;
|
||||||
[[ "$rest" != */* ]] || die "REPOSITORY_URL dla wbudowanego HTTP nie moze zawierac sciezki: $REPOSITORY_URL"
|
*) die "$url_var musi zaczynac sie od http:// albo https://" ;;
|
||||||
hostpart=$rest
|
|
||||||
|
|
||||||
if [[ "$hostpart" =~ ^\[[^]]+\]:([0-9]+)$ ]]; then
|
|
||||||
explicit_port=${BASH_REMATCH[1]}
|
|
||||||
elif [[ "$hostpart" =~ ^[^:]+:([0-9]+)$ ]]; then
|
|
||||||
explicit_port=${BASH_REMATCH[1]}
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$explicit_port" ]]; then
|
|
||||||
HTTP_PORT=$explicit_port
|
|
||||||
else
|
|
||||||
HTTP_PORT=${HTTP_PORT:-$DEFAULT_HTTP_PORT}
|
|
||||||
url="${url}:${HTTP_PORT}"
|
|
||||||
REPOSITORY_URL=$url
|
|
||||||
export REPOSITORY_URL
|
|
||||||
update_env_var REPOSITORY_URL "$REPOSITORY_URL"
|
|
||||||
log "REPOSITORY_URL bez portu - ustawiono $REPOSITORY_URL"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
https://*)
|
|
||||||
# HTTPS is supported as a public URL only when an external TLS proxy points
|
|
||||||
# to the local micro HTTP service. The embedded service itself is HTTP only.
|
|
||||||
HTTP_PORT=${HTTP_PORT:-$DEFAULT_HTTP_PORT}
|
|
||||||
warn "REPOSITORY_URL uzywa HTTPS. Wbudowany serwer nasluchuje po zwyklym HTTP na porcie $HTTP_PORT; HTTPS wymaga zewnetrznego reverse proxy/TLS."
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
die "REPOSITORY_URL musi zaczynac sie od http:// albo https://"
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
[[ "$HTTP_PORT" =~ ^[0-9]+$ ]] || die "HTTP_PORT musi byc liczba"
|
[[ "$rest" != */* ]] || die "$url_var nie moze zawierac sciezki: $url"
|
||||||
(( HTTP_PORT >= 1024 && HTTP_PORT <= 65535 )) || die "HTTP_PORT musi byc z zakresu 1024-65535"
|
hostpart=$rest
|
||||||
|
if [[ "$hostpart" =~ ^\[[^]]+\]:([0-9]+)$ ]]; then
|
||||||
|
explicit_port=${BASH_REMATCH[1]}
|
||||||
|
elif [[ "$hostpart" =~ ^[^:]+:([0-9]+)$ ]]; then
|
||||||
|
explicit_port=${BASH_REMATCH[1]}
|
||||||
|
fi
|
||||||
|
|
||||||
export HTTP_PORT
|
if [[ "$url" == http://* && -n "$explicit_port" ]]; then
|
||||||
update_env_var HTTP_PORT "$HTTP_PORT"
|
port=$explicit_port
|
||||||
|
else
|
||||||
|
port=${port:-$default_port}
|
||||||
|
if [[ "$url" == http://* && -z "$explicit_port" ]]; then
|
||||||
|
url="${url}:${port}"
|
||||||
|
log "$label URL bez portu - ustawiono $url"
|
||||||
|
elif [[ "$url" == https://* ]]; then
|
||||||
|
warn "$label uzywa HTTPS. Wbudowany nginx nasluchuje lokalnie po HTTP na porcie $port; TLS wymaga reverse proxy."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ "$port" =~ ^[0-9]+$ ]] || die "$port_var musi byc liczba"
|
||||||
|
(( port >= 1024 && port <= 65535 )) || die "$port_var musi byc z zakresu 1024-65535"
|
||||||
|
|
||||||
|
printf -v "$url_var" '%s' "$url"
|
||||||
|
printf -v "$port_var" '%s' "$port"
|
||||||
|
export "$url_var" "$port_var"
|
||||||
|
update_env_var "$url_var" "$url"
|
||||||
|
update_env_var "$port_var" "$port"
|
||||||
}
|
}
|
||||||
|
|
||||||
require_cmd docker
|
require_cmd docker
|
||||||
@@ -123,40 +102,40 @@ else
|
|||||||
die "brak Docker Compose (docker compose lub docker-compose)"
|
die "brak Docker Compose (docker compose lub docker-compose)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f "$ENV_FILE" ]]; then
|
[[ -f "$ENV_FILE" ]] || write_env_file
|
||||||
write_env_file
|
|
||||||
fi
|
|
||||||
|
|
||||||
load_env_file
|
load_env_file
|
||||||
|
|
||||||
: "${SOURCE_REPO:?Uzupelnij SOURCE_REPO w .env albo ustaw zmienna przed uruchomieniem start.sh}"
|
: "${SOURCE_REPO:?Uzupelnij SOURCE_REPO w .env albo ustaw zmienna przed uruchomieniem start.sh}"
|
||||||
: "${REPOSITORY_URL:?Uzupelnij REPOSITORY_URL w .env albo ustaw zmienna przed uruchomieniem start.sh}"
|
: "${REPOSITORY_URL:?Uzupelnij REPOSITORY_URL dla APT/RPM}"
|
||||||
|
: "${OPNSENSE_REPOSITORY_URL:?Uzupelnij OPNSENSE_REPOSITORY_URL dla repo FreeBSD pkg}"
|
||||||
|
|
||||||
case "$SOURCE_REPO" in
|
case "$SOURCE_REPO" in
|
||||||
http://*|https://*|ssh://*|git@*|file://*) ;;
|
http://*|https://*|ssh://*|git@*|file://*) ;;
|
||||||
*) die "SOURCE_REPO ma nieobslugiwany format: $SOURCE_REPO" ;;
|
*) die "SOURCE_REPO ma nieobslugiwany format: $SOURCE_REPO" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
normalize_http_settings
|
normalize_repo_url REPOSITORY_URL HTTP_PORT "$DEFAULT_HTTP_PORT" "APT/RPM"
|
||||||
|
normalize_repo_url OPNSENSE_REPOSITORY_URL OPNSENSE_HTTP_PORT "$DEFAULT_OPNSENSE_HTTP_PORT" "OPNsense"
|
||||||
|
[[ "$HTTP_PORT" != "$OPNSENSE_HTTP_PORT" ]] || die "HTTP_PORT i OPNSENSE_HTTP_PORT musza byc rozne"
|
||||||
|
|
||||||
mkdir -p "$DATA_DIR/repository" "$DATA_DIR/state" "$DATA_DIR/cargo" "$DATA_DIR/work" "$GPG_DIR"
|
mkdir -p "$DATA_DIR/repository" "$DATA_DIR/state" "$DATA_DIR/cargo" "$DATA_DIR/work" "$GPG_DIR"
|
||||||
chmod 0700 "$GPG_DIR" 2>/dev/null || true
|
chmod 0700 "$GPG_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
log "budowanie obrazu buildera"
|
log "budowanie obrazu buildera (Linux + FreeBSD cross-toolchain)"
|
||||||
"${COMPOSE[@]}" --env-file "$ENV_FILE" build "$SERVICE"
|
"${COMPOSE[@]}" --env-file "$ENV_FILE" build "$SERVICE"
|
||||||
|
|
||||||
if [[ ! -s "$GPG_DIR/private-key.asc" ]]; then
|
if [[ ! -s "$GPG_DIR/private-key.asc" ]]; then
|
||||||
log "brak klucza GPG - generowanie stalego klucza podpisujacego"
|
log "brak klucza GPG - generowanie stalego klucza podpisujacego Linux APT/RPM"
|
||||||
|
|
||||||
IMAGE_REF=$("${COMPOSE[@]}" --env-file "$ENV_FILE" config | awk '''
|
IMAGE_REF=$("${COMPOSE[@]}" --env-file "$ENV_FILE" config | awk '
|
||||||
$1 == "rust-package-builder:" { in_builder=1; next }
|
$1 == "rust-package-builder:" { in_builder=1; next }
|
||||||
in_builder && $1 == "image:" { print $2; exit }
|
in_builder && $1 == "image:" { print $2; exit }
|
||||||
''')
|
')
|
||||||
[[ -n "$IMAGE_REF" ]] || die "nie mozna ustalic nazwy zbudowanego obrazu buildera"
|
[[ -n "$IMAGE_REF" ]] || die "nie mozna ustalic nazwy zbudowanego obrazu buildera"
|
||||||
docker image inspect "$IMAGE_REF" >/dev/null 2>&1 || die "zbudowany obraz nie istnieje: $IMAGE_REF"
|
docker image inspect "$IMAGE_REF" >/dev/null 2>&1 || die "zbudowany obraz nie istnieje: $IMAGE_REF"
|
||||||
|
|
||||||
uid=$(id -u)
|
uid=$(id -u)
|
||||||
gid=$(id -g)
|
gid=$(id -g)
|
||||||
|
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
--user "$uid:$gid" \
|
--user "$uid:$gid" \
|
||||||
--entrypoint /bin/bash \
|
--entrypoint /bin/bash \
|
||||||
@@ -168,7 +147,6 @@ if [[ ! -s "$GPG_DIR/private-key.asc" ]]; then
|
|||||||
export GNUPGHOME=/tmp/rpb-keygen
|
export GNUPGHOME=/tmp/rpb-keygen
|
||||||
mkdir -p "$GNUPGHOME"
|
mkdir -p "$GNUPGHOME"
|
||||||
chmod 0700 "$GNUPGHOME"
|
chmod 0700 "$GNUPGHOME"
|
||||||
|
|
||||||
cat > /tmp/gpg-batch <<"KEY"
|
cat > /tmp/gpg-batch <<"KEY"
|
||||||
Key-Type: RSA
|
Key-Type: RSA
|
||||||
Key-Length: 3072
|
Key-Length: 3072
|
||||||
@@ -179,11 +157,9 @@ Expire-Date: 0
|
|||||||
%no-protection
|
%no-protection
|
||||||
%commit
|
%commit
|
||||||
KEY
|
KEY
|
||||||
|
|
||||||
gpg --batch --generate-key /tmp/gpg-batch
|
gpg --batch --generate-key /tmp/gpg-batch
|
||||||
fingerprint=$(gpg --batch --with-colons --list-secret-keys | awk -F: '\''/^sec:/ {seen=1; next} seen && /^fpr:/ {print $10; exit}'\'')
|
fingerprint=$(gpg --batch --with-colons --list-secret-keys | awk -F: '\''/^sec:/ {seen=1; next} seen && /^fpr:/ {print $10; exit}'\'')
|
||||||
test -n "$fingerprint"
|
test -n "$fingerprint"
|
||||||
|
|
||||||
gpg --batch --armor --export-secret-keys "$fingerprint" > /out/private-key.asc
|
gpg --batch --armor --export-secret-keys "$fingerprint" > /out/private-key.asc
|
||||||
gpg --batch --armor --export "$fingerprint" > /out/public-key.asc
|
gpg --batch --armor --export "$fingerprint" > /out/public-key.asc
|
||||||
printf "%s\n" "$fingerprint" > /out/fingerprint
|
printf "%s\n" "$fingerprint" > /out/fingerprint
|
||||||
@@ -197,30 +173,25 @@ else
|
|||||||
log "uzywanie istniejacego klucza GPG"
|
log "uzywanie istniejacego klucza GPG"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "uruchamianie buildera i mikro HTTP przez Docker Compose"
|
log "uruchamianie buildera i wspolnego nginx przez Docker Compose"
|
||||||
"${COMPOSE[@]}" --env-file "$ENV_FILE" up -d "$SERVICE" "$HTTP_SERVICE"
|
"${COMPOSE[@]}" --env-file "$ENV_FILE" up -d "$SERVICE" "$HTTP_SERVICE"
|
||||||
|
|
||||||
fingerprint=""
|
fingerprint=""
|
||||||
if [[ -r "$GPG_DIR/fingerprint" ]]; then
|
[[ -r "$GPG_DIR/fingerprint" ]] && fingerprint=$(tr -d '[:space:]' < "$GPG_DIR/fingerprint")
|
||||||
fingerprint=$(tr -d '[:space:]' < "$GPG_DIR/fingerprint")
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "gotowe"
|
log "gotowe"
|
||||||
printf '\n'
|
printf '\nSOURCE_REPO: %s\n' "$SOURCE_REPO"
|
||||||
printf 'SOURCE_REPO: %s\n' "$SOURCE_REPO"
|
printf 'APT/RPM URL: %s/\n' "${REPOSITORY_URL%/}"
|
||||||
printf 'REPOSITORY_URL: %s\n' "$REPOSITORY_URL"
|
printf 'APT/RPM local HTTP port: %s\n' "$HTTP_PORT"
|
||||||
printf 'HTTP port: %s\n' "$HTTP_PORT"
|
printf 'OPNsense pkg URL: %s/\n' "${OPNSENSE_REPOSITORY_URL%/}"
|
||||||
[[ -n "$fingerprint" ]] && printf 'GPG fingerprint: %s\n' "$fingerprint"
|
printf 'OPNsense local HTTP port: %s\n' "$OPNSENSE_HTTP_PORT"
|
||||||
printf 'Publiczny katalog: %s\n' "$DATA_DIR/repository"
|
[[ -n "$fingerprint" ]] && printf 'GPG fingerprint: %s\n' "$fingerprint"
|
||||||
printf '\n'
|
printf 'Publiczny katalog: %s\n\n' "$DATA_DIR/repository"
|
||||||
printf 'Repo/strona: %s/\n' "${REPOSITORY_URL%/}"
|
printf 'Health Linux: http://127.0.0.1:%s/healthz\n' "$HTTP_PORT"
|
||||||
printf 'Healthcheck: http://127.0.0.1:%s/healthz\n' "$HTTP_PORT"
|
printf 'Health OPNsense: http://127.0.0.1:%s/healthz\n\n' "$OPNSENSE_HTTP_PORT"
|
||||||
printf '\n'
|
printf 'Logi buildera:\n '
|
||||||
printf 'Podglad logow buildera:\n '
|
|
||||||
printf '%q ' "${COMPOSE[@]}"
|
printf '%q ' "${COMPOSE[@]}"
|
||||||
printf 'logs -f %q\n' "$SERVICE"
|
printf 'logs -f %q\n' "$SERVICE"
|
||||||
printf 'Podglad logow HTTP:\n '
|
printf 'Logi HTTP:\n '
|
||||||
printf '%q ' "${COMPOSE[@]}"
|
printf '%q ' "${COMPOSE[@]}"
|
||||||
printf 'logs -f %q\n' "$HTTP_SERVICE"
|
printf 'logs -f %q\n' "$HTTP_SERVICE"
|
||||||
printf '\n'
|
|
||||||
printf 'Builder natychmiast sprawdzi branch master (lub SOURCE_BRANCH, jezeli ustawiono go opcjonalnie) i po udanym buildzie opublikuje repozytorium.\n'
|
|
||||||
+31
-18
@@ -5,36 +5,49 @@ ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|||||||
for f in "$ROOT"/start.sh "$ROOT"/scripts/*.sh "$ROOT"/tests/*.sh; do
|
for f in "$ROOT"/start.sh "$ROOT"/scripts/*.sh "$ROOT"/tests/*.sh; do
|
||||||
bash -n "$f"
|
bash -n "$f"
|
||||||
done
|
done
|
||||||
|
python3 -m py_compile "$ROOT/scripts/generate-opnsense-repo.py"
|
||||||
|
|
||||||
|
# Main Linux builder.
|
||||||
grep -q 'FROM rockylinux:9' "$ROOT/Dockerfile"
|
grep -q 'FROM rockylinux:9' "$ROOT/Dockerfile"
|
||||||
grep -q 'dnf -y install --allowerasing' "$ROOT/Dockerfile"
|
|
||||||
grep -q 'python3.11' "$ROOT/Dockerfile"
|
|
||||||
grep -q 'RUST_PROXY_PYTHON=/usr/bin/python3.11' "$ROOT/Dockerfile"
|
|
||||||
grep -q 'nfpm package' "$ROOT/scripts/build-release.sh"
|
grep -q 'nfpm package' "$ROOT/scripts/build-release.sh"
|
||||||
grep -q 'aptly .*publish' "$ROOT/scripts/build-release.sh"
|
grep -q 'aptly .*publish' "$ROOT/scripts/build-release.sh"
|
||||||
grep -q 'createrepo_c' "$ROOT/scripts/build-release.sh"
|
grep -q 'createrepo_c' "$ROOT/scripts/build-release.sh"
|
||||||
grep -q 'current.new' "$ROOT/scripts/build-release.sh"
|
|
||||||
grep -q 'last_package_release' "$ROOT/scripts/build-release.sh"
|
|
||||||
grep -q 'repo add -force-replace' "$ROOT/scripts/build-release.sh"
|
grep -q 'repo add -force-replace' "$ROOT/scripts/build-release.sh"
|
||||||
grep -q '/run/package-builder/gnupg' "$ROOT/scripts/entrypoint.sh"
|
grep -q '/run/package-builder/gnupg' "$ROOT/scripts/entrypoint.sh"
|
||||||
grep -q 'rustproxy-overview.json' "$ROOT/scripts/prepare-package.sh"
|
grep -q 'rustproxy-overview.json' "$ROOT/scripts/prepare-package.sh"
|
||||||
grep -q 'debian/${PACKAGE_NAME}.service' "$ROOT/scripts/prepare-package.sh"
|
|
||||||
grep -q 'private-key.asc' "$ROOT/start.sh"
|
# Integrated FreeBSD/OPNsense build.
|
||||||
grep -q 'docker compose' "$ROOT/start.sh"
|
grep -q 'FREEBSD_RELEASE=15.1-RELEASE' "$ROOT/Dockerfile"
|
||||||
grep -q 'Key-Type: RSA' "$ROOT/start.sh"
|
grep -q 'x86_64-unknown-freebsd' "$ROOT/Dockerfile"
|
||||||
grep -q 'DEFAULT_HTTP_PORT=18765' "$ROOT/start.sh"
|
grep -q '/opt/freebsd-sysroot' "$ROOT/Dockerfile"
|
||||||
grep -q 'repository-http' "$ROOT/start.sh"
|
grep -q 'build-opnsense.sh' "$ROOT/scripts/build-release.sh"
|
||||||
grep -Fq '${SOURCE_REPO:?SOURCE_REPO is required}' "$ROOT/docker-compose.yml"
|
grep -q 'package-opnsense.sh' "$ROOT/scripts/build-release.sh"
|
||||||
|
grep -q 'generate-opnsense-repo.py' "$ROOT/scripts/build-release.sh"
|
||||||
|
grep -q 'OPNSENSE_REPOSITORY_URL' "$ROOT/scripts/entrypoint.sh"
|
||||||
|
grep -q 'OPNSENSE_RETENTION' "$ROOT/scripts/entrypoint.sh"
|
||||||
|
test -f "$ROOT/opnsense/plugin/src/etc/rc.d/rustproxy"
|
||||||
|
test -f "$ROOT/opnsense/patches/0.20.0/0001-opnsense-build.patch"
|
||||||
|
|
||||||
|
# One atomic switch exposes Linux and OPNsense together.
|
||||||
|
grep -q 'current.new' "$ROOT/scripts/build-release.sh"
|
||||||
|
grep -q 'current/opnsense' "$ROOT/scripts/init-storage.sh"
|
||||||
|
grep -q 'last_opnsense_package_version' "$ROOT/scripts/build-release.sh"
|
||||||
|
|
||||||
|
# Two public endpoints from one nginx container.
|
||||||
grep -Fq '${REPOSITORY_URL:?REPOSITORY_URL is required}' "$ROOT/docker-compose.yml"
|
grep -Fq '${REPOSITORY_URL:?REPOSITORY_URL is required}' "$ROOT/docker-compose.yml"
|
||||||
grep -q 'nginxinc/nginx-unprivileged:stable-alpine-slim' "$ROOT/docker-compose.yml"
|
grep -Fq '${OPNSENSE_REPOSITORY_URL:?OPNSENSE_REPOSITORY_URL is required}' "$ROOT/docker-compose.yml"
|
||||||
grep -q 'read_only: true' "$ROOT/docker-compose.yml"
|
|
||||||
grep -q 'no-new-privileges:true' "$ROOT/docker-compose.yml"
|
|
||||||
grep -q 'cap_drop:' "$ROOT/docker-compose.yml"
|
|
||||||
grep -Fq '0.0.0.0:${HTTP_PORT:-18765}:8080' "$ROOT/docker-compose.yml"
|
grep -Fq '0.0.0.0:${HTTP_PORT:-18765}:8080' "$ROOT/docker-compose.yml"
|
||||||
grep -q 'server_tokens off' "$ROOT/http/nginx.conf"
|
grep -Fq '0.0.0.0:${OPNSENSE_HTTP_PORT:-18766}:8081' "$ROOT/docker-compose.yml"
|
||||||
|
grep -q 'listen 8080' "$ROOT/http/nginx.conf"
|
||||||
|
grep -q 'listen 8081' "$ROOT/http/nginx.conf"
|
||||||
|
grep -q 'root /srv/repository/opnsense' "$ROOT/http/nginx.conf"
|
||||||
grep -q 'GET|HEAD' "$ROOT/http/nginx.conf"
|
grep -q 'GET|HEAD' "$ROOT/http/nginx.conf"
|
||||||
grep -q 'autoindex off' "$ROOT/http/nginx.conf"
|
grep -q 'autoindex off' "$ROOT/http/nginx.conf"
|
||||||
grep -q 'X-Content-Type-Options' "$ROOT/http/nginx.conf"
|
|
||||||
grep -q 'healthz' "$ROOT/http/nginx.conf"
|
grep -q 'healthz' "$ROOT/http/nginx.conf"
|
||||||
|
|
||||||
|
# start.sh and example configuration expose both URLs/ports.
|
||||||
|
grep -q 'DEFAULT_HTTP_PORT=18765' "$ROOT/start.sh"
|
||||||
|
grep -q 'DEFAULT_OPNSENSE_HTTP_PORT=18766' "$ROOT/start.sh"
|
||||||
|
grep -q 'OPNSENSE_REPOSITORY_URL=https://opn-rustproxy.linuxiarz.pl' "$ROOT/.env.example"
|
||||||
|
|
||||||
echo 'static checks: OK'
|
echo 'static checks: OK'
|
||||||
Reference in new issue
Block a user