name: ci
on:
workflow_dispatch:
pull_request:
push:
tags: ["*"]
branches: ["main"]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
permissions: {}
jobs:
build:
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os:
- macos-latest
- ubuntu-latest
runs-on: ${{ matrix.os }}
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with:
submodules: recursive
persist-credentials: false
- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 with:
cache: false
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 with:
shared-key: debug-${{ matrix.os }}
save-if: false
- name: Install system dependencies (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: sudo apt-get update && sudo apt-get install -y libudev-dev
- name: Install Rust components
run: rustup component add rustfmt clippy
- name: Build
run: cargo build
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: fnox-${{ matrix.os }}
path: target/debug/fnox
ci-bats:
needs: build
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os:
- macos-latest
- ubuntu-latest
tranche: [0, 1]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
steps:
- run: brew install parallel vault
if: ${{ matrix.os == 'macos-latest' }}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with:
submodules: recursive
persist-credentials: false
- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 with:
cache: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
name: fnox-${{ matrix.os }}
path: target/debug
- run: chmod +x target/debug/fnox
- name: Setup age key for fnox
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
run: mkdir -p ~/.config/fnox && echo "${{ secrets.AGE_SECRET }}" > ~/.config/fnox/age.txt
- name: Setup services (Ubuntu)
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
# Install gnome-keyring for keychain tests
sudo apt-get update
sudo apt-get install -y gnome-keyring libsecret-tools
# Start D-Bus session daemon
mkdir -p ~/.dbus-session
dbus-daemon --session --fork --print-address=1 > ~/.dbus-session/bus-address
DBUS_SESSION_BUS_ADDRESS=$(cat ~/.dbus-session/bus-address)
export DBUS_SESSION_BUS_ADDRESS
echo "DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS" >> "$GITHUB_ENV"
# Start gnome-keyring daemon for keychain tests
echo "foobar" | gnome-keyring-daemon --unlock --components=secrets --daemonize
# Start Vaultwarden with HTTPS (self-signed certificate)
# Generate self-signed certificate
mkdir -p /tmp/vaultwarden-certs
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /tmp/vaultwarden-certs/key.pem \
-out /tmp/vaultwarden-certs/cert.pem \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1" 2>/dev/null || true
# Start Vaultwarden with HTTPS enabled
docker run -d --name vaultwarden \
-p 8080:80 \
-e DOMAIN=https://localhost:8080 \
-e SIGNUPS_ALLOWED=true \
-e DISABLE_ADMIN_TOKEN=true \
-e I_REALLY_WANT_VOLATILE_STORAGE=true \
-e ROCKET_TLS='{certs="/data/certs/cert.pem",key="/data/certs/key.pem"}' \
-v /tmp/vaultwarden-certs:/data/certs:ro \
vaultwarden/server:latest
# Start Vault
docker run -d --name vault --cap-add=IPC_LOCK \
-p 8200:8200 \
-e VAULT_DEV_ROOT_TOKEN_ID=fnox-test-token \
-e VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200 \
-e VAULT_ADDR=http://127.0.0.1:8200 \
hashicorp/vault:latest
# Start Infisical (self-hosted with PostgreSQL and Redis)
docker compose -f test/docker-compose.infisical-ci.yml up -d
# Start LocalStack for AWS tests (STS leases, KMS, Secrets Manager, Parameter Store)
docker run -d --name localstack \
-p 4566:4566 \
-e SERVICES=sts,iam,kms,secretsmanager,ssm \
localstack/localstack:4
# Wait for services to be ready
# Poll LocalStack health endpoint (can take 10-30s on cold runners)
for _i in $(seq 1 30); do
curl -sf http://localhost:4566/_localstack/health && break
sleep 2
done
# Wait for Vault to be ready
for _i in $(seq 1 15); do
curl -sf http://localhost:8200/v1/sys/health && break
sleep 1
done
# Setup LocalStack resources for AWS provider tests
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
export AWS_DEFAULT_REGION=us-east-1
# Create KMS key for tests
LOCALSTACK_KMS_KEY_ID=$(aws --endpoint-url http://localhost:4566 kms create-key \
--region us-east-1 --query 'KeyMetadata.KeyId' --output text)
aws --endpoint-url http://localhost:4566 kms create-alias \
--alias-name alias/fnox-testing \
--target-key-id "$LOCALSTACK_KMS_KEY_ID" \
--region us-east-1
# Create shared test secret in Secrets Manager
aws --endpoint-url http://localhost:4566 secretsmanager create-secret \
--name "fnox/test-secret" \
--secret-string "This is a test secret in AWS Secrets Manager!" \
--region us-east-1
{
echo "VAULT_ADDR=http://localhost:8200"
echo "VAULT_TOKEN=fnox-test-token"
echo "LOCALSTACK_ENDPOINT=http://localhost:4566"
} >> "$GITHUB_ENV"
- name: Setup Bitwarden for tests
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
source ./test/setup-bitwarden-ci.sh
echo "BW_SESSION=$BW_SESSION" >> "$GITHUB_ENV"
- name: Install Infisical CLI
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | sudo -E bash
sudo apt-get update && sudo apt-get install -y infisical
- name: Setup Infisical for tests
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
source ./test/setup-infisical-ci.sh
{
echo "INFISICAL_TOKEN=$INFISICAL_TOKEN"
echo "INFISICAL_CLIENT_ID=$INFISICAL_CLIENT_ID"
echo "INFISICAL_CLIENT_SECRET=$INFISICAL_CLIENT_SECRET"
echo "INFISICAL_PROJECT_ID=$INFISICAL_PROJECT_ID"
echo "INFISICAL_API_URL=http://localhost:8081/api"
} >> "$GITHUB_ENV"
- name: Setup services (macOS)
if: ${{ matrix.os == 'macos-latest' }}
run: |
# Start Vault in dev mode in background
vault server -dev -dev-root-token-id=fnox-test-token -dev-listen-address=127.0.0.1:8200 &
{
echo "VAULT_ADDR=http://127.0.0.1:8200"
echo "VAULT_TOKEN=fnox-test-token"
} >> "$GITHUB_ENV"
# Wait for Vault to be ready
sleep 2
- name: Run bats tests - tranche ${{ matrix.tranche }}
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 env:
TRANCHE: ${{ matrix.tranche }}
TRANCHE_COUNT: 2
KEEPASS_PASSWORD: fnox-test-password
BATS_FILTER_TAGS: ${{ (github.event_name != 'pull_request' || !startsWith(github.head_ref, 'release-plz')) && '!expensive' || '' }}
with:
timeout_minutes: 10
max_attempts: 3
command: mise run test:bats
ci-other:
needs: build
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os:
- macos-latest
- ubuntu-latest
runs-on: ${{ matrix.os }}
timeout-minutes: 20
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with:
submodules: recursive
persist-credentials: false
- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 with:
cache: false
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 with:
shared-key: debug-${{ matrix.os }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install system dependencies (Ubuntu)
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
sudo apt-get update
sudo apt-get install -y gnome-keyring libsecret-tools libudev-dev
- name: Setup gnome-keyring (Ubuntu)
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
# Start D-Bus session daemon
mkdir -p ~/.dbus-session
dbus-daemon --session --fork --print-address=1 > ~/.dbus-session/bus-address
DBUS_SESSION_BUS_ADDRESS=$(cat ~/.dbus-session/bus-address)
export DBUS_SESSION_BUS_ADDRESS
echo "DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS" >> "$GITHUB_ENV"
# Start gnome-keyring daemon for keychain tests
echo "foobar" | gnome-keyring-daemon --unlock --components=secrets --daemonize
- name: Install Rust components
run: rustup component add rustfmt clippy
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
name: fnox-${{ matrix.os }}
path: target/debug
- run: chmod +x target/debug/fnox
- name: Setup age key for fnox
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
run: mkdir -p ~/.config/fnox && echo "${{ secrets.AGE_SECRET }}" > ~/.config/fnox/age.txt
- name: Redact secrets in CI output
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
run: |
fnox ci-redact
# shellcheck disable=SC2016
fnox run -- sh -c 'echo MY_UNIMPORTANT_SECRET: $MY_UNIMPORTANT_SECRET'
- name: Run tests
run: cargo test --workspace
- name: mise run render
run: mise run render
- name: assert render produces no diff
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "::error::'mise run render' produced changes. Run it locally and commit."
git status
git diff HEAD
exit 1
fi
- name: Check formatting
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 with:
timeout_minutes: 5
max_attempts: 3
command: mise run lint
- name: Run clippy
run: cargo clippy --workspace --all-targets -- -D warnings
msrv:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with:
submodules: recursive
persist-credentials: false
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 with:
shared-key: msrv
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 with:
cache: false
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libudev-dev
- run: cargo msrv verify
final:
permissions: {}
needs:
- ci-bats
- ci-other
- msrv
runs-on: ubuntu-latest
timeout-minutes: 1
if: ${{ !cancelled() }}
steps:
- name: Check job results
if: |
needs.ci-bats.result != 'success' ||
needs.ci-other.result != 'success' ||
needs.msrv.result != 'success'
run: exit 1
- run: echo "All CI jobs completed successfully"