From 1f4c620e5df6c1ee7e82f8ddd8a9f0f192dd8917 Mon Sep 17 00:00:00 2001 From: Tudor TIMCU Date: Mon, 9 Feb 2026 17:06:02 +0200 Subject: [PATCH 01/10] Enhance Linux build support and RPM packaging - Updated Makefile to include new targets for building Linux binaries (amd64 and arm64) and added a build-rpm target for creating RPM packages. - Introduced GitHub Actions workflow for building Linux packages, including steps for downloading artifacts, verifying binaries, and building RPMs. - Added RPM packaging scripts and service files to facilitate installation and management of the SafeChain Ultimate application on Linux systems. - Implemented uninstall script for proper cleanup of installed files and services. --- .github/workflows/build-linux.yml | 78 +++++++++++++ .github/workflows/build-unix.yml | 28 +++-- Makefile | 18 ++- packaging/rpm/build-rpm.sh | 139 +++++++++++++++++++++++ packaging/rpm/safechain-ultimate.service | 16 +++ packaging/rpm/safechain-ultimate.spec | 71 ++++++++++++ packaging/rpm/scripts/uninstall | 93 +++++++++++++++ 7 files changed, 432 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/build-linux.yml create mode 100755 packaging/rpm/build-rpm.sh create mode 100644 packaging/rpm/safechain-ultimate.service create mode 100644 packaging/rpm/safechain-ultimate.spec create mode 100755 packaging/rpm/scripts/uninstall diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml new file mode 100644 index 00000000..669dab7e --- /dev/null +++ b/.github/workflows/build-linux.yml @@ -0,0 +1,78 @@ +name: Build Linux Packages + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_call: + inputs: + version: + required: true + type: string + +jobs: + build-unix: + uses: ./.github/workflows/build-unix.yml + with: + version: ${{ inputs.version || 'dev' }} + + build-rpm: + needs: build-unix + strategy: + matrix: + include: + - arch: amd64 + runner: ubuntu-latest + - arch: arm64 + runner: ubuntu-24.04-arm + + runs-on: ${{ matrix.runner }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download agent artifact + uses: actions/download-artifact@v4 + with: + name: safechain-ultimate-linux-${{ matrix.arch }} + path: bin + + - name: Download agent ui artifact + uses: actions/download-artifact@v4 + with: + name: safechain-ultimate-ui-linux-${{ matrix.arch }} + path: bin + + - name: Download proxy artifact + uses: actions/download-artifact@v4 + with: + name: safechain-proxy-linux-${{ matrix.arch }} + path: bin + + - name: Verify binaries exist + run: | + ls -lh bin/ + ls -lh bin/safechain-ultimate-linux-${{ matrix.arch }} + ls -lh bin/safechain-ultimate-ui-linux-${{ matrix.arch }} + ls -lh bin/safechain-proxy-linux-${{ matrix.arch }} + + - name: Install RPM tools + run: sudo apt-get update && sudo apt-get install -y rpm + + - name: Build RPM + run: | + cd packaging/rpm + ./build-rpm.sh -v "${{ inputs.version || 'dev' }}" -a "${{ matrix.arch }}" -b "../../bin" -o "../../dist" + + - name: Rename package + run: | + mv dist/SafeChainUltimate-${{ inputs.version || 'dev' }}-${{ matrix.arch }}.rpm dist/SafeChainUltimate-${{ matrix.arch }}.rpm + + - name: Upload RPM artifact + uses: actions/upload-artifact@v4 + with: + name: SafeChainUltimate-${{ matrix.arch }}.rpm + path: dist/SafeChainUltimate-${{ matrix.arch }}.rpm diff --git a/.github/workflows/build-unix.yml b/.github/workflows/build-unix.yml index 413ad334..61218eb4 100644 --- a/.github/workflows/build-unix.yml +++ b/.github/workflows/build-unix.yml @@ -18,10 +18,18 @@ jobs: strategy: matrix: include: - - arch: amd64 + - os: darwin + arch: amd64 runner: macos-14 - - arch: arm64 + - os: darwin + arch: arm64 runner: macos-14 + - os: linux + arch: amd64 + runner: ubuntu-latest + - os: linux + arch: arm64 + runner: ubuntu-24.04-arm runs-on: ${{ matrix.runner }} steps: @@ -36,26 +44,26 @@ jobs: - name: Run tests run: make test - - name: Build binaries for darwin/${{ matrix.arch }} - run: make build-darwin-${{ matrix.arch }} VERSION="${{ inputs.version || 'dev' }}" + - name: Build binaries for ${{ matrix.os }}/${{ matrix.arch }} + run: make build-${{ matrix.os }}-${{ matrix.arch }} VERSION="${{ inputs.version || 'dev' }}" - name: Prepare artifacts run: | - mv bin/safechain-ultimate-darwin-${{ matrix.arch }} safechain-ultimate-darwin-${{ matrix.arch }} - mv bin/safechain-ultimate-ui-darwin-${{ matrix.arch }} safechain-ultimate-ui-darwin-${{ matrix.arch }} + mv bin/safechain-ultimate-${{ matrix.os }}-${{ matrix.arch }} safechain-ultimate-${{ matrix.os }}-${{ matrix.arch }} + mv bin/safechain-ultimate-ui-${{ matrix.os }}-${{ matrix.arch }} safechain-ultimate-ui-${{ matrix.os }}-${{ matrix.arch }} - name: Upload safechain-ultimate artifact uses: actions/upload-artifact@v4 with: - name: safechain-ultimate-darwin-${{ matrix.arch }} + name: safechain-ultimate-${{ matrix.os }}-${{ matrix.arch }} path: | - safechain-ultimate-darwin-${{ matrix.arch }} + safechain-ultimate-${{ matrix.os }}-${{ matrix.arch }} - name: Upload safechain-ultimate-ui artifact uses: actions/upload-artifact@v4 with: - name: safechain-ultimate-ui-darwin-${{ matrix.arch }} + name: safechain-ultimate-ui-${{ matrix.os }}-${{ matrix.arch }} path: | - safechain-ultimate-ui-darwin-${{ matrix.arch }} + safechain-ultimate-ui-${{ matrix.os }}-${{ matrix.arch }} build-unix-proxy: strategy: diff --git a/Makefile b/Makefile index 4c4334ac..2a99d283 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build build-release build-darwin-amd64 build-darwin-arm64 build-windows-amd64 build-windows-arm64 build-proxy build-pkg build-pkg-sign-local install-pkg uninstall-pkg clean test run help +.PHONY: build build-release build-darwin-amd64 build-darwin-arm64 build-linux-amd64 build-linux-arm64 build-windows-amd64 build-windows-arm64 build-proxy build-pkg build-pkg-sign-local install-pkg uninstall-pkg build-rpm clean test run help BINARY_NAME=safechain-ultimate BINARY_NAME_UI=safechain-ultimate-ui @@ -82,6 +82,12 @@ build-darwin-amd64: build-darwin-arm64: @$(MAKE) GOOS=darwin GOARCH=arm64 build-release +build-linux-amd64: + @$(MAKE) GOOS=linux GOARCH=amd64 build-release + +build-linux-arm64: + @$(MAKE) GOOS=linux GOARCH=arm64 build-release + build-windows-amd64: @$(MAKE) GOOS=windows GOARCH=amd64 build-release @@ -105,6 +111,16 @@ else @exit 1 endif +build-rpm: +ifeq ($(DETECTED_OS),linux) + @echo "Building Linux RPM installer..." + @cd packaging/rpm && ./build-rpm.sh -v $(VERSION) -a $(DETECTED_ARCH) -b ../../$(BIN_DIR) -o ../../$(DIST_DIR) + @echo "RPM built in $(DIST_DIR)/" +else + @echo "Error: RPM building is only supported on Linux" + @exit 1 +endif + build-pkg-sign-local: ifeq ($(DETECTED_OS),darwin) @echo "Building complete macOS package..." diff --git a/packaging/rpm/build-rpm.sh b/packaging/rpm/build-rpm.sh new file mode 100755 index 00000000..d6fbd715 --- /dev/null +++ b/packaging/rpm/build-rpm.sh @@ -0,0 +1,139 @@ +#!/bin/bash + +set -e + +VERSION="" +ARCH="" +BIN_DIR="./bin" +OUTPUT_DIR="./dist" + +while getopts "v:a:b:o:h" opt; do + case $opt in + v) VERSION="$OPTARG" ;; + a) ARCH="$OPTARG" ;; + b) BIN_DIR="$OPTARG" ;; + o) OUTPUT_DIR="$OPTARG" ;; + h) + echo "Usage: $0 -v VERSION -a ARCH [-b BIN_DIR] [-o OUTPUT_DIR]" + echo " -v VERSION Version number (e.g., 1.0.0)" + echo " -a ARCH Architecture (arm64 or amd64)" + echo " -b BIN_DIR Binary directory (default: ./bin)" + echo " -o OUTPUT_DIR Output directory (default: ./dist)" + exit 0 + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + esac +done + +if [ -z "$VERSION" ]; then + echo "Error: VERSION is required (-v)" >&2 + exit 1 +fi + +if [ -z "$ARCH" ]; then + echo "Error: ARCH is required (-a)" >&2 + exit 1 +fi + +if [ "$VERSION" = "dev" ]; then + PKG_VERSION="0.0.0" +else + PKG_VERSION="$VERSION" +fi + +case "$ARCH" in + amd64) RPM_ARCH="x86_64" ;; + arm64) RPM_ARCH="aarch64" ;; + *) + echo "Error: Unsupported architecture: $ARCH (expected amd64 or arm64)" >&2 + exit 1 + ;; +esac + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" +BIN_DIR="$(cd "$BIN_DIR" 2>/dev/null && pwd || echo "$PROJECT_DIR/$BIN_DIR")" +OUTPUT_DIR="$(mkdir -p "$OUTPUT_DIR" && cd "$OUTPUT_DIR" && pwd)" + +echo "Building Linux RPM installer for SafeChain Ultimate v$VERSION" +echo " Architecture: $ARCH ($RPM_ARCH)" +echo " Binary directory: $BIN_DIR" +echo " Output directory: $OUTPUT_DIR" +echo " Project directory: $PROJECT_DIR" + +AGENT_BIN="$BIN_DIR/safechain-ultimate-linux-$ARCH" +AGENT_UI_BIN="$BIN_DIR/safechain-ultimate-ui-linux-$ARCH" +PROXY_BIN="$BIN_DIR/safechain-proxy-linux-$ARCH" + +if [ ! -f "$AGENT_BIN" ]; then + echo "Error: safechain-ultimate binary not found at $AGENT_BIN" >&2 + exit 1 +fi + +if [ ! -f "$AGENT_UI_BIN" ]; then + echo "Error: safechain-ultimate-ui binary not found at $AGENT_UI_BIN" >&2 + exit 1 +fi + +if [ ! -f "$PROXY_BIN" ]; then + echo "Error: safechain-proxy binary not found at $PROXY_BIN" >&2 + exit 1 +fi + +BUILD_DIR="$(mktemp -d)" +trap "rm -rf '$BUILD_DIR'" EXIT + +echo "Using temporary build directory: $BUILD_DIR" + +RPMBUILD_DIR="$BUILD_DIR/rpmbuild" +mkdir -p "$RPMBUILD_DIR"/{BUILD,RPMS,SOURCES,SPECS,SRPMS} + +echo "Copying binaries to SOURCES..." +cp "$AGENT_BIN" "$RPMBUILD_DIR/SOURCES/safechain-ultimate" +cp "$AGENT_UI_BIN" "$RPMBUILD_DIR/SOURCES/safechain-ultimate-ui" +cp "$PROXY_BIN" "$RPMBUILD_DIR/SOURCES/safechain-proxy" + +echo "Copying packaging files to SOURCES..." +cp "$SCRIPT_DIR/safechain-ultimate.service" "$RPMBUILD_DIR/SOURCES/" +cp "$SCRIPT_DIR/scripts/uninstall" "$RPMBUILD_DIR/SOURCES/" + +echo "Copying spec file..." +cp "$SCRIPT_DIR/safechain-ultimate.spec" "$RPMBUILD_DIR/SPECS/" + +echo "Building RPM package..." +rpmbuild -bb \ + --define "_topdir $RPMBUILD_DIR" \ + --define "_pkg_version $PKG_VERSION" \ + --target "$RPM_ARCH" \ + "$RPMBUILD_DIR/SPECS/safechain-ultimate.spec" + +RPM_FILE=$(find "$RPMBUILD_DIR/RPMS/$RPM_ARCH/" -name "safechain-ultimate-*.rpm" | head -1) + +if [ -z "$RPM_FILE" ]; then + echo "Error: RPM file not found after build" >&2 + exit 1 +fi + +OUTPUT_RPM="$OUTPUT_DIR/SafeChainUltimate-$VERSION-$ARCH.rpm" +cp "$RPM_FILE" "$OUTPUT_RPM" + +echo "" +echo "✓ RPM package built successfully: $OUTPUT_RPM" +echo "" + +CHECKSUM=$(sha256sum "$OUTPUT_RPM" | awk '{print $1}') +echo "SHA256: $CHECKSUM" +echo "$CHECKSUM" > "$OUTPUT_RPM.sha256" +echo "" + +echo "Package information:" +rpm -qip "$OUTPUT_RPM" +echo "" + +SIZE=$(du -h "$OUTPUT_RPM" | awk '{print $1}') +echo "Package size: $SIZE" + +exit 0 diff --git a/packaging/rpm/safechain-ultimate.service b/packaging/rpm/safechain-ultimate.service new file mode 100644 index 00000000..08505978 --- /dev/null +++ b/packaging/rpm/safechain-ultimate.service @@ -0,0 +1,16 @@ +[Unit] +Description=SafeChain Ultimate - Security Agent by Aikido Security +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +ExecStart=/opt/aikidosecurity/safechainultimate/bin/safechain-ultimate +WorkingDirectory=/opt/aikidosecurity/safechainultimate +Restart=always +RestartSec=60 +StandardOutput=append:/var/log/aikidosecurity/safechainultimate/safechain-ultimate.log +StandardError=append:/var/log/aikidosecurity/safechainultimate/safechain-ultimate.error.log + +[Install] +WantedBy=multi-user.target diff --git a/packaging/rpm/safechain-ultimate.spec b/packaging/rpm/safechain-ultimate.spec new file mode 100644 index 00000000..92d35992 --- /dev/null +++ b/packaging/rpm/safechain-ultimate.spec @@ -0,0 +1,71 @@ +Name: safechain-ultimate +Version: %{_pkg_version} +Release: 1%{?dist} +Summary: SafeChain Ultimate - Security Agent by Aikido Security + +License: AGPL-3.0-or-later +URL: https://aikido.dev + +AutoReqProv: no + +%description +SafeChain Ultimate is a security agent by Aikido Security that protects +your applications and infrastructure. + +%install +mkdir -p %{buildroot}/opt/aikidosecurity/safechainultimate/bin +mkdir -p %{buildroot}/opt/aikidosecurity/safechainultimate/scripts +mkdir -p %{buildroot}/usr/lib/systemd/system +mkdir -p %{buildroot}/var/log/aikidosecurity/safechainultimate + +install -m 755 %{_sourcedir}/safechain-ultimate %{buildroot}/opt/aikidosecurity/safechainultimate/bin/ +install -m 755 %{_sourcedir}/safechain-ultimate-ui %{buildroot}/opt/aikidosecurity/safechainultimate/bin/ +install -m 755 %{_sourcedir}/safechain-proxy %{buildroot}/opt/aikidosecurity/safechainultimate/bin/ +install -m 755 %{_sourcedir}/uninstall %{buildroot}/opt/aikidosecurity/safechainultimate/scripts/ +install -m 644 %{_sourcedir}/safechain-ultimate.service %{buildroot}/usr/lib/systemd/system/ + +%files +%dir /opt/aikidosecurity +%dir /opt/aikidosecurity/safechainultimate +%dir /opt/aikidosecurity/safechainultimate/bin +%dir /opt/aikidosecurity/safechainultimate/scripts +%dir /var/log/aikidosecurity +%dir /var/log/aikidosecurity/safechainultimate +%attr(755, root, root) /opt/aikidosecurity/safechainultimate/bin/safechain-ultimate +%attr(755, root, root) /opt/aikidosecurity/safechainultimate/bin/safechain-ultimate-ui +%attr(755, root, root) /opt/aikidosecurity/safechainultimate/bin/safechain-proxy +%attr(755, root, root) /opt/aikidosecurity/safechainultimate/scripts/uninstall +%attr(644, root, root) /usr/lib/systemd/system/safechain-ultimate.service + +%pre +if systemctl is-active --quiet safechain-ultimate 2>/dev/null; then + systemctl stop safechain-ultimate || true +fi + +%post +systemctl daemon-reload +systemctl enable safechain-ultimate +systemctl start safechain-ultimate + +echo "" +echo "SafeChain Ultimate has been installed successfully!" +echo " Binaries: /opt/aikidosecurity/safechainultimate/bin" +echo " Logs: /var/log/aikidosecurity/safechainultimate" +echo "" +echo "The agent is now running as a systemd service." + +%preun +if [ $1 -eq 0 ]; then + if systemctl is-active --quiet safechain-ultimate 2>/dev/null; then + systemctl stop safechain-ultimate || true + fi + systemctl disable safechain-ultimate 2>/dev/null || true +fi + +%postun +systemctl daemon-reload +if [ $1 -eq 0 ]; then + rm -rf /var/log/aikidosecurity/safechainultimate + rmdir /var/log/aikidosecurity 2>/dev/null || true + rmdir /opt/aikidosecurity 2>/dev/null || true +fi diff --git a/packaging/rpm/scripts/uninstall b/packaging/rpm/scripts/uninstall new file mode 100755 index 00000000..f6f1f682 --- /dev/null +++ b/packaging/rpm/scripts/uninstall @@ -0,0 +1,93 @@ +#!/bin/bash + +set -e + +INSTALL_DIR="/opt/aikidosecurity/safechainultimate" +LOGS_DIR="/var/log/aikidosecurity/safechainultimate" +SERVICE_FILE="/usr/lib/systemd/system/safechain-ultimate.service" +SERVICE_NAME="safechain-ultimate" +BINARY="$INSTALL_DIR/bin/safechain-ultimate" + +echo "=========================================" +echo "SafeChain Ultimate - Uninstaller" +echo "=========================================" +echo "" + +if [ "$(id -u)" -ne 0 ]; then + echo "Error: This script must be run as root (use sudo)" + exit 1 +fi + +echo "Stopping SafeChain Ultimate processes..." + +if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then + echo " Stopping service..." + systemctl stop "$SERVICE_NAME" || true + echo " ✓ Service stopped" +else + echo " Service not running" +fi + +if systemctl is-enabled --quiet "$SERVICE_NAME" 2>/dev/null; then + echo " Disabling service..." + systemctl disable "$SERVICE_NAME" || true + echo " ✓ Service disabled" +fi + +pkill -f "safechain-ultimate-ui" 2>/dev/null && echo " ✓ UI process stopped" || echo " UI process not running" +pkill -f "safechain-proxy" 2>/dev/null && echo " ✓ Proxy process stopped" || echo " Proxy process not running" +pkill -f "safechain-ultimate" 2>/dev/null && echo " ✓ Daemon process stopped" || echo " Daemon process not running" + +echo "" + +if [ -f "$BINARY" ]; then + echo "Running teardown..." + "$BINARY" --teardown || { + echo " Warning: Teardown command failed (may already be torn down)" + } + echo " ✓ Teardown complete" +else + echo "Warning: Binary not found at $BINARY, skipping teardown" +fi + +echo "" +echo "Removing installed files..." + +if [ -f "$SERVICE_FILE" ]; then + rm -f "$SERVICE_FILE" + echo " ✓ Removed systemd service file" +fi + +if [ -d "$INSTALL_DIR" ]; then + rm -rf "$INSTALL_DIR" + echo " ✓ Removed application files" +fi + +if [ -d "$LOGS_DIR" ]; then + rm -rf "$LOGS_DIR" + echo " ✓ Removed log files" +fi + +AIKIDO_DIR="/opt/aikidosecurity" +if [ -d "$AIKIDO_DIR" ] && [ -z "$(ls -A "$AIKIDO_DIR" 2>/dev/null)" ]; then + rmdir "$AIKIDO_DIR" 2>/dev/null || true + echo " ✓ Removed empty AikidoSecurity directory" +fi + +AIKIDO_LOGS_DIR="/var/log/aikidosecurity" +if [ -d "$AIKIDO_LOGS_DIR" ] && [ -z "$(ls -A "$AIKIDO_LOGS_DIR" 2>/dev/null)" ]; then + rmdir "$AIKIDO_LOGS_DIR" 2>/dev/null || true + echo " ✓ Removed empty AikidoSecurity logs directory" +fi + +echo "" +echo "Reloading systemd..." +systemctl daemon-reload + +echo "" +echo "=========================================" +echo "✓ SafeChain Ultimate has been uninstalled" +echo "=========================================" +echo "" + +exit 0 From 683a3bf763cff67eccdc57cc8438595aaec0d118 Mon Sep 17 00:00:00 2001 From: Tudor TIMCU Date: Mon, 9 Feb 2026 17:19:18 +0200 Subject: [PATCH 02/10] Add Linux platform support with proxy management and logging - Introduced platform_linux.go to implement Linux-specific functionality for SafeChain Ultimate. - Added methods for managing system proxy settings using gsettings, including setting, unsetting, and checking proxy configurations. - Implemented certificate installation and uninstallation for proxy CA management. - Enhanced logging setup and configuration initialization for Linux environments. - Established a structure for service management and user context retrieval on Linux systems. --- internal/platform/platform_linux.go | 330 ++++++++++++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 internal/platform/platform_linux.go diff --git a/internal/platform/platform_linux.go b/internal/platform/platform_linux.go new file mode 100644 index 00000000..5a670b9f --- /dev/null +++ b/internal/platform/platform_linux.go @@ -0,0 +1,330 @@ +//go:build linux + +package platform + +import ( + "context" + "fmt" + "io" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/AikidoSec/safechain-internals/internal/utils" +) + +const ( + SafeChainUltimateLogName = "safechain-ultimate.log" + SafeChainUltimateErrLogName = "safechain-ultimate.error.log" + SafeChainUIBinaryName = "safechain-ultimate-ui" + SafeChainProxyBinaryName = "safechain-proxy" + SafeChainProxyLogName = "safechain-proxy.log" + SafeChainProxyErrLogName = "safechain-proxy.err" + SafeChainInstallScriptName = "install-safe-chain.sh" + SafeChainUninstallScriptName = "uninstall-safe-chain.sh" + + systemCertDir = "/usr/local/share/ca-certificates" + certFileName = "aikidosafechain.crt" +) + +func initConfig() error { + if RunningAsRoot() { + username, err := getLoggedInUser(context.Background()) + if err != nil { + return fmt.Errorf("failed to get logged in user: %v", err) + } + config.HomeDir = filepath.Join("/home", username) + } else { + var err error + config.HomeDir, err = os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to get home directory: %v", err) + } + } + safeChainHomeDir := filepath.Join(config.HomeDir, ".safe-chain") + config.BinaryDir = "/opt/aikidosecurity/safechainultimate/bin" + config.RunDir = "/opt/aikidosecurity/safechainultimate/run" + config.LogDir = "/var/log/aikidosecurity/safechainultimate" + config.SafeChainBinaryPath = filepath.Join(safeChainHomeDir, "bin", "safe-chain") + return nil +} + +func PrepareShellEnvironment(_ context.Context) error { + return nil +} + +func SetupLogging() (io.Writer, error) { + return os.Stdout, nil +} + +func getGsettingsProxy(ctx context.Context) (string, error) { + output, err := exec.CommandContext(ctx, "gsettings", "get", "org.gnome.system.proxy", "mode").Output() + if err != nil { + return "", err + } + return strings.TrimSpace(strings.Trim(string(output), "'")), nil +} + +func getGsettingsAutoConfigURL(ctx context.Context) (string, error) { + output, err := exec.CommandContext(ctx, "gsettings", "get", "org.gnome.system.proxy", "autoconfig-url").Output() + if err != nil { + return "", err + } + return strings.TrimSpace(strings.Trim(string(output), "'")), nil +} + +func hasGsettings() bool { + _, err := exec.LookPath("gsettings") + return err == nil +} + +func SetSystemPAC(ctx context.Context, pacURL string) error { + if !hasGsettings() { + log.Println("gsettings not available, setting proxy environment variables only") + return setEnvironmentProxy(pacURL) + } + + log.Printf("Setting system PAC to %q via gsettings\n", pacURL) + if err := exec.CommandContext(ctx, "gsettings", "set", "org.gnome.system.proxy", "autoconfig-url", pacURL).Run(); err != nil { + return fmt.Errorf("failed to set autoconfig-url: %v", err) + } + if err := exec.CommandContext(ctx, "gsettings", "set", "org.gnome.system.proxy", "mode", "auto").Run(); err != nil { + return fmt.Errorf("failed to set proxy mode to auto: %v", err) + } + return nil +} + +func IsSystemPACSet(ctx context.Context, pacURL string) error { + if !hasGsettings() { + return isEnvironmentProxySet(pacURL) + } + + mode, err := getGsettingsProxy(ctx) + if err != nil { + return fmt.Errorf("failed to get proxy mode: %v", err) + } + if mode != "auto" { + return fmt.Errorf("proxy mode is %q, expected 'auto'", mode) + } + + configURL, err := getGsettingsAutoConfigURL(ctx) + if err != nil { + return fmt.Errorf("failed to get autoconfig-url: %v", err) + } + if configURL != pacURL { + return fmt.Errorf("autoconfig-url is %q, expected %q", configURL, pacURL) + } + return nil +} + +func IsAnySystemProxySet(ctx context.Context) (bool, error) { + if !hasGsettings() { + return isAnyEnvironmentProxySet(), nil + } + + mode, err := getGsettingsProxy(ctx) + if err != nil { + return false, fmt.Errorf("failed to get proxy mode: %v", err) + } + return mode != "none" && mode != "", nil +} + +func UnsetSystemPAC(ctx context.Context, pacURL string) error { + if !hasGsettings() { + return unsetEnvironmentProxy() + } + + log.Println("Unsetting system PAC via gsettings") + errs := []error{} + if err := exec.CommandContext(ctx, "gsettings", "set", "org.gnome.system.proxy", "autoconfig-url", "").Run(); err != nil { + errs = append(errs, err) + } + if err := exec.CommandContext(ctx, "gsettings", "set", "org.gnome.system.proxy", "mode", "none").Run(); err != nil { + errs = append(errs, err) + } + if len(errs) > 0 { + return fmt.Errorf("failed to unset system PAC: %v", errs) + } + return nil +} + +func setEnvironmentProxy(pacURL string) error { + proxyEnvFile := "/etc/profile.d/safechain-proxy.sh" + content := fmt.Sprintf("# SafeChain Ultimate proxy configuration\nexport auto_proxy=\"%s\"\n", pacURL) + if err := os.WriteFile(proxyEnvFile, []byte(content), 0644); err != nil { + return fmt.Errorf("failed to write proxy environment file: %v", err) + } + return nil +} + +func isEnvironmentProxySet(pacURL string) error { + proxyEnvFile := "/etc/profile.d/safechain-proxy.sh" + content, err := os.ReadFile(proxyEnvFile) + if err != nil { + return fmt.Errorf("proxy environment file not found: %v", err) + } + if !strings.Contains(string(content), pacURL) { + return fmt.Errorf("proxy environment file does not contain expected PAC URL") + } + return nil +} + +func isAnyEnvironmentProxySet() bool { + proxyEnvFile := "/etc/profile.d/safechain-proxy.sh" + _, err := os.Stat(proxyEnvFile) + return err == nil +} + +func unsetEnvironmentProxy() error { + proxyEnvFile := "/etc/profile.d/safechain-proxy.sh" + if err := os.Remove(proxyEnvFile); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to remove proxy environment file: %v", err) + } + return nil +} + +func InstallProxyCA(_ context.Context, certPath string) error { + destPath := filepath.Join(systemCertDir, certFileName) + if err := os.MkdirAll(systemCertDir, 0755); err != nil { + return fmt.Errorf("failed to create certificate directory: %v", err) + } + input, err := os.ReadFile(certPath) + if err != nil { + return fmt.Errorf("failed to read certificate: %v", err) + } + if err := os.WriteFile(destPath, input, 0644); err != nil { + return fmt.Errorf("failed to write certificate: %v", err) + } + if _, err := exec.Command("update-ca-certificates").Output(); err != nil { + return fmt.Errorf("failed to update ca certificates: %v", err) + } + return nil +} + +func IsProxyCAInstalled(_ context.Context) error { + destPath := filepath.Join(systemCertDir, certFileName) + if _, err := os.Stat(destPath); os.IsNotExist(err) { + return fmt.Errorf("proxy CA certificate not found at %s", destPath) + } + return nil +} + +func UninstallProxyCA(_ context.Context) error { + errs := []error{} + + destPath := filepath.Join(systemCertDir, certFileName) + if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) { + errs = append(errs, fmt.Errorf("failed to remove certificate: %v", err)) + } + + if _, err := exec.Command("update-ca-certificates", "--fresh").Output(); err != nil { + errs = append(errs, fmt.Errorf("failed to update ca certificates: %v", err)) + } + + if len(errs) > 0 { + return fmt.Errorf("failed to uninstall proxy CA: %v", errs) + } + return nil +} + +type ServiceRunner interface { + Start(ctx context.Context) error + Stop(ctx context.Context) error +} + +func IsWindowsService() bool { + return false +} + +func RunAsWindowsService(runner ServiceRunner, serviceName string) error { + return nil +} + +func getLoggedInUser(ctx context.Context) (string, error) { + output, err := exec.CommandContext(ctx, "loginctl", "list-users", "--no-legend").Output() + if err != nil { + envUser := os.Getenv("SUDO_USER") + if envUser != "" { + return envUser, nil + } + return "", fmt.Errorf("failed to get logged in user: %v", err) + } + + lines := strings.Split(strings.TrimSpace(string(output)), "\n") + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) >= 2 && fields[1] != "root" { + return fields[1], nil + } + } + + envUser := os.Getenv("SUDO_USER") + if envUser != "" { + return envUser, nil + } + return "", fmt.Errorf("no interactive user found") +} + +func RunAsCurrentUser(ctx context.Context, binaryPath string, args []string) (string, error) { + if !RunningAsRoot() { + return utils.RunCommand(ctx, binaryPath, args...) + } + + username, err := getLoggedInUser(ctx) + if err != nil { + return "", fmt.Errorf("failed to get logged in user: %v", err) + } + + suArgs := append([]string{"-u", username, binaryPath}, args...) + return utils.RunCommandWithEnv(ctx, []string{}, "sudo", suArgs...) +} + +func RunInAuditSessionOfCurrentUser(ctx context.Context, binaryPath string, args []string) (string, error) { + return RunAsCurrentUser(ctx, binaryPath, args) +} + +func RunningAsRoot() bool { + return os.Getuid() == 0 +} + +func downloadSafeChainShellScript(ctx context.Context, repoURL, version string, scriptName string) (string, error) { + scriptURL := fmt.Sprintf("%s/releases/download/%s/%s", repoURL, version, scriptName) + scriptPath := filepath.Join(os.TempDir(), scriptName) + verification := utils.DownloadVerification{ + SafeChainReleaseTag: version, + SafeChainAssetName: scriptName, + } + + log.Printf("Downloading script %s from %s...", scriptName, scriptURL) + if err := utils.DownloadAndVerifyBinary(ctx, scriptURL, scriptPath, verification); err != nil { + return "", fmt.Errorf("failed to download script %s: %w", scriptName, err) + } + return scriptPath, nil +} + +func InstallSafeChain(ctx context.Context, repoURL, version string) error { + scriptPath, err := downloadSafeChainShellScript(ctx, repoURL, version, SafeChainInstallScriptName) + if err != nil { + return err + } + defer os.Remove(scriptPath) + if _, err := RunAsCurrentUser(ctx, "sh", []string{scriptPath}); err != nil { + return fmt.Errorf("failed to run install script: %w", err) + } + return nil +} + +func UninstallSafeChain(ctx context.Context, repoURL, version string) error { + scriptPath, err := downloadSafeChainShellScript(ctx, repoURL, version, SafeChainUninstallScriptName) + if err != nil { + return err + } + defer os.Remove(scriptPath) + + if _, err := RunAsCurrentUser(ctx, "sh", []string{scriptPath}); err != nil { + return fmt.Errorf("failed to run uninstall script: %w", err) + } + return nil +} From 7a77d1197726c623dd425995c38af41bc2667920 Mon Sep 17 00:00:00 2001 From: Tudor TIMCU Date: Mon, 9 Feb 2026 17:25:19 +0200 Subject: [PATCH 03/10] Enhance packaging support for SafeChain Ultimate - Updated Makefile to add new targets for building DEB and APK packages from RPM files, specifically for Linux environments. - Modified GitHub Actions workflow to install necessary packaging tools and automate the conversion of RPM to DEB and APK formats. - Introduced a new build-apk.sh script to handle the conversion process for Alpine packages. - Cleaned up unused interface definitions in platform files to streamline the codebase. --- .github/workflows/build-linux.yml | 30 ++++- Makefile | 22 +++- internal/platform/platform.go | 6 + internal/platform/platform_darwin.go | 17 +-- internal/platform/platform_linux.go | 5 - internal/platform/platform_windows.go | 5 - packaging/rpm/build-apk.sh | 166 ++++++++++++++++++++++++++ 7 files changed, 226 insertions(+), 25 deletions(-) create mode 100755 packaging/rpm/build-apk.sh diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 669dab7e..146cd29b 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -59,20 +59,44 @@ jobs: ls -lh bin/safechain-ultimate-ui-linux-${{ matrix.arch }} ls -lh bin/safechain-proxy-linux-${{ matrix.arch }} - - name: Install RPM tools - run: sudo apt-get update && sudo apt-get install -y rpm + - name: Install packaging tools + run: sudo apt-get update && sudo apt-get install -y rpm alien - name: Build RPM run: | cd packaging/rpm ./build-rpm.sh -v "${{ inputs.version || 'dev' }}" -a "${{ matrix.arch }}" -b "../../bin" -o "../../dist" - - name: Rename package + - name: Convert RPM to DEB using alien + run: | + cd dist + sudo alien --to-deb --keep-version SafeChainUltimate-${{ inputs.version || 'dev' }}-${{ matrix.arch }}.rpm + + - name: Convert RPM to APK + run: | + cd packaging/rpm + ./build-apk.sh -v "${{ inputs.version || 'dev' }}" -a "${{ matrix.arch }}" -r "../../dist/SafeChainUltimate-${{ inputs.version || 'dev' }}-${{ matrix.arch }}.rpm" -o "../../dist" + + - name: Rename packages run: | mv dist/SafeChainUltimate-${{ inputs.version || 'dev' }}-${{ matrix.arch }}.rpm dist/SafeChainUltimate-${{ matrix.arch }}.rpm + mv dist/safechain-ultimate_*.deb dist/SafeChainUltimate-${{ matrix.arch }}.deb + mv dist/SafeChainUltimate-${{ inputs.version || 'dev' }}-${{ matrix.arch }}.apk dist/SafeChainUltimate-${{ matrix.arch }}.apk - name: Upload RPM artifact uses: actions/upload-artifact@v4 with: name: SafeChainUltimate-${{ matrix.arch }}.rpm path: dist/SafeChainUltimate-${{ matrix.arch }}.rpm + + - name: Upload DEB artifact + uses: actions/upload-artifact@v4 + with: + name: SafeChainUltimate-${{ matrix.arch }}.deb + path: dist/SafeChainUltimate-${{ matrix.arch }}.deb + + - name: Upload APK artifact + uses: actions/upload-artifact@v4 + with: + name: SafeChainUltimate-${{ matrix.arch }}.apk + path: dist/SafeChainUltimate-${{ matrix.arch }}.apk diff --git a/Makefile b/Makefile index 2a99d283..cb8cea15 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build build-release build-darwin-amd64 build-darwin-arm64 build-linux-amd64 build-linux-arm64 build-windows-amd64 build-windows-arm64 build-proxy build-pkg build-pkg-sign-local install-pkg uninstall-pkg build-rpm clean test run help +.PHONY: build build-release build-darwin-amd64 build-darwin-arm64 build-linux-amd64 build-linux-arm64 build-windows-amd64 build-windows-arm64 build-proxy build-pkg build-pkg-sign-local install-pkg uninstall-pkg build-rpm build-deb build-apk clean test run help BINARY_NAME=safechain-ultimate BINARY_NAME_UI=safechain-ultimate-ui @@ -121,6 +121,26 @@ else @exit 1 endif +build-deb: build-rpm +ifeq ($(DETECTED_OS),linux) + @echo "Converting RPM to DEB using alien..." + @cd $(DIST_DIR) && sudo alien --to-deb --keep-version SafeChainUltimate-$(VERSION)-$(DETECTED_ARCH).rpm + @echo "DEB built in $(DIST_DIR)/" +else + @echo "Error: DEB building is only supported on Linux" + @exit 1 +endif + +build-apk: build-rpm +ifeq ($(DETECTED_OS),linux) + @echo "Converting RPM to Alpine APK..." + @cd packaging/rpm && ./build-apk.sh -v $(VERSION) -a $(DETECTED_ARCH) -r ../../$(DIST_DIR)/SafeChainUltimate-$(VERSION)-$(DETECTED_ARCH).rpm -o ../../$(DIST_DIR) + @echo "APK built in $(DIST_DIR)/" +else + @echo "Error: APK building is only supported on Linux" + @exit 1 +endif + build-pkg-sign-local: ifeq ($(DETECTED_OS),darwin) @echo "Building complete macOS package..." diff --git a/internal/platform/platform.go b/internal/platform/platform.go index e630be91..2a09f601 100644 --- a/internal/platform/platform.go +++ b/internal/platform/platform.go @@ -1,6 +1,7 @@ package platform import ( + "context" "fmt" "os" "path/filepath" @@ -57,3 +58,8 @@ func GetProxyLogPath() string { func GetProxyErrLogPath() string { return filepath.Join(config.LogDir, SafeChainProxyErrLogName) } + +type ServiceRunner interface { + Start(ctx context.Context) error + Stop(ctx context.Context) error +} diff --git a/internal/platform/platform_darwin.go b/internal/platform/platform_darwin.go index 00d53860..98350476 100644 --- a/internal/platform/platform_darwin.go +++ b/internal/platform/platform_darwin.go @@ -19,12 +19,12 @@ import ( ) const ( - SafeChainUltimateLogName = "safechain-ultimate.log" - SafeChainUltimateErrLogName = "safechain-ultimate.error.log" - SafeChainUIBinaryName = "safechain-ultimate-ui" - SafeChainProxyBinaryName = "safechain-proxy" - SafeChainProxyLogName = "safechain-proxy.log" - SafeChainProxyErrLogName = "safechain-proxy.err" + SafeChainUltimateLogName = "safechain-ultimate.log" + SafeChainUltimateErrLogName = "safechain-ultimate.error.log" + SafeChainUIBinaryName = "safechain-ultimate-ui" + SafeChainProxyBinaryName = "safechain-proxy" + SafeChainProxyLogName = "safechain-proxy.log" + SafeChainProxyErrLogName = "safechain-proxy.err" SafeChainInstallScriptName = "install-safe-chain.sh" SafeChainUninstallScriptName = "uninstall-safe-chain.sh" ) @@ -302,11 +302,6 @@ func UninstallProxyCA(ctx context.Context) error { return nil } -type ServiceRunner interface { - Start(ctx context.Context) error - Stop(ctx context.Context) error -} - func IsWindowsService() bool { return false } diff --git a/internal/platform/platform_linux.go b/internal/platform/platform_linux.go index 5a670b9f..2d8c7965 100644 --- a/internal/platform/platform_linux.go +++ b/internal/platform/platform_linux.go @@ -229,11 +229,6 @@ func UninstallProxyCA(_ context.Context) error { return nil } -type ServiceRunner interface { - Start(ctx context.Context) error - Stop(ctx context.Context) error -} - func IsWindowsService() bool { return false } diff --git a/internal/platform/platform_windows.go b/internal/platform/platform_windows.go index b2b94526..4cacc014 100644 --- a/internal/platform/platform_windows.go +++ b/internal/platform/platform_windows.go @@ -212,11 +212,6 @@ func UninstallProxyCA(ctx context.Context) error { return nil } -type ServiceRunner interface { - Start(ctx context.Context) error - Stop(ctx context.Context) error -} - type windowsService struct { runner ServiceRunner } diff --git a/packaging/rpm/build-apk.sh b/packaging/rpm/build-apk.sh new file mode 100755 index 00000000..6d1dc97e --- /dev/null +++ b/packaging/rpm/build-apk.sh @@ -0,0 +1,166 @@ +#!/bin/bash + +set -e + +VERSION="" +ARCH="" +RPM_FILE="" +OUTPUT_DIR="./dist" + +while getopts "v:a:r:o:h" opt; do + case $opt in + v) VERSION="$OPTARG" ;; + a) ARCH="$OPTARG" ;; + r) RPM_FILE="$OPTARG" ;; + o) OUTPUT_DIR="$OPTARG" ;; + h) + echo "Usage: $0 -v VERSION -a ARCH -r RPM_FILE [-o OUTPUT_DIR]" + echo " -v VERSION Version number (e.g., 1.0.0)" + echo " -a ARCH Architecture (arm64 or amd64)" + echo " -r RPM_FILE Path to the RPM file to convert" + echo " -o OUTPUT_DIR Output directory (default: ./dist)" + exit 0 + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + esac +done + +if [ -z "$VERSION" ]; then + echo "Error: VERSION is required (-v)" >&2 + exit 1 +fi + +if [ -z "$ARCH" ]; then + echo "Error: ARCH is required (-a)" >&2 + exit 1 +fi + +if [ -z "$RPM_FILE" ]; then + echo "Error: RPM_FILE is required (-r)" >&2 + exit 1 +fi + +if [ "$VERSION" = "dev" ]; then + PKG_VERSION="0.0.0" +else + PKG_VERSION="$VERSION" +fi + +case "$ARCH" in + amd64) APK_ARCH="x86_64" ;; + arm64) APK_ARCH="aarch64" ;; + *) + echo "Error: Unsupported architecture: $ARCH (expected amd64 or arm64)" >&2 + exit 1 + ;; +esac + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RPM_FILE="$(cd "$(dirname "$RPM_FILE")" && pwd)/$(basename "$RPM_FILE")" +OUTPUT_DIR="$(mkdir -p "$OUTPUT_DIR" && cd "$OUTPUT_DIR" && pwd)" + +if [ ! -f "$RPM_FILE" ]; then + echo "Error: RPM file not found at $RPM_FILE" >&2 + exit 1 +fi + +echo "Converting RPM to Alpine APK for SafeChain Ultimate v$VERSION" +echo " Architecture: $ARCH ($APK_ARCH)" +echo " RPM file: $RPM_FILE" +echo " Output directory: $OUTPUT_DIR" + +BUILD_DIR="$(mktemp -d)" +trap "rm -rf '$BUILD_DIR'" EXIT + +echo "Using temporary build directory: $BUILD_DIR" + +PKG_ROOT="$BUILD_DIR/pkg" +mkdir -p "$PKG_ROOT" + +echo "Extracting RPM contents..." +cd "$PKG_ROOT" +rpm2cpio "$RPM_FILE" | cpio -idm 2>/dev/null + +echo "Creating APK metadata..." +cat > "$PKG_ROOT/.PKGINFO" < "$PKG_ROOT/.scripts/.pre-install" <<'EOF' +#!/bin/sh +if command -v rc-service >/dev/null 2>&1; then + rc-service safechain-ultimate stop 2>/dev/null || true +fi +EOF + +cat > "$PKG_ROOT/.scripts/.post-install" <<'POSTEOF' +#!/bin/sh +mkdir -p /var/log/aikidosecurity/safechainultimate + +if command -v rc-update >/dev/null 2>&1; then + rc-update add safechain-ultimate default 2>/dev/null || true + rc-service safechain-ultimate start 2>/dev/null || true +fi + +echo "" +echo "SafeChain Ultimate has been installed successfully!" +echo " Binaries: /opt/aikidosecurity/safechainultimate/bin" +echo " Logs: /var/log/aikidosecurity/safechainultimate" +POSTEOF + +cat > "$PKG_ROOT/.scripts/.pre-deinstall" <<'EOF' +#!/bin/sh +if command -v rc-service >/dev/null 2>&1; then + rc-service safechain-ultimate stop 2>/dev/null || true + rc-update del safechain-ultimate default 2>/dev/null || true +fi +EOF + +chmod 755 "$PKG_ROOT/.scripts/.pre-install" +chmod 755 "$PKG_ROOT/.scripts/.post-install" +chmod 755 "$PKG_ROOT/.scripts/.pre-deinstall" + +echo "Building APK package..." + +cd "$BUILD_DIR" + +tar -czf "$BUILD_DIR/data.tar.gz" -C "$PKG_ROOT" \ + --exclude='.PKGINFO' \ + --exclude='.scripts' \ + . + +tar -czf "$BUILD_DIR/control.tar.gz" -C "$PKG_ROOT" \ + .PKGINFO \ + .scripts/ + +cat "$BUILD_DIR/control.tar.gz" "$BUILD_DIR/data.tar.gz" > "$BUILD_DIR/combined.tar.gz" + +OUTPUT_APK="$OUTPUT_DIR/SafeChainUltimate-$VERSION-$ARCH.apk" +cp "$BUILD_DIR/combined.tar.gz" "$OUTPUT_APK" + +echo "" +echo "✓ APK package built successfully: $OUTPUT_APK" +echo "" + +CHECKSUM=$(sha256sum "$OUTPUT_APK" | awk '{print $1}') +echo "SHA256: $CHECKSUM" +echo "$CHECKSUM" > "$OUTPUT_APK.sha256" +echo "" + +SIZE=$(du -h "$OUTPUT_APK" | awk '{print $1}') +echo "Package size: $SIZE" + +exit 0 From 82a70041ebbcdbf6b928d194351fd7e92d648256 Mon Sep 17 00:00:00 2001 From: Tudor TIMCU Date: Mon, 9 Feb 2026 17:28:16 +0200 Subject: [PATCH 04/10] Add Linux GUI dependencies installation to build workflow - Updated the GitHub Actions workflow to include a step for installing necessary Linux GUI dependencies, enhancing the build process for Linux environments. - This addition ensures that required libraries are available for GUI-related functionalities during the build. --- .github/workflows/build-unix.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build-unix.yml b/.github/workflows/build-unix.yml index 61218eb4..e1e4cab4 100644 --- a/.github/workflows/build-unix.yml +++ b/.github/workflows/build-unix.yml @@ -41,6 +41,12 @@ jobs: with: go-version: "1.25" + - name: Install Linux GUI dependencies + if: matrix.os == 'linux' + run: | + sudo apt-get update + sudo apt-get install -y libwayland-dev libx11-dev libx11-xcb-dev libxkbcommon-x11-dev libgles2-mesa-dev libegl1-mesa-dev libffi-dev libxcursor-dev libvulkan-dev + - name: Run tests run: make test From 2d086a8ca589ed6a5aef0125adc05985de422478 Mon Sep 17 00:00:00 2001 From: Tudor TIMCU Date: Tue, 10 Feb 2026 12:40:18 +0200 Subject: [PATCH 05/10] Refactor RPM to APK conversion process in build workflow - Updated the Makefile to utilize fpm for converting RPM packages to Alpine APK format, simplifying the build process. - Modified the GitHub Actions workflow to reflect the new conversion method and included the installation of fpm as a dependency. - Removed the outdated build-apk.sh script, streamlining the packaging process. --- .github/workflows/build-linux.yml | 12 ++- Makefile | 4 +- packaging/rpm/build-apk.sh | 166 ------------------------------ 3 files changed, 9 insertions(+), 173 deletions(-) delete mode 100755 packaging/rpm/build-apk.sh diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 146cd29b..caae2136 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -60,7 +60,9 @@ jobs: ls -lh bin/safechain-proxy-linux-${{ matrix.arch }} - name: Install packaging tools - run: sudo apt-get update && sudo apt-get install -y rpm alien + run: | + sudo apt-get update && sudo apt-get install -y rpm alien ruby ruby-dev + sudo gem install fpm --no-document - name: Build RPM run: | @@ -72,16 +74,16 @@ jobs: cd dist sudo alien --to-deb --keep-version SafeChainUltimate-${{ inputs.version || 'dev' }}-${{ matrix.arch }}.rpm - - name: Convert RPM to APK + - name: Convert RPM to APK using fpm run: | - cd packaging/rpm - ./build-apk.sh -v "${{ inputs.version || 'dev' }}" -a "${{ matrix.arch }}" -r "../../dist/SafeChainUltimate-${{ inputs.version || 'dev' }}-${{ matrix.arch }}.rpm" -o "../../dist" + cd dist + fpm -s rpm -t apk SafeChainUltimate-${{ inputs.version || 'dev' }}-${{ matrix.arch }}.rpm - name: Rename packages run: | mv dist/SafeChainUltimate-${{ inputs.version || 'dev' }}-${{ matrix.arch }}.rpm dist/SafeChainUltimate-${{ matrix.arch }}.rpm mv dist/safechain-ultimate_*.deb dist/SafeChainUltimate-${{ matrix.arch }}.deb - mv dist/SafeChainUltimate-${{ inputs.version || 'dev' }}-${{ matrix.arch }}.apk dist/SafeChainUltimate-${{ matrix.arch }}.apk + mv dist/safechain-ultimate-*.apk dist/SafeChainUltimate-${{ matrix.arch }}.apk - name: Upload RPM artifact uses: actions/upload-artifact@v4 diff --git a/Makefile b/Makefile index cb8cea15..c7a88386 100644 --- a/Makefile +++ b/Makefile @@ -133,8 +133,8 @@ endif build-apk: build-rpm ifeq ($(DETECTED_OS),linux) - @echo "Converting RPM to Alpine APK..." - @cd packaging/rpm && ./build-apk.sh -v $(VERSION) -a $(DETECTED_ARCH) -r ../../$(DIST_DIR)/SafeChainUltimate-$(VERSION)-$(DETECTED_ARCH).rpm -o ../../$(DIST_DIR) + @echo "Converting RPM to Alpine APK using fpm..." + @cd $(DIST_DIR) && fpm -s rpm -t apk SafeChainUltimate-$(VERSION)-$(DETECTED_ARCH).rpm @echo "APK built in $(DIST_DIR)/" else @echo "Error: APK building is only supported on Linux" diff --git a/packaging/rpm/build-apk.sh b/packaging/rpm/build-apk.sh deleted file mode 100755 index 6d1dc97e..00000000 --- a/packaging/rpm/build-apk.sh +++ /dev/null @@ -1,166 +0,0 @@ -#!/bin/bash - -set -e - -VERSION="" -ARCH="" -RPM_FILE="" -OUTPUT_DIR="./dist" - -while getopts "v:a:r:o:h" opt; do - case $opt in - v) VERSION="$OPTARG" ;; - a) ARCH="$OPTARG" ;; - r) RPM_FILE="$OPTARG" ;; - o) OUTPUT_DIR="$OPTARG" ;; - h) - echo "Usage: $0 -v VERSION -a ARCH -r RPM_FILE [-o OUTPUT_DIR]" - echo " -v VERSION Version number (e.g., 1.0.0)" - echo " -a ARCH Architecture (arm64 or amd64)" - echo " -r RPM_FILE Path to the RPM file to convert" - echo " -o OUTPUT_DIR Output directory (default: ./dist)" - exit 0 - ;; - \?) - echo "Invalid option: -$OPTARG" >&2 - exit 1 - ;; - esac -done - -if [ -z "$VERSION" ]; then - echo "Error: VERSION is required (-v)" >&2 - exit 1 -fi - -if [ -z "$ARCH" ]; then - echo "Error: ARCH is required (-a)" >&2 - exit 1 -fi - -if [ -z "$RPM_FILE" ]; then - echo "Error: RPM_FILE is required (-r)" >&2 - exit 1 -fi - -if [ "$VERSION" = "dev" ]; then - PKG_VERSION="0.0.0" -else - PKG_VERSION="$VERSION" -fi - -case "$ARCH" in - amd64) APK_ARCH="x86_64" ;; - arm64) APK_ARCH="aarch64" ;; - *) - echo "Error: Unsupported architecture: $ARCH (expected amd64 or arm64)" >&2 - exit 1 - ;; -esac - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -RPM_FILE="$(cd "$(dirname "$RPM_FILE")" && pwd)/$(basename "$RPM_FILE")" -OUTPUT_DIR="$(mkdir -p "$OUTPUT_DIR" && cd "$OUTPUT_DIR" && pwd)" - -if [ ! -f "$RPM_FILE" ]; then - echo "Error: RPM file not found at $RPM_FILE" >&2 - exit 1 -fi - -echo "Converting RPM to Alpine APK for SafeChain Ultimate v$VERSION" -echo " Architecture: $ARCH ($APK_ARCH)" -echo " RPM file: $RPM_FILE" -echo " Output directory: $OUTPUT_DIR" - -BUILD_DIR="$(mktemp -d)" -trap "rm -rf '$BUILD_DIR'" EXIT - -echo "Using temporary build directory: $BUILD_DIR" - -PKG_ROOT="$BUILD_DIR/pkg" -mkdir -p "$PKG_ROOT" - -echo "Extracting RPM contents..." -cd "$PKG_ROOT" -rpm2cpio "$RPM_FILE" | cpio -idm 2>/dev/null - -echo "Creating APK metadata..." -cat > "$PKG_ROOT/.PKGINFO" < "$PKG_ROOT/.scripts/.pre-install" <<'EOF' -#!/bin/sh -if command -v rc-service >/dev/null 2>&1; then - rc-service safechain-ultimate stop 2>/dev/null || true -fi -EOF - -cat > "$PKG_ROOT/.scripts/.post-install" <<'POSTEOF' -#!/bin/sh -mkdir -p /var/log/aikidosecurity/safechainultimate - -if command -v rc-update >/dev/null 2>&1; then - rc-update add safechain-ultimate default 2>/dev/null || true - rc-service safechain-ultimate start 2>/dev/null || true -fi - -echo "" -echo "SafeChain Ultimate has been installed successfully!" -echo " Binaries: /opt/aikidosecurity/safechainultimate/bin" -echo " Logs: /var/log/aikidosecurity/safechainultimate" -POSTEOF - -cat > "$PKG_ROOT/.scripts/.pre-deinstall" <<'EOF' -#!/bin/sh -if command -v rc-service >/dev/null 2>&1; then - rc-service safechain-ultimate stop 2>/dev/null || true - rc-update del safechain-ultimate default 2>/dev/null || true -fi -EOF - -chmod 755 "$PKG_ROOT/.scripts/.pre-install" -chmod 755 "$PKG_ROOT/.scripts/.post-install" -chmod 755 "$PKG_ROOT/.scripts/.pre-deinstall" - -echo "Building APK package..." - -cd "$BUILD_DIR" - -tar -czf "$BUILD_DIR/data.tar.gz" -C "$PKG_ROOT" \ - --exclude='.PKGINFO' \ - --exclude='.scripts' \ - . - -tar -czf "$BUILD_DIR/control.tar.gz" -C "$PKG_ROOT" \ - .PKGINFO \ - .scripts/ - -cat "$BUILD_DIR/control.tar.gz" "$BUILD_DIR/data.tar.gz" > "$BUILD_DIR/combined.tar.gz" - -OUTPUT_APK="$OUTPUT_DIR/SafeChainUltimate-$VERSION-$ARCH.apk" -cp "$BUILD_DIR/combined.tar.gz" "$OUTPUT_APK" - -echo "" -echo "✓ APK package built successfully: $OUTPUT_APK" -echo "" - -CHECKSUM=$(sha256sum "$OUTPUT_APK" | awk '{print $1}') -echo "SHA256: $CHECKSUM" -echo "$CHECKSUM" > "$OUTPUT_APK.sha256" -echo "" - -SIZE=$(du -h "$OUTPUT_APK" | awk '{print $1}') -echo "Package size: $SIZE" - -exit 0 From 989ef3bb3dd3e434846ec928ce16d3eb97b55a37 Mon Sep 17 00:00:00 2001 From: Tudor TIMCU Date: Tue, 10 Feb 2026 16:18:10 +0000 Subject: [PATCH 06/10] Add Docker support for multiple Linux distributions in development environment - Introduced Dockerfiles and configuration files for Alpine, CentOS, Debian, and Ubuntu to facilitate a consistent development environment across different Linux distributions. - Updated the Makefile to include dependencies for building RPM packages, enhancing the build process for Linux environments. - Each Dockerfile installs necessary tools and libraries, including fpm and Rust, to support the packaging and development workflow. --- .devcontainer/alpine/Dockerfile | 26 ++++++++++++++++++++++ .devcontainer/alpine/devcontainer.json | 7 ++++++ .devcontainer/centos/Dockerfile | 30 ++++++++++++++++++++++++++ .devcontainer/centos/devcontainer.json | 7 ++++++ .devcontainer/debian/Dockerfile | 20 +++++++++++++++++ .devcontainer/debian/devcontainer.json | 7 ++++++ .devcontainer/ubuntu/Dockerfile | 20 +++++++++++++++++ .devcontainer/ubuntu/devcontainer.json | 7 ++++++ Makefile | 2 +- 9 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/alpine/Dockerfile create mode 100644 .devcontainer/alpine/devcontainer.json create mode 100644 .devcontainer/centos/Dockerfile create mode 100644 .devcontainer/centos/devcontainer.json create mode 100644 .devcontainer/debian/Dockerfile create mode 100644 .devcontainer/debian/devcontainer.json create mode 100644 .devcontainer/ubuntu/Dockerfile create mode 100644 .devcontainer/ubuntu/devcontainer.json diff --git a/.devcontainer/alpine/Dockerfile b/.devcontainer/alpine/Dockerfile new file mode 100644 index 00000000..3b6c6402 --- /dev/null +++ b/.devcontainer/alpine/Dockerfile @@ -0,0 +1,26 @@ +FROM golang:1.25-alpine AS go-builder + +FROM alpine:3.21 + +COPY --from=go-builder /usr/local/go /usr/local/go +ENV PATH="/usr/local/go/bin:/root/go/bin:/root/.cargo/bin:${PATH}" +ENV GOPATH="/root/go" + +RUN apk add --no-cache \ + build-base git make curl sudo pkgconf \ + rpm \ + ruby ruby-dev \ + wayland-dev libx11-dev libxkbcommon-dev mesa-dev libxcursor-dev vulkan-loader-dev libffi-dev \ + perl dpkg dpkg-dev xz wget + +RUN gem install fpm --no-document + +RUN cd /tmp && \ + wget -q "https://deb.debian.org/debian/pool/main/a/alien/alien_8.95.8.tar.xz" && \ + tar xf alien_8.95.8.tar.xz && \ + cd alien && perl Makefile.PL && make && make install && \ + cd / && rm -rf /tmp/alien* + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +WORKDIR /workspace diff --git a/.devcontainer/alpine/devcontainer.json b/.devcontainer/alpine/devcontainer.json new file mode 100644 index 00000000..0819a62e --- /dev/null +++ b/.devcontainer/alpine/devcontainer.json @@ -0,0 +1,7 @@ +{ + "name": "SafeChain Ultimate - Alpine", + "build": { + "dockerfile": "Dockerfile" + }, + "remoteUser": "root" +} diff --git a/.devcontainer/centos/Dockerfile b/.devcontainer/centos/Dockerfile new file mode 100644 index 00000000..fa077467 --- /dev/null +++ b/.devcontainer/centos/Dockerfile @@ -0,0 +1,30 @@ +FROM golang:1.25 AS go-builder + +FROM quay.io/centos/centos:stream10 + +COPY --from=go-builder /usr/local/go /usr/local/go +ENV PATH="/usr/local/go/bin:/root/go/bin:/root/.cargo/bin:${PATH}" +ENV GOPATH="/root/go" + +RUN dnf install -y epel-release && \ + dnf config-manager --set-enabled crb && \ + dnf install -y \ + gcc gcc-c++ git make curl sudo pkgconf \ + rpm-build \ + dpkg \ + ruby ruby-devel \ + perl wget xz \ + wayland-devel libX11-devel libxkbcommon-devel libxkbcommon-x11-devel \ + mesa-libGLES-devel mesa-libEGL-devel libffi-devel libXcursor-devel vulkan-loader-devel \ + && gem install fpm --no-document \ + && dnf clean all + +RUN cd /tmp && \ + wget -q "https://deb.debian.org/debian/pool/main/a/alien/alien_8.95.8.tar.xz" && \ + tar xf alien_8.95.8.tar.xz && \ + cd alien && perl Makefile.PL && make && make install && \ + cd / && rm -rf /tmp/alien* + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +WORKDIR /workspace diff --git a/.devcontainer/centos/devcontainer.json b/.devcontainer/centos/devcontainer.json new file mode 100644 index 00000000..2a1c9813 --- /dev/null +++ b/.devcontainer/centos/devcontainer.json @@ -0,0 +1,7 @@ +{ + "name": "SafeChain Ultimate - CentOS Stream 10", + "build": { + "dockerfile": "Dockerfile" + }, + "remoteUser": "root" +} diff --git a/.devcontainer/debian/Dockerfile b/.devcontainer/debian/Dockerfile new file mode 100644 index 00000000..b8547cb5 --- /dev/null +++ b/.devcontainer/debian/Dockerfile @@ -0,0 +1,20 @@ +FROM golang:1.25 AS go-builder + +FROM debian:12 + +COPY --from=go-builder /usr/local/go /usr/local/go +ENV PATH="/usr/local/go/bin:/root/go/bin:/root/.cargo/bin:${PATH}" +ENV GOPATH="/root/go" + +RUN apt-get update && apt-get install -y \ + build-essential git make curl sudo pkg-config \ + rpm alien \ + ruby ruby-dev \ + libwayland-dev libx11-dev libx11-xcb-dev libxkbcommon-x11-dev \ + libgles2-mesa-dev libegl1-mesa-dev libffi-dev libxcursor-dev libvulkan-dev \ + && gem install fpm --no-document \ + && rm -rf /var/lib/apt/lists/* + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +WORKDIR /workspace diff --git a/.devcontainer/debian/devcontainer.json b/.devcontainer/debian/devcontainer.json new file mode 100644 index 00000000..00711d15 --- /dev/null +++ b/.devcontainer/debian/devcontainer.json @@ -0,0 +1,7 @@ +{ + "name": "SafeChain Ultimate - Debian", + "build": { + "dockerfile": "Dockerfile" + }, + "remoteUser": "root" +} diff --git a/.devcontainer/ubuntu/Dockerfile b/.devcontainer/ubuntu/Dockerfile new file mode 100644 index 00000000..b8c48202 --- /dev/null +++ b/.devcontainer/ubuntu/Dockerfile @@ -0,0 +1,20 @@ +FROM golang:1.25 AS go-builder + +FROM ubuntu:24.04 + +COPY --from=go-builder /usr/local/go /usr/local/go +ENV PATH="/usr/local/go/bin:/root/go/bin:/root/.cargo/bin:${PATH}" +ENV GOPATH="/root/go" + +RUN apt-get update && apt-get install -y \ + build-essential git make curl sudo pkg-config \ + rpm alien \ + ruby ruby-dev \ + libwayland-dev libx11-dev libx11-xcb-dev libxkbcommon-x11-dev \ + libgles2-mesa-dev libegl1-mesa-dev libffi-dev libxcursor-dev libvulkan-dev \ + && gem install fpm --no-document \ + && rm -rf /var/lib/apt/lists/* + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +WORKDIR /workspace diff --git a/.devcontainer/ubuntu/devcontainer.json b/.devcontainer/ubuntu/devcontainer.json new file mode 100644 index 00000000..ec646f8f --- /dev/null +++ b/.devcontainer/ubuntu/devcontainer.json @@ -0,0 +1,7 @@ +{ + "name": "SafeChain Ultimate - Ubuntu", + "build": { + "dockerfile": "Dockerfile" + }, + "remoteUser": "root" +} diff --git a/Makefile b/Makefile index c7a88386..ca7612db 100644 --- a/Makefile +++ b/Makefile @@ -111,7 +111,7 @@ else @exit 1 endif -build-rpm: +build-rpm: build-release build-proxy ifeq ($(DETECTED_OS),linux) @echo "Building Linux RPM installer..." @cd packaging/rpm && ./build-rpm.sh -v $(VERSION) -a $(DETECTED_ARCH) -b ../../$(BIN_DIR) -o ../../$(DIST_DIR) From 466987ff7e16d2585deba4c235d0bfef74c67fd7 Mon Sep 17 00:00:00 2001 From: Tudor TIMCU Date: Tue, 10 Feb 2026 20:19:27 +0000 Subject: [PATCH 07/10] Update CentOS Dockerfile to include cmake and clang-devel in the build dependencies --- .devcontainer/centos/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/centos/Dockerfile b/.devcontainer/centos/Dockerfile index fa077467..ff413012 100644 --- a/.devcontainer/centos/Dockerfile +++ b/.devcontainer/centos/Dockerfile @@ -9,7 +9,7 @@ ENV GOPATH="/root/go" RUN dnf install -y epel-release && \ dnf config-manager --set-enabled crb && \ dnf install -y \ - gcc gcc-c++ git make curl sudo pkgconf \ + gcc gcc-c++ git make cmake curl sudo pkgconf clang-devel \ rpm-build \ dpkg \ ruby ruby-devel \ From d88869b8b062e79a9d9fe742ae9cf35c142a6c63 Mon Sep 17 00:00:00 2001 From: Tudor TIMCU Date: Tue, 10 Feb 2026 21:12:54 +0000 Subject: [PATCH 08/10] Enhance Linux package management in Makefile and RPM scripts - Added new targets in the Makefile for installing and uninstalling RPM, DEB, and APK packages, improving the build process for Linux environments. - Updated RPM spec file and uninstall scripts to handle service management more robustly, ensuring proper installation and uninstallation procedures. - Modified Dockerfile to include procps-ng for enhanced process management capabilities. --- .devcontainer/centos/Dockerfile | 2 +- Makefile | 62 ++++++++++++++++++++++++++- internal/platform/platform_linux.go | 6 ++- internal/utils/utils.go | 2 + packaging/rpm/safechain-ultimate.spec | 45 ++++++++++++------- packaging/rpm/scripts/uninstall | 34 +++++++++------ 6 files changed, 118 insertions(+), 33 deletions(-) diff --git a/.devcontainer/centos/Dockerfile b/.devcontainer/centos/Dockerfile index ff413012..d4ee8809 100644 --- a/.devcontainer/centos/Dockerfile +++ b/.devcontainer/centos/Dockerfile @@ -9,7 +9,7 @@ ENV GOPATH="/root/go" RUN dnf install -y epel-release && \ dnf config-manager --set-enabled crb && \ dnf install -y \ - gcc gcc-c++ git make cmake curl sudo pkgconf clang-devel \ + gcc gcc-c++ git make cmake curl sudo pkgconf clang-devel procps-ng \ rpm-build \ dpkg \ ruby ruby-devel \ diff --git a/Makefile b/Makefile index ca7612db..4f248e05 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build build-release build-darwin-amd64 build-darwin-arm64 build-linux-amd64 build-linux-arm64 build-windows-amd64 build-windows-arm64 build-proxy build-pkg build-pkg-sign-local install-pkg uninstall-pkg build-rpm build-deb build-apk clean test run help +.PHONY: build build-release build-darwin-amd64 build-darwin-arm64 build-linux-amd64 build-linux-arm64 build-windows-amd64 build-windows-arm64 build-proxy build-pkg build-pkg-sign-local install-pkg uninstall-pkg build-rpm build-deb build-apk install-rpm install-deb install-apk uninstall-rpm uninstall-deb uninstall-apk clean test run help BINARY_NAME=safechain-ultimate BINARY_NAME_UI=safechain-ultimate-ui @@ -141,6 +141,66 @@ else @exit 1 endif +install-rpm: build-rpm +ifeq ($(DETECTED_OS),linux) + @echo "Installing RPM package..." + @sudo rpm -U --force $(DIST_DIR)/SafeChainUltimate-$(VERSION)-$(DETECTED_ARCH).rpm + @echo "RPM package installed." +else + @echo "Error: RPM installation is only supported on Linux" + @exit 1 +endif + +uninstall-rpm: +ifeq ($(DETECTED_OS),linux) + @echo "Uninstalling RPM package..." + @sudo rpm -e safechain-ultimate + @echo "RPM package uninstalled." +else + @echo "Error: RPM uninstallation is only supported on Linux" + @exit 1 +endif + +install-deb: build-deb +ifeq ($(DETECTED_OS),linux) + @echo "Installing DEB package..." + @sudo dpkg -i $(DIST_DIR)/safechain-ultimate_$(VERSION)*.deb + @echo "DEB package installed." +else + @echo "Error: DEB installation is only supported on Linux" + @exit 1 +endif + +uninstall-deb: +ifeq ($(DETECTED_OS),linux) + @echo "Uninstalling DEB package..." + @sudo dpkg -r safechain-ultimate + @echo "DEB package uninstalled." +else + @echo "Error: DEB uninstallation is only supported on Linux" + @exit 1 +endif + +install-apk: build-apk +ifeq ($(DETECTED_OS),linux) + @echo "Installing APK package..." + @sudo apk add --allow-untrusted $(DIST_DIR)/SafeChainUltimate-$(VERSION)*.apk + @echo "APK package installed." +else + @echo "Error: APK installation is only supported on Linux" + @exit 1 +endif + +uninstall-apk: +ifeq ($(DETECTED_OS),linux) + @echo "Uninstalling APK package..." + @sudo apk del safechain-ultimate + @echo "APK package uninstalled." +else + @echo "Error: APK uninstallation is only supported on Linux" + @exit 1 +endif + build-pkg-sign-local: ifeq ($(DETECTED_OS),darwin) @echo "Building complete macOS package..." diff --git a/internal/platform/platform_linux.go b/internal/platform/platform_linux.go index 2d8c7965..d7a24c23 100644 --- a/internal/platform/platform_linux.go +++ b/internal/platform/platform_linux.go @@ -33,9 +33,11 @@ func initConfig() error { if RunningAsRoot() { username, err := getLoggedInUser(context.Background()) if err != nil { - return fmt.Errorf("failed to get logged in user: %v", err) + log.Printf("Warning: %v, falling back to root home directory", err) + config.HomeDir = "/root" + } else { + config.HomeDir = filepath.Join("/home", username) } - config.HomeDir = filepath.Join("/home", username) } else { var err error config.HomeDir, err = os.UserHomeDir() diff --git a/internal/utils/utils.go b/internal/utils/utils.go index df64b925..82a944a0 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -66,6 +66,8 @@ func FetchLatestVersion(ctx context.Context, repoURL, binaryName string) (string func DetectOS() (string, string) { switch runtime.GOOS { + case "linux": + return "linux", "" case "darwin": return "macos", "" case "windows": diff --git a/packaging/rpm/safechain-ultimate.spec b/packaging/rpm/safechain-ultimate.spec index 92d35992..06fc8b61 100644 --- a/packaging/rpm/safechain-ultimate.spec +++ b/packaging/rpm/safechain-ultimate.spec @@ -38,32 +38,47 @@ install -m 644 %{_sourcedir}/safechain-ultimate.service %{buildroot}/usr/lib/sys %attr(644, root, root) /usr/lib/systemd/system/safechain-ultimate.service %pre -if systemctl is-active --quiet safechain-ultimate 2>/dev/null; then +if pidof systemd &>/dev/null && systemctl is-active --quiet safechain-ultimate 2>/dev/null; then systemctl stop safechain-ultimate || true fi %post -systemctl daemon-reload -systemctl enable safechain-ultimate -systemctl start safechain-ultimate - -echo "" -echo "SafeChain Ultimate has been installed successfully!" -echo " Binaries: /opt/aikidosecurity/safechainultimate/bin" -echo " Logs: /var/log/aikidosecurity/safechainultimate" -echo "" -echo "The agent is now running as a systemd service." +if pidof systemd &>/dev/null; then + systemctl daemon-reload + systemctl enable safechain-ultimate + systemctl start safechain-ultimate + echo "" + echo "SafeChain Ultimate has been installed successfully!" + echo " Binaries: /opt/aikidosecurity/safechainultimate/bin" + echo " Logs: /var/log/aikidosecurity/safechainultimate" + echo "" + echo "The agent is now running as a systemd service." +else + /opt/aikidosecurity/safechainultimate/bin/safechain-ultimate & + echo "" + echo "SafeChain Ultimate has been installed successfully!" + echo " Binaries: /opt/aikidosecurity/safechainultimate/bin" + echo " Logs: /var/log/aikidosecurity/safechainultimate" + echo "" + echo "The agent is now running in the background (PID: $!)." +fi %preun if [ $1 -eq 0 ]; then - if systemctl is-active --quiet safechain-ultimate 2>/dev/null; then - systemctl stop safechain-ultimate || true + if pidof systemd &>/dev/null; then + if systemctl is-active --quiet safechain-ultimate 2>/dev/null; then + systemctl stop safechain-ultimate || true + fi + systemctl disable safechain-ultimate 2>/dev/null || true + else + pkill -f /opt/aikidosecurity/safechainultimate/bin/safechain-ultimate || true fi - systemctl disable safechain-ultimate 2>/dev/null || true fi %postun -systemctl daemon-reload +if pidof systemd &>/dev/null; then + systemctl daemon-reload +fi if [ $1 -eq 0 ]; then rm -rf /var/log/aikidosecurity/safechainultimate rmdir /var/log/aikidosecurity 2>/dev/null || true diff --git a/packaging/rpm/scripts/uninstall b/packaging/rpm/scripts/uninstall index f6f1f682..f47eb3f2 100755 --- a/packaging/rpm/scripts/uninstall +++ b/packaging/rpm/scripts/uninstall @@ -20,18 +20,22 @@ fi echo "Stopping SafeChain Ultimate processes..." -if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then - echo " Stopping service..." - systemctl stop "$SERVICE_NAME" || true - echo " ✓ Service stopped" +if pidof systemd &>/dev/null; then + if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then + echo " Stopping service..." + systemctl stop "$SERVICE_NAME" || true + echo " ✓ Service stopped" + else + echo " Service not running" + fi + + if systemctl is-enabled --quiet "$SERVICE_NAME" 2>/dev/null; then + echo " Disabling service..." + systemctl disable "$SERVICE_NAME" || true + echo " ✓ Service disabled" + fi else - echo " Service not running" -fi - -if systemctl is-enabled --quiet "$SERVICE_NAME" 2>/dev/null; then - echo " Disabling service..." - systemctl disable "$SERVICE_NAME" || true - echo " ✓ Service disabled" + echo " systemd not detected, skipping service management" fi pkill -f "safechain-ultimate-ui" 2>/dev/null && echo " ✓ UI process stopped" || echo " UI process not running" @@ -80,9 +84,11 @@ if [ -d "$AIKIDO_LOGS_DIR" ] && [ -z "$(ls -A "$AIKIDO_LOGS_DIR" 2>/dev/null)" ] echo " ✓ Removed empty AikidoSecurity logs directory" fi -echo "" -echo "Reloading systemd..." -systemctl daemon-reload +if pidof systemd &>/dev/null; then + echo "" + echo "Reloading systemd..." + systemctl daemon-reload +fi echo "" echo "=========================================" From f99efb76017bf6ab21a791dc78f0b76925be50b9 Mon Sep 17 00:00:00 2001 From: Tudor TIMCU Date: Tue, 10 Feb 2026 21:19:16 +0000 Subject: [PATCH 09/10] Add ClearProxyKeyring function to manage keyring entries - Introduced ClearProxyKeyring function to invalidate the proxy keyring entry, enhancing security by ensuring sensitive keys are properly managed. - Updated UninstallProxyCA function to call ClearProxyKeyring, ensuring the keyring is cleared during the uninstallation process. - Improved error handling and logging for keyring operations, providing better visibility into the uninstallation workflow. --- internal/platform/platform_linux.go | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/internal/platform/platform_linux.go b/internal/platform/platform_linux.go index d7a24c23..1e6ef842 100644 --- a/internal/platform/platform_linux.go +++ b/internal/platform/platform_linux.go @@ -213,7 +213,28 @@ func IsProxyCAInstalled(_ context.Context) error { return nil } -func UninstallProxyCA(_ context.Context) error { +func ClearProxyKeyring(ctx context.Context) error { + keyDescription := "keyring:safechain-proxy@tls-root-ca-key" + + output, err := exec.CommandContext(ctx, "keyctl", "search", "@s", "user", keyDescription).Output() + if err != nil { + return nil + } + + keyID := strings.TrimSpace(string(output)) + if keyID == "" { + return nil + } + + if err := exec.CommandContext(ctx, "keyctl", "invalidate", keyID).Run(); err != nil { + return fmt.Errorf("failed to invalidate keyring entry %s: %v", keyID, err) + } + + log.Printf("Cleared proxy keyring entry: %s (key ID: %s)", keyDescription, keyID) + return nil +} + +func UninstallProxyCA(ctx context.Context) error { errs := []error{} destPath := filepath.Join(systemCertDir, certFileName) @@ -225,6 +246,10 @@ func UninstallProxyCA(_ context.Context) error { errs = append(errs, fmt.Errorf("failed to update ca certificates: %v", err)) } + if err := ClearProxyKeyring(ctx); err != nil { + errs = append(errs, fmt.Errorf("failed to clear proxy keyring: %v", err)) + } + if len(errs) > 0 { return fmt.Errorf("failed to uninstall proxy CA: %v", errs) } From b1e6a3dfa75cb681d6d0232e6205942045767321 Mon Sep 17 00:00:00 2001 From: Tudor TIMCU Date: Tue, 10 Feb 2026 23:04:12 +0000 Subject: [PATCH 10/10] Update Dockerfiles to include Node.js and Python package managers - Added Node.js, npm, and Python3 pip to the installation lists in Dockerfiles for Alpine, CentOS, Debian, and Ubuntu, enhancing the development environment with additional package management capabilities. - This update ensures that necessary tools for JavaScript and Python development are readily available in the containerized environments. --- .devcontainer/alpine/Dockerfile | 1 + .devcontainer/centos/Dockerfile | 1 + .devcontainer/debian/Dockerfile | 1 + .devcontainer/ubuntu/Dockerfile | 1 + .github/workflows/build-linux.yml | 3 +- internal/platform/platform_linux.go | 52 ++++++++++++++++++++++----- packaging/rpm/safechain-ultimate.spec | 4 ++- 7 files changed, 52 insertions(+), 11 deletions(-) diff --git a/.devcontainer/alpine/Dockerfile b/.devcontainer/alpine/Dockerfile index 3b6c6402..f1859bc7 100644 --- a/.devcontainer/alpine/Dockerfile +++ b/.devcontainer/alpine/Dockerfile @@ -10,6 +10,7 @@ RUN apk add --no-cache \ build-base git make curl sudo pkgconf \ rpm \ ruby ruby-dev \ + nodejs npm py3-pip \ wayland-dev libx11-dev libxkbcommon-dev mesa-dev libxcursor-dev vulkan-loader-dev libffi-dev \ perl dpkg dpkg-dev xz wget diff --git a/.devcontainer/centos/Dockerfile b/.devcontainer/centos/Dockerfile index d4ee8809..5348b428 100644 --- a/.devcontainer/centos/Dockerfile +++ b/.devcontainer/centos/Dockerfile @@ -13,6 +13,7 @@ RUN dnf install -y epel-release && \ rpm-build \ dpkg \ ruby ruby-devel \ + nodejs npm python3-pip \ perl wget xz \ wayland-devel libX11-devel libxkbcommon-devel libxkbcommon-x11-devel \ mesa-libGLES-devel mesa-libEGL-devel libffi-devel libXcursor-devel vulkan-loader-devel \ diff --git a/.devcontainer/debian/Dockerfile b/.devcontainer/debian/Dockerfile index b8547cb5..b6434dec 100644 --- a/.devcontainer/debian/Dockerfile +++ b/.devcontainer/debian/Dockerfile @@ -10,6 +10,7 @@ RUN apt-get update && apt-get install -y \ build-essential git make curl sudo pkg-config \ rpm alien \ ruby ruby-dev \ + nodejs npm python3-pip \ libwayland-dev libx11-dev libx11-xcb-dev libxkbcommon-x11-dev \ libgles2-mesa-dev libegl1-mesa-dev libffi-dev libxcursor-dev libvulkan-dev \ && gem install fpm --no-document \ diff --git a/.devcontainer/ubuntu/Dockerfile b/.devcontainer/ubuntu/Dockerfile index b8c48202..8d454620 100644 --- a/.devcontainer/ubuntu/Dockerfile +++ b/.devcontainer/ubuntu/Dockerfile @@ -10,6 +10,7 @@ RUN apt-get update && apt-get install -y \ build-essential git make curl sudo pkg-config \ rpm alien \ ruby ruby-dev \ + nodejs npm python3-pip \ libwayland-dev libx11-dev libx11-xcb-dev libxkbcommon-x11-dev \ libgles2-mesa-dev libegl1-mesa-dev libffi-dev libxcursor-dev libvulkan-dev \ && gem install fpm --no-document \ diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index caae2136..3ae285d1 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -77,13 +77,12 @@ jobs: - name: Convert RPM to APK using fpm run: | cd dist - fpm -s rpm -t apk SafeChainUltimate-${{ inputs.version || 'dev' }}-${{ matrix.arch }}.rpm + fpm -s rpm -t apk -p SafeChainUltimate-${{ matrix.arch }}.apk SafeChainUltimate-${{ inputs.version || 'dev' }}-${{ matrix.arch }}.rpm - name: Rename packages run: | mv dist/SafeChainUltimate-${{ inputs.version || 'dev' }}-${{ matrix.arch }}.rpm dist/SafeChainUltimate-${{ matrix.arch }}.rpm mv dist/safechain-ultimate_*.deb dist/SafeChainUltimate-${{ matrix.arch }}.deb - mv dist/safechain-ultimate-*.apk dist/SafeChainUltimate-${{ matrix.arch }}.apk - name: Upload RPM artifact uses: actions/upload-artifact@v4 diff --git a/internal/platform/platform_linux.go b/internal/platform/platform_linux.go index 1e6ef842..9ec9b727 100644 --- a/internal/platform/platform_linux.go +++ b/internal/platform/platform_linux.go @@ -25,10 +25,42 @@ const ( SafeChainInstallScriptName = "install-safe-chain.sh" SafeChainUninstallScriptName = "uninstall-safe-chain.sh" - systemCertDir = "/usr/local/share/ca-certificates" + debianCertDir = "/usr/local/share/ca-certificates" + rhelCertDir = "/etc/pki/ca-trust/source/anchors" certFileName = "aikidosafechain.crt" ) +func getCertDir() string { + if _, err := exec.LookPath("update-ca-trust"); err == nil { + return rhelCertDir + } + return debianCertDir +} + +func updateCACertificates() error { + if path, err := exec.LookPath("update-ca-trust"); err == nil { + _, err := exec.Command(path, "extract").Output() + return err + } + if path, err := exec.LookPath("update-ca-certificates"); err == nil { + _, err := exec.Command(path).Output() + return err + } + return fmt.Errorf("no supported CA certificate update tool found (tried update-ca-trust, update-ca-certificates)") +} + +func refreshCACertificates() error { + if path, err := exec.LookPath("update-ca-trust"); err == nil { + _, err := exec.Command(path, "extract").Output() + return err + } + if path, err := exec.LookPath("update-ca-certificates"); err == nil { + _, err := exec.Command(path, "--fresh").Output() + return err + } + return fmt.Errorf("no supported CA certificate update tool found (tried update-ca-trust, update-ca-certificates)") +} + func initConfig() error { if RunningAsRoot() { username, err := getLoggedInUser(context.Background()) @@ -78,7 +110,10 @@ func getGsettingsAutoConfigURL(ctx context.Context) (string, error) { } func hasGsettings() bool { - _, err := exec.LookPath("gsettings") + if _, err := exec.LookPath("gsettings"); err != nil { + return false + } + err := exec.Command("gsettings", "get", "org.gnome.system.proxy", "mode").Run() return err == nil } @@ -188,8 +223,9 @@ func unsetEnvironmentProxy() error { } func InstallProxyCA(_ context.Context, certPath string) error { - destPath := filepath.Join(systemCertDir, certFileName) - if err := os.MkdirAll(systemCertDir, 0755); err != nil { + certDir := getCertDir() + destPath := filepath.Join(certDir, certFileName) + if err := os.MkdirAll(certDir, 0755); err != nil { return fmt.Errorf("failed to create certificate directory: %v", err) } input, err := os.ReadFile(certPath) @@ -199,14 +235,14 @@ func InstallProxyCA(_ context.Context, certPath string) error { if err := os.WriteFile(destPath, input, 0644); err != nil { return fmt.Errorf("failed to write certificate: %v", err) } - if _, err := exec.Command("update-ca-certificates").Output(); err != nil { + if err := updateCACertificates(); err != nil { return fmt.Errorf("failed to update ca certificates: %v", err) } return nil } func IsProxyCAInstalled(_ context.Context) error { - destPath := filepath.Join(systemCertDir, certFileName) + destPath := filepath.Join(getCertDir(), certFileName) if _, err := os.Stat(destPath); os.IsNotExist(err) { return fmt.Errorf("proxy CA certificate not found at %s", destPath) } @@ -237,12 +273,12 @@ func ClearProxyKeyring(ctx context.Context) error { func UninstallProxyCA(ctx context.Context) error { errs := []error{} - destPath := filepath.Join(systemCertDir, certFileName) + destPath := filepath.Join(getCertDir(), certFileName) if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) { errs = append(errs, fmt.Errorf("failed to remove certificate: %v", err)) } - if _, err := exec.Command("update-ca-certificates", "--fresh").Output(); err != nil { + if err := refreshCACertificates(); err != nil { errs = append(errs, fmt.Errorf("failed to update ca certificates: %v", err)) } diff --git a/packaging/rpm/safechain-ultimate.spec b/packaging/rpm/safechain-ultimate.spec index 06fc8b61..228a77fc 100644 --- a/packaging/rpm/safechain-ultimate.spec +++ b/packaging/rpm/safechain-ultimate.spec @@ -54,7 +54,9 @@ if pidof systemd &>/dev/null; then echo "" echo "The agent is now running as a systemd service." else - /opt/aikidosecurity/safechainultimate/bin/safechain-ultimate & + /opt/aikidosecurity/safechainultimate/bin/safechain-ultimate \ + >>/var/log/aikidosecurity/safechainultimate/safechain-ultimate.log \ + 2>>/var/log/aikidosecurity/safechainultimate/safechain-ultimate.error.log & echo "" echo "SafeChain Ultimate has been installed successfully!" echo " Binaries: /opt/aikidosecurity/safechainultimate/bin"