liven 0.0.3

LIVEN is a fast, lightweight database built to capture, store, and stream data in real time.
Documentation
name: Build & Verify Liven

on:
  push:
    branches: [main]
    tags: ["v*.*.*"]
  pull_request:
    branches: [main]

# Ensure only one workflow runs per branch at a time to optimize resources
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  # ── Fast check: runs on every PR and push ──
  check:
    name: Lint & Core Tests
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - name: Checkout repository
        uses: actions/checkout@v7

      - name: Set up Node.js
        uses: actions/setup-node@v6
        with:
          node-version: "24"
          cache: "npm"
          cache-dependency-path: "ui/package-lock.json"

      - name: Install UI dependencies & Build Frontend
        working-directory: ui
        shell: bash
        run: |
          npm ci --legacy-peer-deps
          npm run build

      - name: Install Rust stable
        uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy, rustfmt

      - name: Cache Cargo dependencies
        uses: Swatinem/rust-cache@v2

      - name: Verify code formatting
        run: cargo fmt --all -- --check

      - name: Lint with clippy (lib + tests, no benches)
        run: cargo clippy --lib --tests -- -D warnings

      - name: Test embedded core (no default features)
        run: cargo test --no-default-features --lib --tests

      - name: Test full suite (lib + tests, no benches)
        run: cargo test --lib --tests

      - name: Audit dependencies
        run: |
          cargo install cargo-audit --locked 2>&1 || true
          cargo audit

  # ── Full build: per-target cross-platform matrix ──
  build:
    name: Build (${{ matrix.target }})
    if: github.event_name == 'push'
    runs-on: ${{ matrix.os }}
    timeout-minutes: 30
    strategy:
      fail-fast: false
      matrix:
        include:
          # ── Linux amd64 (native) ──
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            native: true
            platform: linux
            arch: amd64
          # ── Linux arm64 (cross-compiled) ──
          # NOTE: requires the gcc-aarch64-linux-gnu cross toolchain, installed below.
          # Install/boot tests are skipped here since this binary can't run on the
          # x86_64 runner without QEMU emulation.
          - os: ubuntu-latest
            target: aarch64-unknown-linux-gnu
            native: false
            platform: linux
            arch: arm64
          # ── macOS Apple Silicon (native — GitHub's macos-latest runners are arm64) ──
          - os: macos-latest
            target: aarch64-apple-darwin
            native: true
            platform: macos
            arch: arm64
          # ── macOS Intel (cross-compiled via Xcode's multi-arch clang) ──
          - os: macos-latest
            target: x86_64-apple-darwin
            native: false
            platform: macos
            arch: amd64
          # ── Windows amd64 (native) ──
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            native: true
            platform: windows
            arch: amd64
          # ── Windows arm64 (cross-compiled) ──
          # NOTE: assumes the hosted windows-latest runner's MSVC toolset includes
          # the ARM64 build component. If this leg fails on the linker step, the
          # runner image needs the "VS.Component.VC.Tools.ARM64" workload added
          # explicitly via a setup step before `rustup target add` will succeed.
          - os: windows-latest
            target: aarch64-pc-windows-msvc
            native: false
            platform: windows
            arch: arm64

    steps:
      - name: Checkout repository
        uses: actions/checkout@v7

      - name: Set up Node.js
        uses: actions/setup-node@v6
        with:
          node-version: "24"
          cache: "npm"
          cache-dependency-path: "ui/package-lock.json"

      - name: Install UI dependencies & Build Frontend
        working-directory: ui
        shell: bash
        run: |
          npm ci --legacy-peer-deps
          npm run build

      - name: Install Rust stable
        uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy
          targets: ${{ matrix.target }}

      - name: Cache Cargo dependencies
        uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.target }}

      # ── Use mold linker on native Linux amd64 for ~5x faster LTO linking ──
      - name: Install mold linker (Linux amd64)
        if: matrix.target == 'x86_64-unknown-linux-gnu'
        run: |
          sudo apt-get update -qq
          sudo apt-get install -y -qq mold
          echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=clang" >> $GITHUB_ENV
          echo "RUSTFLAGS=-C link-arg=-fuse-ld=mold" >> $GITHUB_ENV

      # ── Cross toolchain for Linux arm64 ──
      - name: Install aarch64 cross toolchain (Linux arm64)
        if: matrix.target == 'aarch64-unknown-linux-gnu'
        run: |
          sudo apt-get update -qq
          sudo apt-get install -y -qq gcc-aarch64-linux-gnu
          echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV

      - name: Build release binary
        run: cargo build --release --target ${{ matrix.target }}

      - name: Stage runtime config alongside binary
        shell: bash
        run: cp liven.toml target/${{ matrix.target }}/release/liven.toml

      # ── Installation tests: only run on the runner's native target ──
      - name: Run Installation Tests (Linux amd64)
        if: matrix.target == 'x86_64-unknown-linux-gnu'
        timeout-minutes: 10
        run: |
          echo "==== Linux Installation Tests ===="
          chmod +x setup.sh

          # setup.sh expects the binary at target/release/, not the
          # target-triple-scoped path cargo writes to with --target.
          mkdir -p target/release
          cp target/${{ matrix.target }}/release/liven target/release/liven

          # 1. Production install
          sudo ./setup.sh --env production

          grep -q 'environment = "production"' /etc/liven/liven.toml
          test -f /etc/liven/certs/ca.crt
          test -f /etc/liven/certs/ca.key
          test -f /etc/liven/certs/server.crt
          test -f /etc/liven/certs/server.key
          [ "$(stat -c '%a' /etc/liven/certs/server.key)" = "600" ]
          [ "$(stat -c '%a' /etc/liven/certs/ca.key)" = "600" ]
          test -f /etc/systemd/system/liven.service

          # 2. Container fallback (Debian)
          docker run --rm -v ${{ github.workspace }}:/workspace debian:bookworm-slim sh -c "
            cp /workspace/setup.sh /setup.sh &&
            chmod +x /setup.sh &&
            mkdir -p /target/release &&
            cp /workspace/target/${{ matrix.target }}/release/liven /target/release/liven &&
            chmod +x /target/release/liven &&
            ./setup.sh --env development &&
            test -f /usr/local/bin/liven &&
            test -f /usr/local/bin/liven-entrypoint &&
            ! test -f /etc/systemd/system/liven.service &&
            grep -q 'environment = \"development\"' /etc/liven/liven.toml
          "

          # 3. Live-process boot
          echo "Booting mTLS protected production LIVEN in background..."
          sudo /usr/local/bin/liven start --config /etc/liven/liven.toml &
          DB_PID=$!
          sleep 3
          sudo ss -tlnp | grep -q 43121
          # --max-time bounds the WHOLE request (not just the TCP connect phase).
          # Without it, a TCP connection that's accepted but never completes the
          # TLS handshake leaves curl blocked indefinitely waiting on the socket.
          curl -v --connect-timeout 3 --max-time 8 http://127.0.0.1:43121/api/streams \
            && (echo "FAIL: Cleartext succeeded in production!" && exit 1) \
            || echo "PASS: Cleartext rejected by mTLS shield"
          sudo kill -15 $DB_PID || true

      - name: Run Installation Tests (macOS arm64)
        if: matrix.target == 'aarch64-apple-darwin'
        timeout-minutes: 10
        run: |
          echo "==== macOS Installation Tests ===="
          chmod +x setup.sh

          # setup.sh expects the binary at target/release/, not the
          # target-triple-scoped path cargo writes to with --target.
          mkdir -p target/release
          cp target/${{ matrix.target }}/release/liven target/release/liven

          sudo ./setup.sh --env development
          grep -q 'environment = "development"' /etc/liven/liven.toml
          test -f /Library/LaunchDaemons/com.liven.liven.plist
          test -f /usr/local/bin/liven
          echo "Booting cleartext development LIVEN in background..."
          sudo /usr/local/bin/liven start --config /etc/liven/liven.toml &
          DB_PID=$!
          sleep 3
          if curl -v --connect-timeout 3 --max-time 8 http://127.0.0.1:43120/api/streams; then
            echo "PASS: Development API connection succeeded"
          else
            echo "FAIL: Development API connection failed" && exit 1
          fi
          sudo kill -15 $DB_PID || true

      - name: Run Installation Tests (Windows amd64)
        if: matrix.target == 'x86_64-pc-windows-msvc'
        shell: bash
        timeout-minutes: 10
        run: |
          echo "==== Windows Installation Tests ===="
          INSTALL_OUTPUT=$(./setup.sh)
          echo "$INSTALL_OUTPUT" | grep -q "Windows Deployment Instructions"
          echo "PASS: Windows instructions successfully returned"

      - name: Upload compiled executable (Unix)
        if: matrix.platform != 'windows'
        uses: actions/upload-artifact@v7
        with:
          name: liven-${{ matrix.target }}
          path: |
            target/${{ matrix.target }}/release/liven
            target/${{ matrix.target }}/release/liven.toml
          if-no-files-found: error

      - name: Upload compiled executable (Windows)
        if: matrix.platform == 'windows'
        uses: actions/upload-artifact@v7
        with:
          name: liven-${{ matrix.target }}
          path: |
            target/${{ matrix.target }}/release/liven.exe
            target/${{ matrix.target }}/release/liven.toml
          if-no-files-found: error

  # ── Release: only runs when a vX.Y.Z tag is pushed ──
  release:
    name: Publish GitHub Release
    if: startsWith(github.ref, 'refs/tags/v')
    needs: build
    runs-on: ubuntu-latest
    timeout-minutes: 10
    permissions:
      contents: write
    steps:
      - name: Download all binary artifacts
        uses: actions/download-artifact@v8
        with:
          pattern: liven-*
          path: release-assets
          merge-multiple: true

      - name: List release assets
        run: ls -la release-assets

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          files: release-assets/*
          generate_release_notes: true
          draft: false
          prerelease: false