diff --git a/.build/.gitignore b/.build/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/.build/DEB/control b/.build/DEB/control new file mode 100644 index 0000000..526d11b --- /dev/null +++ b/.build/DEB/control @@ -0,0 +1,10 @@ +Package: rpxy +Version: @BUILD_VERSION@-1 +Maintainer: Jun Kurihara +Homepage: https://github.com/junkurihara/rust-rpxy +Architecture: amd64 +Depends: systemd +Recommends: rpxy-webui +Priority: optional +Section: base +Description: A simple and ultrafast reverse-proxy serving multiple domain names with TLS termination, written in Rust diff --git a/.build/DEB/postinst b/.build/DEB/postinst new file mode 100644 index 0000000..0ec4552 --- /dev/null +++ b/.build/DEB/postinst @@ -0,0 +1,35 @@ +#!/bin/sh +set -e + +# Source debconf library +. /usr/share/debconf/confmodule + +# Create rpxy user if it doesn't exist +if ! getent passwd rpxy > /dev/null; then + adduser --system --group --no-create-home --shell /usr/sbin/nologin rpxy +fi + +# Set correct ownership for config directory +if [ -d /etc/rpxy ]; then + chown -R rpxy:rpxy /etc/rpxy +fi + +# Reload systemd, enable and start the service +if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then + deb-systemd-helper unmask rpxy.service >/dev/null || true + if deb-systemd-helper --quiet was-enabled rpxy.service; then + deb-systemd-helper enable rpxy.service >/dev/null || true + else + deb-systemd-helper update-state rpxy.service >/dev/null || true + fi + if [ -d /run/systemd/system ]; then + systemctl --system daemon-reload >/dev/null || true + if [ -n "$2" ]; then + deb-systemd-invoke try-restart rpxy.service >/dev/null || true + else + deb-systemd-invoke start rpxy.service >/dev/null || true + fi + fi +fi + +exit 0 diff --git a/.build/DEB/postrm b/.build/DEB/postrm new file mode 100644 index 0000000..4cfa3ea --- /dev/null +++ b/.build/DEB/postrm @@ -0,0 +1,22 @@ +#!/bin/sh +set -e + +if [ "$1" = "purge" ]; then + # Remove the rpxy user + if getent passwd rpxy >/dev/null; then + deluser --quiet --system rpxy >/dev/null || true + fi + + # Remove config directory + rm -rf /etc/rpxy + + # Remove systemd service state + deb-systemd-helper purge rpxy.service >/dev/null || true + deb-systemd-helper unmask rpxy.service >/dev/null || true +fi + +if [ -d /run/systemd/system ]; then + systemctl --system daemon-reload >/dev/null || true +fi + +exit 0 diff --git a/.build/DEB/prerm b/.build/DEB/prerm new file mode 100644 index 0000000..d088bc4 --- /dev/null +++ b/.build/DEB/prerm @@ -0,0 +1,8 @@ +#!/bin/sh +set -e + +if [ -d /run/systemd/system ] && [ "$1" = remove ]; then + deb-systemd-invoke stop rpxy.service >/dev/null || true +fi + +exit 0 diff --git a/.build/Jenkinsfile b/.build/Jenkinsfile new file mode 100644 index 0000000..e34c5fb --- /dev/null +++ b/.build/Jenkinsfile @@ -0,0 +1,208 @@ +pipeline { + agent none + + environment { + // Define common variables used throughout the pipeline + REPO_URL = 'https://github.com/junkurihara/rust-rpxy.git' + BINARY_NAME = 'rpxy' + // BUILD_VERSION is not set because it will be extracted from Cargo.toml in the first step + // BUILD_VERSION = '' + } + + stages { + stage('Prepare Build Environment') { + agent { + kubernetes { + inheritFrom 'default' + yaml """ + apiVersion: v1 + kind: Pod + spec: + containers: + - name: rust-cargo + image: rust:slim + command: + - cat + tty: true + """ + } + } + steps { + container('rust-cargo') { + // Install git + sh 'apt-get update && apt-get -y install git --no-install-recommends' + + // Clone and Prepare Repository + sh "git clone ${REPO_URL}" + + dir('rust-rpxy') { + sh """ + # Update submodule URLs to HTTPS (allows cloning without SSH keys) + sed -i 's|git@github.com:|https://github.com/|g' .gitmodules + + # Initialize and update submodules + git submodule update --init + """ + + // Extract BUILD_VERSION from Cargo.toml + script { + // Extract version from Cargo.toml and set it as an environment variable + def buildVersion = sh(script: 'grep "^version" Cargo.toml | sed \'s/version = "\\([0-9.]*\\)"/\\1/\'', returnStdout: true).trim() + + if (buildVersion) { + env.BUILD_VERSION = buildVersion + echo "Using extracted version: ${env.BUILD_VERSION}" + } else { + error "Version not found in Cargo.toml" + } + } + + // Build the binary + sh 'cargo build --release' + + // Prepare and stash files + sh """ + # Move binary to workspace root for easier access + mv target/release/${BINARY_NAME} .. + + # Move necessary files for packaging + mv .build/DEB/* .. + mv .build/RPM/* .. + mv .build/rpxy* .. + mv .build/config.toml .. + mv README.md .. + mv LICENSE .. + """ + } + + // Stash files for use in later stages + stash includes: "${BINARY_NAME}", name: "binary" + stash includes: "control, postinst, prerm, postrm, rpxy-start.sh", name: "deb-files" + stash includes: "${BINARY_NAME}.spec", name: "rpm-files" + stash includes: "rpxy.service, config.toml", name: "service-file" + stash includes: "LICENSE, README.md", name: "docs" + + // Archive the binary as an artifact + archiveArtifacts artifacts: "${BINARY_NAME}", allowEmptyArchive: false, fingerprint: true + } + } + } + + stage('Build RPM Package') { + agent { + kubernetes { + inheritFrom 'default' + yaml """ + apiVersion: v1 + kind: Pod + spec: + containers: + - name: rpm-build + image: rockylinux:9 + command: + - cat + tty: true + """ + } + } + steps { + container('rpm-build') { + // Prepare the RPM build environment + unstash 'binary' + unstash 'rpm-files' + unstash 'service-file' + unstash 'docs' + + // Install necessary tools for RPM building + sh 'dnf update -y && dnf install -y rpmdevtools tar' + + // Create the RPM package + sh """ + # Create RPM build directory structure + mkdir -p rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} + mkdir -p ${BINARY_NAME}-${BUILD_VERSION} + + # Move files to the appropriate locations + mv ${BINARY_NAME} ${BINARY_NAME}.service LICENSE README.md config.toml ${BINARY_NAME}-${BUILD_VERSION}/ + tar -czf rpmbuild/SOURCES/${BINARY_NAME}-${BUILD_VERSION}.tar.gz ${BINARY_NAME}-${BUILD_VERSION}/ + mv ${BINARY_NAME}.spec rpmbuild/SPECS/ + + # Update spec file with correct version and source + sed -i 's/@BUILD_VERSION@/${BUILD_VERSION}/; s/@Source0@/${BINARY_NAME}-${BUILD_VERSION}.tar.gz/' rpmbuild/SPECS/${BINARY_NAME}.spec + + # Build the RPM package + rpmbuild --define "_topdir ${WORKSPACE}/rpmbuild" --define "_version ${BUILD_VERSION}" -bb rpmbuild/SPECS/${BINARY_NAME}.spec + + # Move RPM to root for archiving + mv rpmbuild/RPMS/x86_64/${BINARY_NAME}-${BUILD_VERSION}-1.el9.x86_64.rpm . + """ + + // Archive the RPM package + archiveArtifacts artifacts: "${BINARY_NAME}-${BUILD_VERSION}-1.el9.x86_64.rpm", allowEmptyArchive: false, fingerprint: true + } + } + } + + stage('Build DEB Package') { + agent { + kubernetes { + inheritFrom 'default' + yaml """ + apiVersion: v1 + kind: Pod + spec: + containers: + - name: debian-build + image: debian:stable-slim + command: + - cat + tty: true + """ + } + } + steps { + container('debian-build') { + // Prepare the DEB build environment + unstash 'binary' + unstash 'deb-files' + unstash 'service-file' + unstash 'docs' + + // Install necessary tools for DEB building + sh 'apt-get update && apt-get install -y dpkg-dev --no-install-recommends' + + // Create the DEB package + sh """ + # Define DEB package directory + DEB_DIR=${BINARY_NAME}_${BUILD_VERSION}-1_amd64 + + # Create directory structure for DEB package + bash -c \"mkdir -p \$DEB_DIR/{DEBIAN,usr/{bin,local/bin,share/doc/${BINARY_NAME}},etc/{systemd/system,${BINARY_NAME}/acme_registry}}\" + + # Move files to appropriate locations + mv postinst prerm postrm \$DEB_DIR/DEBIAN/ + chmod 755 \$DEB_DIR/DEBIAN/postinst + chmod 755 \$DEB_DIR/DEBIAN/prerm + chmod 755 \$DEB_DIR/DEBIAN/postrm + mv rpxy-start.sh \$DEB_DIR/usr/local/bin/ + chmod 0755 \$DEB_DIR/usr/local/bin/rpxy-start.sh + mv ${BINARY_NAME} \$DEB_DIR/usr/bin/ + mv rpxy.service \$DEB_DIR/etc/systemd/system/ + mv LICENSE README.md \$DEB_DIR/usr/share/doc/${BINARY_NAME}/ + mv config.toml \$DEB_DIR/etc/${BINARY_NAME}/ + mv control \$DEB_DIR/DEBIAN/ + + # Update control file with correct version + sed -i 's/@BUILD_VERSION@/${BUILD_VERSION}/' \$DEB_DIR/DEBIAN/control + + # Build the DEB package + dpkg-deb --build --root-owner-group \$DEB_DIR + """ + + // Archive the DEB package + archiveArtifacts artifacts: "${BINARY_NAME}_${BUILD_VERSION}-1_amd64.deb", allowEmptyArchive: false, fingerprint: true + } + } + } + } +} diff --git a/.build/RPM/rpxy.spec b/.build/RPM/rpxy.spec new file mode 100644 index 0000000..16e3ec1 --- /dev/null +++ b/.build/RPM/rpxy.spec @@ -0,0 +1,78 @@ +Name: rpxy +Version: @BUILD_VERSION@ +Release: 1%{?dist} +Summary: A simple and ultrafast reverse-proxy serving multiple domain names with TLS termination, written in Rust + +License: MIT +URL: https://github.com/junkurihara/rust-rpxy +Source0: @Source0@ +BuildArch: x86_64 + +Requires: systemd + +%description +This rpm installs rpxy into /usr/bin and sets up a systemd service. + +# Prep section: Unpack the source +%prep +%autosetup + +# Install section: Copy files to their destinations +%install +rm -rf %{buildroot} + +# Create necessary directories +mkdir -p %{buildroot}%{_bindir} +mkdir -p %{buildroot}%{_sysconfdir}/systemd/system +mkdir -p %{buildroot}%{_sysconfdir}/rpxy/acme_registry +mkdir -p %{buildroot}%{_docdir}/rpxy + +# Copy files +cp rpxy %{buildroot}%{_bindir}/ +cp rpxy.service %{buildroot}%{_sysconfdir}/systemd/system/ +cp config.toml %{buildroot}%{_sysconfdir}/rpxy/ +cp LICENSE README.md %{buildroot}%{_docdir}/rpxy/ + +# Clean section: Remove buildroot +%clean +rm -rf %{buildroot} + +# Pre-install script +%pre +# Create the rpxy user if it does not exist +if ! getent passwd rpxy >/dev/null; then + useradd -r -s /sbin/nologin -d / -c "rpxy system user" rpxy +fi + +# Post-install script +%post +# Set ownership of config file to rpxy user +chown -R rpxy:rpxy %{_sysconfdir}/rpxy + +# Reload systemd, enable and start rpxy service +%systemd_post rpxy.service + +# Pre-uninstall script +%preun +%systemd_preun rpxy.service + +# Post-uninstall script +%postun +%systemd_postun_with_restart rpxy.service + +# Only remove user and config on full uninstall +if [ $1 -eq 0 ]; then + # Remove rpxy user + userdel rpxy + + # Remove the configuration directory if it exists + [ -d %{_sysconfdir}/rpxy ] && rm -rf %{_sysconfdir}/rpxy +fi + +# Files section: List all files included in the package +%files +%license %{_docdir}/rpxy/LICENSE +%doc %{_docdir}/rpxy/README.md +%{_sysconfdir}/systemd/system/rpxy.service +%attr(755, rpxy, rpxy) %{_bindir}/rpxy +%attr(644, rpxy, rpxy) %config(noreplace) %{_sysconfdir}/rpxy/config.toml diff --git a/.build/config.toml b/.build/config.toml new file mode 100644 index 0000000..40279c4 --- /dev/null +++ b/.build/config.toml @@ -0,0 +1,94 @@ +######################################## +# # +# rust-rxpy configuration # +# # +######################################## +################################### +# Global settings # +################################### +# Both or either one of http/https ports must be specified +listen_port = 80 +listen_port_tls = 443 + +# Optional: If your https is listening on a custom port like 8443 +# When you specify this, the server sends a redirection response 301 with specified port to the client for plaintext http request. +# Otherwise, the server sends 301 with the same port as `listen_port_tls`. +# disabled means http -> https and enabled means http -> https: +# https_redirection_port = 443 + +# Optional for h2 and http1.1 +tcp_listen_backlog = 1024 + +# Optional for h2 and http1.1 +max_concurrent_streams = 100 + +# Optional. Counted in total for http1.1, 2, 3 +max_clients = 512 + +# Optional: Listen [::] +listen_ipv6 = false + +# Optional: App that serves all plaintext http request by referring to HOSTS or request header +# execpt for configured application. +# Note that this is only for http. +# Note that nothing is served for requests via https since secure channel cannot be +# established for unconfigured server_name, and they are always rejected by checking SNI. +# default_app = 'another_localhost' + +################################### +# Backend settings # +################################### +[apps] + +###################################################################### +## Registering a backend app served by a domain name "localhost" +[apps.localhost] +server_name = 'localhost' # Domain name + +reverse_proxy = [{ upstream = [{ location = 'localhost:8080' }] }] + +# Optional: TLS setting. if https_port is specified and tls is true above, either of this must be given. +#tls = { https_redirection = true, tls_cert_path = '/certs/server.crt', tls_cert_key_path = '/certs/server.key' } +#tls = { https_redirection = true, acme = true } + +############################################ +# For more settings check: # +# https://github.com/junkurihara/rust-rpxy # +############################################ + +################################### +# Experimantal settings # +################################### +[experimental] +# Higly recommend not to be true. If true, you ignore RFC. if not specified, it is always false. +# This might be required to be true when a certificate is used by multiple backend hosts, especially in case where a TLS connection is re-used. +# We should note that this strongly depends on the client implementation. +ignore_sni_consistency = false + +# Force connection handling timeout regardless of the connection status, i.e., idle or not. +# 0 represents an infinite timeout. [default: 0] +# Note that idel and header read timeouts are always specified independently of this. +connection_handling_timeout = 0 # sec + +# If this specified, h3 is enabled +[experimental.h3] +alt_svc_max_age = 3600 # sec +request_max_body_size = 65536 # bytes +max_concurrent_connections = 10000 +max_concurrent_bidistream = 100 +max_concurrent_unistream = 100 +max_idle_timeout = 10 # secs. 0 represents an infinite timeout. +# WARNING: If a peer or its network path malfunctions or acts maliciously, an infinite idle timeout can result in permanently hung futures! + +# If this specified, file cache feature is enabled +[experimental.cache] +cache_dir = '/tmp/rpxy/.cache' # optional. default is "./cache" relative to the current working directory +max_cache_entry = 1000 # optional. default is 1k +max_cache_each_size = 65535 # optional. default is 64k +max_cache_each_size_on_memory = 4096 # optional. default is 4k if 0, it is always file cache. + +# ACME settings. Unless specified, ACME is disabled. +[experimental.acme] +dir_url = "https://acme-v02.api.letsencrypt.org/directory" +email = "test@example.com" +registry_path = "/etc/rpxy/acme_registry" diff --git a/.build/rpxy-start.sh b/.build/rpxy-start.sh new file mode 100644 index 0000000..97800da --- /dev/null +++ b/.build/rpxy-start.sh @@ -0,0 +1,85 @@ +#!/bin/sh + +set -e + +CACHE_DIR="/tmp/rpxy/.cache" +CONFIG_DIR="/etc/rpxy" +CONFIG_FILE="$CONFIG_DIR/config.toml" +WEBUI_CONFIG="/var/www/rpxy-webui/storage/app/config.toml" +COMMENT_MARKER="# IMPORTANT: DEACTIVATED This config is deactivated because rpxy-webui is installed" + +setup_directories() { + # Check if systemd is available + if [ -d /run/systemd/system ]; then + # Use systemd RuntimeDirectory if available + if [ -d /run/rpxy ]; then + RUNTIME_DIR="/run/rpxy" + # If not available use PrivateTmp + elif [ -d /tmp/systemd-private-*/tmp ]; then + RUNTIME_DIR=$(find /tmp/systemd-private-*/tmp -type d -name "rpxy" 2>/dev/null | head -n 1) + fi + + # Create subdirectory for cache + CACHE_DIR="$RUNTIME_DIR/.cache" + # Ensure the cache directory exists as it could get deleted on system restart + mkdir -p "$CACHE_DIR" + chown rpxy:rpxy "$CACHE_DIR" # not recursively because parent folder is managed by systemd + chmod 700 "$CACHE_DIR" + else + # Fallback to linux tmp directory if no systemd is found + RUNTIME_DIR="/tmp/rpxy" + CACHE_DIR="$RUNTIME_DIR/.cache" + # Ensure the cache directory exists as it could get deleted on system restart + mkdir -p "$CACHE_DIR" + chown -R rpxy:rpxy "$RUNTIME_DIR" + chmod 700 "$CACHE_DIR" + fi + + echo "Using runtime directory: $RUNTIME_DIR" + echo "Using cache directory: $CACHE_DIR" +} + +# Check if rpxy-webui is installed +is_package_installed() { + if command -v rpm >/dev/null 2>&1; then + rpm -q "$1" >/dev/null 2>&1 + elif command -v dpkg-query >/dev/null 2>&1; then + dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -q "install ok installed" + else + echo "Neither rpm nor dpkg-query found. Cannot verify installation status of rpxy-webui package." >&2 + return 1 + fi +} + +# Create the config file if it doesn't exist +ensure_config_exists() { + mkdir -p "$CONFIG_DIR" + [ -f "$CONFIG_FILE" ] || echo "# Standard rpxy Konfigurationsdatei" > "$CONFIG_FILE" +} + +add_comment_to_config() { + if ! grep -q "^$COMMENT_MARKER" "$CONFIG_FILE"; then + sed -i "1i$COMMENT_MARKER\n" "$CONFIG_FILE" + fi +} + +remove_comment_from_config() { + sed -i "/^$COMMENT_MARKER/d" "$CONFIG_FILE" +} + +main() { + setup_directories + ensure_config_exists + + if is_package_installed rpxy-webui; then + echo "rpxy-webui is installed. Starting rpxy with rpxy-webui" + add_comment_to_config + exec /usr/bin/rpxy -w -c "$WEBUI_CONFIG" + else + echo "rpxy-webui is not installed. Starting with default config" + remove_comment_from_config + exec /usr/bin/rpxy -c "$CONFIG_FILE" + fi +} + +main "$@" diff --git a/.build/rpxy.service b/.build/rpxy.service new file mode 100644 index 0000000..49d4379 --- /dev/null +++ b/.build/rpxy.service @@ -0,0 +1,23 @@ +[Unit] +Description=rpxy system service +Documentation=https://github.com/junkurihara/rust-rpxy +After=network.target +Wants=network-online.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/rpxy-start.sh +Restart=on-failure +RestartSec=5 +User=rpxy +Group=rpxy +AmbientCapabilities=CAP_NET_BIND_SERVICE +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=full +ProtectHome=true +RuntimeDirectory=rpxy +RuntimeDirectoryMode=0750 + +[Install] +WantedBy=multi-user.target diff --git a/README.md b/README.md index 274a096..2569555 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ By default, `rpxy` provides the *TLS connection sanitization* by correctly bindi ## Installing/Building an Executable Binary of `rpxy` +### Building from Source + You can build an executable binary yourself by checking out this Git repository. ```bash @@ -45,6 +47,12 @@ Then you have an executive binary `rust-rpxy/target/release/rpxy`. Note that we do not have an option of installation via [`crates.io`](https://crates.io/), i.e., `cargo install`, at this point since some dependencies are not published yet. Alternatively, you can use docker image (see below) as the easiest way for `amd64` environment. +### Package Installation for Linux (RPM/DEB) + +You can found the Jenkins CI/CD build scripts for `rpxy` in the [./build](./build) directory. + +Prebuilt packages for Linux RPM and DEB are available at [https://rpxy.gamerboy59.dev](https://rpxy.gamerboy59.dev), provided by [@Gamerboy59](https://github.com/Gamerboy59). + ## Usage `rpxy` always refers to a configuration file in TOML format, e.g., `config.toml`. You can find an example of the configuration file, `config-example.toml`, in this repository.