Compare commits

..

1 commit

Author SHA1 Message Date
Conrad Kramer
083ec73613 Switch to gRPC client in Swift app 2024-09-08 16:51:17 -07:00
670 changed files with 2614 additions and 60875 deletions

View file

@ -1,10 +0,0 @@
.build
.derived-data
.git
.nscloud-cache
Apple/build
ci-artifacts
dist
publish
release
target

View file

@ -1 +0,0 @@
9.1.0

View file

@ -1,3 +1,6 @@
[target.'cfg(unix)']
runner = "sudo -E"
[alias] # command aliases
rr = "run --release"
bb = "build --release"

View file

@ -1,327 +0,0 @@
name: Decrypt Release Secrets
description: Decrypts age-sealed Burrow release secrets and exports CI paths/env.
inputs:
root:
description: Repository root
required: false
default: .
runs:
using: composite
steps:
- shell: bash
run: |
set -euo pipefail
ROOT_INPUT='${{ inputs.root }}'
ROOT="$(cd "$ROOT_INPUT" && pwd)"
resolve_nix_bin() {
local candidate
for candidate in \
"${NIX_BIN:-}" \
"$(command -v nix 2>/dev/null || true)" \
/nix/var/nix/profiles/default/bin/nix \
"${HOME:-}/.nix-profile/bin/nix" \
"${HOME:-}/.local/state/nix/profile/bin/nix" \
/Users/runner/.nix-profile/bin/nix \
/Users/runner/.local/state/nix/profile/bin/nix \
/home/runner/.nix-profile/bin/nix \
/home/runner/.local/state/nix/profile/bin/nix \
; do
if [[ -n "$candidate" && -x "$candidate" ]]; then
printf '%s\n' "$candidate"
return 0
fi
done
return 1
}
resolve_decrypt_tool() {
local candidate
for candidate in \
"${AGE_BIN:-}" \
"$(command -v age 2>/dev/null || true)" \
/opt/homebrew/bin/age \
/usr/local/bin/age \
/usr/bin/age \
/run/current-system/sw/bin/age \
/etc/profiles/per-user/runner/bin/age \
"${HOME:-}/.nix-profile/bin/age" \
"${HOME:-}/.local/state/nix/profile/bin/age" \
/Users/runner/.nix-profile/bin/age \
/Users/runner/.local/state/nix/profile/bin/age \
/home/runner/.nix-profile/bin/age \
/home/runner/.local/state/nix/profile/bin/age \
; do
if [[ -n "$candidate" && -x "$candidate" ]]; then
printf 'age:%s\n' "$candidate"
return 0
fi
done
for candidate in \
"${AGENIX_BIN:-}" \
"$(command -v agenix 2>/dev/null || true)" \
; do
if [[ -n "$candidate" && -x "$candidate" ]]; then
printf 'agenix:%s\n' "$candidate"
return 0
fi
done
local nix_bin
nix_bin="$(resolve_nix_bin || true)"
if [[ -n "$nix_bin" ]]; then
local built
built="$("$nix_bin" --extra-experimental-features 'nix-command flakes' build --no-link "$ROOT#age" --print-out-paths 2>/dev/null | tail -n 1 || true)"
if [[ -n "$built" && -x "$built/bin/age" ]]; then
printf 'age:%s\n' "$built/bin/age"
return 0
fi
built="$("$nix_bin" --extra-experimental-features 'nix-command flakes' build --no-link "$ROOT#agenix" --print-out-paths 2>/dev/null | tail -n 1 || true)"
if [[ -n "$built" && -x "$built/bin/agenix" ]]; then
printf 'agenix:%s\n' "$built/bin/agenix"
return 0
fi
fi
echo "::error ::age or agenix is required to decrypt release secrets but neither is available." >&2
exit 1
}
decrypt_tool="$(resolve_decrypt_tool)"
decrypt_kind="${decrypt_tool%%:*}"
decrypt_bin="${decrypt_tool#*:}"
KEY="${AGE_IDENTITY_PATH:-}"
if [[ -z "$KEY" || ! -s "$KEY" ]]; then
echo "::error ::Missing age identity. Run Scripts/resolve-age-identity.sh before this action." >&2
exit 1
fi
decrypt_age_file() {
local file_path="$1"
if [[ "$decrypt_kind" == "agenix" ]]; then
local agenix_path="$file_path"
if [[ "$file_path" == "$ROOT/"* ]]; then
agenix_path="${file_path#"$ROOT"/}"
fi
(cd "$ROOT" && "$decrypt_bin" --identity "$KEY" --decrypt "$agenix_path")
else
"$decrypt_bin" --decrypt -i "$KEY" "$file_path"
fi
}
write_env() {
local name="$1"
local value="$2"
echo "${name}=${value}" >> "$GITHUB_ENV"
}
mask_multiline() {
local value="$1"
echo "::add-mask::${value}"
while IFS= read -r line; do
[[ -n "$line" ]] || continue
echo "::add-mask::${line}"
done <<<"$value"
}
decrypt_env_optional() {
local name="$1"
local file="$2"
if [[ ! -f "$file" ]]; then
echo "::notice ::Skipping optional release secret ${file}"
return 0
fi
local value
value="$(decrypt_age_file "$file")"
mask_multiline "$value"
{
echo "${name}<<EOF"
printf '%s\n' "$value"
echo "EOF"
} >> "$GITHUB_ENV"
}
decrypt_file_optional() {
local name="$1"
local file="$2"
local suffix="$3"
if [[ ! -f "$file" ]]; then
echo "::notice ::Skipping optional release secret ${file}"
return 0
fi
local temp_dir="${RUNNER_TEMP:-/tmp}"
local temp_file
temp_file="$(mktemp "${temp_dir}/${name}.XXXXXX")"
if [[ -n "$suffix" ]]; then
local with_suffix="${temp_file}.${suffix}"
mv "$temp_file" "$with_suffix"
temp_file="$with_suffix"
fi
decrypt_age_file "$file" > "$temp_file"
chmod 600 "$temp_file"
write_env "$name" "$temp_file"
}
decrypt_dotenv_optional() {
local file="$1"
if [[ ! -f "$file" ]]; then
echo "::notice ::Skipping optional release secret ${file}"
return 0
fi
local temp_file
temp_file="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-release-dotenv.XXXXXX")"
decrypt_age_file "$file" > "$temp_file"
# shellcheck disable=SC1090
set -a
source "$temp_file"
set +a
rm -f "$temp_file"
if [[ -z "${MINIO_ROOT_USER:-}" || -z "${MINIO_ROOT_PASSWORD:-}" ]]; then
echo "::error ::${file} must define MINIO_ROOT_USER and MINIO_ROOT_PASSWORD." >&2
exit 1
fi
echo "::add-mask::${MINIO_ROOT_USER}"
echo "::add-mask::${MINIO_ROOT_PASSWORD}"
{
echo "AWS_ACCESS_KEY_ID=${MINIO_ROOT_USER}"
echo "AWS_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}"
echo "AWS_DEFAULT_REGION=${BLOB_REGION:-hel1}"
echo "AWS_REGION=${BLOB_REGION:-hel1}"
echo "AWS_S3_ENDPOINT_URL=https://${BLOB_ENDPOINT:-hel1.your-objectstorage.com}"
} >> "$GITHUB_ENV"
}
decode_base64_stream() {
if command -v base64 >/dev/null 2>&1; then
base64 -d 2>/dev/null || base64 -D
elif command -v openssl >/dev/null 2>&1; then
openssl base64 -d -A
else
echo "::error ::base64 or openssl is required to decode release secrets." >&2
return 127
fi
}
decode_base64_to_file() {
local output="$1"
decode_base64_stream > "$output"
}
decrypt_appstore_connect() {
local file="$ROOT/secrets/apple/appstore-connect.env.age"
if [[ ! -f "$file" ]]; then
echo "::notice ::Skipping optional release secret ${file}"
return 0
fi
local temp_file key_path key_value
temp_file="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-asc-env.XXXXXX")"
decrypt_age_file "$file" > "$temp_file"
# shellcheck disable=SC1090
set -a
source "$temp_file"
set +a
rm -f "$temp_file"
if [[ -z "${ASC_API_KEY_ID:-}" || -z "${ASC_API_ISSUER_ID:-}" ]]; then
echo "::error ::${file} must define ASC_API_KEY_ID and ASC_API_ISSUER_ID." >&2
exit 1
fi
key_path="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-asc-key.XXXXXX.p8")"
if [[ -n "${ASC_API_KEY_BASE64:-}" ]]; then
printf '%s' "$ASC_API_KEY_BASE64" | decode_base64_to_file "$key_path"
elif [[ -n "${ASC_API_KEY_P8_BASE64:-}" ]]; then
printf '%s' "$ASC_API_KEY_P8_BASE64" | decode_base64_to_file "$key_path"
elif [[ -n "${APPSTORE_KEY:-}" ]]; then
printf '%s\n' "$APPSTORE_KEY" > "$key_path"
elif [[ -n "${ASC_API_KEY:-}" ]]; then
printf '%s\n' "$ASC_API_KEY" > "$key_path"
else
echo "::error ::${file} must define ASC_API_KEY_BASE64 or ASC_API_KEY." >&2
exit 1
fi
chmod 600 "$key_path"
key_value="$(cat "$key_path")"
mask_multiline "$key_value"
echo "::add-mask::${ASC_API_KEY_ID}"
echo "::add-mask::${ASC_API_ISSUER_ID}"
write_env ASC_API_KEY_ID "$ASC_API_KEY_ID"
write_env ASC_API_ISSUER_ID "$ASC_API_ISSUER_ID"
write_env ASC_API_KEY_PATH "$key_path"
}
decrypt_distribution_signing() {
local file="$ROOT/secrets/apple/distribution-signing.env.age"
if [[ ! -f "$file" ]]; then
echo "::notice ::Skipping optional release secret ${file}"
return 0
fi
local temp_file cert_path cert_password cert_base64
temp_file="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-signing-env.XXXXXX")"
decrypt_age_file "$file" > "$temp_file"
# shellcheck disable=SC1090
set -a
source "$temp_file"
set +a
rm -f "$temp_file"
cert_base64="${APPLE_SIGNING_CERTIFICATE_BASE64:-${APPLE_DISTRIBUTION_CERTIFICATE_BASE64:-}}"
cert_password="${APPLE_SIGNING_CERTIFICATE_PASSWORD:-${APPLE_DISTRIBUTION_CERTIFICATE_PASSWORD:-}}"
if [[ -z "$cert_password" && -n "${APPLE_SIGNING_CERTIFICATE_PASSWORD_BASE64:-}" ]]; then
cert_password="$(printf '%s' "$APPLE_SIGNING_CERTIFICATE_PASSWORD_BASE64" | decode_base64_stream)"
fi
if [[ -z "$cert_base64" || -z "$cert_password" ]]; then
echo "::error ::${file} must define APPLE_SIGNING_CERTIFICATE_BASE64 and APPLE_SIGNING_CERTIFICATE_PASSWORD." >&2
exit 1
fi
cert_path="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-apple-signing.XXXXXX.p12")"
printf '%s' "$cert_base64" | decode_base64_to_file "$cert_path"
chmod 600 "$cert_path"
echo "::add-mask::${cert_password}"
write_env APPLE_SIGNING_CERTIFICATE_PATH "$cert_path"
write_env APPLE_SIGNING_CERTIFICATE_PASSWORD "$cert_password"
write_env APPLE_KEYCHAIN_PASSWORD "$cert_password"
}
decrypt_sparkle() {
local file="$ROOT/secrets/apple/sparkle.env.age"
if [[ ! -f "$file" ]]; then
echo "::notice ::Skipping optional release secret ${file}"
return 0
fi
local temp_file key_path key_base64 key_value
temp_file="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-sparkle-env.XXXXXX")"
decrypt_age_file "$file" > "$temp_file"
# shellcheck disable=SC1090
set -a
source "$temp_file"
set +a
rm -f "$temp_file"
key_base64="${SPARKLE_EDDSA_KEY_BASE64:-${SPARKLE_PRIVATE_KEY_BASE64:-}}"
key_path="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-sparkle-eddsa.XXXXXX.key")"
if [[ -n "$key_base64" ]]; then
printf '%s' "$key_base64" | decode_base64_to_file "$key_path"
elif [[ -n "${SPARKLE_EDDSA_KEY:-}" ]]; then
printf '%s\n' "$SPARKLE_EDDSA_KEY" > "$key_path"
elif [[ -n "${SPARKLE_PRIVATE_KEY:-}" ]]; then
printf '%s\n' "$SPARKLE_PRIVATE_KEY" > "$key_path"
else
echo "::error ::${file} must define SPARKLE_EDDSA_KEY_BASE64 or SPARKLE_EDDSA_KEY." >&2
exit 1
fi
chmod 600 "$key_path"
key_value="$(cat "$key_path")"
mask_multiline "$key_value"
write_env SPARKLE_EDDSA_KEY_PATH "$key_path"
}
decrypt_appstore_connect
decrypt_distribution_signing
decrypt_sparkle
decrypt_dotenv_optional "$ROOT/secrets/hetzner/object-storage.age"

View file

@ -1,98 +0,0 @@
name: "Apple: Developer ID KMS CSR"
run-name: "Apple: Developer ID KMS CSR (${{ github.event.inputs.kms_key || 'apple-developer-id-application' }})"
on:
workflow_dispatch:
inputs:
kms_key:
description: Google KMS key name in the Burrow identity key ring
required: false
default: apple-developer-id-application
kms_version:
description: Google KMS key version
required: false
default: "1"
common_name:
description: CSR common name
required: false
default: Burrow Developer ID Application
email_address:
description: CSR email address
required: false
default: release@burrow.net
permissions:
contents: read
concurrency:
group: apple-developer-id-kms-csr-${{ github.ref }}
cancel-in-progress: false
env:
BURROW_GOOGLE_PROJECT_ID: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
GOOGLE_CLOUD_PROJECT: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
BURROW_GOOGLE_PROJECT_NUMBER: ${{ vars.GOOGLE_PROJECT_NUMBER || '416198671487' }}
BURROW_GOOGLE_WIF_POOL_ID: ${{ vars.BURROW_GOOGLE_WIF_POOL_ID || 'burrow-authentik' }}
BURROW_GOOGLE_WIF_PROVIDER_ID: ${{ vars.BURROW_GOOGLE_WIF_PROVIDER_ID || 'forgejo-runners' }}
BURROW_GOOGLE_WIF_SERVICE_ACCOUNT: ${{ vars.BURROW_GOOGLE_WIF_SERVICE_ACCOUNT || 'burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com' }}
BURROW_AUTHENTIK_WIF_ISSUER: ${{ vars.BURROW_AUTHENTIK_WIF_ISSUER || 'https://auth.burrow.net/application/o/google-cloud/' }}
BURROW_AUTHENTIK_WIF_AUDIENCE: ${{ vars.BURROW_AUTHENTIK_WIF_AUDIENCE || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_ID: ${{ vars.BURROW_AUTHENTIK_WIF_CLIENT_ID || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_SECRET: ${{ secrets.BURROW_AUTHENTIK_WIF_CLIENT_SECRET }}
BURROW_KMS_LOCATION: global
BURROW_KMS_KEYRING: burrow-identity
jobs:
csr:
name: Generate CSR
runs-on: [self-hosted, linux, x86_64, burrow-forge]
timeout-minutes: 20
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Authenticate Google WIF
shell: bash
run: nix develop .#ci -c Scripts/ci/google-wif-auth.sh
- name: Generate KMS-backed CSR
shell: bash
env:
BURROW_APPLE_DEVELOPER_ID_KMS_KEY: ${{ github.event.inputs.kms_key || 'apple-developer-id-application' }}
BURROW_KMS_VERSION: ${{ github.event.inputs.kms_version || '1' }}
BURROW_APPLE_DEVELOPER_ID_CSR_COMMON_NAME: ${{ github.event.inputs.common_name || 'Burrow Developer ID Application' }}
BURROW_APPLE_DEVELOPER_ID_CSR_EMAIL: ${{ github.event.inputs.email_address || 'release@burrow.net' }}
run: |
set -euo pipefail
mkdir -p dist/apple-developer-id
nix develop .#ci -c Scripts/apple/google-kms-csr.py \
--output dist/apple-developer-id/developer-id-application.certSigningRequest \
--public-key-output dist/apple-developer-id/google-kms-public-key.pem
if command -v openssl >/dev/null 2>&1; then
openssl req -in dist/apple-developer-id/developer-id-application.certSigningRequest -noout -verify
fi
cat > dist/apple-developer-id/README.txt <<'EOF'
This CSR is backed by a non-exportable Google Cloud KMS key in the Burrow
identity key ring. Apple currently documents Developer ID certificate creation
through the Developer website or Xcode, not App Store Connect API creation.
Upload developer-id-application.certSigningRequest in:
Certificates, Identifiers & Profiles -> Certificates -> + -> Developer ID ->
Developer ID Application.
Keep the returned .cer file with release artifacts. The private key remains in
Google Cloud KMS and is never exported as a p12.
EOF
- name: Upload CSR Artifact
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: burrow-developer-id-kms-csr
path: dist/apple-developer-id

View file

@ -1,530 +0,0 @@
name: "Apple: Distribute Testers"
run-name: "Apple: Distribute Testers (${{ github.ref_name }})"
on:
workflow_dispatch:
inputs:
build_number_override:
description: Optional explicit build number
required: false
default: ""
upload_app_store:
description: Upload iOS artifact to App Store Connect and TestFlight
required: false
default: "true"
upload_sparkle:
description: Publish Sparkle appcast and macOS artifact
required: false
default: "true"
sparkle_channel:
description: Sparkle channel, defaults to build-<number>
required: false
default: ""
sparkle_point_main:
description: Update Sparkle main channel pointers
required: false
default: "true"
testflight_distribute_external:
description: Distribute to external TestFlight groups
required: false
default: "false"
testflight_groups:
description: Optional comma-separated TestFlight groups
required: false
default: ""
ios_uses_non_exempt_encryption:
description: Set true only when the iOS build uses non-exempt encryption
required: false
default: "false"
permissions:
actions: write
contents: read
concurrency:
group: burrow-apple-distribute-testers-${{ github.ref }}
cancel-in-progress: true
env:
BURROW_RELEASE_GCS_BUCKET: ${{ vars.BURROW_RELEASE_GCS_BUCKET || 'burrow-net-releases' }}
BURROW_RELEASE_GCS_BACKUP: ${{ vars.BURROW_RELEASE_GCS_BACKUP || 'true' }}
BURROW_RELEASE_GARAGE_BUCKET: ${{ vars.BURROW_RELEASE_GARAGE_BUCKET || 'burrow-releases' }}
BURROW_RELEASE_REQUIRE_GARAGE: ${{ vars.BURROW_RELEASE_REQUIRE_GARAGE || 'true' }}
BURROW_GARAGE_ENDPOINT: ${{ vars.BURROW_GARAGE_ENDPOINT || 'https://objects.burrow.net' }}
BURROW_GARAGE_REGION: ${{ vars.BURROW_GARAGE_REGION || 'garage' }}
AWS_ACCESS_KEY_ID: ${{ secrets.BURROW_GARAGE_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.BURROW_GARAGE_SECRET_ACCESS_KEY }}
BURROW_GOOGLE_PROJECT_ID: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
GOOGLE_CLOUD_PROJECT: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
BURROW_GOOGLE_PROJECT_NUMBER: ${{ vars.GOOGLE_PROJECT_NUMBER || '416198671487' }}
BURROW_GOOGLE_WIF_POOL_ID: ${{ vars.BURROW_GOOGLE_WIF_POOL_ID || 'burrow-authentik' }}
BURROW_GOOGLE_WIF_PROVIDER_ID: ${{ vars.BURROW_GOOGLE_WIF_PROVIDER_ID || 'forgejo-runners' }}
BURROW_GOOGLE_WIF_SERVICE_ACCOUNT: ${{ vars.BURROW_GOOGLE_WIF_SERVICE_ACCOUNT || 'burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com' }}
BURROW_AUTHENTIK_WIF_ISSUER: ${{ vars.BURROW_AUTHENTIK_WIF_ISSUER || 'https://auth.burrow.net/application/o/google-cloud/' }}
BURROW_AUTHENTIK_WIF_AUDIENCE: ${{ vars.BURROW_AUTHENTIK_WIF_AUDIENCE || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_ID: ${{ vars.BURROW_AUTHENTIK_WIF_CLIENT_ID || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_SECRET: ${{ secrets.BURROW_AUTHENTIK_WIF_CLIENT_SECRET }}
BURROW_NIX_CACHE_URL: ${{ vars.BURROW_NIX_CACHE_URL || 'https://nix.burrow.net/burrow' }}
BURROW_NIX_CACHE_PUBLIC_KEY: ${{ vars.BURROW_NIX_CACHE_PUBLIC_KEY }}
BURROW_APPLE_BUNDLE_ID: ${{ vars.BURROW_APPLE_BUNDLE_ID || 'net.burrow.app' }}
BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID: ${{ vars.BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID || 'net.burrow.app.network' }}
BURROW_IOS_DISTRIBUTION_CERTIFICATE_ID: ${{ vars.BURROW_IOS_DISTRIBUTION_CERTIFICATE_ID || '3G42677598' }}
BURROW_DEVELOPER_ID_CERTIFICATE_ID: ${{ vars.BURROW_DEVELOPER_ID_CERTIFICATE_ID || '9JKN6HXBHC' }}
BURROW_APPLE_SIGNING_MODE: google-kms-staged
BURROW_NOTARIZE_MACOS: ${{ vars.BURROW_NOTARIZE_MACOS || 'true' }}
BURROW_SPARKLE_SIGN_WITH_KMS: ${{ vars.BURROW_SPARKLE_SIGN_WITH_KMS || 'true' }}
BURROW_SPARKLE_KMS_KEY: ${{ vars.BURROW_SPARKLE_KMS_KEY || 'sparkle-ed25519' }}
BURROW_SPARKLE_KMS_VERSION: ${{ vars.BURROW_SPARKLE_KMS_VERSION || '1' }}
BURROW_SPARKLE_FEED_URL: ${{ vars.BURROW_SPARKLE_FEED_URL || 'https://releases.burrow.net/sparkle/appcast.xml' }}
BURROW_SPARKLE_PUBLIC_ED_KEY: ${{ vars.BURROW_SPARKLE_PUBLIC_ED_KEY || 'uugZuJeqvvKd91NZ6F1Fv2cQenUbIG/ZW3L9MuaEz30=' }}
BURROW_KMS_LOCATION: ${{ vars.BURROW_KMS_LOCATION || 'global' }}
BURROW_KMS_KEYRING: ${{ vars.BURROW_KMS_KEYRING || 'burrow-identity' }}
TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS: ${{ vars.TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS || '7200' }}
BURROW_VERSION_REQUIRE_REMOTE: "1"
jobs:
prepare:
name: Prepare Apple Distribution
runs-on: namespace-profile-linux-medium-apple-v2
outputs:
build_number: ${{ steps.plan.outputs.build_number }}
sparkle_channel: ${{ steps.plan.outputs.sparkle_channel }}
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Fetch Build Tags
shell: bash
run: git fetch --force --prune origin "+refs/tags/builds/*:refs/tags/builds/*"
- name: Compute Apple Distribution Plan
id: plan
shell: bash
env:
BUILD_NUMBER_OVERRIDE: ${{ github.event.inputs.build_number_override || '' }}
SPARKLE_CHANNEL_INPUT: ${{ github.event.inputs.sparkle_channel || '' }}
run: |
set -euo pipefail
if [[ -n "$BUILD_NUMBER_OVERRIDE" ]]; then
build_number="$BUILD_NUMBER_OVERRIDE"
else
build_number="$(Scripts/version.sh pre-increment)"
fi
channel="$SPARKLE_CHANNEL_INPUT"
if [[ -z "$channel" ]]; then
channel="build-${build_number}"
fi
echo "build_number=${build_number}" >> "$GITHUB_OUTPUT"
echo "sparkle_channel=${channel}" >> "$GITHUB_OUTPUT"
printf 'build_number=%s\nsparkle_channel=%s\n' "$build_number" "$channel"
build-ios:
name: Build Apple iOS
needs: prepare
runs-on: namespace-profile-macos-large-apple-v2
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
submodules: recursive
- name: Select Apple SDK
shell: bash
run: |
set -euo pipefail
xcrun --find swiftc
xcrun --sdk iphoneos --show-sdk-path
xcrun --sdk iphonesimulator --show-sdk-path
- name: Prepare Rust Apple Targets
shell: bash
run: |
set -euo pipefail
if ! command -v rustup >/dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable
export PATH="$HOME/.cargo/bin:$PATH"
fi
rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
- name: Bootstrap Runner Environment
shell: bash
run: |
set -euo pipefail
runner_home="${RUNNER_HOME:-}"
if [[ -z "$runner_home" ]]; then
runner_home="$(python3 -c 'import os,pwd; print(pwd.getpwuid(os.getuid()).pw_dir)' 2>/dev/null || true)"
if [[ -z "$runner_home" ]]; then
runner_home="$PWD/.home"
fi
fi
mkdir -p "$runner_home" "$PWD/.tmp"
{
echo "HOME=$runner_home"
echo "XDG_CACHE_HOME=$runner_home/.cache"
echo "XDG_CONFIG_HOME=$runner_home/.config"
echo "XDG_DATA_HOME=$runner_home/.local/share"
echo "TMPDIR=$PWD/.tmp"
echo "TMP=$PWD/.tmp"
} >> "$GITHUB_ENV"
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Namespace Cache
shell: bash
run: |
set -euo pipefail
bash Scripts/ci/nscloud-cache.sh bazel
- name: Build Staged iOS Artifact
shell: bash
env:
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
run: |
set -euo pipefail
bazel_args=()
if [[ -n "${BURROW_BAZEL_NSCCACHE_BAZELRC:-}" && -f "${BURROW_BAZEL_NSCCACHE_BAZELRC:-}" ]]; then
bazel_args+=("--bazelrc=${BURROW_BAZEL_NSCCACHE_BAZELRC}")
fi
NIX_BIN="$(bash Scripts/ci/resolve-nix-bin.sh)"
ci_path="$("$NIX_BIN" develop .#ci --command bash -lc 'printf "%s" "$PATH"')"
ci_protoc="$("$NIX_BIN" develop .#ci --command bash -lc 'command -v protoc')"
"$NIX_BIN" develop .#ci --command bazel "${bazel_args[@]}" build \
--verbose_failures \
--show_timestamps \
--experimental_ui_max_stdouterr_bytes=16777216 \
--action_env=PATH="$ci_path" \
--action_env=PROTOC="$ci_protoc" \
--action_env=BUILD_WORKSPACE_DIRECTORY="$PWD" \
--action_env=BUILD_NUMBER="$BUILD_NUMBER" \
--action_env=BURROW_RELEASE_OUT="$PWD/dist/builds/$BUILD_NUMBER" \
--action_env=BURROW_APPLE_SIGNING_MODE="$BURROW_APPLE_SIGNING_MODE" \
--action_env=BURROW_APPLE_REQUIRE_SIGNING=true \
--action_env=BURROW_APPLE_BUNDLE_ID="$BURROW_APPLE_BUNDLE_ID" \
--action_env=BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID="$BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID" \
--action_env=HOME="$HOME" \
--action_env=XDG_CACHE_HOME="$XDG_CACHE_HOME" \
//bazel/apple:release_ios_stamp
- name: Upload Staged iOS Artifact
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: burrow-apple-ios-${{ needs.prepare.outputs.build_number }}
path: |
dist/builds/${{ needs.prepare.outputs.build_number }}/apple/Burrow-iOS-${{ needs.prepare.outputs.build_number }}.unsigned.ipa
dist/builds/${{ needs.prepare.outputs.build_number }}/apple/Burrow-iOS-${{ needs.prepare.outputs.build_number }}.unsigned.ipa.sha256
if-no-files-found: error
build-macos:
name: Build Apple macOS
needs: prepare
runs-on: namespace-profile-macos-large-apple-v2
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
submodules: recursive
- name: Select Apple SDK
shell: bash
run: |
set -euo pipefail
xcrun --find swiftc
xcrun --sdk macosx --show-sdk-path
- name: Prepare Rust Apple Targets
shell: bash
run: |
set -euo pipefail
if ! command -v rustup >/dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable
export PATH="$HOME/.cargo/bin:$PATH"
fi
rustup target add aarch64-apple-darwin x86_64-apple-darwin
- name: Bootstrap Runner Environment
shell: bash
run: |
set -euo pipefail
runner_home="${RUNNER_HOME:-}"
if [[ -z "$runner_home" ]]; then
runner_home="$(python3 -c 'import os,pwd; print(pwd.getpwuid(os.getuid()).pw_dir)' 2>/dev/null || true)"
if [[ -z "$runner_home" ]]; then
runner_home="$PWD/.home"
fi
fi
mkdir -p "$runner_home" "$PWD/.tmp"
{
echo "HOME=$runner_home"
echo "XDG_CACHE_HOME=$runner_home/.cache"
echo "XDG_CONFIG_HOME=$runner_home/.config"
echo "XDG_DATA_HOME=$runner_home/.local/share"
echo "TMPDIR=$PWD/.tmp"
echo "TMP=$PWD/.tmp"
} >> "$GITHUB_ENV"
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Namespace Cache
shell: bash
run: |
set -euo pipefail
bash Scripts/ci/nscloud-cache.sh bazel
- name: Build Staged macOS Artifact
shell: bash
env:
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
run: |
set -euo pipefail
bazel_args=()
if [[ -n "${BURROW_BAZEL_NSCCACHE_BAZELRC:-}" && -f "${BURROW_BAZEL_NSCCACHE_BAZELRC:-}" ]]; then
bazel_args+=("--bazelrc=${BURROW_BAZEL_NSCCACHE_BAZELRC}")
fi
NIX_BIN="$(bash Scripts/ci/resolve-nix-bin.sh)"
ci_path="$("$NIX_BIN" develop .#ci --command bash -lc 'printf "%s" "$PATH"')"
ci_protoc="$("$NIX_BIN" develop .#ci --command bash -lc 'command -v protoc')"
"$NIX_BIN" develop .#ci --command bazel "${bazel_args[@]}" build \
--verbose_failures \
--show_timestamps \
--experimental_ui_max_stdouterr_bytes=16777216 \
--action_env=PATH="$ci_path" \
--action_env=PROTOC="$ci_protoc" \
--action_env=BUILD_WORKSPACE_DIRECTORY="$PWD" \
--action_env=BUILD_NUMBER="$BUILD_NUMBER" \
--action_env=BURROW_RELEASE_OUT="$PWD/dist/builds/$BUILD_NUMBER" \
--action_env=BURROW_APPLE_SIGNING_MODE="$BURROW_APPLE_SIGNING_MODE" \
--action_env=BURROW_APPLE_REQUIRE_SIGNING=true \
--action_env=BURROW_APPLE_BUNDLE_ID="$BURROW_APPLE_BUNDLE_ID" \
--action_env=BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID="$BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID" \
--action_env=BURROW_SPARKLE_FEED_URL="$BURROW_SPARKLE_FEED_URL" \
--action_env=BURROW_SPARKLE_PUBLIC_ED_KEY="$BURROW_SPARKLE_PUBLIC_ED_KEY" \
--action_env=HOME="$HOME" \
--action_env=XDG_CACHE_HOME="$XDG_CACHE_HOME" \
//bazel/apple:release_macos_stamp
- name: Upload Staged macOS Artifact
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: burrow-apple-macos-${{ needs.prepare.outputs.build_number }}
path: |
dist/builds/${{ needs.prepare.outputs.build_number }}/apple/Burrow-macOS-${{ needs.prepare.outputs.build_number }}.unsigned.zip
dist/builds/${{ needs.prepare.outputs.build_number }}/apple/Burrow-macOS-${{ needs.prepare.outputs.build_number }}.unsigned.zip.sha256
if-no-files-found: error
sign-apple:
name: Sign Apple Artifacts
needs:
- prepare
- build-ios
- build-macos
runs-on: namespace-profile-linux-medium-apple-v2
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Configure Age
shell: bash
env:
AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"
- name: Decrypt Release Secrets
uses: ./.forgejo/actions/decrypt-release-secrets
- name: Validate App Store Credentials
shell: bash
run: Scripts/ci/check-release-config.sh app-store
- name: Authenticate Google WIF
shell: bash
run: nix develop .#ci -c Scripts/ci/google-wif-auth.sh
- name: Download Staged Apple Artifacts
uses: https://code.forgejo.org/actions/download-artifact@v3
with:
path: dist/downloaded
- name: Prepare Staged Apple Directory
shell: bash
env:
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
run: |
set -euo pipefail
mkdir -p "dist/builds/${BUILD_NUMBER}/apple"
find dist/downloaded -type f -name 'Burrow-*.unsigned.*' -exec cp -v {} "dist/builds/${BUILD_NUMBER}/apple/" \;
find "dist/builds/${BUILD_NUMBER}/apple" -type f -print | sort
- name: Sync Apple Provisioning Profiles
shell: bash
run: nix develop .#ci -c Scripts/ci/sync-apple-provisioning-profiles.sh all
- name: Sign Apple Artifacts With KMS
shell: bash
env:
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
SPARKLE_CHANNEL: ${{ needs.prepare.outputs.sparkle_channel }}
run: nix develop .#ci -c Scripts/apple/sign-release-artifacts-kms.sh all
- name: Upload Signed Apple Artifacts To Release Storage
shell: bash
env:
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
run: nix develop .#ci -c Scripts/ci/upload-release-storage.sh
- name: Upload Signed Apple Artifacts
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: burrow-apple-signed-${{ needs.prepare.outputs.build_number }}
path: |
dist/builds/${{ needs.prepare.outputs.build_number }}/apple/Burrow-iOS-${{ needs.prepare.outputs.build_number }}.ipa
dist/builds/${{ needs.prepare.outputs.build_number }}/apple/Burrow-iOS-${{ needs.prepare.outputs.build_number }}.ipa.sha256
dist/builds/${{ needs.prepare.outputs.build_number }}/apple/Burrow-macOS-${{ needs.prepare.outputs.build_number }}.zip
dist/builds/${{ needs.prepare.outputs.build_number }}/apple/Burrow-macOS-${{ needs.prepare.outputs.build_number }}.zip.sha256
dist/builds/${{ needs.prepare.outputs.build_number }}/sparkle/**
if-no-files-found: error
upload-testflight:
name: Upload TestFlight
needs:
- prepare
- sign-apple
if: ${{ github.event.inputs.upload_app_store == 'true' }}
runs-on: namespace-profile-macos-large-testflight
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Configure Age
shell: bash
env:
AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"
- name: Decrypt Release Secrets
uses: ./.forgejo/actions/decrypt-release-secrets
- name: Download Signed Apple Artifacts
uses: https://code.forgejo.org/actions/download-artifact@v3
with:
name: burrow-apple-signed-${{ needs.prepare.outputs.build_number }}
path: publish
- name: Upload iOS IPA To App Store Connect
shell: bash
env:
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
run: |
set -euo pipefail
Scripts/ci/check-release-config.sh app-store
key_dir="${RUNNER_TEMP:-/tmp}/burrow-asc"
altool_key_dir="${HOME}/.appstoreconnect/private_keys"
mkdir -p "$key_dir" "$altool_key_dir"
key_path="$key_dir/AuthKey_${ASC_API_KEY_ID}.p8"
if [[ -n "${ASC_API_KEY_PATH:-}" && -f "${ASC_API_KEY_PATH:-}" ]]; then
cp "$ASC_API_KEY_PATH" "$key_path"
else
printf '%s' "$ASC_API_KEY_BASE64" | base64 --decode > "$key_path"
fi
cp "$key_path" "$altool_key_dir/AuthKey_${ASC_API_KEY_ID}.p8"
chmod 600 "$key_path" "$altool_key_dir/AuthKey_${ASC_API_KEY_ID}.p8"
ipa="publish/apple/Burrow-iOS-${BUILD_NUMBER}.ipa"
if [[ ! -s "$ipa" ]]; then
echo "::error ::No signed iOS IPA found at $ipa" >&2
find publish -type f -print | sort
exit 1
fi
upload_log="${RUNNER_TEMP:-/tmp}/burrow-altool-ios-${BUILD_NUMBER}.log"
set +e
xcrun altool --upload-app \
--type ios \
--file "$ipa" \
--apiKey "$ASC_API_KEY_ID" \
--apiIssuer "$ASC_API_ISSUER_ID" 2>&1 | tee "$upload_log"
altool_status=${PIPESTATUS[0]}
set -e
if (( altool_status != 0 )) || grep -Eq 'UPLOAD FAILED|Validation failed|ERROR:' "$upload_log"; then
echo "::error ::altool reported upload validation failure for $ipa" >&2
exit 1
fi
- name: Distribute iOS Build To TestFlight
shell: bash
env:
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
TESTFLIGHT_DISTRIBUTE_EXTERNAL: ${{ github.event.inputs.testflight_distribute_external || 'false' }}
TESTFLIGHT_GROUPS: ${{ github.event.inputs.testflight_groups || '' }}
IOS_USES_NON_EXEMPT_ENCRYPTION: ${{ github.event.inputs.ios_uses_non_exempt_encryption || 'false' }}
run: Scripts/ci/distribute-testflight.sh
publish-sparkle:
name: Publish Sparkle
needs:
- prepare
- sign-apple
if: ${{ github.event.inputs.upload_sparkle == 'true' }}
runs-on: namespace-profile-linux-medium-apple-v2
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Authenticate Google WIF
shell: bash
run: nix develop .#ci -c Scripts/ci/google-wif-auth.sh
- name: Download Signed Apple Artifacts
uses: https://code.forgejo.org/actions/download-artifact@v3
with:
name: burrow-apple-signed-${{ needs.prepare.outputs.build_number }}
path: publish
- name: Publish Sparkle Channel
shell: bash
env:
CHANNEL: ${{ needs.prepare.outputs.sparkle_channel }}
SPARKLE_POINT_MAIN: ${{ github.event.inputs.sparkle_point_main || 'true' }}
run: |
set -euo pipefail
if [[ ! -f "publish/sparkle/${CHANNEL}/appcast.xml" ]]; then
echo "::error ::No Sparkle appcast staged for channel ${CHANNEL}" >&2
find publish -type f -print | sort
exit 1
fi
nix develop .#ci -c gcloud storage rsync --recursive "publish/sparkle/${CHANNEL}" "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/${CHANNEL}"
if [[ "$SPARKLE_POINT_MAIN" == "true" ]]; then
printf '%s\n' "$CHANNEL" > main-channel.txt
nix develop .#ci -c gcloud storage cp main-channel.txt "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/main-channel.txt"
nix develop .#ci -c gcloud storage cp "publish/sparkle/${CHANNEL}/appcast.xml" "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/appcast.xml"
nix develop .#ci -c gcloud storage rsync --recursive "publish/sparkle/${CHANNEL}" "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/default"
fi

View file

@ -1,99 +0,0 @@
name: "Apple: iOS Distribution KMS CSR"
run-name: "Apple: iOS Distribution KMS CSR (${{ github.event.inputs.kms_key || 'apple-ios-distribution' }})"
on:
workflow_dispatch:
inputs:
kms_key:
description: Google KMS key name in the Burrow identity key ring
required: false
default: apple-ios-distribution
kms_version:
description: Google KMS key version
required: false
default: "1"
common_name:
description: CSR common name
required: false
default: Burrow iOS Distribution
email_address:
description: CSR email address
required: false
default: release@burrow.net
permissions:
contents: read
concurrency:
group: apple-ios-distribution-kms-csr-${{ github.ref }}
cancel-in-progress: false
env:
BURROW_GOOGLE_PROJECT_ID: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
GOOGLE_CLOUD_PROJECT: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
BURROW_GOOGLE_PROJECT_NUMBER: ${{ vars.GOOGLE_PROJECT_NUMBER || '416198671487' }}
BURROW_GOOGLE_WIF_POOL_ID: ${{ vars.BURROW_GOOGLE_WIF_POOL_ID || 'burrow-authentik' }}
BURROW_GOOGLE_WIF_PROVIDER_ID: ${{ vars.BURROW_GOOGLE_WIF_PROVIDER_ID || 'forgejo-runners' }}
BURROW_GOOGLE_WIF_SERVICE_ACCOUNT: ${{ vars.BURROW_GOOGLE_WIF_SERVICE_ACCOUNT || 'burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com' }}
BURROW_AUTHENTIK_WIF_ISSUER: ${{ vars.BURROW_AUTHENTIK_WIF_ISSUER || 'https://auth.burrow.net/application/o/google-cloud/' }}
BURROW_AUTHENTIK_WIF_AUDIENCE: ${{ vars.BURROW_AUTHENTIK_WIF_AUDIENCE || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_ID: ${{ vars.BURROW_AUTHENTIK_WIF_CLIENT_ID || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_SECRET: ${{ secrets.BURROW_AUTHENTIK_WIF_CLIENT_SECRET }}
BURROW_KMS_LOCATION: global
BURROW_KMS_KEYRING: burrow-identity
jobs:
csr:
name: Generate CSR
runs-on: [self-hosted, linux, x86_64, burrow-forge]
timeout-minutes: 20
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Authenticate Google WIF
shell: bash
run: nix develop .#ci -c Scripts/ci/google-wif-auth.sh
- name: Generate KMS-backed CSR
shell: bash
run: |
set -euo pipefail
mkdir -p dist/apple-ios-distribution
nix develop .#ci -c Scripts/apple/google-kms-csr.py \
--kms-key "${{ github.event.inputs.kms_key || 'apple-ios-distribution' }}" \
--kms-version "${{ github.event.inputs.kms_version || '1' }}" \
--common-name "${{ github.event.inputs.common_name || 'Burrow iOS Distribution' }}" \
--email-address "${{ github.event.inputs.email_address || 'release@burrow.net' }}" \
--output dist/apple-ios-distribution/ios-distribution.certSigningRequest \
--public-key-output dist/apple-ios-distribution/google-kms-public-key.pem
if command -v openssl >/dev/null 2>&1; then
openssl req -in dist/apple-ios-distribution/ios-distribution.certSigningRequest -noout -verify
fi
cat > dist/apple-ios-distribution/README.txt <<'EOF'
This CSR is backed by a non-exportable Google Cloud KMS key in the Burrow
identity key ring.
Create an iOS Distribution certificate explicitly through App Store Connect:
Scripts/apple/create-asc-certificate.mjs \
--certificate-type IOS_DISTRIBUTION \
--csr-file ios-distribution.certSigningRequest \
--out-dir Apple/Certificates
Keep the returned .cer file with release artifacts. The private key remains in
Google Cloud KMS and is never exported as a p12.
EOF
- name: Upload CSR Artifact
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: burrow-ios-distribution-kms-csr
path: dist/apple-ios-distribution

View file

@ -1,67 +0,0 @@
name: "Backup: Garage Storage"
run-name: "Backup Garage Storage (${{ github.event.inputs.set || 'all' }})"
on:
schedule:
- cron: "17 9 * * *"
workflow_dispatch:
inputs:
set:
description: "Comma-separated backup set: all, releases, packages, nix-cache"
required: false
default: all
delete_unmatched:
description: Delete destination objects that are missing from Garage
required: false
default: "false"
permissions:
contents: read
concurrency:
group: backup-garage-storage-${{ github.event.inputs.set || 'all' }}
cancel-in-progress: false
jobs:
backup:
name: Mirror Garage To GCS
runs-on: namespace-profile-linux-medium
timeout-minutes: 90
env:
BURROW_GARAGE_ENDPOINT: ${{ vars.BURROW_GARAGE_ENDPOINT || 'https://objects.burrow.net' }}
BURROW_GARAGE_REGION: ${{ vars.BURROW_GARAGE_REGION || 'garage' }}
BURROW_GARAGE_BACKUP_SET: ${{ github.event.inputs.set || 'all' }}
BURROW_GARAGE_BACKUP_DELETE: ${{ github.event.inputs.delete_unmatched || 'false' }}
BURROW_RELEASE_GARAGE_BUCKET: ${{ vars.BURROW_RELEASE_GARAGE_BUCKET || 'burrow-releases' }}
BURROW_PACKAGE_GARAGE_BUCKET: ${{ vars.BURROW_PACKAGE_GARAGE_BUCKET || 'burrow-packages' }}
BURROW_NIX_CACHE_GARAGE_BUCKET: ${{ vars.BURROW_NIX_CACHE_GARAGE_BUCKET || 'attic' }}
BURROW_RELEASE_GCS_BUCKET: ${{ vars.BURROW_RELEASE_GCS_BUCKET || 'burrow-net-releases' }}
BURROW_PACKAGE_GCS_BUCKET: ${{ vars.BURROW_PACKAGE_GCS_BUCKET || 'burrow-net-packages' }}
BURROW_NIX_CACHE_GCS_BUCKET: ${{ vars.BURROW_NIX_CACHE_GCS_BUCKET || 'burrow-net-nix-cache' }}
AWS_ACCESS_KEY_ID: ${{ secrets.BURROW_GARAGE_BACKUP_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.BURROW_GARAGE_BACKUP_SECRET_ACCESS_KEY }}
BURROW_GOOGLE_PROJECT_ID: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
GOOGLE_CLOUD_PROJECT: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
BURROW_GOOGLE_PROJECT_NUMBER: ${{ vars.GOOGLE_PROJECT_NUMBER || '416198671487' }}
BURROW_GOOGLE_WIF_POOL_ID: ${{ vars.BURROW_GOOGLE_WIF_POOL_ID || 'burrow-authentik' }}
BURROW_GOOGLE_WIF_PROVIDER_ID: ${{ vars.BURROW_GOOGLE_WIF_PROVIDER_ID || 'forgejo-runners' }}
BURROW_GOOGLE_WIF_SERVICE_ACCOUNT: ${{ vars.BURROW_GOOGLE_WIF_SERVICE_ACCOUNT || 'burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com' }}
BURROW_AUTHENTIK_WIF_ISSUER: ${{ vars.BURROW_AUTHENTIK_WIF_ISSUER || 'https://auth.burrow.net/application/o/google-cloud/' }}
BURROW_AUTHENTIK_WIF_AUDIENCE: ${{ vars.BURROW_AUTHENTIK_WIF_AUDIENCE || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_ID: ${{ vars.BURROW_AUTHENTIK_WIF_CLIENT_ID || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_SECRET: ${{ secrets.BURROW_AUTHENTIK_WIF_CLIENT_SECRET }}
BURROW_NIX_CACHE_URL: ${{ vars.BURROW_NIX_CACHE_URL || 'https://nix.burrow.net/burrow' }}
BURROW_NIX_CACHE_PUBLIC_KEY: ${{ vars.BURROW_NIX_CACHE_PUBLIC_KEY }}
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
fetch-depth: 0
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Mirror Garage Buckets To GCS
shell: bash
run: nix develop .#ci -c Scripts/ci/backup-garage-to-gcs.sh

View file

@ -1,74 +0,0 @@
name: "Build: Android"
run-name: "Build: Android (${{ github.ref_name }})"
on:
push:
branches:
- main
paths:
- ".forgejo/workflows/build-android.yml"
- "Android/**"
- "BUILD.bazel"
- "MODULE.bazel"
- "MODULE.bazel.lock"
- "bazel/android/**"
- "Cargo.lock"
- "Cargo.toml"
- "crates/burrow-core/**"
- "crates/burrow-mobile-ffi/**"
- "rust-toolchain.toml"
- "Scripts/ci/nscloud-cache.sh"
pull_request:
paths:
- ".forgejo/workflows/build-android.yml"
- "Android/**"
- "BUILD.bazel"
- "MODULE.bazel"
- "MODULE.bazel.lock"
- "bazel/android/**"
- "Cargo.lock"
- "Cargo.toml"
- "crates/burrow-core/**"
- "crates/burrow-mobile-ffi/**"
- "rust-toolchain.toml"
- "Scripts/ci/nscloud-cache.sh"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: burrow-build-android-${{ github.ref }}
cancel-in-progress: true
env:
BURROW_NIX_CACHE_URL: ${{ vars.BURROW_NIX_CACHE_URL || 'https://nix.burrow.net/burrow' }}
BURROW_NIX_CACHE_PUBLIC_KEY: ${{ vars.BURROW_NIX_CACHE_PUBLIC_KEY }}
jobs:
stub:
name: Android Rust Core Stub
runs-on: namespace-profile-linux-medium
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Configure Cache
shell: bash
run: |
set -euo pipefail
bash Scripts/ci/ensure-nix.sh
nix develop .#ci -c Scripts/ci/nscloud-cache.sh bazel
- name: Build Android Stub Check
shell: bash
run: |
set -euo pipefail
if [[ -n "${BURROW_BAZEL_NSCCACHE_BAZELRC:-}" ]]; then
bazel --bazelrc="$BURROW_BAZEL_NSCCACHE_BAZELRC" build //bazel/android:check_stub_stamp
else
bazel build //bazel/android:check_stub_stamp
fi

View file

@ -1,480 +0,0 @@
name: "Build: Release"
run-name: "Build: Release (${{ github.ref_name }})"
on:
workflow_dispatch:
inputs:
build_number_override:
description: Optional explicit build number
required: false
default: ""
upload_app_store:
description: Trigger downstream App Store Connect upload workflow
required: false
default: "true"
upload_sparkle:
description: Trigger downstream Sparkle publish workflow
required: false
default: "true"
upload_microsoft_store:
description: Trigger downstream Microsoft Store beta upload workflow
required: false
default: "false"
sparkle_point_main:
description: Update Sparkle main channel pointers
required: false
default: "true"
testflight_distribute_external:
description: Distribute to external TestFlight groups
required: false
default: "false"
testflight_groups:
description: Optional comma-separated TestFlight groups for external distribution
required: false
default: ""
ios_uses_non_exempt_encryption:
description: Set true only when the iOS build uses non-exempt encryption
required: false
default: "false"
push:
tags:
- "release/*"
permissions:
actions: write
contents: write
concurrency:
group: burrow-build-release-${{ github.ref }}
cancel-in-progress: true
env:
BURROW_RELEASE_GCS_BUCKET: ${{ vars.BURROW_RELEASE_GCS_BUCKET || 'burrow-net-releases' }}
BURROW_RELEASE_GCS_BACKUP: ${{ vars.BURROW_RELEASE_GCS_BACKUP || 'true' }}
BURROW_RELEASE_GARAGE_BUCKET: ${{ vars.BURROW_RELEASE_GARAGE_BUCKET || 'burrow-releases' }}
BURROW_RELEASE_REQUIRE_GARAGE: ${{ vars.BURROW_RELEASE_REQUIRE_GARAGE || 'true' }}
BURROW_GARAGE_ENDPOINT: ${{ vars.BURROW_GARAGE_ENDPOINT || 'https://objects.burrow.net' }}
BURROW_GARAGE_REGION: ${{ vars.BURROW_GARAGE_REGION || 'garage' }}
AWS_ACCESS_KEY_ID: ${{ secrets.BURROW_GARAGE_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.BURROW_GARAGE_SECRET_ACCESS_KEY }}
BURROW_NIX_CACHE_URL: ${{ vars.BURROW_NIX_CACHE_URL || 'https://nix.burrow.net/burrow' }}
BURROW_NIX_CACHE_PUBLIC_KEY: ${{ vars.BURROW_NIX_CACHE_PUBLIC_KEY }}
BURROW_GOOGLE_PROJECT_ID: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
GOOGLE_CLOUD_PROJECT: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
BURROW_GOOGLE_PROJECT_NUMBER: ${{ vars.GOOGLE_PROJECT_NUMBER || '416198671487' }}
BURROW_GOOGLE_WIF_POOL_ID: ${{ vars.BURROW_GOOGLE_WIF_POOL_ID || 'burrow-authentik' }}
BURROW_GOOGLE_WIF_PROVIDER_ID: ${{ vars.BURROW_GOOGLE_WIF_PROVIDER_ID || 'forgejo-runners' }}
BURROW_GOOGLE_WIF_SERVICE_ACCOUNT: ${{ vars.BURROW_GOOGLE_WIF_SERVICE_ACCOUNT || 'burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com' }}
BURROW_AUTHENTIK_WIF_ISSUER: ${{ vars.BURROW_AUTHENTIK_WIF_ISSUER || 'https://auth.burrow.net/application/o/google-cloud/' }}
BURROW_AUTHENTIK_WIF_AUDIENCE: ${{ vars.BURROW_AUTHENTIK_WIF_AUDIENCE || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_ID: ${{ vars.BURROW_AUTHENTIK_WIF_CLIENT_ID || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_SECRET: ${{ secrets.BURROW_AUTHENTIK_WIF_CLIENT_SECRET }}
BURROW_APPLE_BUNDLE_ID: ${{ vars.BURROW_APPLE_BUNDLE_ID || 'net.burrow.app' }}
BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID: ${{ vars.BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID || 'net.burrow.app.network' }}
BURROW_IOS_DISTRIBUTION_CERTIFICATE_ID: ${{ vars.BURROW_IOS_DISTRIBUTION_CERTIFICATE_ID || '3G42677598' }}
BURROW_DEVELOPER_ID_CERTIFICATE_ID: ${{ vars.BURROW_DEVELOPER_ID_CERTIFICATE_ID || '9JKN6HXBHC' }}
BURROW_APPLE_CREATE_MISSING_BUNDLE_IDS: ${{ vars.BURROW_APPLE_CREATE_MISSING_BUNDLE_IDS || 'false' }}
BURROW_APPLE_ENABLE_CAPABILITIES: ${{ vars.BURROW_APPLE_ENABLE_CAPABILITIES || 'false' }}
BURROW_APPLE_SIGNING_MODE: ${{ vars.BURROW_APPLE_SIGNING_MODE || 'google-kms-staged' }}
BURROW_NOTARIZE_MACOS: ${{ vars.BURROW_NOTARIZE_MACOS || 'true' }}
BURROW_SPARKLE_SIGN_WITH_KMS: ${{ vars.BURROW_SPARKLE_SIGN_WITH_KMS || 'true' }}
BURROW_SPARKLE_KMS_KEY: ${{ vars.BURROW_SPARKLE_KMS_KEY || 'sparkle-ed25519' }}
BURROW_SPARKLE_KMS_VERSION: ${{ vars.BURROW_SPARKLE_KMS_VERSION || '1' }}
BURROW_SPARKLE_FEED_URL: ${{ vars.BURROW_SPARKLE_FEED_URL || 'https://releases.burrow.net/sparkle/appcast.xml' }}
BURROW_SPARKLE_PUBLIC_ED_KEY: ${{ vars.BURROW_SPARKLE_PUBLIC_ED_KEY || 'uugZuJeqvvKd91NZ6F1Fv2cQenUbIG/ZW3L9MuaEz30=' }}
BURROW_VERSION_REQUIRE_REMOTE: "1"
jobs:
prepare:
name: Prepare
runs-on: namespace-profile-linux-medium
outputs:
do_release: ${{ steps.plan.outputs.do_release }}
build_number: ${{ steps.plan.outputs.build_number }}
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Fetch Build Tags
shell: bash
run: git fetch --force --prune origin "+refs/tags/builds/*:refs/tags/builds/*"
- name: Compute Release Plan
id: plan
shell: bash
env:
BURROW_BUILD_NUMBER_OVERRIDE: ${{ github.event.inputs.build_number_override || '' }}
run: |
set -euo pipefail
status="$(Scripts/version.sh status)"
if [[ "$status" == "clean" ]]; then
echo "do_release=false" >> "$GITHUB_OUTPUT"
echo "build_number=" >> "$GITHUB_OUTPUT"
echo "No unreleased changes."
exit 0
fi
build_number="$(Scripts/version.sh pre-increment)"
echo "do_release=true" >> "$GITHUB_OUTPUT"
echo "build_number=${build_number}" >> "$GITHUB_OUTPUT"
build-linux:
name: Build (Linux)
needs: prepare
if: ${{ needs.prepare.outputs.do_release == 'true' }}
runs-on: namespace-profile-linux-medium
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Build Linux Release
shell: bash
env:
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
run: |
set -euo pipefail
bash Scripts/ci/ensure-nix.sh
nix develop .#ci -c Scripts/ci/nscloud-cache.sh nix
nix develop .#ci -c Scripts/ci/package-release-artifacts.sh linux
- name: Upload Linux Artifacts
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: burrow-linux-${{ needs.prepare.outputs.build_number }}
path: dist/builds/${{ needs.prepare.outputs.build_number }}/linux/*
if-no-files-found: error
- name: Upload Linux Artifacts To Release Storage
shell: bash
env:
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
run: |
set -euo pipefail
bash Scripts/ci/ensure-nix.sh
nix develop .#ci -c Scripts/ci/upload-release-storage.sh
build-windows:
name: Build (Windows Stub)
needs: prepare
if: ${{ needs.prepare.outputs.do_release == 'true' }}
runs-on: namespace-profile-windows-large
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Build Rust Stub
shell: pwsh
env:
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
run: |
if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) {
Invoke-WebRequest -Uri "https://win.rustup.rs" -OutFile "rustup-init.exe"
.\rustup-init.exe -y --profile minimal --default-toolchain stable
$env:Path = "$env:USERPROFILE\.cargo\bin;$env:Path"
}
cargo build --locked --release -p burrow-windows-stub
New-Item -ItemType Directory -Force -Path "dist/builds/$env:BUILD_NUMBER/windows/burrow-$env:BUILD_NUMBER-x86_64-pc-windows-msvc" | Out-Null
Copy-Item "target/release/burrow.exe" "dist/builds/$env:BUILD_NUMBER/windows/burrow-$env:BUILD_NUMBER-x86_64-pc-windows-msvc/burrow.exe"
Copy-Item "README.md" "dist/builds/$env:BUILD_NUMBER/windows/burrow-$env:BUILD_NUMBER-x86_64-pc-windows-msvc/README.md"
Compress-Archive -Path "dist/builds/$env:BUILD_NUMBER/windows/burrow-$env:BUILD_NUMBER-x86_64-pc-windows-msvc" -DestinationPath "dist/builds/$env:BUILD_NUMBER/windows/burrow-$env:BUILD_NUMBER-x86_64-pc-windows-msvc.zip" -Force
Get-FileHash "dist/builds/$env:BUILD_NUMBER/windows/burrow-$env:BUILD_NUMBER-x86_64-pc-windows-msvc.zip" -Algorithm SHA256 | ForEach-Object { "$($_.Hash.ToLower()) burrow-$env:BUILD_NUMBER-x86_64-pc-windows-msvc.zip" } | Set-Content "dist/builds/$env:BUILD_NUMBER/windows/burrow-$env:BUILD_NUMBER-x86_64-pc-windows-msvc.zip.sha256"
- name: Upload Windows Artifacts
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: burrow-windows-${{ needs.prepare.outputs.build_number }}
path: dist/builds/${{ needs.prepare.outputs.build_number }}/windows/*
if-no-files-found: error
build-apple:
name: Build (Apple ${{ matrix.platform_name }})
needs: prepare
if: ${{ needs.prepare.outputs.do_release == 'true' }}
runs-on: namespace-profile-macos-large
strategy:
fail-fast: false
max-parallel: 2
matrix:
include:
- platform: ios
platform_name: iOS
bazel_target: //bazel/apple:release_ios_stamp
- platform: macos
platform_name: macOS
bazel_target: //bazel/apple:release_macos_stamp
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
submodules: recursive
- name: Select Apple SDK
shell: bash
run: |
set -euo pipefail
if ! command -v xcrun >/dev/null 2>&1; then
echo "::error ::xcrun is required for Apple release builds" >&2
exit 1
fi
xcrun --find swiftc
xcrun --sdk macosx --show-sdk-path
xcrun --sdk iphoneos --show-sdk-path
xcrun --sdk iphonesimulator --show-sdk-path
- name: Prepare Rust Apple Targets
shell: bash
env:
PLATFORM: ${{ matrix.platform }}
run: |
set -euo pipefail
if ! command -v rustup >/dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable
export PATH="$HOME/.cargo/bin:$PATH"
fi
case "$PLATFORM" in
ios)
rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
;;
macos)
rustup target add aarch64-apple-darwin x86_64-apple-darwin
;;
*)
echo "::error ::unsupported Apple platform ${PLATFORM}" >&2
exit 1
;;
esac
- name: Bootstrap Runner Environment
shell: bash
run: |
set -euo pipefail
runner_home="${RUNNER_HOME:-}"
if [[ -z "$runner_home" ]]; then
runner_home="$(python3 -c 'import os,pwd; print(pwd.getpwuid(os.getuid()).pw_dir)' 2>/dev/null || true)"
if [[ -z "$runner_home" ]]; then
runner_home="$PWD/.home"
fi
fi
mkdir -p "$runner_home" "$PWD/.tmp"
{
echo "HOME=$runner_home"
echo "XDG_CACHE_HOME=$runner_home/.cache"
echo "XDG_CONFIG_HOME=$runner_home/.config"
echo "XDG_DATA_HOME=$runner_home/.local/share"
echo "TMPDIR=$PWD/.tmp"
echo "TMP=$PWD/.tmp"
} >> "$GITHUB_ENV"
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Namespace Cache
shell: bash
run: |
set -euo pipefail
NIX_BIN="$(bash Scripts/ci/resolve-nix-bin.sh)"
"$NIX_BIN" develop .#ci --command bash Scripts/ci/nscloud-cache.sh bazel
- name: Configure Age
id: age_apple
continue-on-error: true
shell: bash
env:
AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"
- name: Decrypt Release Secrets
if: ${{ steps.age_apple.outcome == 'success' }}
uses: ./.forgejo/actions/decrypt-release-secrets
- name: Sync Apple Provisioning Profiles
shell: bash
env:
PLATFORM: ${{ matrix.platform }}
run: |
set -euo pipefail
NIX_BIN="$(bash Scripts/ci/resolve-nix-bin.sh)"
"$NIX_BIN" develop .#ci --command Scripts/ci/sync-apple-provisioning-profiles.sh "$PLATFORM"
- name: Install Apple Signing Assets
shell: bash
run: Scripts/ci/install-apple-signing-assets.sh
- name: Build Apple Release
shell: bash
env:
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
PLATFORM: ${{ matrix.platform }}
UPLOAD_APP_STORE: ${{ github.event.inputs.upload_app_store || 'true' }}
UPLOAD_SPARKLE: ${{ github.event.inputs.upload_sparkle || 'true' }}
SPARKLE_CHANNEL: build-${{ needs.prepare.outputs.build_number }}
run: |
set -euo pipefail
require_signing=false
if [[ "$UPLOAD_APP_STORE" == "true" && "$PLATFORM" == "ios" ]]; then
require_signing=true
fi
if [[ "$UPLOAD_SPARKLE" == "true" && "$PLATFORM" == "macos" ]]; then
require_signing=true
fi
bazel_args=()
if [[ -n "${BURROW_BAZEL_NSCCACHE_BAZELRC:-}" && -f "${BURROW_BAZEL_NSCCACHE_BAZELRC:-}" ]]; then
bazel_args+=("--bazelrc=${BURROW_BAZEL_NSCCACHE_BAZELRC}")
fi
NIX_BIN="$(bash Scripts/ci/resolve-nix-bin.sh)"
ci_path="$("$NIX_BIN" develop .#ci --command bash -lc 'printf "%s" "$PATH"')"
ci_protoc="$("$NIX_BIN" develop .#ci --command bash -lc 'command -v protoc')"
"$NIX_BIN" develop .#ci --command bazel "${bazel_args[@]}" build \
--verbose_failures \
--show_timestamps \
--experimental_ui_max_stdouterr_bytes=16777216 \
--action_env=PATH="$ci_path" \
--action_env=PROTOC="$ci_protoc" \
--action_env=BUILD_WORKSPACE_DIRECTORY="$PWD" \
--action_env=BUILD_NUMBER="$BUILD_NUMBER" \
--action_env=BURROW_RELEASE_OUT="$PWD/dist/builds/$BUILD_NUMBER" \
--action_env=BURROW_APPLE_SIGNING_READY="${BURROW_APPLE_SIGNING_READY:-0}" \
--action_env=BURROW_APPLE_SIGNING_MODE="${BURROW_APPLE_SIGNING_MODE:-keychain}" \
--action_env=BURROW_APPLE_REQUIRE_SIGNING="$require_signing" \
--action_env=BURROW_APPLE_BUNDLE_ID="${BURROW_APPLE_BUNDLE_ID:-}" \
--action_env=BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID="${BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID:-}" \
--action_env=BURROW_APPLE_KEYCHAIN_PATH="${BURROW_APPLE_KEYCHAIN_PATH:-}" \
--action_env=BURROW_NOTARIZE_MACOS="${BURROW_NOTARIZE_MACOS:-false}" \
--action_env=BURROW_SPARKLE_SIGN_WITH_KMS="${BURROW_SPARKLE_SIGN_WITH_KMS:-false}" \
--action_env=BURROW_SPARKLE_KMS_KEY="${BURROW_SPARKLE_KMS_KEY:-sparkle-ed25519}" \
--action_env=BURROW_SPARKLE_KMS_VERSION="${BURROW_SPARKLE_KMS_VERSION:-1}" \
--action_env=BURROW_SPARKLE_FEED_URL="${BURROW_SPARKLE_FEED_URL:-https://releases.burrow.net/sparkle/appcast.xml}" \
--action_env=BURROW_SPARKLE_PUBLIC_ED_KEY="${BURROW_SPARKLE_PUBLIC_ED_KEY:-}" \
--action_env=GOOGLE_CLOUD_PROJECT="${GOOGLE_CLOUD_PROJECT:-}" \
--action_env=ASC_API_KEY_ID="${ASC_API_KEY_ID:-}" \
--action_env=ASC_API_ISSUER_ID="${ASC_API_ISSUER_ID:-}" \
--action_env=ASC_API_KEY_PATH="${ASC_API_KEY_PATH:-}" \
--action_env=SPARKLE_CHANNEL="$SPARKLE_CHANNEL" \
--action_env=HOME="$HOME" \
--action_env=XDG_CACHE_HOME="$XDG_CACHE_HOME" \
"${{ matrix.bazel_target }}"
- name: Upload Apple Artifacts
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: burrow-apple-${{ matrix.platform }}-${{ needs.prepare.outputs.build_number }}
path: |
dist/builds/${{ needs.prepare.outputs.build_number }}/apple/*
dist/builds/${{ needs.prepare.outputs.build_number }}/sparkle/**
if-no-files-found: error
- name: Upload Apple Artifacts To Release Storage
shell: bash
env:
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
run: |
set -euo pipefail
bash Scripts/ci/ensure-nix.sh
nix develop .#ci -c Scripts/ci/upload-release-storage.sh
publish:
name: Publish (Forgejo)
needs:
- prepare
- build-linux
- build-windows
- build-apple
if: ${{ always() && needs.prepare.outputs.do_release == 'true' }}
runs-on: namespace-profile-linux-medium
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Download Artifacts
uses: https://code.forgejo.org/actions/download-artifact@v3
with:
path: dist/downloaded
- name: Prepare Release Assets
shell: bash
env:
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
run: |
set -euo pipefail
mkdir -p dist
mkdir -p "dist/builds/${BUILD_NUMBER}/windows"
find dist/downloaded -path '*/burrow-windows-*/*' -type f -exec cp {} "dist/builds/${BUILD_NUMBER}/windows/" \;
find dist/downloaded -type f \( -name '*.tar.gz' -o -name '*.zip' -o -name '*.sha256' -o -name '*.ipa' -o -name '*.pkg' -o -name '*.dmg' -o -name 'README*.txt' \) -exec cp {} dist/ \;
rm -rf dist/downloaded
find dist -maxdepth 1 -type f -print | sort
- name: Upload Windows Artifacts To Release Storage
shell: bash
env:
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
run: |
set -euo pipefail
if ! find "dist/builds/${BUILD_NUMBER}/windows" -type f -print -quit | grep -q .; then
echo "::warning ::No Windows artifacts staged for release storage upload."
exit 0
fi
bash Scripts/ci/ensure-nix.sh
nix develop .#ci -c Scripts/ci/upload-release-storage.sh
- name: Tag Build
shell: bash
env:
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
run: |
set -euo pipefail
tag="builds/${BUILD_NUMBER}"
head_commit="$(git rev-parse HEAD)"
remote_commit="$(git ls-remote --tags --refs origin "refs/tags/${tag}" | awk 'NR==1 { print $1 }')"
if [[ -n "$remote_commit" && "$remote_commit" != "$head_commit" ]]; then
echo "::error ::Remote tag ${tag} already points at ${remote_commit}, not ${head_commit}" >&2
exit 1
fi
git tag -f "$tag" "$head_commit"
git push --quiet --no-verify origin "refs/tags/${tag}:refs/tags/${tag}"
- name: Publish Forgejo Release
shell: bash
env:
RELEASE_TAG: builds/${{ needs.prepare.outputs.build_number }}
API_URL: ${{ github.api_url }}
REPOSITORY: ${{ github.repository }}
TOKEN: ${{ github.token }}
run: Scripts/ci/publish-forgejo-release.sh
- name: Dispatch Store Uploads
shell: bash
env:
GITHUB_TOKEN: ${{ github.token }}
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
UPLOAD_APP_STORE: ${{ github.event.inputs.upload_app_store || 'true' }}
UPLOAD_SPARKLE: ${{ github.event.inputs.upload_sparkle || 'true' }}
UPLOAD_MICROSOFT_STORE: ${{ github.event.inputs.upload_microsoft_store || 'false' }}
SPARKLE_POINT_MAIN: ${{ github.event.inputs.sparkle_point_main || 'true' }}
TESTFLIGHT_DISTRIBUTE_EXTERNAL: ${{ github.event.inputs.testflight_distribute_external || 'false' }}
TESTFLIGHT_GROUPS: ${{ github.event.inputs.testflight_groups || '' }}
IOS_USES_NON_EXEMPT_ENCRYPTION: ${{ github.event.inputs.ios_uses_non_exempt_encryption || 'false' }}
run: |
set -euo pipefail
inputs="$(python3 -c 'import json, os; print(json.dumps({"build_number":os.environ["BUILD_NUMBER"],"upload_app_store":os.environ["UPLOAD_APP_STORE"],"upload_sparkle":os.environ["UPLOAD_SPARKLE"],"upload_microsoft_store":os.environ["UPLOAD_MICROSOFT_STORE"],"sparkle_point_main":os.environ["SPARKLE_POINT_MAIN"],"testflight_distribute_external":os.environ["TESTFLIGHT_DISTRIBUTE_EXTERNAL"],"testflight_groups":os.environ["TESTFLIGHT_GROUPS"],"ios_uses_non_exempt_encryption":os.environ["IOS_USES_NON_EXEMPT_ENCRYPTION"]}))')"
curl -fsS \
-X POST \
-H "Authorization: token ${GITHUB_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"ref\":\"main\",\"inputs\":${inputs}}" \
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/actions/workflows/publish-store-uploads.yml/dispatches"

View file

@ -1,42 +0,0 @@
name: Build Rust
on:
push:
branches:
- main
pull_request:
branches:
- "**"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
rust:
name: Cargo Test
runs-on: [self-hosted, linux, x86_64, burrow-forge]
steps:
- name: Checkout
shell: bash
run: |
set -euo pipefail
repo_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
if [ ! -d .git ]; then
git init .
fi
if git remote get-url origin >/dev/null 2>&1; then
git remote set-url origin "${repo_url}"
else
git remote add origin "${repo_url}"
fi
git fetch --force --tags origin "${GITHUB_SHA}"
git checkout --force --detach FETCH_HEAD
git clean -ffdqx
- name: Test
shell: bash
run: |
set -euo pipefail
nix develop .#ci -c cargo test --workspace --all-features

View file

@ -1,42 +0,0 @@
name: Build Site
on:
push:
branches:
- main
pull_request:
branches:
- "**"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
site:
name: Next.js Build
runs-on: [self-hosted, linux, x86_64, burrow-forge]
steps:
- name: Checkout
shell: bash
run: |
set -euo pipefail
repo_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
if [ ! -d .git ]; then
git init .
fi
if git remote get-url origin >/dev/null 2>&1; then
git remote set-url origin "${repo_url}"
else
git remote add origin "${repo_url}"
fi
git fetch --force --tags origin "${GITHUB_SHA}"
git checkout --force --detach FETCH_HEAD
git clean -ffdqx
- name: Build
shell: bash
run: |
set -euo pipefail
nix develop .#ci -c bash -lc 'cd site && npm ci --no-audit --no-fund && npm run build'

View file

@ -1,71 +0,0 @@
name: "Apple: Distribute TestFlight"
run-name: "Apple: Distribute TestFlight (Build ${{ inputs.build_number }})"
on:
workflow_dispatch:
inputs:
build_number:
description: Already-uploaded App Store Connect build number
required: true
testflight_distribute_external:
description: Distribute to external TestFlight groups
required: false
default: "false"
testflight_groups:
description: Optional comma-separated TestFlight groups
required: false
default: ""
ios_uses_non_exempt_encryption:
description: Set true only when the iOS build uses non-exempt encryption
required: false
default: "false"
permissions:
contents: read
concurrency:
group: burrow-distribute-testflight-${{ inputs.build_number }}
cancel-in-progress: true
env:
BURROW_NIX_CACHE_URL: ${{ vars.BURROW_NIX_CACHE_URL || 'https://nix.burrow.net/burrow' }}
BURROW_NIX_CACHE_PUBLIC_KEY: ${{ vars.BURROW_NIX_CACHE_PUBLIC_KEY }}
BURROW_APPLE_BUNDLE_ID: ${{ vars.BURROW_APPLE_BUNDLE_ID || 'net.burrow.app' }}
TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS: ${{ vars.TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS || '7200' }}
jobs:
distribute-ios-testflight:
name: Distribute (TestFlight iOS Testers)
runs-on: namespace-profile-linux-testflight
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Configure Age
shell: bash
env:
AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"
- name: Decrypt Release Secrets
uses: ./.forgejo/actions/decrypt-release-secrets
- name: Validate App Store Credentials
shell: bash
run: Scripts/ci/check-release-config.sh app-store
- name: Distribute iOS Build To TestFlight
shell: bash
env:
BUILD_NUMBER: ${{ inputs.build_number }}
TESTFLIGHT_DISTRIBUTE_EXTERNAL: ${{ inputs.testflight_distribute_external || 'false' }}
TESTFLIGHT_GROUPS: ${{ inputs.testflight_groups || '' }}
IOS_USES_NON_EXEMPT_ENCRYPTION: ${{ inputs.ios_uses_non_exempt_encryption || 'false' }}
run: Scripts/ci/distribute-testflight.sh

View file

@ -1,237 +0,0 @@
name: "Infra: OpenTofu"
run-name: "Infra: OpenTofu (${{ github.event.inputs.stack || 'grafana' }} ${{ github.event.inputs.mode || 'validate' }})"
on:
push:
branches:
- main
paths:
- ".forgejo/workflows/infra-opentofu.yml"
- "Scripts/grafana-tofu.sh"
- "Scripts/identity-tofu.sh"
- "Scripts/openbao-tofu.sh"
- "Scripts/releases-tofu.sh"
- "infra/grafana/**"
- "infra/identity/**"
- "infra/openbao/**"
- "infra/releases/**"
- "services/grafana/**"
workflow_dispatch:
inputs:
stack:
description: OpenTofu stack to run
required: true
default: grafana
type: choice
options:
- grafana
- identity
- openbao
- releases
mode:
description: OpenTofu mode
required: true
default: plan
type: choice
options:
- fmt
- validate
- plan
- apply
- import
apply_confirmation:
description: Type apply-burrow-<stack>-tofu to run apply
required: false
default: ""
import_address:
description: OpenTofu resource address for import mode
required: false
default: ""
import_id:
description: Provider import id for import mode
required: false
default: ""
permissions:
contents: read
concurrency:
group: infra-opentofu-${{ github.ref }}-${{ github.event.inputs.stack || 'grafana' }}
cancel-in-progress: false
jobs:
opentofu:
name: OpenTofu (${{ github.event.inputs.stack || 'grafana' }})
runs-on: [self-hosted, linux, x86_64, burrow-forge]
timeout-minutes: 30
env:
MODE: ${{ github.event.inputs.mode || 'validate' }}
STACK: ${{ github.event.inputs.stack || 'grafana' }}
APPLY_CONFIRMATION: ${{ github.event.inputs.apply_confirmation || '' }}
IMPORT_ADDRESS: ${{ github.event.inputs.import_address || '' }}
IMPORT_ID: ${{ github.event.inputs.import_id || '' }}
TF_INPUT: "false"
TF_IN_AUTOMATION: "true"
TF_VAR_grafana_url: ${{ vars.GRAFANA_URL || 'https://graphs.burrow.net' }}
TF_VAR_google_project_id: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
TF_VAR_google_project_number: ${{ vars.GOOGLE_PROJECT_NUMBER || '416198671487' }}
TF_VAR_google_runner_service_account_email: ${{ vars.BURROW_GOOGLE_WIF_SERVICE_ACCOUNT || 'burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com' }}
TF_VAR_google_wif_runner_client_id: ${{ vars.BURROW_AUTHENTIK_WIF_CLIENT_ID || 'google-wif.burrow.net' }}
TF_VAR_google_release_bucket_name: ${{ vars.BURROW_RELEASE_GCS_BUCKET || 'burrow-net-releases' }}
TF_VAR_google_package_bucket_name: ${{ vars.BURROW_PACKAGE_GCS_BUCKET || 'burrow-net-packages' }}
TF_VAR_google_nix_cache_bucket_name: ${{ vars.BURROW_NIX_CACHE_GCS_BUCKET || 'burrow-net-nix-cache' }}
TF_VAR_google_nix_cache_public_read: ${{ vars.BURROW_NIX_CACHE_GCS_PUBLIC_READ || 'false' }}
TF_VAR_google_storage_location: ${{ vars.BURROW_GCS_LOCATION || 'US' }}
BURROW_GOOGLE_PROJECT_ID: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
GOOGLE_CLOUD_PROJECT: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
BURROW_GOOGLE_PROJECT_NUMBER: ${{ vars.GOOGLE_PROJECT_NUMBER || '416198671487' }}
BURROW_GOOGLE_WIF_POOL_ID: ${{ vars.BURROW_GOOGLE_WIF_POOL_ID || 'burrow-authentik' }}
BURROW_GOOGLE_WIF_PROVIDER_ID: ${{ vars.BURROW_GOOGLE_WIF_PROVIDER_ID || 'forgejo-runners' }}
BURROW_GOOGLE_WIF_SERVICE_ACCOUNT: ${{ vars.BURROW_GOOGLE_WIF_SERVICE_ACCOUNT || 'burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com' }}
BURROW_AUTHENTIK_WIF_ISSUER: ${{ vars.BURROW_AUTHENTIK_WIF_ISSUER || 'https://auth.burrow.net/application/o/google-cloud/' }}
BURROW_AUTHENTIK_WIF_AUDIENCE: ${{ vars.BURROW_AUTHENTIK_WIF_AUDIENCE || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_ID: ${{ vars.BURROW_AUTHENTIK_WIF_CLIENT_ID || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_SECRET: ${{ secrets.BURROW_AUTHENTIK_WIF_CLIENT_SECRET }}
BURROW_NIX_CACHE_URL: ${{ vars.BURROW_NIX_CACHE_URL || 'https://nix.burrow.net/burrow' }}
BURROW_NIX_CACHE_PUBLIC_KEY: ${{ vars.BURROW_NIX_CACHE_PUBLIC_KEY }}
OPENTOFU_STATE_BUCKET: ${{ vars.OPENTOFU_STATE_BUCKET || '' }}
OPENTOFU_STATE_REGION: ${{ vars.OPENTOFU_STATE_REGION || vars.AWS_REGION || 'us-west-2' }}
OPENTOFU_STATE_DYNAMODB_TABLE: ${{ vars.OPENTOFU_STATE_DYNAMODB_TABLE || '' }}
steps:
- name: Checkout
shell: bash
run: |
set -euo pipefail
repo_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
if [ ! -d .git ]; then
git init .
fi
if git remote get-url origin >/dev/null 2>&1; then
git remote set-url origin "${repo_url}"
else
git remote add origin "${repo_url}"
fi
git fetch --force --tags origin "${GITHUB_SHA}"
git checkout --force --detach FETCH_HEAD
git clean -ffdqx
- name: Validate Request
shell: bash
run: |
set -euo pipefail
case "$STACK" in
grafana|identity|openbao|releases) ;;
*)
echo "::error ::Unsupported OpenTofu stack: $STACK" >&2
exit 2
;;
esac
case "$MODE" in
fmt|validate|plan|apply|import) ;;
*)
echo "::error ::Unsupported OpenTofu mode: $MODE" >&2
exit 2
;;
esac
if [[ "${GITHUB_EVENT_NAME}" != "workflow_dispatch" && "$MODE" != "validate" ]]; then
echo "::error ::Non-manual runs may only validate." >&2
exit 2
fi
if [[ "$MODE" == "apply" && "$APPLY_CONFIRMATION" != "apply-burrow-${STACK}-tofu" ]]; then
echo "::error ::apply_confirmation must be exactly apply-burrow-${STACK}-tofu." >&2
exit 1
fi
if [[ "$MODE" == "import" && ( -z "$IMPORT_ADDRESS" || -z "$IMPORT_ID" ) ]]; then
echo "::error ::import mode requires import_address and import_id." >&2
exit 2
fi
- name: Configure Age
if: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.mode == 'plan' || github.event.inputs.mode == 'apply' || github.event.inputs.mode == 'import') }}
shell: bash
env:
AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"
- name: Configure Grafana Provider Auth
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.stack == 'grafana' && (github.event.inputs.mode == 'plan' || github.event.inputs.mode == 'apply' || github.event.inputs.mode == 'import') }}
shell: bash
run: |
set -euo pipefail
: "${AGE_IDENTITY_PATH:?AGE_IDENTITY_PATH is required}"
agenix="$(nix build --no-link .#agenix --print-out-paths | tail -n1)/bin/agenix"
password="$("$agenix" --identity "$AGE_IDENTITY_PATH" --decrypt secrets/infra/grafana-admin-password.age | tr -d '\r')"
echo "::add-mask::${password}"
{
echo "TF_VAR_grafana_auth<<EOF"
printf 'admin:%s\n' "$password"
echo "EOF"
} >> "$GITHUB_ENV"
- name: Configure OpenTofu Backend
shell: bash
run: |
set -euo pipefail
stack_dir="infra/${STACK}"
rm -f "${stack_dir}/backend.hcl"
if [[ -n "$OPENTOFU_STATE_BUCKET" ]]; then
{
printf 'bucket = "%s"\n' "$OPENTOFU_STATE_BUCKET"
printf 'key = "%s"\n' "${STACK}/${STACK}.tfstate"
printf 'region = "%s"\n' "$OPENTOFU_STATE_REGION"
printf 'encrypt = true\n'
if [[ -n "$OPENTOFU_STATE_DYNAMODB_TABLE" ]]; then
printf 'dynamodb_table = "%s"\n' "$OPENTOFU_STATE_DYNAMODB_TABLE"
fi
} > "${stack_dir}/backend.hcl"
elif [[ "$MODE" == "apply" || "$MODE" == "import" ]]; then
echo "::error ::OPENTOFU_STATE_BUCKET is required for apply/import." >&2
exit 1
fi
- name: Authenticate Google WIF
if: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.stack == 'identity' || github.event.inputs.stack == 'releases') && (github.event.inputs.mode == 'plan' || github.event.inputs.mode == 'apply' || github.event.inputs.mode == 'import') }}
shell: bash
run: |
set -euo pipefail
nix develop .#ci -c Scripts/ci/google-wif-auth.sh
- name: Run OpenTofu
shell: bash
run: |
set -euo pipefail
stack_script="Scripts/${STACK}-tofu.sh"
case "$MODE" in
fmt)
nix develop .#ci -c tofu -chdir="infra/${STACK}" fmt -check
;;
validate)
"$stack_script" init -backend=false
"$stack_script" validate
;;
plan)
if [[ -f "infra/${STACK}/backend.hcl" ]]; then
"$stack_script" init -backend-config=backend.hcl
else
"$stack_script" init -backend=false
fi
"$stack_script" plan
;;
apply)
"$stack_script" init -backend-config=backend.hcl
"$stack_script" apply -auto-approve
;;
import)
"$stack_script" init -backend-config=backend.hcl
"$stack_script" import "$IMPORT_ADDRESS" "$IMPORT_ID"
;;
esac

View file

@ -1,38 +0,0 @@
name: Lint Governance
on:
push:
branches:
- main
pull_request:
branches:
- "**"
workflow_dispatch:
jobs:
governance:
name: BEP Metadata
runs-on: [self-hosted, linux, x86_64, burrow-forge]
steps:
- name: Checkout
shell: bash
run: |
set -euo pipefail
repo_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
if [ ! -d .git ]; then
git init .
fi
if git remote get-url origin >/dev/null 2>&1; then
git remote set-url origin "${repo_url}"
else
git remote add origin "${repo_url}"
fi
git fetch --force --tags origin "${GITHUB_SHA}"
git checkout --force --detach FETCH_HEAD
git clean -ffdqx
- name: Validate BEP metadata
shell: bash
run: |
set -euo pipefail
python3 Scripts/check-bep-metadata.py

View file

@ -1,63 +0,0 @@
name: "Cache: Publish Nix"
run-name: "Publish Nix Cache (${{ github.ref_name }})"
on:
push:
branches:
- main
paths:
- ".forgejo/workflows/publish-nix-cache.yml"
- "Cargo.lock"
- "Cargo.toml"
- "flake.lock"
- "flake.nix"
- "crates/**"
- "services/forgejo-nsc/**"
- "nixos/**"
- "Scripts/ci/ensure-nix.sh"
- "Scripts/ci/publish-nix-cache.sh"
workflow_dispatch:
inputs:
attrs:
description: Space-separated flake attrs to build and push
required: false
default: ".#burrow .#forgejo-nsc-dispatcher .#forgejo-nsc-autoscaler"
required:
description: Fail when the cache push token is missing
required: false
default: "false"
permissions:
contents: read
concurrency:
group: publish-nix-cache-${{ github.ref }}
cancel-in-progress: true
jobs:
publish:
name: Publish Nix Cache
runs-on: namespace-profile-linux-medium
timeout-minutes: 45
env:
BURROW_NIX_CACHE_SERVER_NAME: ${{ vars.BURROW_NIX_CACHE_SERVER_NAME || 'burrow' }}
BURROW_NIX_CACHE_SERVER_URL: ${{ vars.BURROW_NIX_CACHE_SERVER_URL || 'https://nix.burrow.net' }}
BURROW_NIX_CACHE_NAME: ${{ vars.BURROW_NIX_CACHE_NAME || 'burrow' }}
BURROW_NIX_CACHE_URL: ${{ vars.BURROW_NIX_CACHE_URL || 'https://nix.burrow.net/burrow' }}
BURROW_NIX_CACHE_PUBLIC_KEY: ${{ vars.BURROW_NIX_CACHE_PUBLIC_KEY }}
BURROW_NIX_CACHE_PUSH_TOKEN: ${{ secrets.BURROW_NIX_CACHE_PUSH_TOKEN }}
BURROW_NIX_CACHE_ATTRS: ${{ github.event.inputs.attrs || '.#burrow .#forgejo-nsc-dispatcher .#forgejo-nsc-autoscaler' }}
BURROW_NIX_CACHE_REQUIRED: ${{ github.event.inputs.required || 'false' }}
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
fetch-depth: 0
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Publish Cache Paths
shell: bash
run: nix develop .#ci -c Scripts/ci/publish-nix-cache.sh

View file

@ -1,148 +0,0 @@
name: "Publish: Package Repositories"
run-name: "Publish Package Repositories (${{ github.event.inputs.channel || 'stable' }})"
on:
workflow_dispatch:
inputs:
build_number:
description: Build number or release label for package artifact paths
required: false
default: ""
channel:
description: Native package repository channel
required: true
default: stable
type: choice
options:
- stable
- beta
- nightly
publish_gcs:
description: Publish repository tree to configured package storage targets
required: false
default: "true"
permissions:
contents: read
concurrency:
group: publish-package-repositories-${{ github.ref }}-${{ github.event.inputs.channel || 'stable' }}
cancel-in-progress: false
jobs:
publish:
name: Build Native Repositories
runs-on: [self-hosted, linux, x86_64, burrow-forge]
timeout-minutes: 45
env:
BUILD_NUMBER: ${{ github.event.inputs.build_number || github.sha }}
BURROW_PACKAGE_CHANNEL: ${{ github.event.inputs.channel || 'stable' }}
BURROW_KMS_LOCATION: ${{ vars.BURROW_KMS_LOCATION || 'global' }}
BURROW_KMS_KEYRING: ${{ vars.BURROW_KMS_KEYRING || 'burrow-identity' }}
BURROW_KMS_VERSION: ${{ vars.BURROW_KMS_VERSION || '1' }}
BURROW_GOOGLE_PROJECT_ID: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
GOOGLE_CLOUD_PROJECT: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
BURROW_GOOGLE_PROJECT_NUMBER: ${{ vars.GOOGLE_PROJECT_NUMBER || '416198671487' }}
BURROW_GOOGLE_WIF_POOL_ID: ${{ vars.BURROW_GOOGLE_WIF_POOL_ID || 'burrow-authentik' }}
BURROW_GOOGLE_WIF_PROVIDER_ID: ${{ vars.BURROW_GOOGLE_WIF_PROVIDER_ID || 'forgejo-runners' }}
BURROW_GOOGLE_WIF_SERVICE_ACCOUNT: ${{ vars.BURROW_GOOGLE_WIF_SERVICE_ACCOUNT || 'burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com' }}
BURROW_AUTHENTIK_WIF_ISSUER: ${{ vars.BURROW_AUTHENTIK_WIF_ISSUER || 'https://auth.burrow.net/application/o/google-cloud/' }}
BURROW_AUTHENTIK_WIF_AUDIENCE: ${{ vars.BURROW_AUTHENTIK_WIF_AUDIENCE || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_ID: ${{ vars.BURROW_AUTHENTIK_WIF_CLIENT_ID || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_SECRET: ${{ secrets.BURROW_AUTHENTIK_WIF_CLIENT_SECRET }}
BURROW_PACKAGE_GCS_BUCKET: ${{ vars.BURROW_PACKAGE_GCS_BUCKET || 'burrow-net-packages' }}
BURROW_PACKAGE_GCS_PREFIX: ${{ vars.BURROW_PACKAGE_GCS_PREFIX || '' }}
BURROW_PACKAGE_GCS_BACKUP: ${{ vars.BURROW_PACKAGE_GCS_BACKUP || 'true' }}
BURROW_PACKAGE_GARAGE_BUCKET: ${{ vars.BURROW_PACKAGE_GARAGE_BUCKET || 'burrow-packages' }}
BURROW_PACKAGE_GARAGE_PREFIX: ${{ vars.BURROW_PACKAGE_GARAGE_PREFIX || vars.BURROW_PACKAGE_GCS_PREFIX || '' }}
BURROW_PACKAGE_REQUIRE_GARAGE: ${{ vars.BURROW_PACKAGE_REQUIRE_GARAGE || 'true' }}
BURROW_GARAGE_ENDPOINT: ${{ vars.BURROW_GARAGE_ENDPOINT || 'https://objects.burrow.net' }}
BURROW_GARAGE_REGION: ${{ vars.BURROW_GARAGE_REGION || 'garage' }}
AWS_ACCESS_KEY_ID: ${{ secrets.BURROW_GARAGE_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.BURROW_GARAGE_SECRET_ACCESS_KEY }}
BURROW_NIX_CACHE_URL: ${{ vars.BURROW_NIX_CACHE_URL || 'https://nix.burrow.net/burrow' }}
BURROW_NIX_CACHE_PUBLIC_KEY: ${{ vars.BURROW_NIX_CACHE_PUBLIC_KEY }}
steps:
- name: Checkout
shell: bash
run: |
set -euo pipefail
repo_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
if [ ! -d .git ]; then
git init .
fi
if git remote get-url origin >/dev/null 2>&1; then
git remote set-url origin "${repo_url}"
else
git remote add origin "${repo_url}"
fi
git fetch --force --tags origin "${GITHUB_SHA}"
git checkout --force --detach FETCH_HEAD
git clean -ffdqx
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Build Linux Packages
shell: bash
run: |
set -euo pipefail
export BURROW_RELEASE_OUT="$PWD/dist/builds/$BUILD_NUMBER"
nix develop .#ci -c Scripts/ci/package-release-artifacts.sh linux
- name: Authenticate Google WIF
shell: bash
run: |
set -euo pipefail
nix develop .#ci -c Scripts/ci/google-wif-auth.sh
- name: Export KMS Public Keys
shell: bash
run: |
set -euo pipefail
mkdir -p "$PWD/.kms"
export BURROW_APT_REPO_KMS_KEY="${BURROW_APT_REPO_KMS_KEY:-package-apt-repository}"
export BURROW_RPM_REPO_KMS_KEY="${BURROW_RPM_REPO_KMS_KEY:-package-rpm-repository}"
export BURROW_PACMAN_REPO_KMS_KEY="${BURROW_PACMAN_REPO_KMS_KEY:-package-pacman-repository}"
for item in \
"APT:${BURROW_APT_REPO_KMS_KEY}:apt" \
"RPM:${BURROW_RPM_REPO_KMS_KEY}:rpm" \
"PACMAN:${BURROW_PACMAN_REPO_KMS_KEY}:pacman"
do
IFS=: read -r env_prefix key_name file_prefix <<< "$item"
pem="$PWD/.kms/${file_prefix}.pem"
nix develop .#ci -c gcloud kms keys versions get-public-key "$BURROW_KMS_VERSION" \
--location "$BURROW_KMS_LOCATION" \
--keyring "$BURROW_KMS_KEYRING" \
--key "$key_name" \
--project "$GOOGLE_CLOUD_PROJECT" \
--output-file "$pem"
echo "BURROW_${env_prefix}_REPO_KMS_PUBLIC_KEY_PEM=$pem" >> "$GITHUB_ENV"
echo "BURROW_${env_prefix}_REPO_KMS_KEY=$key_name" >> "$GITHUB_ENV"
done
- name: Build Repository Metadata
shell: bash
run: |
set -euo pipefail
export BURROW_PACKAGE_INPUT_DIR="$PWD/dist/builds/$BUILD_NUMBER/linux"
export BURROW_PACKAGE_REPOSITORY_DIR="$PWD/dist/builds/$BUILD_NUMBER/repositories"
nix develop .#ci -c Scripts/package/build-repositories.sh all
- name: Upload Repository Artifact
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: burrow-package-repositories-${{ github.event.inputs.channel || 'stable' }}-${{ github.event.inputs.build_number || github.sha }}
path: dist/builds/${{ github.event.inputs.build_number || github.sha }}/repositories/**
if-no-files-found: error
- name: Publish Package Repository To Storage
if: ${{ github.event.inputs.publish_gcs == 'true' }}
shell: bash
run: |
set -euo pipefail
export BURROW_PACKAGE_REPOSITORY_DIR="$PWD/dist/builds/$BUILD_NUMBER/repositories"
nix develop .#ci -c Scripts/ci/upload-package-storage.sh

View file

@ -1,385 +0,0 @@
name: "Publish: Release Uploads"
run-name: "Publish: Release Uploads (Build ${{ inputs.build_number }})"
on:
workflow_dispatch:
inputs:
build_number:
description: Build number to publish
required: true
upload_app_store:
description: Upload iOS/macOS/visionOS artifacts to App Store Connect
required: false
default: "true"
distribute_testflight:
description: Distribute an already-uploaded iOS build to TestFlight
required: false
default: "false"
upload_sparkle:
description: Publish Sparkle artifacts
required: false
default: "true"
upload_microsoft_store:
description: Upload Windows package to Microsoft Partner Center beta lane
required: false
default: "false"
sparkle_channel:
description: Sparkle channel, defaults to build-<number>
required: false
default: ""
sparkle_point_main:
description: Point Sparkle main appcast/default channel to selected channel
required: false
default: "true"
testflight_distribute_external:
description: Distribute to external TestFlight groups
required: false
default: "false"
testflight_groups:
description: Optional comma-separated TestFlight groups for external distribution
required: false
default: ""
ios_uses_non_exempt_encryption:
description: Set true only when the iOS build uses non-exempt encryption
required: false
default: "false"
permissions:
contents: read
concurrency:
group: burrow-publish-store-uploads-${{ inputs.build_number }}
cancel-in-progress: true
env:
BURROW_RELEASE_GCS_BUCKET: ${{ vars.BURROW_RELEASE_GCS_BUCKET || 'burrow-net-releases' }}
BURROW_GOOGLE_PROJECT_ID: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
GOOGLE_CLOUD_PROJECT: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
BURROW_GOOGLE_PROJECT_NUMBER: ${{ vars.GOOGLE_PROJECT_NUMBER || '416198671487' }}
BURROW_GOOGLE_WIF_POOL_ID: ${{ vars.BURROW_GOOGLE_WIF_POOL_ID || 'burrow-authentik' }}
BURROW_GOOGLE_WIF_PROVIDER_ID: ${{ vars.BURROW_GOOGLE_WIF_PROVIDER_ID || 'forgejo-runners' }}
BURROW_GOOGLE_WIF_SERVICE_ACCOUNT: ${{ vars.BURROW_GOOGLE_WIF_SERVICE_ACCOUNT || 'burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com' }}
BURROW_AUTHENTIK_WIF_ISSUER: ${{ vars.BURROW_AUTHENTIK_WIF_ISSUER || 'https://auth.burrow.net/application/o/google-cloud/' }}
BURROW_AUTHENTIK_WIF_AUDIENCE: ${{ vars.BURROW_AUTHENTIK_WIF_AUDIENCE || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_ID: ${{ vars.BURROW_AUTHENTIK_WIF_CLIENT_ID || 'google-wif.burrow.net' }}
BURROW_AUTHENTIK_WIF_CLIENT_SECRET: ${{ secrets.BURROW_AUTHENTIK_WIF_CLIENT_SECRET }}
BURROW_NIX_CACHE_URL: ${{ vars.BURROW_NIX_CACHE_URL || 'https://nix.burrow.net/burrow' }}
BURROW_NIX_CACHE_PUBLIC_KEY: ${{ vars.BURROW_NIX_CACHE_PUBLIC_KEY }}
BURROW_APPLE_BUNDLE_ID: ${{ vars.BURROW_APPLE_BUNDLE_ID || 'net.burrow.app' }}
BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID: ${{ vars.BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID || 'net.burrow.app.network' }}
BURROW_IOS_DISTRIBUTION_CERTIFICATE_ID: ${{ vars.BURROW_IOS_DISTRIBUTION_CERTIFICATE_ID || '3G42677598' }}
BURROW_DEVELOPER_ID_CERTIFICATE_ID: ${{ vars.BURROW_DEVELOPER_ID_CERTIFICATE_ID || '9JKN6HXBHC' }}
BURROW_APPLE_SIGNING_MODE: ${{ vars.BURROW_APPLE_SIGNING_MODE || 'google-kms-staged' }}
BURROW_NOTARIZE_MACOS: ${{ vars.BURROW_NOTARIZE_MACOS || 'true' }}
BURROW_SPARKLE_SIGN_WITH_KMS: ${{ vars.BURROW_SPARKLE_SIGN_WITH_KMS || 'true' }}
BURROW_SPARKLE_KMS_KEY: ${{ vars.BURROW_SPARKLE_KMS_KEY || 'sparkle-ed25519' }}
BURROW_SPARKLE_KMS_VERSION: ${{ vars.BURROW_SPARKLE_KMS_VERSION || '1' }}
BURROW_KMS_LOCATION: ${{ vars.BURROW_KMS_LOCATION || 'global' }}
BURROW_KMS_KEYRING: ${{ vars.BURROW_KMS_KEYRING || 'burrow-identity' }}
TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS: ${{ vars.TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS || '7200' }}
jobs:
sign-apple-kms:
name: Sign (Apple KMS)
if: ${{ inputs.upload_app_store == 'true' || inputs.upload_sparkle == 'true' }}
runs-on: namespace-profile-linux-medium
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Configure Age
shell: bash
env:
AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"
- name: Decrypt Release Secrets
uses: ./.forgejo/actions/decrypt-release-secrets
- name: Validate App Store Credentials
shell: bash
run: Scripts/ci/check-release-config.sh app-store
- name: Download Staged Apple Artifacts
shell: bash
env:
BUILD_NUMBER: ${{ inputs.build_number }}
run: |
set -euo pipefail
nix develop .#ci -c Scripts/ci/google-wif-auth.sh
mkdir -p "dist/builds/${BUILD_NUMBER}/apple"
nix develop .#ci -c gcloud storage rsync --recursive "gs://${BURROW_RELEASE_GCS_BUCKET}/builds/${BUILD_NUMBER}/apple" "dist/builds/${BUILD_NUMBER}/apple"
find "dist/builds/${BUILD_NUMBER}/apple" -type f -print | sort
- name: Sync Apple Provisioning Profiles
shell: bash
run: nix develop .#ci -c Scripts/ci/sync-apple-provisioning-profiles.sh all
- name: Sign Apple Artifacts With KMS
shell: bash
env:
BUILD_NUMBER: ${{ inputs.build_number }}
SPARKLE_CHANNEL_INPUT: ${{ inputs.sparkle_channel }}
run: |
set -euo pipefail
channel="${SPARKLE_CHANNEL_INPUT:-}"
if [[ -z "$channel" ]]; then
channel="build-${BUILD_NUMBER}"
fi
SPARKLE_CHANNEL="$channel" nix develop .#ci -c Scripts/apple/sign-release-artifacts-kms.sh all
- name: Upload Signed Apple Artifacts
shell: bash
env:
BUILD_NUMBER: ${{ inputs.build_number }}
run: nix develop .#ci -c Scripts/ci/upload-release-storage.sh
upload-app-store:
name: Upload (App Store Connect)
needs: sign-apple-kms
if: ${{ inputs.upload_app_store == 'true' }}
runs-on: namespace-profile-macos-large
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Configure Age
shell: bash
env:
AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"
- name: Decrypt Release Secrets
uses: ./.forgejo/actions/decrypt-release-secrets
- name: Validate App Store Credentials
shell: bash
run: Scripts/ci/check-release-config.sh app-store
- name: Download Apple Artifacts From GCS
shell: bash
env:
BUILD_NUMBER: ${{ inputs.build_number }}
run: |
set -euo pipefail
nix develop .#ci -c Scripts/ci/google-wif-auth.sh
mkdir -p publish/apple
nix develop .#ci -c gcloud storage rsync --recursive "gs://${BURROW_RELEASE_GCS_BUCKET}/builds/${BUILD_NUMBER}/apple" publish/apple
find publish/apple -type f -print | sort
- name: Upload Apple Packages
shell: bash
run: |
set -euo pipefail
Scripts/ci/check-release-config.sh app-store
key_dir="${RUNNER_TEMP:-/tmp}/burrow-asc"
altool_key_dir="${HOME}/.appstoreconnect/private_keys"
mkdir -p "$key_dir" "$altool_key_dir"
key_path="$key_dir/AuthKey_${ASC_API_KEY_ID}.p8"
if [[ -n "${ASC_API_KEY_PATH:-}" && -f "${ASC_API_KEY_PATH:-}" ]]; then
cp "$ASC_API_KEY_PATH" "$key_path"
else
printf '%s' "$ASC_API_KEY_BASE64" | base64 --decode > "$key_path"
fi
cp "$key_path" "$altool_key_dir/AuthKey_${ASC_API_KEY_ID}.p8"
chmod 600 "$key_path" "$altool_key_dir/AuthKey_${ASC_API_KEY_ID}.p8"
shopt -s nullglob
artifacts=()
for artifact in publish/apple/*.ipa publish/apple/*.pkg; do
[[ "$artifact" == *.unsigned.ipa ]] && continue
artifacts+=("$artifact")
done
if (( ${#artifacts[@]} == 0 )); then
echo "::error ::No Apple upload artifacts found for build ${{ inputs.build_number }}."
exit 1
fi
for artifact in "${artifacts[@]}"; do
upload_type="ios"
if [[ "$artifact" == *.pkg ]]; then
upload_type="macos"
fi
upload_log="${RUNNER_TEMP:-/tmp}/burrow-altool-${upload_type}-$(basename "$artifact").log"
set +e
xcrun altool --upload-app \
--type "$upload_type" \
--file "$artifact" \
--apiKey "$ASC_API_KEY_ID" \
--apiIssuer "$ASC_API_ISSUER_ID" 2>&1 | tee "$upload_log"
altool_status=${PIPESTATUS[0]}
set -e
if (( altool_status != 0 )) || grep -Eq 'UPLOAD FAILED|Validation failed|ERROR:' "$upload_log"; then
echo "::error ::altool reported upload validation failure for $artifact" >&2
exit 1
fi
done
release-ios-testflight:
name: Release (TestFlight iOS Testers)
needs: upload-app-store
if: ${{ inputs.upload_app_store == 'true' }}
runs-on: namespace-profile-linux-testflight
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Configure Age
shell: bash
env:
AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"
- name: Decrypt Release Secrets
uses: ./.forgejo/actions/decrypt-release-secrets
- name: Validate App Store Credentials
shell: bash
run: Scripts/ci/check-release-config.sh app-store
- name: Distribute iOS Build To TestFlight
shell: bash
env:
BUILD_NUMBER: ${{ inputs.build_number }}
TESTFLIGHT_DISTRIBUTE_EXTERNAL: ${{ inputs.testflight_distribute_external || 'false' }}
TESTFLIGHT_GROUPS: ${{ inputs.testflight_groups || '' }}
IOS_USES_NON_EXEMPT_ENCRYPTION: ${{ inputs.ios_uses_non_exempt_encryption || 'false' }}
run: Scripts/ci/distribute-testflight.sh
distribute-ios-testflight:
name: Distribute (TestFlight iOS Testers)
if: ${{ inputs.upload_app_store != 'true' && inputs.distribute_testflight == 'true' }}
runs-on: namespace-profile-linux-testflight
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Configure Age
shell: bash
env:
AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"
- name: Decrypt Release Secrets
uses: ./.forgejo/actions/decrypt-release-secrets
- name: Validate App Store Credentials
shell: bash
run: Scripts/ci/check-release-config.sh app-store
- name: Distribute iOS Build To TestFlight
shell: bash
env:
BUILD_NUMBER: ${{ inputs.build_number }}
TESTFLIGHT_DISTRIBUTE_EXTERNAL: ${{ inputs.testflight_distribute_external || 'false' }}
TESTFLIGHT_GROUPS: ${{ inputs.testflight_groups || '' }}
IOS_USES_NON_EXEMPT_ENCRYPTION: ${{ inputs.ios_uses_non_exempt_encryption || 'false' }}
run: Scripts/ci/distribute-testflight.sh
upload-sparkle:
name: Upload (Sparkle)
needs: sign-apple-kms
if: ${{ inputs.upload_sparkle == 'true' }}
runs-on: namespace-profile-linux-medium
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Resolve Sparkle Channel
id: channel
shell: bash
env:
BUILD_NUMBER: ${{ inputs.build_number }}
run: |
set -euo pipefail
channel="${{ inputs.sparkle_channel }}"
if [[ -z "$channel" ]]; then
channel="build-${BUILD_NUMBER}"
fi
echo "channel=$channel" >> "$GITHUB_OUTPUT"
- name: Ensure Nix
shell: bash
run: bash Scripts/ci/ensure-nix.sh
- name: Publish Sparkle Channel
shell: bash
env:
BUILD_NUMBER: ${{ inputs.build_number }}
CHANNEL: ${{ steps.channel.outputs.channel }}
SPARKLE_POINT_MAIN: ${{ inputs.sparkle_point_main }}
run: |
set -euo pipefail
nix develop .#ci -c Scripts/ci/google-wif-auth.sh
mkdir -p "publish/sparkle/${CHANNEL}"
nix develop .#ci -c gcloud storage rsync --recursive "gs://${BURROW_RELEASE_GCS_BUCKET}/builds/${BUILD_NUMBER}/sparkle/${CHANNEL}" "publish/sparkle/${CHANNEL}" || true
if [[ ! -f "publish/sparkle/${CHANNEL}/appcast.xml" ]]; then
echo "::warning ::No Sparkle appcast staged for build ${BUILD_NUMBER}; skipping Sparkle publish."
find "publish/sparkle/${CHANNEL}" -maxdepth 2 -type f -print || true
exit 0
fi
nix develop .#ci -c gcloud storage rsync --recursive "publish/sparkle/${CHANNEL}" "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/${CHANNEL}"
if [[ "$SPARKLE_POINT_MAIN" == "true" ]]; then
printf '%s\n' "$CHANNEL" > main-channel.txt
nix develop .#ci -c gcloud storage cp main-channel.txt "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/main-channel.txt"
nix develop .#ci -c gcloud storage cp "publish/sparkle/${CHANNEL}/appcast.xml" "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/appcast.xml"
nix develop .#ci -c gcloud storage rsync --recursive "publish/sparkle/${CHANNEL}" "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/default"
fi
upload-microsoft-store:
name: Upload (Microsoft Store Beta)
if: ${{ inputs.upload_microsoft_store == 'true' }}
runs-on: namespace-profile-windows-large
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Validate Microsoft Store Credentials
shell: pwsh
env:
MICROSOFT_TENANT_ID: ${{ secrets.MICROSOFT_TENANT_ID }}
MICROSOFT_CLIENT_ID: ${{ secrets.MICROSOFT_CLIENT_ID }}
MICROSOFT_CLIENT_SECRET: ${{ secrets.MICROSOFT_CLIENT_SECRET }}
run: |
foreach ($name in "MICROSOFT_TENANT_ID","MICROSOFT_CLIENT_ID","MICROSOFT_CLIENT_SECRET") {
if ([string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($name))) {
throw "missing required environment variable: $name"
}
}
- name: Stop Until Store Package Exists
shell: pwsh
run: |
Write-Host "::warning ::Burrow only builds a Windows Rust stub today; Microsoft Store package upload is wired for credentials but waits on MSIX packaging."

View file

@ -1,92 +0,0 @@
name: "Release: If Needed"
run-name: "Release: If Needed (${{ github.ref_name }})"
on:
push:
branches:
- main
schedule:
- cron: "*/30 * * * *"
workflow_dispatch:
inputs:
upload_app_store:
description: Trigger downstream App Store Connect upload workflow
required: false
default: "true"
upload_sparkle:
description: Trigger downstream Sparkle publish workflow
required: false
default: "true"
upload_microsoft_store:
description: Trigger downstream Microsoft Store beta upload workflow
required: false
default: "false"
sparkle_point_main:
description: Update Sparkle main channel pointers
required: false
default: "true"
testflight_distribute_external:
description: Distribute to external TestFlight groups
required: false
default: "false"
testflight_groups:
description: Optional comma-separated TestFlight groups for external distribution
required: false
default: ""
ios_uses_non_exempt_encryption:
description: Set true only when the iOS build uses non-exempt encryption
required: false
default: "false"
permissions:
contents: read
actions: write
concurrency:
group: burrow-release-if-needed-main
cancel-in-progress: true
env:
GITHUB_TOKEN: ${{ github.token }}
BURROW_VERSION_REQUIRE_REMOTE: "1"
jobs:
check:
name: Check (Release Needed)
runs-on: namespace-profile-linux-medium
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Fetch Build Tags
shell: bash
run: git fetch --force --prune origin "+refs/tags/builds/*:refs/tags/builds/*"
- name: Dispatch Release Build
shell: bash
env:
UPLOAD_APP_STORE: ${{ github.event.inputs.upload_app_store || 'true' }}
UPLOAD_SPARKLE: ${{ github.event.inputs.upload_sparkle || 'true' }}
UPLOAD_MICROSOFT_STORE: ${{ github.event.inputs.upload_microsoft_store || 'false' }}
SPARKLE_POINT_MAIN: ${{ github.event.inputs.sparkle_point_main || 'true' }}
TESTFLIGHT_DISTRIBUTE_EXTERNAL: ${{ github.event.inputs.testflight_distribute_external || 'false' }}
TESTFLIGHT_GROUPS: ${{ github.event.inputs.testflight_groups || '' }}
IOS_USES_NON_EXEMPT_ENCRYPTION: ${{ github.event.inputs.ios_uses_non_exempt_encryption || 'false' }}
run: |
set -euo pipefail
status="$(Scripts/version.sh status)"
if [[ "$status" == "clean" ]]; then
echo "No unreleased changes."
exit 0
fi
inputs="$(python3 -c 'import json, os; print(json.dumps({"upload_app_store":os.environ["UPLOAD_APP_STORE"],"upload_sparkle":os.environ["UPLOAD_SPARKLE"],"upload_microsoft_store":os.environ["UPLOAD_MICROSOFT_STORE"],"sparkle_point_main":os.environ["SPARKLE_POINT_MAIN"],"testflight_distribute_external":os.environ["TESTFLIGHT_DISTRIBUTE_EXTERNAL"],"testflight_groups":os.environ["TESTFLIGHT_GROUPS"],"ios_uses_non_exempt_encryption":os.environ["IOS_USES_NON_EXEMPT_ENCRYPTION"]}))')"
curl -fsS \
-X POST \
-H "Authorization: token ${GITHUB_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"ref\":\"main\",\"inputs\":${inputs}}" \
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/actions/workflows/build-release.yml/dispatches"
echo "Dispatched build-release.yml (status=${status})."

View file

@ -1,77 +0,0 @@
name: Release
on:
push:
tags:
- "v*"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
env:
BURROW_NIX_CACHE_URL: ${{ vars.BURROW_NIX_CACHE_URL || 'https://nix.burrow.net/burrow' }}
BURROW_NIX_CACHE_PUBLIC_KEY: ${{ vars.BURROW_NIX_CACHE_PUBLIC_KEY }}
jobs:
release:
name: Release Build
runs-on: namespace-profile-linux-medium
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ github.token }}
fetch-depth: 0
- name: Bootstrap Nix
shell: bash
run: |
set -euo pipefail
chmod +x Scripts/ci/ensure-nix.sh
Scripts/ci/ensure-nix.sh
- name: Build release artifacts
shell: bash
env:
RELEASE_REF: ${{ github.ref_name }}
run: |
set -euo pipefail
ref="${RELEASE_REF:-manual-${GITHUB_SHA::7}}"
export RELEASE_REF="${ref}"
chmod +x Scripts/ci/build-release-artifacts.sh
nix develop .#ci -c Scripts/ci/build-release-artifacts.sh
- name: Flatten release assets
shell: bash
env:
RELEASE_REF: ${{ github.ref_name }}
run: |
set -euo pipefail
ref="${RELEASE_REF:-manual-${GITHUB_SHA::7}}"
mkdir -p dist/release-assets
find "dist/builds/${ref}" -type f \( -name '*.tar.gz' -o -name '*.zip' -o -name '*.sha256' -o -name 'README.txt' \) -exec cp {} dist/release-assets/ \;
rm -rf dist/builds
cp dist/release-assets/* dist/
find dist -maxdepth 1 -type f -print | sort
- name: Upload release artifacts
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: burrow-release-${{ github.ref_name }}
path: dist/*
if-no-files-found: error
- name: Publish Forgejo release
if: startsWith(github.ref, 'refs/tags/')
shell: bash
env:
RELEASE_TAG: ${{ github.ref_name }}
API_URL: ${{ github.api_url }}
REPOSITORY: ${{ github.repository }}
TOKEN: ${{ github.token }}
run: |
set -euo pipefail
chmod +x Scripts/ci/publish-forgejo-release.sh
nix develop .#ci -c Scripts/ci/publish-forgejo-release.sh

View file

@ -54,7 +54,6 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.85.0
targets: ${{ join(matrix.rust-targets, ', ') }}
- name: Install Protobuf
shell: bash

View file

@ -6,9 +6,6 @@ on:
pull_request:
branches:
- "*"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build Crate (${{ matrix.platform }})
@ -75,14 +72,14 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.85.0
toolchain: stable
components: rustfmt
targets: ${{ join(matrix.targets, ', ') }}
- name: Setup Rust Cache
uses: Swatinem/rust-cache@v2
- name: Build
shell: bash
run: cargo build --locked --verbose --workspace --all-features --target ${{ join(matrix.targets, ' --target ') }} --target ${{ join(matrix.test-targets, ' --target ') }}
run: cargo build --verbose --workspace --all-features --target ${{ join(matrix.targets, ' --target ') }} --target ${{ join(matrix.test-targets, ' --target ') }}
- name: Test
shell: bash
run: cargo test --locked --verbose --workspace --all-features --target ${{ join(matrix.test-targets, ' --target ') }}
run: cargo test --verbose --workspace --all-features --target ${{ join(matrix.test-targets, ' --target ') }}

View file

@ -1,23 +0,0 @@
name: Governance Lint
on:
pull_request:
branches:
- "*"
jobs:
governance:
name: BEP Metadata
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Validate BEP metadata
shell: bash
run: |
set -euo pipefail
python3 Scripts/check-bep-metadata.py

View file

@ -47,7 +47,6 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.85.0
targets: ${{ join(matrix.rust-targets, ', ') }}
- name: Install Protobuf
shell: bash

15
.gitignore vendored
View file

@ -1,10 +1,5 @@
# Xcode
xcuserdata
Apple/build/
.derived-data/
AuthKey_*.p8
burrow-certs-pass.txt
*.p12
# Swift
Apple/Package/.swiftpm/
@ -13,20 +8,10 @@ Apple/Package/.swiftpm/
target/
.env
# Bazel
bazel-*
.DS_STORE
.idea/
tmp/
.cache/
intake/
dist/
publish/
release/
ci-artifacts/
*.db
*.sqlite3
*.sock

View file

@ -1,14 +0,0 @@
# instructions for agents
1. Spell the project name as `Burrow` in user-facing copy and `burrow` in code, package, and protocol identifiers unless an existing integration requires a different literal.
2. Read [CONSTITUTION.md](CONSTITUTION.md) before changing Apple clients, the daemon, the control plane, forge infrastructure, identity, or security-sensitive code.
3. Anchor non-trivial changes in a Burrow Evolution Proposal (BEP) under [evolution/](evolution/README.md) so future contributors can inherit the rationale, safeguards, and rollout shape.
4. Before touching the Apple app, daemon IPC, or Tailnet flows, review:
- [evolution/proposals/BEP-0002-control-plane-bootstrap-and-local-auth.md](evolution/proposals/BEP-0002-control-plane-bootstrap-and-local-auth.md)
- [evolution/proposals/BEP-0003-connect-ip-and-negotiation-roadmap.md](evolution/proposals/BEP-0003-connect-ip-and-negotiation-roadmap.md)
- [evolution/proposals/BEP-0005-daemon-ipc-and-apple-boundary.md](evolution/proposals/BEP-0005-daemon-ipc-and-apple-boundary.md)
- [evolution/proposals/BEP-0006-tailnet-authority-first-control-plane.md](evolution/proposals/BEP-0006-tailnet-authority-first-control-plane.md)
5. Apple clients must talk only to the daemon over gRPC. Do not add direct HTTP, control-plane, or helper-process calls from Swift UI code.
6. Treat Tailnet as one protocol family. Tailscale-managed and self-hosted Headscale-style deployments differ by authority, policy, and auth details, not by a separate user-facing protocol surface.
7. Maintain canonical identity and operator metadata in [contributors.nix](contributors.nix). If Burrow forge, Authentik, Headscale, or admin/group mappings need to change, edit that registry first and derive runtime configuration from it.
8. When process or architecture is unclear, stop and draft or update a BEP instead of improvising durable behavior in code.

View file

@ -1,14 +0,0 @@
# Burrow Android
The Android app starts as a Kotlin shell linked to `crates/burrow-mobile-ffi`,
which exposes the cross-platform `burrow-core` crate through JNI.
The current stub proves the package shape and Rust core boundary. Full VPN
support must use Android `VpnService` and keep platform consent, Play Store
policy, and disclosure requirements in the release lane.
The Bazel entrypoint is:
```sh
bazel build //bazel/android:check_stub_stamp
```

View file

@ -1,21 +0,0 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "net.burrow.android"
compileSdk = 36
defaultConfig {
applicationId = "net.burrow.android"
minSdk = 26
targetSdk = 36
versionCode = 1
versionName = "0.1.0"
}
}
kotlin {
jvmToolchain(17)
}

View file

@ -1,18 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/BurrowTheme">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -1,9 +0,0 @@
package net.burrow.android
object BurrowCore {
init {
System.loadLibrary("burrow_mobile_ffi")
}
external fun version(): String
}

View file

@ -1,32 +0,0 @@
package net.burrow.android
import android.app.Activity
import android.os.Bundle
import android.widget.LinearLayout
import android.widget.TextView
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val versionText = runCatching { BurrowCore.version() }
.getOrElse { "burrow-core unavailable" }
val root = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(40, 40, 40, 40)
}
root.addView(TextView(this).apply {
text = getString(R.string.app_name)
textSize = 24f
})
root.addView(TextView(this).apply {
text = versionText
textSize = 14f
})
setContentView(root)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View file

@ -1,3 +0,0 @@
<resources>
<string name="app_name">Burrow</string>
</resources>

View file

@ -1,8 +0,0 @@
<resources>
<style name="BurrowTheme" parent="android:style/Theme.Material.Light.NoActionBar">
<item name="android:fontFamily">sans</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:navigationBarColor">#ffffff</item>
<item name="android:statusBarColor">#ffffff</item>
</style>
</resources>

View file

@ -1,4 +0,0 @@
plugins {
id("com.android.application") version "8.10.1" apply false
id("org.jetbrains.kotlin.android") version "2.1.21" apply false
}

View file

@ -1,18 +0,0 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "BurrowAndroid"
include(":app")

View file

@ -10,8 +10,6 @@ INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphone*] = YES
INFOPLIST_KEY_UIStatusBarStyle[sdk=iphone*] = UIStatusBarStyleDefault
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad[sdk=iphone*] = UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone[sdk=iphone*] = UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES[sdk=iphone*] = BurrowPanel02 BurrowPanel03 BurrowPanel04 BurrowPanel05 BurrowPanel06
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS[sdk=iphone*] = YES
TARGETED_DEVICE_FAMILY[sdk=iphone*] = 1,2
EXCLUDED_SOURCE_FILE_NAMES = MainMenu.xib
@ -20,10 +18,6 @@ INFOPLIST_KEY_LSUIElement[sdk=macosx*] = YES
INFOPLIST_KEY_NSMainNibFile[sdk=macosx*] = MainMenu
INFOPLIST_KEY_NSPrincipalClass[sdk=macosx*] = NSApplication
INFOPLIST_KEY_LSApplicationCategoryType[sdk=macosx*] = public.app-category.utilities
BURROW_SPARKLE_FEED_URL = https:/$()/releases.burrow.net/sparkle/appcast.xml
BURROW_SPARKLE_PUBLIC_ED_KEY = uugZuJeqvvKd91NZ6F1Fv2cQenUbIG/ZW3L9MuaEz30=
INFOPLIST_KEY_SUFeedURL[sdk=macosx*] = $(BURROW_SPARKLE_FEED_URL)
INFOPLIST_KEY_SUPublicEDKey[sdk=macosx*] = $(BURROW_SPARKLE_PUBLIC_ED_KEY)
CODE_SIGN_ENTITLEMENTS = App/App-iOS.entitlements
CODE_SIGN_ENTITLEMENTS[sdk=macosx*] = App/App-macOS.entitlements

View file

@ -1,19 +1,11 @@
#if os(macOS)
import AppKit
import BurrowUI
import Sparkle
import SwiftUI
@main
@MainActor
class AppDelegate: NSObject, NSApplicationDelegate {
private var windowController: NSWindowController?
private let updaterController = SPUStandardUpdaterController(
startingUpdater: true,
updaterDelegate: nil,
userDriverDelegate: nil
)
private let quitItem: NSMenuItem = {
let quitItem = NSMenuItem(
title: "Quit Burrow",
@ -25,27 +17,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return quitItem
}()
private lazy var openItem: NSMenuItem = {
let item = NSMenuItem(
title: "Open Burrow",
action: #selector(openWindow),
keyEquivalent: "o"
)
item.target = self
item.keyEquivalentModifierMask = .command
return item
}()
private lazy var checkForUpdatesItem: NSMenuItem = {
let item = NSMenuItem(
title: "Check for Updates...",
action: #selector(checkForUpdates(_:)),
keyEquivalent: ""
)
item.target = self
return item
}()
private let toggleItem: NSMenuItem = {
let toggleView = NSHostingView(rootView: MenuItemToggleView())
toggleView.frame.size = CGSize(width: 300, height: 32)
@ -60,8 +31,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let menu = NSMenu()
menu.items = [
toggleItem,
openItem,
checkForUpdatesItem,
.separator(),
quitItem
]
@ -72,7 +41,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let statusBar = NSStatusBar.system
let statusItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
if let button = statusItem.button {
button.image = NSImage(systemSymbolName: "pipe.and.drop.fill", accessibilityDescription: nil)
button.image = NSImage(systemSymbolName: "network.badge.shield.half.filled", accessibilityDescription: nil)
}
return statusItem
}()
@ -80,33 +49,5 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
statusItem.menu = menu
}
@objc
private func openWindow() {
if let window = windowController?.window {
window.makeKeyAndOrderFront(nil)
NSApplication.shared.activate(ignoringOtherApps: true)
return
}
let contentView = BurrowView()
let hostingController = NSHostingController(rootView: contentView)
let window = NSWindow(contentViewController: hostingController)
window.title = "Burrow"
window.setContentSize(NSSize(width: 820, height: 720))
window.styleMask.insert([.titled, .closable, .miniaturizable, .resizable])
window.center()
let controller = NSWindowController(window: window)
controller.shouldCascadeWindows = true
controller.showWindow(nil)
windowController = controller
NSApplication.shared.activate(ignoringOtherApps: true)
}
@objc
private func checkForUpdates(_ sender: Any?) {
updaterController.checkForUpdates(sender)
}
}
#endif

View file

@ -1,46 +1,14 @@
#if !os(macOS)
import BurrowUI
import SwiftUI
#if os(iOS)
import UIKit
#endif
@MainActor
@main
struct BurrowApp: App {
var body: some Scene {
WindowGroup {
#if os(iOS)
BurrowView(appIconManager: UIKitAppIconManager())
#else
BurrowView()
#endif
}
}
}
#if os(iOS)
@MainActor
private struct UIKitAppIconManager: AppIconManaging {
var supportsAlternateIcons: Bool {
UIApplication.shared.supportsAlternateIcons
}
var alternateIconName: String? {
UIApplication.shared.alternateIconName
}
func setAlternateIconName(_ iconName: String?) async throws {
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, any Error>) in
UIApplication.shared.setAlternateIconName(iconName) { error in
if let error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: ())
}
}
}
}
}
#endif
#endif

View file

@ -1,439 +0,0 @@
import XCTest
import UIKit
@MainActor
final class BurrowTailnetLoginUITests: XCTestCase {
private enum TailnetLoginMode: String, Decodable {
case tailscale
case discovered
}
private struct TestConfig: Decodable {
let email: String
let username: String
let password: String
let mode: TailnetLoginMode?
}
override func setUpWithError() throws {
continueAfterFailure = false
}
func testTailnetLoginThroughAuthentikWebSession() throws {
let config = try loadTestConfig()
let email = config.email
let username = config.username
let password = config.password
let mode = config.mode ?? .tailscale
let browserIdentity = mode == .tailscale ? email : username
let app = XCUIApplication()
app.launch()
let tailnetButton = app.buttons["quick-add-tailnet"]
XCTAssertTrue(tailnetButton.waitForExistence(timeout: 15), "Tailnet add button did not appear")
tailnetButton.tap()
configureTailnetIfNeeded(in: app, mode: mode)
let discoveryField = app.textFields["tailnet-discovery-email"]
XCTAssertTrue(discoveryField.waitForExistence(timeout: 10), "Tailnet discovery email field did not appear")
replaceText(in: discoveryField, with: email)
let serverCard = app.descendants(matching: .any)
.matching(identifier: "tailnet-server-card")
.firstMatch
XCTAssertTrue(serverCard.waitForExistence(timeout: 5), "Tailnet server card did not appear")
let signInButton = app.buttons["tailnet-start-sign-in"]
XCTAssertTrue(signInButton.waitForExistence(timeout: 10), "Tailnet sign-in button did not appear")
signInButton.tap()
acceptAuthenticationPromptIfNeeded(in: app, timeout: 20)
let webSession = webAuthenticationSession()
XCTAssertTrue(webSession.waitForExistence(timeout: 20), "Safari authentication session did not appear")
signIntoAuthentik(in: webSession, username: browserIdentity, password: password)
app.activate()
XCTAssertTrue(
waitForTailnetSignedIn(in: app, timeout: 60),
"Tailnet sign-in never reached the running state"
)
}
private func configureTailnetIfNeeded(in app: XCUIApplication, mode: TailnetLoginMode) {
guard mode == .discovered else { return }
openTailnetMenu(in: app)
tapMenuButton(named: "Edit Custom Server", in: app)
openTailnetMenu(in: app)
tapMenuButton(named: "Show Advanced Settings", in: app)
let authorityField = app.textFields["tailnet-authority"]
XCTAssertTrue(authorityField.waitForExistence(timeout: 10), "Tailnet authority field did not appear")
replaceText(in: authorityField, with: "")
}
private func openTailnetMenu(in app: XCUIApplication) {
let moreButton = app.buttons["More"]
XCTAssertTrue(moreButton.waitForExistence(timeout: 5), "Tailnet menu button did not appear")
moreButton.tap()
}
private func tapMenuButton(named title: String, in app: XCUIApplication) {
let menuButton = firstExistingElement(
from: [
app.buttons[title],
app.descendants(matching: .button)[title],
],
timeout: 5
)
XCTAssertTrue(menuButton.exists, "Menu action \(title) did not appear")
menuButton.tap()
}
private func acceptAuthenticationPromptIfNeeded(
in app: XCUIApplication,
timeout: TimeInterval
) {
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let deadline = Date().addingTimeInterval(timeout)
repeat {
let promptCandidates = [
springboard.buttons["Continue"],
springboard.buttons["Allow"],
app.buttons["Continue"],
app.buttons["Allow"],
]
for button in promptCandidates where button.exists && button.isHittable {
button.tap()
return
}
RunLoop.current.run(until: Date().addingTimeInterval(0.25))
} while Date() < deadline
let promptCandidates = [
springboard.buttons["Continue"],
springboard.buttons["Allow"],
app.buttons["Continue"],
app.buttons["Allow"],
]
for button in promptCandidates where button.exists {
button.tap()
return
}
}
private func webAuthenticationSession() -> XCUIApplication {
let safariViewService = XCUIApplication(bundleIdentifier: "com.apple.SafariViewService")
if safariViewService.waitForExistence(timeout: 5) {
return safariViewService
}
let safari = XCUIApplication(bundleIdentifier: "com.apple.mobilesafari")
_ = safari.waitForExistence(timeout: 5)
return safari
}
private func signIntoAuthentik(in webSession: XCUIApplication, username: String, password: String) {
followTailnetRedirectIfNeeded(in: webSession)
if !webSession.exists {
return
}
let immediatePasswordField = firstExistingSecureField(in: webSession, timeout: 2)
if immediatePasswordField.exists {
replaceSecureText(in: immediatePasswordField, within: webSession, with: password)
submitAuthenticationForm(in: webSession, focusedField: immediatePasswordField)
return
}
let usernameField = firstExistingElement(
in: webSession,
queries: [
{ $0.textFields["Username"] },
{ $0.textFields["Email or Username"] },
{ $0.textFields["Email address"] },
{ $0.textFields["Email"] },
{ $0.webViews.textFields["Username"] },
{ $0.webViews.textFields["Email or Username"] },
{ $0.descendants(matching: .textField).firstMatch },
],
timeout: 12
)
if !usernameField.exists {
return
}
replaceText(in: usernameField, with: username)
tapFirstExistingButton(
in: webSession,
titles: ["Continue", "Next", "Sign In", "Log in", "Login"],
timeout: 5
)
let passwordField = firstExistingSecureField(in: webSession, timeout: 20)
XCTAssertTrue(passwordField.exists, "Authentik password field did not appear")
replaceSecureText(in: passwordField, within: webSession, with: password)
submitAuthenticationForm(in: webSession, focusedField: passwordField)
}
private func followTailnetRedirectIfNeeded(in webSession: XCUIApplication) {
let redirectCandidates = [
webSession.links["Found"],
webSession.webViews.links["Found"],
webSession.buttons["Found"],
webSession.webViews.buttons["Found"],
]
let redirectLink = firstExistingElement(from: redirectCandidates, timeout: 8)
if redirectLink.exists {
redirectLink.tap()
}
}
private func firstExistingSecureField(in app: XCUIApplication, timeout: TimeInterval) -> XCUIElement {
let candidates = [
app.descendants(matching: .secureTextField).firstMatch,
app.secureTextFields["Password"],
app.secureTextFields["Password or Token"],
app.webViews.secureTextFields["Password"],
app.webViews.secureTextFields["Password or Token"],
]
return firstExistingElement(from: candidates, timeout: timeout)
}
private func tapFirstExistingButton(
in app: XCUIApplication,
titles: [String],
timeout: TimeInterval
) {
let candidates = titles.flatMap { title in
[
app.buttons[title],
app.webViews.buttons[title],
]
} + [app.descendants(matching: .button).firstMatch]
let button = firstExistingElement(from: candidates, timeout: timeout)
XCTAssertTrue(button.exists, "Expected one of \(titles.joined(separator: ", ")) to appear")
button.tap()
}
private func submitAuthenticationForm(in app: XCUIApplication, focusedField: XCUIElement) {
focus(focusedField)
focusedField.typeText("\n")
if waitForAny(
[
{ !focusedField.exists },
{ !app.staticTexts["Burrow Tailnet Authentication"].exists },
],
timeout: 1.5
) {
return
}
let keyboard = app.keyboards.firstMatch
if keyboard.waitForExistence(timeout: 2) {
let keyboardCandidates = [
"Return",
"return",
"Go",
"go",
"Continue",
"continue",
"Done",
"done",
"Join",
"join",
"Sign In",
"Log In",
"Login",
]
for title in keyboardCandidates {
let key = keyboard.buttons[title]
if key.exists && key.isHittable {
key.tap()
return
}
}
if let lastKey = keyboard.buttons.allElementsBoundByIndex.last,
lastKey.exists,
lastKey.isHittable
{
lastKey.tap()
return
}
}
tapFirstExistingButton(
in: app,
titles: ["Continue", "Sign In", "Log in", "Login"],
timeout: 5
)
}
private func loadTestConfig() throws -> TestConfig {
let environment = ProcessInfo.processInfo.environment
if let email = nonEmptyEnvironment("BURROW_UI_TEST_EMAIL"),
let password = nonEmptyEnvironment("BURROW_UI_TEST_PASSWORD")
{
return TestConfig(
email: email,
username: nonEmptyEnvironment("BURROW_UI_TEST_USERNAME") ?? email,
password: password,
mode: nonEmptyEnvironment("BURROW_UI_TEST_TAILNET_MODE")
.flatMap(TailnetLoginMode.init(rawValue:))
)
}
let configPath = environment["BURROW_UI_TEST_CONFIG_PATH"] ?? "/tmp/burrow-ui-test-config.json"
let configURL = URL(fileURLWithPath: configPath)
guard FileManager.default.fileExists(atPath: configURL.path) else {
throw XCTSkip(
"Missing UI test configuration. Expected env vars or config file at \(configURL.path)"
)
}
let data = try Data(contentsOf: configURL)
return try JSONDecoder().decode(TestConfig.self, from: data)
}
private func nonEmptyEnvironment(_ key: String) -> String? {
guard let value = ProcessInfo.processInfo.environment[key]?
.trimmingCharacters(in: .whitespacesAndNewlines),
!value.isEmpty
else {
return nil
}
return value
}
private func waitForFieldValue(
_ field: XCUIElement,
containing substring: String,
timeout: TimeInterval
) -> Bool {
let predicate = NSPredicate(format: "value CONTAINS %@", substring)
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: field)
return XCTWaiter.wait(for: [expectation], timeout: timeout) == .completed
}
private func waitForButtonLabel(
_ button: XCUIElement,
equals expected: String,
timeout: TimeInterval
) -> Bool {
let predicate = NSPredicate(format: "label == %@", expected)
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: button)
return XCTWaiter.wait(for: [expectation], timeout: timeout) == .completed
}
private func waitForTailnetSignedIn(in app: XCUIApplication, timeout: TimeInterval) -> Bool {
let button = app.buttons["tailnet-start-sign-in"]
let deadline = Date().addingTimeInterval(timeout)
repeat {
acceptAuthenticationPromptIfNeeded(in: app, timeout: 1)
if button.exists, button.label == "Signed In" {
return true
}
RunLoop.current.run(until: Date().addingTimeInterval(0.3))
} while Date() < deadline
return button.exists && button.label == "Signed In"
}
private func waitForAny(_ conditions: [() -> Bool], timeout: TimeInterval) -> Bool {
let deadline = Date().addingTimeInterval(timeout)
repeat {
if conditions.contains(where: { $0() }) {
return true
}
RunLoop.current.run(until: Date().addingTimeInterval(0.2))
} while Date() < deadline
return conditions.contains(where: { $0() })
}
private func firstExistingElement(
in app: XCUIApplication,
queries: [(XCUIApplication) -> XCUIElement],
timeout: TimeInterval
) -> XCUIElement {
firstExistingElement(from: queries.map { $0(app) }, timeout: timeout)
}
private func firstExistingElement(from candidates: [XCUIElement], timeout: TimeInterval) -> XCUIElement {
let deadline = Date().addingTimeInterval(timeout)
repeat {
for candidate in candidates where candidate.exists {
return candidate
}
RunLoop.current.run(until: Date().addingTimeInterval(0.2))
} while Date() < deadline
return candidates[0]
}
private func replaceText(in element: XCUIElement, with value: String) {
focus(element)
clearText(in: element)
element.typeText(value)
}
private func replaceSecureText(in element: XCUIElement, within app: XCUIApplication, with value: String) {
UIPasteboard.general.string = value
focus(element)
for revealMenu in [
{ element.doubleTap() },
{ element.press(forDuration: 1.2) },
] {
revealMenu()
let pasteButton = firstExistingElement(from: pasteCandidates(in: app), timeout: 3)
if pasteButton.exists {
pasteButton.tap()
return
}
}
focus(element)
element.typeText(value)
}
private func clearText(in element: XCUIElement) {
guard let currentValue = element.value as? String, !currentValue.isEmpty else {
return
}
let deleteSequence = String(repeating: XCUIKeyboardKey.delete.rawValue, count: currentValue.count)
element.typeText(deleteSequence)
}
private func focus(_ element: XCUIElement) {
element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
RunLoop.current.run(until: Date().addingTimeInterval(0.3))
}
private func pasteCandidates(in app: XCUIApplication) -> [XCUIElement] {
let pasteLabels = ["Paste", "Incolla", "Paste from Clipboard"]
return pasteLabels.flatMap { label in
[
app.menuItems[label],
app.buttons[label],
app.webViews.buttons[label],
app.descendants(matching: .button).matching(NSPredicate(format: "label == %@", label)).firstMatch,
app.descendants(matching: .menuItem).matching(NSPredicate(format: "label == %@", label)).firstMatch,
]
}
}
}

View file

@ -8,7 +8,6 @@
/* Begin PBXBuildFile section */
D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00AA8962A4669BC005C8102 /* AppDelegate.swift */; };
D11000012F70000100112233 /* BurrowUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D11000042F70000100112233 /* BurrowUITests.swift */; };
D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */; };
D020F65D29E4A697002790F6 /* BurrowNetworkExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D020F65329E4A697002790F6 /* BurrowNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
D03383AD2C8E67E300F7C44E /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = D078F7E22C8DA375008A8CEC /* SwiftProtobuf */; };
@ -17,7 +16,6 @@
D03383B02C8E67E300F7C44E /* NIOTransportServices in Frameworks */ = {isa = PBXBuildFile; productRef = D044EE952C8DAB2800778185 /* NIOTransportServices */; };
D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */; };
D09150422B9D2AF700BE3CB0 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D09150412B9D2AF700BE3CB0 /* MainMenu.xib */; platformFilters = (macos, ); };
D0A11C102F90000100112233 /* DequeModule in Frameworks */ = {isa = PBXBuildFile; productRef = D0A11C0F2F90000100112233 /* DequeModule */; };
D0B1D1102C436152004B7823 /* AsyncAlgorithms in Frameworks */ = {isa = PBXBuildFile; productRef = D0B1D10F2C436152004B7823 /* AsyncAlgorithms */; };
D0BCC6092A09A03E00AD070D /* libburrow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D0BCC6032A09535900AD070D /* libburrow.a */; };
D0BF09522C8E66F6000D8DEC /* BurrowConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */; };
@ -25,6 +23,7 @@
D0D4E53A2C8D996F007F820A /* BurrowCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D0D4E56B2C8D9C2F007F820A /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E49A2C8D921A007F820A /* Logging.swift */; };
D0D4E5702C8D9C62007F820A /* BurrowCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; };
D0D4E5712C8D9C6F007F820A /* HackClub.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E49D2C8D921A007F820A /* HackClub.swift */; };
D0D4E5722C8D9C6F007F820A /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E49E2C8D921A007F820A /* Network.swift */; };
D0D4E5732C8D9C6F007F820A /* WireGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E49F2C8D921A007F820A /* WireGuard.swift */; };
D0D4E5742C8D9C6F007F820A /* BurrowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A22C8D921A007F820A /* BurrowView.swift */; };
@ -34,10 +33,10 @@
D0D4E5782C8D9C6F007F820A /* NetworkExtension+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A62C8D921A007F820A /* NetworkExtension+Async.swift */; };
D0D4E5792C8D9C6F007F820A /* NetworkExtensionTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A72C8D921A007F820A /* NetworkExtensionTunnel.swift */; };
D0D4E57A2C8D9C6F007F820A /* NetworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A82C8D921A007F820A /* NetworkView.swift */; };
D0D4E57B2C8D9C6F007F820A /* OAuth2.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A92C8D921A007F820A /* OAuth2.swift */; };
D0D4E57C2C8D9C6F007F820A /* Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4AA2C8D921A007F820A /* Tunnel.swift */; };
D0D4E57D2C8D9C6F007F820A /* TunnelButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4AB2C8D921A007F820A /* TunnelButton.swift */; };
D0D4E57E2C8D9C6F007F820A /* TunnelStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4AC2C8D921A007F820A /* TunnelStatusView.swift */; };
D0D4E5812C8D9C94007F820A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D0D4E4A12C8D921A007F820A /* Assets.xcassets */; };
D0D4E5892C8D9C94007F820A /* BurrowUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5582C8D9BF2007F820A /* BurrowUI.framework */; };
D0D4E58A2C8D9C9E007F820A /* BurrowUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5582C8D9BF2007F820A /* BurrowUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D0D4E58B2C8D9CA4007F820A /* BurrowConfiguration.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@ -45,21 +44,13 @@
D0D4E5A62C8D9E65007F820A /* BurrowCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; };
D0F4FAD32C8DC79C0068730A /* BurrowCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; };
D0F7594E2C8DAB6B00126CF3 /* GRPC in Frameworks */ = {isa = PBXBuildFile; productRef = D078F7E02C8DA375008A8CEC /* GRPC */; };
D0FA10012D10200100112233 /* burrow.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA10032D10200100112233 /* burrow.pb.swift */; };
D0FA10022D10200100112233 /* burrow.grpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA10042D10200100112233 /* burrow.grpc.swift */; };
D0F759612C8DB24B00126CF3 /* grpc-swift-config.json in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4962C8D921A007F820A /* grpc-swift-config.json */; };
D0F759622C8DB24B00126CF3 /* swift-protobuf-config.json in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4972C8D921A007F820A /* swift-protobuf-config.json */; };
D0F7597E2C8DB30500126CF3 /* CGRPCZlib in Frameworks */ = {isa = PBXBuildFile; productRef = D0F7597D2C8DB30500126CF3 /* CGRPCZlib */; };
D0F7598D2C8DB3DA00126CF3 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4992C8D921A007F820A /* Client.swift */; };
D0C0DE102FA0000100112233 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; platformFilters = (macos, ); productRef = D0C0DE122FA0000100112233 /* Sparkle */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
D11000022F70000100112233 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */;
proxyType = 1;
remoteGlobalIDString = D05B9F7129E39EEC008CB1F9;
remoteInfo = App;
};
D020F65B29E4A697002790F6 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */;
@ -141,9 +132,6 @@
/* Begin PBXFileReference section */
D00117422B30348D00D87C25 /* Configuration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Configuration.xcconfig; sourceTree = "<group>"; };
D00AA8962A4669BC005C8102 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
D11000032F70000100112233 /* BurrowUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BurrowUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
D11000042F70000100112233 /* BurrowUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowUITests.swift; sourceTree = "<group>"; };
D11000052F70000100112233 /* UITests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UITests.xcconfig; sourceTree = "<group>"; };
D020F63D29E4A1FF002790F6 /* Identity.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Identity.xcconfig; sourceTree = "<group>"; };
D020F64029E4A1FF002790F6 /* Compiler.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Compiler.xcconfig; sourceTree = "<group>"; };
D020F64229E4A1FF002790F6 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -168,8 +156,11 @@
D0BCC6032A09535900AD070D /* libburrow.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libburrow.a; sourceTree = BUILT_PRODUCTS_DIR; };
D0BF09582C8E6789000D8DEC /* UI.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UI.xcconfig; sourceTree = "<group>"; };
D0D4E4952C8D921A007F820A /* burrow.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = burrow.proto; sourceTree = "<group>"; };
D0D4E4962C8D921A007F820A /* grpc-swift-config.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "grpc-swift-config.json"; sourceTree = "<group>"; };
D0D4E4972C8D921A007F820A /* swift-protobuf-config.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "swift-protobuf-config.json"; sourceTree = "<group>"; };
D0D4E4992C8D921A007F820A /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = "<group>"; };
D0D4E49A2C8D921A007F820A /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; };
D0D4E49D2C8D921A007F820A /* HackClub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HackClub.swift; sourceTree = "<group>"; };
D0D4E49E2C8D921A007F820A /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = "<group>"; };
D0D4E49F2C8D921A007F820A /* WireGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuard.swift; sourceTree = "<group>"; };
D0D4E4A12C8D921A007F820A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -180,6 +171,7 @@
D0D4E4A62C8D921A007F820A /* NetworkExtension+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkExtension+Async.swift"; sourceTree = "<group>"; };
D0D4E4A72C8D921A007F820A /* NetworkExtensionTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkExtensionTunnel.swift; sourceTree = "<group>"; };
D0D4E4A82C8D921A007F820A /* NetworkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkView.swift; sourceTree = "<group>"; };
D0D4E4A92C8D921A007F820A /* OAuth2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2.swift; sourceTree = "<group>"; };
D0D4E4AA2C8D921A007F820A /* Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tunnel.swift; sourceTree = "<group>"; };
D0D4E4AB2C8D921A007F820A /* TunnelButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelButton.swift; sourceTree = "<group>"; };
D0D4E4AC2C8D921A007F820A /* TunnelStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatusView.swift; sourceTree = "<group>"; };
@ -191,18 +183,9 @@
D0D4E58E2C8D9D0A007F820A /* Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = "<group>"; };
D0D4E58F2C8D9D0A007F820A /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
D0D4E5902C8D9D0A007F820A /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
D0FA10032D10200100112233 /* burrow.pb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Generated/burrow.pb.swift; sourceTree = "<group>"; };
D0FA10042D10200100112233 /* burrow.grpc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Generated/burrow.grpc.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
D11000062F70000100112233 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
D020F65029E4A697002790F6 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -221,7 +204,6 @@
D0BF09552C8E66FD000D8DEC /* BurrowConfiguration.framework in Frameworks */,
D0F4FAD32C8DC79C0068730A /* BurrowCore.framework in Frameworks */,
D0D4E5892C8D9C94007F820A /* BurrowUI.framework in Frameworks */,
D0C0DE102FA0000100112233 /* Sparkle in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -242,7 +224,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D0A11C102F90000100112233 /* DequeModule in Frameworks */,
D0D4E5702C8D9C62007F820A /* BurrowCore.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -266,7 +247,6 @@
D0D4E4F72C8D941D007F820A /* Framework.xcconfig */,
D020F64029E4A1FF002790F6 /* Compiler.xcconfig */,
D0D4E4F62C8D932D007F820A /* Debug.xcconfig */,
D11000052F70000100112233 /* UITests.xcconfig */,
D04A3E1D2BAF465F0043EC85 /* Version.xcconfig */,
D020F64229E4A1FF002790F6 /* Info.plist */,
D0D4E5912C8D9D0A007F820A /* Constants */,
@ -292,7 +272,6 @@
isa = PBXGroup;
children = (
D05B9F7429E39EEC008CB1F9 /* App */,
D11000072F70000100112233 /* AppUITests */,
D020F65629E4A697002790F6 /* NetworkExtension */,
D0D4E49C2C8D921A007F820A /* Core */,
D0D4E4AD2C8D921A007F820A /* UI */,
@ -306,7 +285,6 @@
isa = PBXGroup;
children = (
D05B9F7229E39EEC008CB1F9 /* Burrow.app */,
D11000032F70000100112233 /* BurrowUITests.xctest */,
D020F65329E4A697002790F6 /* BurrowNetworkExtension.appex */,
D0BCC6032A09535900AD070D /* libburrow.a */,
D0D4E5312C8D996F007F820A /* BurrowCore.framework */,
@ -329,14 +307,6 @@
path = App;
sourceTree = "<group>";
};
D11000072F70000100112233 /* AppUITests */ = {
isa = PBXGroup;
children = (
D11000042F70000100112233 /* BurrowUITests.swift */,
);
path = AppUITests;
sourceTree = "<group>";
};
D0B98FD729FDDB57004E7149 /* libburrow */ = {
isa = PBXGroup;
children = (
@ -351,8 +321,8 @@
isa = PBXGroup;
children = (
D0D4E4952C8D921A007F820A /* burrow.proto */,
D0FA10032D10200100112233 /* burrow.pb.swift */,
D0FA10042D10200100112233 /* burrow.grpc.swift */,
D0D4E4962C8D921A007F820A /* grpc-swift-config.json */,
D0D4E4972C8D921A007F820A /* swift-protobuf-config.json */,
);
path = Client;
sourceTree = "<group>";
@ -370,6 +340,7 @@
D0D4E4A02C8D921A007F820A /* Networks */ = {
isa = PBXGroup;
children = (
D0D4E49D2C8D921A007F820A /* HackClub.swift */,
D0D4E49E2C8D921A007F820A /* Network.swift */,
D0D4E49F2C8D921A007F820A /* WireGuard.swift */,
);
@ -387,6 +358,7 @@
D0D4E4A62C8D921A007F820A /* NetworkExtension+Async.swift */,
D0D4E4A72C8D921A007F820A /* NetworkExtensionTunnel.swift */,
D0D4E4A82C8D921A007F820A /* NetworkView.swift */,
D0D4E4A92C8D921A007F820A /* OAuth2.swift */,
D0D4E4AA2C8D921A007F820A /* Tunnel.swift */,
D0D4E4AB2C8D921A007F820A /* TunnelButton.swift */,
D0D4E4AC2C8D921A007F820A /* TunnelStatusView.swift */,
@ -409,24 +381,6 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
D11000082F70000100112233 /* BurrowUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = D110000E2F70000100112233 /* Build configuration list for PBXNativeTarget "BurrowUITests" */;
buildPhases = (
D110000A2F70000100112233 /* Sources */,
D11000062F70000100112233 /* Frameworks */,
D11000092F70000100112233 /* Resources */,
);
buildRules = (
);
dependencies = (
D110000B2F70000100112233 /* PBXTargetDependency */,
);
name = BurrowUITests;
productName = BurrowUITests;
productReference = D11000032F70000100112233 /* BurrowUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
D020F65229E4A697002790F6 /* NetworkExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = D020F65E29E4A697002790F6 /* Build configuration list for PBXNativeTarget "NetworkExtension" */;
@ -465,9 +419,6 @@
D020F65C29E4A697002790F6 /* PBXTargetDependency */,
);
name = App;
packageProductDependencies = (
D0C0DE122FA0000100112233 /* Sparkle */,
);
productName = Burrow;
productReference = D05B9F7229E39EEC008CB1F9 /* Burrow.app */;
productType = "com.apple.product-type.application";
@ -483,6 +434,8 @@
);
dependencies = (
D0F7598A2C8DB34200126CF3 /* PBXTargetDependency */,
D0F7595E2C8DB24400126CF3 /* PBXTargetDependency */,
D0F759602C8DB24400126CF3 /* PBXTargetDependency */,
);
name = Core;
packageProductDependencies = (
@ -512,7 +465,6 @@
);
name = UI;
packageProductDependencies = (
D0A11C0F2F90000100112233 /* DequeModule */,
);
productName = Core;
productReference = D0D4E5582C8D9BF2007F820A /* BurrowUI.framework */;
@ -546,10 +498,6 @@
LastSwiftUpdateCheck = 1600;
LastUpgradeCheck = 1520;
TargetAttributes = {
D11000082F70000100112233 = {
CreatedOnToolsVersion = 16.0;
TestTargetID = D05B9F7129E39EEC008CB1F9;
};
D020F65229E4A697002790F6 = {
CreatedOnToolsVersion = 14.3;
};
@ -576,15 +524,12 @@
D0D4E4852C8D8F29007F820A /* XCRemoteSwiftPackageReference "swift-protobuf" */,
D044EE8F2C8DAB2000778185 /* XCRemoteSwiftPackageReference "swift-nio" */,
D044EE942C8DAB2800778185 /* XCRemoteSwiftPackageReference "swift-nio-transport-services" */,
D0A11C0D2F90000100112233 /* XCRemoteSwiftPackageReference "swift-collections" */,
D0C0DE112FA0000100112233 /* XCRemoteSwiftPackageReference "Sparkle" */,
);
productRefGroup = D05B9F7329E39EEC008CB1F9 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
D05B9F7129E39EEC008CB1F9 /* App */,
D11000082F70000100112233 /* BurrowUITests */,
D020F65229E4A697002790F6 /* NetworkExtension */,
D0D4E5502C8D9BF2007F820A /* UI */,
D0D4E5302C8D996F007F820A /* Core */,
@ -594,19 +539,11 @@
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
D11000092F70000100112233 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
D05B9F7029E39EEC008CB1F9 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D09150422B9D2AF700BE3CB0 /* MainMenu.xib in Resources */,
D0D4E5812C8D9C94007F820A /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -665,14 +602,6 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
D110000A2F70000100112233 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D11000012F70000100112233 /* BurrowUITests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D020F64F29E4A697002790F6 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -694,8 +623,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D0FA10012D10200100112233 /* burrow.pb.swift in Sources */,
D0FA10022D10200100112233 /* burrow.grpc.swift in Sources */,
D0F759612C8DB24B00126CF3 /* grpc-swift-config.json in Sources */,
D0F759622C8DB24B00126CF3 /* swift-protobuf-config.json in Sources */,
D0F7598D2C8DB3DA00126CF3 /* Client.swift in Sources */,
D0D4E56B2C8D9C2F007F820A /* Logging.swift in Sources */,
);
@ -705,6 +634,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D0D4E5712C8D9C6F007F820A /* HackClub.swift in Sources */,
D0D4E5722C8D9C6F007F820A /* Network.swift in Sources */,
D0D4E5732C8D9C6F007F820A /* WireGuard.swift in Sources */,
D0D4E5742C8D9C6F007F820A /* BurrowView.swift in Sources */,
@ -714,6 +644,7 @@
D0D4E5782C8D9C6F007F820A /* NetworkExtension+Async.swift in Sources */,
D0D4E5792C8D9C6F007F820A /* NetworkExtensionTunnel.swift in Sources */,
D0D4E57A2C8D9C6F007F820A /* NetworkView.swift in Sources */,
D0D4E57B2C8D9C6F007F820A /* OAuth2.swift in Sources */,
D0D4E57C2C8D9C6F007F820A /* Tunnel.swift in Sources */,
D0D4E57D2C8D9C6F007F820A /* TunnelButton.swift in Sources */,
D0D4E57E2C8D9C6F007F820A /* TunnelStatusView.swift in Sources */,
@ -731,11 +662,6 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
D110000B2F70000100112233 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D05B9F7129E39EEC008CB1F9 /* App */;
targetProxy = D11000022F70000100112233 /* PBXContainerItemProxy */;
};
D020F65C29E4A697002790F6 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D020F65229E4A697002790F6 /* NetworkExtension */;
@ -771,6 +697,14 @@
target = D0D4E5302C8D996F007F820A /* Core */;
targetProxy = D0F4FAD12C8DC7960068730A /* PBXContainerItemProxy */;
};
D0F7595E2C8DB24400126CF3 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = D0F7595D2C8DB24400126CF3 /* GRPCSwiftPlugin */;
};
D0F759602C8DB24400126CF3 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = D0F7595F2C8DB24400126CF3 /* SwiftProtobufPlugin */;
};
D0F7598A2C8DB34200126CF3 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = D0F759892C8DB34200126CF3 /* GRPC */;
@ -778,20 +712,6 @@
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
D110000C2F70000100112233 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D11000052F70000100112233 /* UITests.xcconfig */;
buildSettings = {
};
name = Debug;
};
D110000D2F70000100112233 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D11000052F70000100112233 /* UITests.xcconfig */;
buildSettings = {
};
name = Release;
};
D020F65F29E4A697002790F6 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D020F66229E4A6E5002790F6 /* NetworkExtension.xcconfig */;
@ -879,15 +799,6 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
D110000E2F70000100112233 /* Build configuration list for PBXNativeTarget "BurrowUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D110000C2F70000100112233 /* Debug */,
D110000D2F70000100112233 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D020F65E29E4A697002790F6 /* Build configuration list for PBXNativeTarget "NetworkExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@ -961,14 +872,6 @@
minimumVersion = 1.21.0;
};
};
D0A11C0D2F90000100112233 /* XCRemoteSwiftPackageReference "swift-collections" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-collections.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.1.3;
};
};
D0B1D10E2C436152004B7823 /* XCRemoteSwiftPackageReference "swift-async-algorithms" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-async-algorithms.git";
@ -977,14 +880,6 @@
minimumVersion = 1.0.1;
};
};
D0C0DE112FA0000100112233 /* XCRemoteSwiftPackageReference "Sparkle" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/sparkle-project/Sparkle.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.9.2;
};
};
D0D4E4822C8D8EF6007F820A /* XCRemoteSwiftPackageReference "grpc-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/grpc/grpc-swift.git";
@ -1029,20 +924,20 @@
package = D0D4E4852C8D8F29007F820A /* XCRemoteSwiftPackageReference "swift-protobuf" */;
productName = SwiftProtobuf;
};
D0A11C0F2F90000100112233 /* DequeModule */ = {
isa = XCSwiftPackageProductDependency;
package = D0A11C0D2F90000100112233 /* XCRemoteSwiftPackageReference "swift-collections" */;
productName = DequeModule;
};
D0B1D10F2C436152004B7823 /* AsyncAlgorithms */ = {
isa = XCSwiftPackageProductDependency;
package = D0B1D10E2C436152004B7823 /* XCRemoteSwiftPackageReference "swift-async-algorithms" */;
productName = AsyncAlgorithms;
};
D0C0DE122FA0000100112233 /* Sparkle */ = {
D0F7595D2C8DB24400126CF3 /* GRPCSwiftPlugin */ = {
isa = XCSwiftPackageProductDependency;
package = D0C0DE112FA0000100112233 /* XCRemoteSwiftPackageReference "Sparkle" */;
productName = Sparkle;
package = D0D4E4822C8D8EF6007F820A /* XCRemoteSwiftPackageReference "grpc-swift" */;
productName = "plugin:GRPCSwiftPlugin";
};
D0F7595F2C8DB24400126CF3 /* SwiftProtobufPlugin */ = {
isa = XCSwiftPackageProductDependency;
package = D0D4E4852C8D8F29007F820A /* XCRemoteSwiftPackageReference "swift-protobuf" */;
productName = "plugin:SwiftProtobufPlugin";
};
D0F7597D2C8DB30500126CF3 /* CGRPCZlib */ = {
isa = XCSwiftPackageProductDependency;

View file

@ -1,5 +1,5 @@
{
"originHash" : "5d81b59cbecacd7aae6aa598b32e3513c636250dadab9280a55b905bb0e0ee60",
"originHash" : "fa512b990383b7e309c5854a5279817052294a8191a6d3c55c49cfb38e88c0c3",
"pins" : [
{
"identity" : "grpc-swift",
@ -10,15 +10,6 @@
"version" : "1.23.0"
}
},
{
"identity" : "sparkle",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sparkle-project/Sparkle.git",
"state" : {
"revision" : "6276ba2b404829d139c45ff98427cf90e2efc59b",
"version" : "2.9.2"
}
},
{
"identity" : "swift-async-algorithms",
"kind" : "remoteSourceControl",

View file

@ -28,20 +28,7 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "NO">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D11000082F70000100112233"
BuildableName = "BurrowUITests.xctest"
BlueprintName = "BurrowUITests"
ReferencedContainer = "container:Burrow.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"

View file

@ -1,41 +0,0 @@
# Apple Certificates
Public Apple certificate material that can be inspected by release tooling lives
here. Private keys do not belong in this directory.
## Developer ID Application
- Apple certificate id: `9JKN6HXBHC`
- Certificate file: `Apple/Certificates/developer-id-application-9JKN6HXBHC.cer`
- Subject: `Developer ID Application: Conrad Kramer (87PW93R2ZR)`
- Issuer: `Developer ID Certification Authority`, `G2`
- Team id: `87PW93R2ZR`
- Validity: `2026-06-07T12:13:47Z` through `2031-06-08T12:13:46Z`
- SHA-256 fingerprint: `7B:2D:58:A5:BB:1E:34:5E:FB:FF:7F:E0:A1:CA:4F:B1:5B:6E:08:AF:CE:FD:BE:D0:2B:0A:E9:9C:82:E3:4F:74`
- Google KMS key:
`projects/project-88c23ce9-918a-470a-b33/locations/global/keyRings/burrow-identity/cryptoKeys/apple-developer-id-application/cryptoKeyVersions/1`
- KMS public-key SHA-256:
`9cfaf4755158f445f20a215ef52e7a7ba00da1a310e2e934ba7db5dbb29d2869`
The certificate was created from a CSR signed by the non-exportable Google KMS
key above. There is no `.p12` for this identity. Release signing must use a
signer that delegates the relevant Apple code-signing operations to Google KMS.
## iOS Distribution
- Apple certificate id: `3G42677598`
- Certificate file: `Apple/Certificates/ios-distribution-3G42677598.cer`
- Subject: `iPhone Distribution: Conrad Kramer (87PW93R2ZR)`
- Issuer: `Apple Worldwide Developer Relations Certification Authority`, `G3`
- Team id: `87PW93R2ZR`
- Validity: `2026-06-07T12:21:32Z` through `2027-06-07T12:21:31Z`
- SHA-256 fingerprint: `5A:2F:BA:6E:FB:CA:2D:58:01:1F:A0:C8:0F:CF:6B:58:BE:AA:73:7F:4E:F8:06:1C:8A:14:1B:22:8A:2A:29:2B`
- Google KMS key:
`projects/project-88c23ce9-918a-470a-b33/locations/global/keyRings/burrow-identity/cryptoKeys/apple-ios-distribution/cryptoKeyVersions/1`
- KMS public-key SHA-256:
`f8613ce059de90b29acf01cbd4a7deb37567653bd154bdf6c447098b5c4fae91`
The certificate was created from a CSR signed by the non-exportable Google KMS
key above through App Store Connect certificate creation. There is no `.p12`
for this identity. iOS signing requires a signing path that can delegate Apple
code-signing operations to Google KMS.

View file

@ -40,4 +40,5 @@ APP_GROUP_IDENTIFIER = group.$(APP_BUNDLE_IDENTIFIER)
APP_GROUP_IDENTIFIER[sdk=macosx*] = $(DEVELOPMENT_TEAM).$(APP_BUNDLE_IDENTIFIER)
NETWORK_EXTENSION_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).network
OTHER_SWIFT_FLAGS = $(inherited)
// https://github.com/grpc/grpc-swift/issues/683#issuecomment-1130118953
OTHER_SWIFT_FLAGS = $(inherited) -Xcc -fmodule-map-file=$(GENERATED_MODULEMAP_DIR)/CNIOAtomics.modulemap -Xcc -fmodule-map-file=$(GENERATED_MODULEMAP_DIR)/CNIODarwin.modulemap -Xcc -fmodule-map-file=$(GENERATED_MODULEMAP_DIR)/CGRPCZlib.modulemap

View file

@ -1,5 +1,4 @@
@_implementationOnly import CConstants
import Foundation
import OSLog
public enum Constants {
@ -28,26 +27,9 @@ public enum Constants {
private static let _groupContainerURL: Result<URL, Error> = {
switch FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) {
case .some(let url): .success(url)
case .none:
fallbackContainerURL().mapError { _ in .invalidAppGroupIdentifier }
case .none: .failure(.invalidAppGroupIdentifier)
}
}()
private static func fallbackContainerURL() -> Result<URL, any Swift.Error> {
#if targetEnvironment(simulator)
Result {
// The simulator app's Application Support path lives inside its sandbox container,
// so the host daemon cannot reach it. Use a shared host temp location instead.
let url = URL(filePath: "/tmp", directoryHint: .isDirectory)
.appending(component: bundleIdentifier, directoryHint: .isDirectory)
.appending(component: "SimulatorFallback", directoryHint: .isDirectory)
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
return url
}
#else
.failure(Error.invalidAppGroupIdentifier)
#endif
}
}
extension Logger {

View file

@ -4,9 +4,5 @@
<dict>
<key>CFBundleName</key>
<string>$(INFOPLIST_KEY_CFBundleDisplayName)</string>
<key>SUFeedURL</key>
<string>$(BURROW_SPARKLE_FEED_URL)</string>
<key>SUPublicEDKey</key>
<string>$(BURROW_SPARKLE_PUBLIC_ED_KEY)</string>
</dict>
</plist>

View file

@ -1,14 +0,0 @@
#include "Compiler.xcconfig"
SUPPORTED_PLATFORMS = iphonesimulator iphoneos
TARGETED_DEVICE_FAMILY[sdk=iphone*] = 1,2
PRODUCT_NAME = $(TARGET_NAME)
PRODUCT_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).uitests
STRING_CATALOG_GENERATE_SYMBOLS = NO
SWIFT_EMIT_LOC_STRINGS = NO
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks
TEST_TARGET_NAME = App

View file

@ -1,7 +1,5 @@
import Foundation
import GRPC
import NIOTransportServices
import SwiftProtobuf
public typealias TunnelClient = Burrow_TunnelAsyncClient
public typealias NetworksClient = Burrow_NetworksAsyncClient
@ -32,477 +30,3 @@ extension NetworksClient: Client {
self.init(channel: channel, defaultCallOptions: .init(), interceptors: .none)
}
}
public struct Burrow_TailnetDiscoverRequest: Sendable {
public var email: String = ""
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct Burrow_TailnetDiscoverResponse: Sendable {
public var domain: String = ""
public var authority: String = ""
public var oidcIssuer: String = ""
public var managed: Bool = false
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct Burrow_TailnetProbeRequest: Sendable {
public var authority: String = ""
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct Burrow_TailnetProbeResponse: Sendable {
public var authority: String = ""
public var statusCode: Int32 = 0
public var summary: String = ""
public var detail: String = ""
public var reachable: Bool = false
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct Burrow_TailnetLoginStartRequest: Sendable {
public var accountName: String = ""
public var identityName: String = ""
public var hostname: String = ""
public var authority: String = ""
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct Burrow_TailnetLoginStatusRequest: Sendable {
public var sessionID: String = ""
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct Burrow_TailnetLoginCancelRequest: Sendable {
public var sessionID: String = ""
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct Burrow_TailnetLoginStatusResponse: Sendable {
public var sessionID: String = ""
public var backendState: String = ""
public var authURL: String = ""
public var running: Bool = false
public var needsLogin: Bool = false
public var tailnetName: String = ""
public var magicDNSSuffix: String = ""
public var selfDNSName: String = ""
public var tailnetIPs: [String] = []
public var health: [String] = []
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct Burrow_TunnelPacket: Sendable {
public var payload = Data()
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
extension Burrow_TailnetDiscoverRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = "burrow.TailnetDiscoverRequest"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "email")
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularStringField(value: &self.email)
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.email.isEmpty {
try visitor.visitSingularStringField(value: self.email, fieldNumber: 1)
}
try unknownFields.traverse(visitor: &visitor)
}
}
extension Burrow_TailnetDiscoverResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = "burrow.TailnetDiscoverResponse"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "domain"),
2: .same(proto: "authority"),
3: .same(proto: "oidc_issuer"),
4: .same(proto: "managed"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularStringField(value: &self.domain)
case 2: try decoder.decodeSingularStringField(value: &self.authority)
case 3: try decoder.decodeSingularStringField(value: &self.oidcIssuer)
case 4: try decoder.decodeSingularBoolField(value: &self.managed)
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.domain.isEmpty {
try visitor.visitSingularStringField(value: self.domain, fieldNumber: 1)
}
if !self.authority.isEmpty {
try visitor.visitSingularStringField(value: self.authority, fieldNumber: 2)
}
if !self.oidcIssuer.isEmpty {
try visitor.visitSingularStringField(value: self.oidcIssuer, fieldNumber: 3)
}
if self.managed {
try visitor.visitSingularBoolField(value: self.managed, fieldNumber: 4)
}
try unknownFields.traverse(visitor: &visitor)
}
}
extension Burrow_TailnetProbeRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = "burrow.TailnetProbeRequest"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "authority")
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularStringField(value: &self.authority)
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.authority.isEmpty {
try visitor.visitSingularStringField(value: self.authority, fieldNumber: 1)
}
try unknownFields.traverse(visitor: &visitor)
}
}
extension Burrow_TailnetProbeResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = "burrow.TailnetProbeResponse"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "authority"),
2: .same(proto: "status_code"),
3: .same(proto: "summary"),
4: .same(proto: "detail"),
5: .same(proto: "reachable"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularStringField(value: &self.authority)
case 2: try decoder.decodeSingularInt32Field(value: &self.statusCode)
case 3: try decoder.decodeSingularStringField(value: &self.summary)
case 4: try decoder.decodeSingularStringField(value: &self.detail)
case 5: try decoder.decodeSingularBoolField(value: &self.reachable)
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.authority.isEmpty {
try visitor.visitSingularStringField(value: self.authority, fieldNumber: 1)
}
if self.statusCode != 0 {
try visitor.visitSingularInt32Field(value: self.statusCode, fieldNumber: 2)
}
if !self.summary.isEmpty {
try visitor.visitSingularStringField(value: self.summary, fieldNumber: 3)
}
if !self.detail.isEmpty {
try visitor.visitSingularStringField(value: self.detail, fieldNumber: 4)
}
if self.reachable {
try visitor.visitSingularBoolField(value: self.reachable, fieldNumber: 5)
}
try unknownFields.traverse(visitor: &visitor)
}
}
extension Burrow_TailnetLoginStartRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = "burrow.TailnetLoginStartRequest"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "account_name"),
2: .standard(proto: "identity_name"),
3: .same(proto: "hostname"),
4: .same(proto: "authority"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularStringField(value: &self.accountName)
case 2: try decoder.decodeSingularStringField(value: &self.identityName)
case 3: try decoder.decodeSingularStringField(value: &self.hostname)
case 4: try decoder.decodeSingularStringField(value: &self.authority)
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.accountName.isEmpty {
try visitor.visitSingularStringField(value: self.accountName, fieldNumber: 1)
}
if !self.identityName.isEmpty {
try visitor.visitSingularStringField(value: self.identityName, fieldNumber: 2)
}
if !self.hostname.isEmpty {
try visitor.visitSingularStringField(value: self.hostname, fieldNumber: 3)
}
if !self.authority.isEmpty {
try visitor.visitSingularStringField(value: self.authority, fieldNumber: 4)
}
try unknownFields.traverse(visitor: &visitor)
}
}
extension Burrow_TailnetLoginStatusRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = "burrow.TailnetLoginStatusRequest"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "session_id")
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularStringField(value: &self.sessionID)
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.sessionID.isEmpty {
try visitor.visitSingularStringField(value: self.sessionID, fieldNumber: 1)
}
try unknownFields.traverse(visitor: &visitor)
}
}
extension Burrow_TailnetLoginCancelRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = "burrow.TailnetLoginCancelRequest"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "session_id")
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularStringField(value: &self.sessionID)
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.sessionID.isEmpty {
try visitor.visitSingularStringField(value: self.sessionID, fieldNumber: 1)
}
try unknownFields.traverse(visitor: &visitor)
}
}
extension Burrow_TailnetLoginStatusResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = "burrow.TailnetLoginStatusResponse"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "session_id"),
2: .standard(proto: "backend_state"),
3: .standard(proto: "auth_url"),
4: .same(proto: "running"),
5: .standard(proto: "needs_login"),
6: .standard(proto: "tailnet_name"),
7: .standard(proto: "magic_dns_suffix"),
8: .standard(proto: "self_dns_name"),
9: .standard(proto: "tailnet_ips"),
10: .same(proto: "health"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularStringField(value: &self.sessionID)
case 2: try decoder.decodeSingularStringField(value: &self.backendState)
case 3: try decoder.decodeSingularStringField(value: &self.authURL)
case 4: try decoder.decodeSingularBoolField(value: &self.running)
case 5: try decoder.decodeSingularBoolField(value: &self.needsLogin)
case 6: try decoder.decodeSingularStringField(value: &self.tailnetName)
case 7: try decoder.decodeSingularStringField(value: &self.magicDNSSuffix)
case 8: try decoder.decodeSingularStringField(value: &self.selfDNSName)
case 9: try decoder.decodeRepeatedStringField(value: &self.tailnetIPs)
case 10: try decoder.decodeRepeatedStringField(value: &self.health)
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.sessionID.isEmpty {
try visitor.visitSingularStringField(value: self.sessionID, fieldNumber: 1)
}
if !self.backendState.isEmpty {
try visitor.visitSingularStringField(value: self.backendState, fieldNumber: 2)
}
if !self.authURL.isEmpty {
try visitor.visitSingularStringField(value: self.authURL, fieldNumber: 3)
}
if self.running {
try visitor.visitSingularBoolField(value: self.running, fieldNumber: 4)
}
if self.needsLogin {
try visitor.visitSingularBoolField(value: self.needsLogin, fieldNumber: 5)
}
if !self.tailnetName.isEmpty {
try visitor.visitSingularStringField(value: self.tailnetName, fieldNumber: 6)
}
if !self.magicDNSSuffix.isEmpty {
try visitor.visitSingularStringField(value: self.magicDNSSuffix, fieldNumber: 7)
}
if !self.selfDNSName.isEmpty {
try visitor.visitSingularStringField(value: self.selfDNSName, fieldNumber: 8)
}
if !self.tailnetIPs.isEmpty {
try visitor.visitRepeatedStringField(value: self.tailnetIPs, fieldNumber: 9)
}
if !self.health.isEmpty {
try visitor.visitRepeatedStringField(value: self.health, fieldNumber: 10)
}
try unknownFields.traverse(visitor: &visitor)
}
}
extension Burrow_TunnelPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = "burrow.TunnelPacket"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "payload")
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularBytesField(value: &self.payload)
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.payload.isEmpty {
try visitor.visitSingularBytesField(value: self.payload, fieldNumber: 1)
}
try unknownFields.traverse(visitor: &visitor)
}
}
public struct TailnetClient: Client, GRPCClient {
public let channel: GRPCChannel
public var defaultCallOptions: CallOptions
public init(channel: any GRPCChannel) {
self.channel = channel
self.defaultCallOptions = .init()
}
public func discover(
_ request: Burrow_TailnetDiscoverRequest,
callOptions: CallOptions? = nil
) async throws -> Burrow_TailnetDiscoverResponse {
try await self.performAsyncUnaryCall(
path: "/burrow.TailnetControl/Discover",
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: []
)
}
public func probe(
_ request: Burrow_TailnetProbeRequest,
callOptions: CallOptions? = nil
) async throws -> Burrow_TailnetProbeResponse {
try await self.performAsyncUnaryCall(
path: "/burrow.TailnetControl/Probe",
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: []
)
}
public func loginStart(
_ request: Burrow_TailnetLoginStartRequest,
callOptions: CallOptions? = nil
) async throws -> Burrow_TailnetLoginStatusResponse {
try await self.performAsyncUnaryCall(
path: "/burrow.TailnetControl/LoginStart",
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: []
)
}
public func loginStatus(
_ request: Burrow_TailnetLoginStatusRequest,
callOptions: CallOptions? = nil
) async throws -> Burrow_TailnetLoginStatusResponse {
try await self.performAsyncUnaryCall(
path: "/burrow.TailnetControl/LoginStatus",
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: []
)
}
public func loginCancel(
_ request: Burrow_TailnetLoginCancelRequest,
callOptions: CallOptions? = nil
) async throws -> Burrow_Empty {
try await self.performAsyncUnaryCall(
path: "/burrow.TailnetControl/LoginCancel",
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: []
)
}
}
public struct TunnelPacketClient: Client, GRPCClient {
public let channel: GRPCChannel
public var defaultCallOptions: CallOptions
public init(channel: any GRPCChannel) {
self.channel = channel
self.defaultCallOptions = .init()
}
public func makeTunnelPacketsCall(
callOptions: CallOptions? = nil
) -> GRPCAsyncBidirectionalStreamingCall<Burrow_TunnelPacket, Burrow_TunnelPacket> {
self.makeAsyncBidirectionalStreamingCall(
path: "/burrow.Tunnel/TunnelPackets",
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: []
)
}
}

View file

@ -1,761 +0,0 @@
//
// DO NOT EDIT.
// swift-format-ignore-file
//
// Generated by the protocol buffer compiler.
// Source: burrow.proto
//
import GRPC
import NIO
import NIOConcurrencyHelpers
import SwiftProtobuf
/// Usage: instantiate `Burrow_TunnelClient`, then call methods of this protocol to make API calls.
public protocol Burrow_TunnelClientProtocol: GRPCClient {
var serviceName: String { get }
var interceptors: Burrow_TunnelClientInterceptorFactoryProtocol? { get }
func tunnelConfiguration(
_ request: Burrow_Empty,
callOptions: CallOptions?,
handler: @escaping (Burrow_TunnelConfigurationResponse) -> Void
) -> ServerStreamingCall<Burrow_Empty, Burrow_TunnelConfigurationResponse>
func tunnelStart(
_ request: Burrow_Empty,
callOptions: CallOptions?
) -> UnaryCall<Burrow_Empty, Burrow_Empty>
func tunnelStop(
_ request: Burrow_Empty,
callOptions: CallOptions?
) -> UnaryCall<Burrow_Empty, Burrow_Empty>
func tunnelStatus(
_ request: Burrow_Empty,
callOptions: CallOptions?,
handler: @escaping (Burrow_TunnelStatusResponse) -> Void
) -> ServerStreamingCall<Burrow_Empty, Burrow_TunnelStatusResponse>
}
extension Burrow_TunnelClientProtocol {
public var serviceName: String {
return "burrow.Tunnel"
}
/// Server streaming call to TunnelConfiguration
///
/// - Parameters:
/// - request: Request to send to TunnelConfiguration.
/// - callOptions: Call options.
/// - handler: A closure called when each response is received from the server.
/// - Returns: A `ServerStreamingCall` with futures for the metadata and status.
public func tunnelConfiguration(
_ request: Burrow_Empty,
callOptions: CallOptions? = nil,
handler: @escaping (Burrow_TunnelConfigurationResponse) -> Void
) -> ServerStreamingCall<Burrow_Empty, Burrow_TunnelConfigurationResponse> {
return self.makeServerStreamingCall(
path: Burrow_TunnelClientMetadata.Methods.tunnelConfiguration.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeTunnelConfigurationInterceptors() ?? [],
handler: handler
)
}
/// Unary call to TunnelStart
///
/// - Parameters:
/// - request: Request to send to TunnelStart.
/// - callOptions: Call options.
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
public func tunnelStart(
_ request: Burrow_Empty,
callOptions: CallOptions? = nil
) -> UnaryCall<Burrow_Empty, Burrow_Empty> {
return self.makeUnaryCall(
path: Burrow_TunnelClientMetadata.Methods.tunnelStart.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeTunnelStartInterceptors() ?? []
)
}
/// Unary call to TunnelStop
///
/// - Parameters:
/// - request: Request to send to TunnelStop.
/// - callOptions: Call options.
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
public func tunnelStop(
_ request: Burrow_Empty,
callOptions: CallOptions? = nil
) -> UnaryCall<Burrow_Empty, Burrow_Empty> {
return self.makeUnaryCall(
path: Burrow_TunnelClientMetadata.Methods.tunnelStop.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeTunnelStopInterceptors() ?? []
)
}
/// Server streaming call to TunnelStatus
///
/// - Parameters:
/// - request: Request to send to TunnelStatus.
/// - callOptions: Call options.
/// - handler: A closure called when each response is received from the server.
/// - Returns: A `ServerStreamingCall` with futures for the metadata and status.
public func tunnelStatus(
_ request: Burrow_Empty,
callOptions: CallOptions? = nil,
handler: @escaping (Burrow_TunnelStatusResponse) -> Void
) -> ServerStreamingCall<Burrow_Empty, Burrow_TunnelStatusResponse> {
return self.makeServerStreamingCall(
path: Burrow_TunnelClientMetadata.Methods.tunnelStatus.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeTunnelStatusInterceptors() ?? [],
handler: handler
)
}
}
@available(*, deprecated)
extension Burrow_TunnelClient: @unchecked Sendable {}
@available(*, deprecated, renamed: "Burrow_TunnelNIOClient")
public final class Burrow_TunnelClient: Burrow_TunnelClientProtocol {
private let lock = Lock()
private var _defaultCallOptions: CallOptions
private var _interceptors: Burrow_TunnelClientInterceptorFactoryProtocol?
public let channel: GRPCChannel
public var defaultCallOptions: CallOptions {
get { self.lock.withLock { return self._defaultCallOptions } }
set { self.lock.withLockVoid { self._defaultCallOptions = newValue } }
}
public var interceptors: Burrow_TunnelClientInterceptorFactoryProtocol? {
get { self.lock.withLock { return self._interceptors } }
set { self.lock.withLockVoid { self._interceptors = newValue } }
}
/// Creates a client for the burrow.Tunnel service.
///
/// - Parameters:
/// - channel: `GRPCChannel` to the service host.
/// - defaultCallOptions: Options to use for each service call if the user doesn't provide them.
/// - interceptors: A factory providing interceptors for each RPC.
public init(
channel: GRPCChannel,
defaultCallOptions: CallOptions = CallOptions(),
interceptors: Burrow_TunnelClientInterceptorFactoryProtocol? = nil
) {
self.channel = channel
self._defaultCallOptions = defaultCallOptions
self._interceptors = interceptors
}
}
public struct Burrow_TunnelNIOClient: Burrow_TunnelClientProtocol {
public var channel: GRPCChannel
public var defaultCallOptions: CallOptions
public var interceptors: Burrow_TunnelClientInterceptorFactoryProtocol?
/// Creates a client for the burrow.Tunnel service.
///
/// - Parameters:
/// - channel: `GRPCChannel` to the service host.
/// - defaultCallOptions: Options to use for each service call if the user doesn't provide them.
/// - interceptors: A factory providing interceptors for each RPC.
public init(
channel: GRPCChannel,
defaultCallOptions: CallOptions = CallOptions(),
interceptors: Burrow_TunnelClientInterceptorFactoryProtocol? = nil
) {
self.channel = channel
self.defaultCallOptions = defaultCallOptions
self.interceptors = interceptors
}
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
public protocol Burrow_TunnelAsyncClientProtocol: GRPCClient {
static var serviceDescriptor: GRPCServiceDescriptor { get }
var interceptors: Burrow_TunnelClientInterceptorFactoryProtocol? { get }
func makeTunnelConfigurationCall(
_ request: Burrow_Empty,
callOptions: CallOptions?
) -> GRPCAsyncServerStreamingCall<Burrow_Empty, Burrow_TunnelConfigurationResponse>
func makeTunnelStartCall(
_ request: Burrow_Empty,
callOptions: CallOptions?
) -> GRPCAsyncUnaryCall<Burrow_Empty, Burrow_Empty>
func makeTunnelStopCall(
_ request: Burrow_Empty,
callOptions: CallOptions?
) -> GRPCAsyncUnaryCall<Burrow_Empty, Burrow_Empty>
func makeTunnelStatusCall(
_ request: Burrow_Empty,
callOptions: CallOptions?
) -> GRPCAsyncServerStreamingCall<Burrow_Empty, Burrow_TunnelStatusResponse>
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension Burrow_TunnelAsyncClientProtocol {
public static var serviceDescriptor: GRPCServiceDescriptor {
return Burrow_TunnelClientMetadata.serviceDescriptor
}
public var interceptors: Burrow_TunnelClientInterceptorFactoryProtocol? {
return nil
}
public func makeTunnelConfigurationCall(
_ request: Burrow_Empty,
callOptions: CallOptions? = nil
) -> GRPCAsyncServerStreamingCall<Burrow_Empty, Burrow_TunnelConfigurationResponse> {
return self.makeAsyncServerStreamingCall(
path: Burrow_TunnelClientMetadata.Methods.tunnelConfiguration.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeTunnelConfigurationInterceptors() ?? []
)
}
public func makeTunnelStartCall(
_ request: Burrow_Empty,
callOptions: CallOptions? = nil
) -> GRPCAsyncUnaryCall<Burrow_Empty, Burrow_Empty> {
return self.makeAsyncUnaryCall(
path: Burrow_TunnelClientMetadata.Methods.tunnelStart.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeTunnelStartInterceptors() ?? []
)
}
public func makeTunnelStopCall(
_ request: Burrow_Empty,
callOptions: CallOptions? = nil
) -> GRPCAsyncUnaryCall<Burrow_Empty, Burrow_Empty> {
return self.makeAsyncUnaryCall(
path: Burrow_TunnelClientMetadata.Methods.tunnelStop.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeTunnelStopInterceptors() ?? []
)
}
public func makeTunnelStatusCall(
_ request: Burrow_Empty,
callOptions: CallOptions? = nil
) -> GRPCAsyncServerStreamingCall<Burrow_Empty, Burrow_TunnelStatusResponse> {
return self.makeAsyncServerStreamingCall(
path: Burrow_TunnelClientMetadata.Methods.tunnelStatus.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeTunnelStatusInterceptors() ?? []
)
}
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension Burrow_TunnelAsyncClientProtocol {
public func tunnelConfiguration(
_ request: Burrow_Empty,
callOptions: CallOptions? = nil
) -> GRPCAsyncResponseStream<Burrow_TunnelConfigurationResponse> {
return self.performAsyncServerStreamingCall(
path: Burrow_TunnelClientMetadata.Methods.tunnelConfiguration.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeTunnelConfigurationInterceptors() ?? []
)
}
public func tunnelStart(
_ request: Burrow_Empty,
callOptions: CallOptions? = nil
) async throws -> Burrow_Empty {
return try await self.performAsyncUnaryCall(
path: Burrow_TunnelClientMetadata.Methods.tunnelStart.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeTunnelStartInterceptors() ?? []
)
}
public func tunnelStop(
_ request: Burrow_Empty,
callOptions: CallOptions? = nil
) async throws -> Burrow_Empty {
return try await self.performAsyncUnaryCall(
path: Burrow_TunnelClientMetadata.Methods.tunnelStop.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeTunnelStopInterceptors() ?? []
)
}
public func tunnelStatus(
_ request: Burrow_Empty,
callOptions: CallOptions? = nil
) -> GRPCAsyncResponseStream<Burrow_TunnelStatusResponse> {
return self.performAsyncServerStreamingCall(
path: Burrow_TunnelClientMetadata.Methods.tunnelStatus.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeTunnelStatusInterceptors() ?? []
)
}
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
public struct Burrow_TunnelAsyncClient: Burrow_TunnelAsyncClientProtocol {
public var channel: GRPCChannel
public var defaultCallOptions: CallOptions
public var interceptors: Burrow_TunnelClientInterceptorFactoryProtocol?
public init(
channel: GRPCChannel,
defaultCallOptions: CallOptions = CallOptions(),
interceptors: Burrow_TunnelClientInterceptorFactoryProtocol? = nil
) {
self.channel = channel
self.defaultCallOptions = defaultCallOptions
self.interceptors = interceptors
}
}
public protocol Burrow_TunnelClientInterceptorFactoryProtocol: Sendable {
/// - Returns: Interceptors to use when invoking 'tunnelConfiguration'.
func makeTunnelConfigurationInterceptors() -> [ClientInterceptor<Burrow_Empty, Burrow_TunnelConfigurationResponse>]
/// - Returns: Interceptors to use when invoking 'tunnelStart'.
func makeTunnelStartInterceptors() -> [ClientInterceptor<Burrow_Empty, Burrow_Empty>]
/// - Returns: Interceptors to use when invoking 'tunnelStop'.
func makeTunnelStopInterceptors() -> [ClientInterceptor<Burrow_Empty, Burrow_Empty>]
/// - Returns: Interceptors to use when invoking 'tunnelStatus'.
func makeTunnelStatusInterceptors() -> [ClientInterceptor<Burrow_Empty, Burrow_TunnelStatusResponse>]
}
public enum Burrow_TunnelClientMetadata {
public static let serviceDescriptor = GRPCServiceDescriptor(
name: "Tunnel",
fullName: "burrow.Tunnel",
methods: [
Burrow_TunnelClientMetadata.Methods.tunnelConfiguration,
Burrow_TunnelClientMetadata.Methods.tunnelStart,
Burrow_TunnelClientMetadata.Methods.tunnelStop,
Burrow_TunnelClientMetadata.Methods.tunnelStatus,
]
)
public enum Methods {
public static let tunnelConfiguration = GRPCMethodDescriptor(
name: "TunnelConfiguration",
path: "/burrow.Tunnel/TunnelConfiguration",
type: GRPCCallType.serverStreaming
)
public static let tunnelStart = GRPCMethodDescriptor(
name: "TunnelStart",
path: "/burrow.Tunnel/TunnelStart",
type: GRPCCallType.unary
)
public static let tunnelStop = GRPCMethodDescriptor(
name: "TunnelStop",
path: "/burrow.Tunnel/TunnelStop",
type: GRPCCallType.unary
)
public static let tunnelStatus = GRPCMethodDescriptor(
name: "TunnelStatus",
path: "/burrow.Tunnel/TunnelStatus",
type: GRPCCallType.serverStreaming
)
}
}
/// Usage: instantiate `Burrow_NetworksClient`, then call methods of this protocol to make API calls.
public protocol Burrow_NetworksClientProtocol: GRPCClient {
var serviceName: String { get }
var interceptors: Burrow_NetworksClientInterceptorFactoryProtocol? { get }
func networkAdd(
_ request: Burrow_Network,
callOptions: CallOptions?
) -> UnaryCall<Burrow_Network, Burrow_Empty>
func networkList(
_ request: Burrow_Empty,
callOptions: CallOptions?,
handler: @escaping (Burrow_NetworkListResponse) -> Void
) -> ServerStreamingCall<Burrow_Empty, Burrow_NetworkListResponse>
func networkReorder(
_ request: Burrow_NetworkReorderRequest,
callOptions: CallOptions?
) -> UnaryCall<Burrow_NetworkReorderRequest, Burrow_Empty>
func networkDelete(
_ request: Burrow_NetworkDeleteRequest,
callOptions: CallOptions?
) -> UnaryCall<Burrow_NetworkDeleteRequest, Burrow_Empty>
}
extension Burrow_NetworksClientProtocol {
public var serviceName: String {
return "burrow.Networks"
}
/// Unary call to NetworkAdd
///
/// - Parameters:
/// - request: Request to send to NetworkAdd.
/// - callOptions: Call options.
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
public func networkAdd(
_ request: Burrow_Network,
callOptions: CallOptions? = nil
) -> UnaryCall<Burrow_Network, Burrow_Empty> {
return self.makeUnaryCall(
path: Burrow_NetworksClientMetadata.Methods.networkAdd.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeNetworkAddInterceptors() ?? []
)
}
/// Server streaming call to NetworkList
///
/// - Parameters:
/// - request: Request to send to NetworkList.
/// - callOptions: Call options.
/// - handler: A closure called when each response is received from the server.
/// - Returns: A `ServerStreamingCall` with futures for the metadata and status.
public func networkList(
_ request: Burrow_Empty,
callOptions: CallOptions? = nil,
handler: @escaping (Burrow_NetworkListResponse) -> Void
) -> ServerStreamingCall<Burrow_Empty, Burrow_NetworkListResponse> {
return self.makeServerStreamingCall(
path: Burrow_NetworksClientMetadata.Methods.networkList.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeNetworkListInterceptors() ?? [],
handler: handler
)
}
/// Unary call to NetworkReorder
///
/// - Parameters:
/// - request: Request to send to NetworkReorder.
/// - callOptions: Call options.
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
public func networkReorder(
_ request: Burrow_NetworkReorderRequest,
callOptions: CallOptions? = nil
) -> UnaryCall<Burrow_NetworkReorderRequest, Burrow_Empty> {
return self.makeUnaryCall(
path: Burrow_NetworksClientMetadata.Methods.networkReorder.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeNetworkReorderInterceptors() ?? []
)
}
/// Unary call to NetworkDelete
///
/// - Parameters:
/// - request: Request to send to NetworkDelete.
/// - callOptions: Call options.
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
public func networkDelete(
_ request: Burrow_NetworkDeleteRequest,
callOptions: CallOptions? = nil
) -> UnaryCall<Burrow_NetworkDeleteRequest, Burrow_Empty> {
return self.makeUnaryCall(
path: Burrow_NetworksClientMetadata.Methods.networkDelete.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeNetworkDeleteInterceptors() ?? []
)
}
}
@available(*, deprecated)
extension Burrow_NetworksClient: @unchecked Sendable {}
@available(*, deprecated, renamed: "Burrow_NetworksNIOClient")
public final class Burrow_NetworksClient: Burrow_NetworksClientProtocol {
private let lock = Lock()
private var _defaultCallOptions: CallOptions
private var _interceptors: Burrow_NetworksClientInterceptorFactoryProtocol?
public let channel: GRPCChannel
public var defaultCallOptions: CallOptions {
get { self.lock.withLock { return self._defaultCallOptions } }
set { self.lock.withLockVoid { self._defaultCallOptions = newValue } }
}
public var interceptors: Burrow_NetworksClientInterceptorFactoryProtocol? {
get { self.lock.withLock { return self._interceptors } }
set { self.lock.withLockVoid { self._interceptors = newValue } }
}
/// Creates a client for the burrow.Networks service.
///
/// - Parameters:
/// - channel: `GRPCChannel` to the service host.
/// - defaultCallOptions: Options to use for each service call if the user doesn't provide them.
/// - interceptors: A factory providing interceptors for each RPC.
public init(
channel: GRPCChannel,
defaultCallOptions: CallOptions = CallOptions(),
interceptors: Burrow_NetworksClientInterceptorFactoryProtocol? = nil
) {
self.channel = channel
self._defaultCallOptions = defaultCallOptions
self._interceptors = interceptors
}
}
public struct Burrow_NetworksNIOClient: Burrow_NetworksClientProtocol {
public var channel: GRPCChannel
public var defaultCallOptions: CallOptions
public var interceptors: Burrow_NetworksClientInterceptorFactoryProtocol?
/// Creates a client for the burrow.Networks service.
///
/// - Parameters:
/// - channel: `GRPCChannel` to the service host.
/// - defaultCallOptions: Options to use for each service call if the user doesn't provide them.
/// - interceptors: A factory providing interceptors for each RPC.
public init(
channel: GRPCChannel,
defaultCallOptions: CallOptions = CallOptions(),
interceptors: Burrow_NetworksClientInterceptorFactoryProtocol? = nil
) {
self.channel = channel
self.defaultCallOptions = defaultCallOptions
self.interceptors = interceptors
}
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
public protocol Burrow_NetworksAsyncClientProtocol: GRPCClient {
static var serviceDescriptor: GRPCServiceDescriptor { get }
var interceptors: Burrow_NetworksClientInterceptorFactoryProtocol? { get }
func makeNetworkAddCall(
_ request: Burrow_Network,
callOptions: CallOptions?
) -> GRPCAsyncUnaryCall<Burrow_Network, Burrow_Empty>
func makeNetworkListCall(
_ request: Burrow_Empty,
callOptions: CallOptions?
) -> GRPCAsyncServerStreamingCall<Burrow_Empty, Burrow_NetworkListResponse>
func makeNetworkReorderCall(
_ request: Burrow_NetworkReorderRequest,
callOptions: CallOptions?
) -> GRPCAsyncUnaryCall<Burrow_NetworkReorderRequest, Burrow_Empty>
func makeNetworkDeleteCall(
_ request: Burrow_NetworkDeleteRequest,
callOptions: CallOptions?
) -> GRPCAsyncUnaryCall<Burrow_NetworkDeleteRequest, Burrow_Empty>
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension Burrow_NetworksAsyncClientProtocol {
public static var serviceDescriptor: GRPCServiceDescriptor {
return Burrow_NetworksClientMetadata.serviceDescriptor
}
public var interceptors: Burrow_NetworksClientInterceptorFactoryProtocol? {
return nil
}
public func makeNetworkAddCall(
_ request: Burrow_Network,
callOptions: CallOptions? = nil
) -> GRPCAsyncUnaryCall<Burrow_Network, Burrow_Empty> {
return self.makeAsyncUnaryCall(
path: Burrow_NetworksClientMetadata.Methods.networkAdd.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeNetworkAddInterceptors() ?? []
)
}
public func makeNetworkListCall(
_ request: Burrow_Empty,
callOptions: CallOptions? = nil
) -> GRPCAsyncServerStreamingCall<Burrow_Empty, Burrow_NetworkListResponse> {
return self.makeAsyncServerStreamingCall(
path: Burrow_NetworksClientMetadata.Methods.networkList.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeNetworkListInterceptors() ?? []
)
}
public func makeNetworkReorderCall(
_ request: Burrow_NetworkReorderRequest,
callOptions: CallOptions? = nil
) -> GRPCAsyncUnaryCall<Burrow_NetworkReorderRequest, Burrow_Empty> {
return self.makeAsyncUnaryCall(
path: Burrow_NetworksClientMetadata.Methods.networkReorder.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeNetworkReorderInterceptors() ?? []
)
}
public func makeNetworkDeleteCall(
_ request: Burrow_NetworkDeleteRequest,
callOptions: CallOptions? = nil
) -> GRPCAsyncUnaryCall<Burrow_NetworkDeleteRequest, Burrow_Empty> {
return self.makeAsyncUnaryCall(
path: Burrow_NetworksClientMetadata.Methods.networkDelete.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeNetworkDeleteInterceptors() ?? []
)
}
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension Burrow_NetworksAsyncClientProtocol {
public func networkAdd(
_ request: Burrow_Network,
callOptions: CallOptions? = nil
) async throws -> Burrow_Empty {
return try await self.performAsyncUnaryCall(
path: Burrow_NetworksClientMetadata.Methods.networkAdd.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeNetworkAddInterceptors() ?? []
)
}
public func networkList(
_ request: Burrow_Empty,
callOptions: CallOptions? = nil
) -> GRPCAsyncResponseStream<Burrow_NetworkListResponse> {
return self.performAsyncServerStreamingCall(
path: Burrow_NetworksClientMetadata.Methods.networkList.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeNetworkListInterceptors() ?? []
)
}
public func networkReorder(
_ request: Burrow_NetworkReorderRequest,
callOptions: CallOptions? = nil
) async throws -> Burrow_Empty {
return try await self.performAsyncUnaryCall(
path: Burrow_NetworksClientMetadata.Methods.networkReorder.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeNetworkReorderInterceptors() ?? []
)
}
public func networkDelete(
_ request: Burrow_NetworkDeleteRequest,
callOptions: CallOptions? = nil
) async throws -> Burrow_Empty {
return try await self.performAsyncUnaryCall(
path: Burrow_NetworksClientMetadata.Methods.networkDelete.path,
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeNetworkDeleteInterceptors() ?? []
)
}
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
public struct Burrow_NetworksAsyncClient: Burrow_NetworksAsyncClientProtocol {
public var channel: GRPCChannel
public var defaultCallOptions: CallOptions
public var interceptors: Burrow_NetworksClientInterceptorFactoryProtocol?
public init(
channel: GRPCChannel,
defaultCallOptions: CallOptions = CallOptions(),
interceptors: Burrow_NetworksClientInterceptorFactoryProtocol? = nil
) {
self.channel = channel
self.defaultCallOptions = defaultCallOptions
self.interceptors = interceptors
}
}
public protocol Burrow_NetworksClientInterceptorFactoryProtocol: Sendable {
/// - Returns: Interceptors to use when invoking 'networkAdd'.
func makeNetworkAddInterceptors() -> [ClientInterceptor<Burrow_Network, Burrow_Empty>]
/// - Returns: Interceptors to use when invoking 'networkList'.
func makeNetworkListInterceptors() -> [ClientInterceptor<Burrow_Empty, Burrow_NetworkListResponse>]
/// - Returns: Interceptors to use when invoking 'networkReorder'.
func makeNetworkReorderInterceptors() -> [ClientInterceptor<Burrow_NetworkReorderRequest, Burrow_Empty>]
/// - Returns: Interceptors to use when invoking 'networkDelete'.
func makeNetworkDeleteInterceptors() -> [ClientInterceptor<Burrow_NetworkDeleteRequest, Burrow_Empty>]
}
public enum Burrow_NetworksClientMetadata {
public static let serviceDescriptor = GRPCServiceDescriptor(
name: "Networks",
fullName: "burrow.Networks",
methods: [
Burrow_NetworksClientMetadata.Methods.networkAdd,
Burrow_NetworksClientMetadata.Methods.networkList,
Burrow_NetworksClientMetadata.Methods.networkReorder,
Burrow_NetworksClientMetadata.Methods.networkDelete,
]
)
public enum Methods {
public static let networkAdd = GRPCMethodDescriptor(
name: "NetworkAdd",
path: "/burrow.Networks/NetworkAdd",
type: GRPCCallType.unary
)
public static let networkList = GRPCMethodDescriptor(
name: "NetworkList",
path: "/burrow.Networks/NetworkList",
type: GRPCCallType.serverStreaming
)
public static let networkReorder = GRPCMethodDescriptor(
name: "NetworkReorder",
path: "/burrow.Networks/NetworkReorder",
type: GRPCCallType.unary
)
public static let networkDelete = GRPCMethodDescriptor(
name: "NetworkDelete",
path: "/burrow.Networks/NetworkDelete",
type: GRPCCallType.unary
)
}
}

View file

@ -1,598 +0,0 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: burrow.proto
//
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
// was generated by a version of the `protoc` Swift plug-in that is
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
public enum Burrow_NetworkType: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
case wireGuard // = 0
case tailnet // = 1
case UNRECOGNIZED(Int)
public init() {
self = .wireGuard
}
public init?(rawValue: Int) {
switch rawValue {
case 0: self = .wireGuard
case 1: self = .tailnet
default: self = .UNRECOGNIZED(rawValue)
}
}
public var rawValue: Int {
switch self {
case .wireGuard: return 0
case .tailnet: return 1
case .UNRECOGNIZED(let i): return i
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Burrow_NetworkType] = [
.wireGuard,
.tailnet,
]
}
public enum Burrow_State: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
case stopped // = 0
case running // = 1
case UNRECOGNIZED(Int)
public init() {
self = .stopped
}
public init?(rawValue: Int) {
switch rawValue {
case 0: self = .stopped
case 1: self = .running
default: self = .UNRECOGNIZED(rawValue)
}
}
public var rawValue: Int {
switch self {
case .stopped: return 0
case .running: return 1
case .UNRECOGNIZED(let i): return i
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Burrow_State] = [
.stopped,
.running,
]
}
public struct Burrow_NetworkReorderRequest: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
public var id: Int32 = 0
public var index: Int32 = 0
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct Burrow_WireGuardPeer: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
public var endpoint: String = String()
public var subnet: [String] = []
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct Burrow_WireGuardNetwork: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
public var address: String = String()
public var dns: String = String()
public var peer: [Burrow_WireGuardPeer] = []
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct Burrow_NetworkDeleteRequest: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
public var id: Int32 = 0
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct Burrow_Network: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
public var id: Int32 = 0
public var type: Burrow_NetworkType = .wireGuard
public var payload: Data = Data()
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct Burrow_NetworkListResponse: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
public var network: [Burrow_Network] = []
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct Burrow_Empty: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct Burrow_TunnelStatusResponse: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
public var state: Burrow_State = .stopped
public var start: SwiftProtobuf.Google_Protobuf_Timestamp {
get {return _start ?? SwiftProtobuf.Google_Protobuf_Timestamp()}
set {_start = newValue}
}
/// Returns true if `start` has been explicitly set.
public var hasStart: Bool {return self._start != nil}
/// Clears the value of `start`. Subsequent reads from it will return its default value.
public mutating func clearStart() {self._start = nil}
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
fileprivate var _start: SwiftProtobuf.Google_Protobuf_Timestamp? = nil
}
public struct Burrow_TunnelConfigurationResponse: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
public var addresses: [String] = []
public var mtu: Int32 = 0
public var routes: [String] = []
public var dnsServers: [String] = []
public var searchDomains: [String] = []
public var includeDefaultRoute: Bool = false
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "burrow"
extension Burrow_NetworkType: SwiftProtobuf._ProtoNameProviding {
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "WireGuard"),
1: .same(proto: "Tailnet"),
]
}
extension Burrow_State: SwiftProtobuf._ProtoNameProviding {
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "Stopped"),
1: .same(proto: "Running"),
]
}
extension Burrow_NetworkReorderRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".NetworkReorderRequest"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "id"),
2: .same(proto: "index"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularInt32Field(value: &self.id) }()
case 2: try { try decoder.decodeSingularInt32Field(value: &self.index) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.id != 0 {
try visitor.visitSingularInt32Field(value: self.id, fieldNumber: 1)
}
if self.index != 0 {
try visitor.visitSingularInt32Field(value: self.index, fieldNumber: 2)
}
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: Burrow_NetworkReorderRequest, rhs: Burrow_NetworkReorderRequest) -> Bool {
if lhs.id != rhs.id {return false}
if lhs.index != rhs.index {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension Burrow_WireGuardPeer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".WireGuardPeer"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "endpoint"),
2: .same(proto: "subnet"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularStringField(value: &self.endpoint) }()
case 2: try { try decoder.decodeRepeatedStringField(value: &self.subnet) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.endpoint.isEmpty {
try visitor.visitSingularStringField(value: self.endpoint, fieldNumber: 1)
}
if !self.subnet.isEmpty {
try visitor.visitRepeatedStringField(value: self.subnet, fieldNumber: 2)
}
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: Burrow_WireGuardPeer, rhs: Burrow_WireGuardPeer) -> Bool {
if lhs.endpoint != rhs.endpoint {return false}
if lhs.subnet != rhs.subnet {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension Burrow_WireGuardNetwork: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".WireGuardNetwork"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "address"),
2: .same(proto: "dns"),
3: .same(proto: "peer"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularStringField(value: &self.address) }()
case 2: try { try decoder.decodeSingularStringField(value: &self.dns) }()
case 3: try { try decoder.decodeRepeatedMessageField(value: &self.peer) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.address.isEmpty {
try visitor.visitSingularStringField(value: self.address, fieldNumber: 1)
}
if !self.dns.isEmpty {
try visitor.visitSingularStringField(value: self.dns, fieldNumber: 2)
}
if !self.peer.isEmpty {
try visitor.visitRepeatedMessageField(value: self.peer, fieldNumber: 3)
}
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: Burrow_WireGuardNetwork, rhs: Burrow_WireGuardNetwork) -> Bool {
if lhs.address != rhs.address {return false}
if lhs.dns != rhs.dns {return false}
if lhs.peer != rhs.peer {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension Burrow_NetworkDeleteRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".NetworkDeleteRequest"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "id"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularInt32Field(value: &self.id) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.id != 0 {
try visitor.visitSingularInt32Field(value: self.id, fieldNumber: 1)
}
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: Burrow_NetworkDeleteRequest, rhs: Burrow_NetworkDeleteRequest) -> Bool {
if lhs.id != rhs.id {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension Burrow_Network: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".Network"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "id"),
2: .same(proto: "type"),
3: .same(proto: "payload"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularInt32Field(value: &self.id) }()
case 2: try { try decoder.decodeSingularEnumField(value: &self.type) }()
case 3: try { try decoder.decodeSingularBytesField(value: &self.payload) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.id != 0 {
try visitor.visitSingularInt32Field(value: self.id, fieldNumber: 1)
}
if self.type != .wireGuard {
try visitor.visitSingularEnumField(value: self.type, fieldNumber: 2)
}
if !self.payload.isEmpty {
try visitor.visitSingularBytesField(value: self.payload, fieldNumber: 3)
}
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: Burrow_Network, rhs: Burrow_Network) -> Bool {
if lhs.id != rhs.id {return false}
if lhs.type != rhs.type {return false}
if lhs.payload != rhs.payload {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension Burrow_NetworkListResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".NetworkListResponse"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "network"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeRepeatedMessageField(value: &self.network) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.network.isEmpty {
try visitor.visitRepeatedMessageField(value: self.network, fieldNumber: 1)
}
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: Burrow_NetworkListResponse, rhs: Burrow_NetworkListResponse) -> Bool {
if lhs.network != rhs.network {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension Burrow_Empty: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".Empty"
public static let _protobuf_nameMap = SwiftProtobuf._NameMap()
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
// Load everything into unknown fields
while try decoder.nextFieldNumber() != nil {}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: Burrow_Empty, rhs: Burrow_Empty) -> Bool {
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension Burrow_TunnelStatusResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".TunnelStatusResponse"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "state"),
2: .same(proto: "start"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularEnumField(value: &self.state) }()
case 2: try { try decoder.decodeSingularMessageField(value: &self._start) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
if self.state != .stopped {
try visitor.visitSingularEnumField(value: self.state, fieldNumber: 1)
}
try { if let v = self._start {
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
} }()
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: Burrow_TunnelStatusResponse, rhs: Burrow_TunnelStatusResponse) -> Bool {
if lhs.state != rhs.state {return false}
if lhs._start != rhs._start {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension Burrow_TunnelConfigurationResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".TunnelConfigurationResponse"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "addresses"),
2: .same(proto: "mtu"),
3: .same(proto: "routes"),
4: .standard(proto: "dns_servers"),
5: .standard(proto: "search_domains"),
6: .standard(proto: "include_default_route"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeRepeatedStringField(value: &self.addresses) }()
case 2: try { try decoder.decodeSingularInt32Field(value: &self.mtu) }()
case 3: try { try decoder.decodeRepeatedStringField(value: &self.routes) }()
case 4: try { try decoder.decodeRepeatedStringField(value: &self.dnsServers) }()
case 5: try { try decoder.decodeRepeatedStringField(value: &self.searchDomains) }()
case 6: try { try decoder.decodeSingularBoolField(value: &self.includeDefaultRoute) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.addresses.isEmpty {
try visitor.visitRepeatedStringField(value: self.addresses, fieldNumber: 1)
}
if self.mtu != 0 {
try visitor.visitSingularInt32Field(value: self.mtu, fieldNumber: 2)
}
if !self.routes.isEmpty {
try visitor.visitRepeatedStringField(value: self.routes, fieldNumber: 3)
}
if !self.dnsServers.isEmpty {
try visitor.visitRepeatedStringField(value: self.dnsServers, fieldNumber: 4)
}
if !self.searchDomains.isEmpty {
try visitor.visitRepeatedStringField(value: self.searchDomains, fieldNumber: 5)
}
if self.includeDefaultRoute {
try visitor.visitSingularBoolField(value: self.includeDefaultRoute, fieldNumber: 6)
}
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: Burrow_TunnelConfigurationResponse, rhs: Burrow_TunnelConfigurationResponse) -> Bool {
if lhs.addresses != rhs.addresses {return false}
if lhs.mtu != rhs.mtu {return false}
if lhs.routes != rhs.routes {return false}
if lhs.dnsServers != rhs.dnsServers {return false}
if lhs.searchDomains != rhs.searchDomains {return false}
if lhs.includeDefaultRoute != rhs.includeDefaultRoute {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}

View file

@ -1,64 +0,0 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option cc_enable_arenas = true;
option go_package = "google.golang.org/protobuf/types/known/timestamppb";
option java_package = "com.google.protobuf";
option java_outer_classname = "TimestampProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
// A Timestamp represents a point in time independent of any time zone or local
// calendar, encoded as a count of seconds and fractions of seconds at
// nanosecond resolution. The count is relative to an epoch at UTC midnight on
// January 1, 1970, in the proleptic Gregorian calendar which extends the
// Gregorian calendar backwards to year one.
//
// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
// second table is needed for interpretation, using a 24-hour linear smear.
//
// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
// restricting to that range, we ensure that we can convert to and from RFC
// 3339 date strings.
message Timestamp {
// Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
// Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive.
int64 seconds = 1;
// Non-negative fractions of a second at nanosecond resolution. Negative
// second values with fractions must still have non-negative nanos values
// that count forward in time. Must be from 0 to 999,999,999 inclusive.
int32 nanos = 2;
}

View file

@ -0,0 +1,11 @@
{
"invocations": [
{
"protoFiles": [
"burrow.proto",
],
"server": false,
"visibility": "public"
}
]
}

View file

@ -0,0 +1,10 @@
{
"invocations": [
{
"protoFiles": [
"burrow.proto",
],
"visibility": "public"
}
]
}

View file

@ -1,28 +1,16 @@
import AsyncAlgorithms
import BurrowConfiguration
import BurrowCore
import GRPC
import libburrow
import NetworkExtension
import os
private final class SendableCallbackBox<Callback>: @unchecked Sendable {
let callback: Callback
init(_ callback: Callback) {
self.callback = callback
}
}
final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
class PacketTunnelProvider: NEPacketTunnelProvider {
enum Error: Swift.Error {
case missingTunnelConfiguration
}
private let logger = Logger.logger(for: PacketTunnelProvider.self)
private var packetCall: GRPCAsyncBidirectionalStreamingCall<Burrow_TunnelPacket, Burrow_TunnelPacket>?
private var inboundPacketTask: Task<Void, Never>?
private var outboundPacketTask: Task<Void, Never>?
private var client: TunnelClient {
get throws { try _client.get() }
@ -42,285 +30,45 @@ final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
}
}
override func startTunnel(
options: [String: NSObject]?,
completionHandler: @escaping (Swift.Error?) -> Void
) {
let completion = SendableCallbackBox(completionHandler)
Task {
do {
_ = try await client.tunnelStart(.init())
let configuration = try await Array(client.tunnelConfiguration(.init()).prefix(1)).first
guard let settings = configuration?.settings else {
throw Error.missingTunnelConfiguration
}
try await setTunnelNetworkSettings(settings)
try startPacketBridge()
logger.log("Started tunnel with network settings: \(settings)")
completion.callback(nil)
} catch {
logger.error("Failed to start tunnel: \(error)")
stopPacketBridge()
completion.callback(error)
override func startTunnel(options: [String: NSObject]? = nil) async throws {
do {
let configuration = try await Array(client.tunnelConfiguration(.init()).prefix(1)).first
guard let settings = configuration?.settings else {
throw Error.missingTunnelConfiguration
}
try await setTunnelNetworkSettings(settings)
_ = try await client.tunnelStart(.init())
logger.log("Started tunnel with network settings: \(settings)")
} catch {
logger.error("Failed to start tunnel: \(error)")
throw error
}
}
override func stopTunnel(
with reason: NEProviderStopReason,
completionHandler: @escaping () -> Void
) {
let completion = SendableCallbackBox(completionHandler)
Task {
stopPacketBridge()
do {
_ = try await client.tunnelStop(.init())
logger.log("Stopped client")
} catch {
logger.error("Failed to stop tunnel: \(error)")
}
completion.callback()
}
}
}
extension PacketTunnelProvider {
private func startPacketBridge() throws {
stopPacketBridge()
let packetClient = TunnelPacketClient.unix(socketURL: try Constants.socketURL)
let call = packetClient.makeTunnelPacketsCall()
self.packetCall = call
inboundPacketTask = Task { [weak self] in
guard let self else { return }
do {
for try await packet in call.responseStream {
let payload = packet.payload
self.packetFlow.writePackets(
[payload],
withProtocols: [Self.protocolNumber(for: payload)]
)
}
} catch {
guard !Task.isCancelled else { return }
self.logger.error("Tunnel packet receive loop failed: \(error)")
}
}
outboundPacketTask = Task { [weak self] in
guard let self else { return }
defer { call.requestStream.finish() }
do {
while !Task.isCancelled {
let packets = await self.readPacketsBatch()
for (payload, _) in packets {
var packet = Burrow_TunnelPacket()
packet.payload = payload
try await call.requestStream.send(packet)
}
}
} catch {
guard !Task.isCancelled else { return }
self.logger.error("Tunnel packet send loop failed: \(error)")
}
}
}
private func stopPacketBridge() {
inboundPacketTask?.cancel()
inboundPacketTask = nil
outboundPacketTask?.cancel()
outboundPacketTask = nil
packetCall?.cancel()
packetCall = nil
}
private func readPacketsBatch() async -> [(Data, NSNumber)] {
await withCheckedContinuation { continuation in
packetFlow.readPackets { packets, protocols in
continuation.resume(returning: Array(zip(packets, protocols)))
}
}
}
private static func protocolNumber(for payload: Data) -> NSNumber {
guard let version = payload.first.map({ $0 >> 4 }) else {
return NSNumber(value: AF_INET)
}
switch version {
case 6:
return NSNumber(value: AF_INET6)
default:
return NSNumber(value: AF_INET)
override func stopTunnel(with reason: NEProviderStopReason) async {
do {
_ = try await client.tunnelStop(.init())
logger.log("Stopped client")
} catch {
logger.error("Failed to stop tunnel: \(error)")
}
}
}
extension Burrow_TunnelConfigurationResponse {
fileprivate var settings: NEPacketTunnelNetworkSettings {
let parsedAddresses = addresses.compactMap(ParsedTunnelAddress.init(rawValue:))
let ipv4Addresses = parsedAddresses.compactMap(\.ipv4Address)
let ipv6Addresses = parsedAddresses.compactMap(\.ipv6Address)
let parsedRoutes = routes.compactMap(ParsedTunnelRoute.init(rawValue:))
var ipv4Routes = parsedRoutes.compactMap(\.ipv4Route)
var ipv6Routes = parsedRoutes.compactMap(\.ipv6Route)
if includeDefaultRoute {
ipv4Routes.append(.default())
ipv6Routes.append(.default())
}
let ipv6Addresses = addresses.filter { IPv6Address($0) != nil }
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
settings.mtu = NSNumber(value: mtu)
if !ipv4Addresses.isEmpty {
let ipv4Settings = NEIPv4Settings(
addresses: ipv4Addresses.map(\.address),
subnetMasks: ipv4Addresses.map(\.subnetMask)
)
if !ipv4Routes.isEmpty {
ipv4Settings.includedRoutes = ipv4Routes
}
settings.ipv4Settings = ipv4Settings
}
if !ipv6Addresses.isEmpty {
let ipv6Settings = NEIPv6Settings(
addresses: ipv6Addresses.map(\.address),
networkPrefixLengths: ipv6Addresses.map(\.prefixLength)
)
if !ipv6Routes.isEmpty {
ipv6Settings.includedRoutes = ipv6Routes
}
settings.ipv6Settings = ipv6Settings
}
if !dnsServers.isEmpty {
let dnsSettings = NEDNSSettings(servers: dnsServers)
if !searchDomains.isEmpty {
dnsSettings.matchDomains = searchDomains
}
settings.dnsSettings = dnsSettings
}
settings.ipv4Settings = NEIPv4Settings(
addresses: addresses.filter { IPv4Address($0) != nil },
subnetMasks: ["255.255.255.0"]
)
settings.ipv6Settings = NEIPv6Settings(
addresses: ipv6Addresses,
networkPrefixLengths: ipv6Addresses.map { _ in 64 }
)
return settings
}
}
private struct ParsedTunnelAddress {
struct IPv4AddressSetting {
let address: String
let subnetMask: String
}
struct IPv6AddressSetting {
let address: String
let prefixLength: NSNumber
}
let ipv4Address: IPv4AddressSetting?
let ipv6Address: IPv6AddressSetting?
init?(rawValue: String) {
let components = rawValue.split(separator: "/", maxSplits: 1).map(String.init)
let address = components.first?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !address.isEmpty else {
return nil
}
let prefix = components.count == 2 ? Int(components[1]) : nil
if IPv4Address(address) != nil {
let prefixLength = prefix ?? 32
guard (0 ... 32).contains(prefixLength) else {
return nil
}
ipv4Address = IPv4AddressSetting(
address: address,
subnetMask: Self.ipv4SubnetMask(prefixLength: prefixLength)
)
ipv6Address = nil
return
}
if IPv6Address(address) != nil {
let prefixLength = prefix ?? 128
guard (0 ... 128).contains(prefixLength) else {
return nil
}
ipv4Address = nil
ipv6Address = IPv6AddressSetting(
address: address,
prefixLength: NSNumber(value: prefixLength)
)
return
}
return nil
}
private static func ipv4SubnetMask(prefixLength: Int) -> String {
guard prefixLength > 0 else {
return "0.0.0.0"
}
let mask = UInt32.max << (32 - prefixLength)
let octets = [
(mask >> 24) & 0xff,
(mask >> 16) & 0xff,
(mask >> 8) & 0xff,
mask & 0xff,
]
return octets.map(String.init).joined(separator: ".")
}
}
private struct ParsedTunnelRoute {
let ipv4Route: NEIPv4Route?
let ipv6Route: NEIPv6Route?
init?(rawValue: String) {
let components = rawValue.split(separator: "/", maxSplits: 1).map(String.init)
let address = components.first?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !address.isEmpty else {
return nil
}
let prefix = components.count == 2 ? Int(components[1]) : nil
if IPv4Address(address) != nil {
let prefixLength = prefix ?? 32
guard (0 ... 32).contains(prefixLength) else {
return nil
}
ipv4Route = NEIPv4Route(
destinationAddress: address,
subnetMask: Self.ipv4SubnetMask(prefixLength: prefixLength)
)
ipv6Route = nil
return
}
if IPv6Address(address) != nil {
let prefixLength = prefix ?? 128
guard (0 ... 128).contains(prefixLength) else {
return nil
}
ipv4Route = nil
ipv6Route = NEIPv6Route(
destinationAddress: address,
networkPrefixLength: NSNumber(value: prefixLength)
)
return
}
return nil
}
private static func ipv4SubnetMask(prefixLength: Int) -> String {
var mask = UInt32.max << (32 - prefixLength)
if prefixLength == 0 {
mask = 0
}
let octets = [
String((mask >> 24) & 0xff),
String((mask >> 16) & 0xff),
String((mask >> 8) & 0xff),
String(mask & 0xff),
]
return octets.joined(separator: ".")
}
}

View file

@ -45,27 +45,6 @@ for ARCH in "${BURROW_ARCHS[@]}"; do
esac
done
if [[ -x "$(command -v rustup)" ]]; then
INSTALLED_TARGETS="$(rustup target list --installed)"
MISSING_TARGETS=()
for TARGET in "${RUST_TARGETS[@]}"; do
if ! grep -qx "$TARGET" <<< "$INSTALLED_TARGETS"; then
MISSING_TARGETS+=("$TARGET")
fi
done
if (( ${#MISSING_TARGETS[@]} > 0 )); then
rustup target add "${MISSING_TARGETS[@]}"
fi
else
SYSROOT="$(rustc --print sysroot)"
for TARGET in "${RUST_TARGETS[@]}"; do
if [[ ! -d "${SYSROOT}/lib/rustlib/${TARGET}" ]]; then
echo "error: Rust target ${TARGET} is not installed and rustup is unavailable" >&2
exit 1
fi
done
fi
# Pass all RUST_TARGETS in a single invocation
CARGO_ARGS=()
for TARGET in "${RUST_TARGETS[@]}"; do
@ -84,39 +63,17 @@ else
fi
if [[ -x "$(command -v rustup)" ]]; then
CARGO_PATH="$(dirname "$(rustup which cargo)"):/usr/bin"
CARGO_PATH="$(dirname $(rustup which cargo)):/usr/bin"
else
CARGO_PATH="$(dirname "$(command -v cargo)"):/usr/bin"
CARGO_PATH="$(dirname $(readlink -f $(which cargo))):/usr/bin"
fi
if [[ -n "${PROTOC:-}" ]]; then
if [[ ! -x "$PROTOC" ]]; then
echo "error: PROTOC is set but is not executable: $PROTOC" >&2
exit 127
fi
elif ! PROTOC="$(command -v protoc 2>/dev/null)"; then
echo 'error: Unable to find protoc; pass PROTOC or include it in PATH for the Xcode Rust build phase' >&2
exit 127
fi
PROTOC=$(readlink -f $(which protoc))
CARGO_PATH="$(dirname $PROTOC):$CARGO_PATH"
# Run cargo without the various environment variables set by Xcode.
# Those variables can confuse cargo and the build scripts it runs.
CARGO_ENV=(
"PATH=$CARGO_PATH"
"PROTOC=$PROTOC"
"CARGO_TARGET_DIR=${CONFIGURATION_TEMP_DIR}/target"
)
if [[ -n "$IPHONEOS_DEPLOYMENT_TARGET" ]]; then
CARGO_ENV+=("IPHONEOS_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET")
fi
if [[ -n "$MACOSX_DEPLOYMENT_TARGET" ]]; then
CARGO_ENV+=("MACOSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET")
fi
env -i "${CARGO_ENV[@]}" cargo build "${CARGO_ARGS[@]}"
env -i PATH="$CARGO_PATH" PROTOC="$PROTOC" CARGO_TARGET_DIR="${CONFIGURATION_TEMP_DIR}/target" IPHONEOS_DEPLOYMENT_TARGET="$IPHONEOS_DEPLOYMENT_TARGET" MACOSX_DEPLOYMENT_TARGET="$MACOSX_DEPLOYMENT_TARGET" cargo build "${CARGO_ARGS[@]}"
mkdir -p "${BUILT_PRODUCTS_DIR}"

View file

@ -1,15 +1,6 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x31",
"green" : "0x7A",
"red" : "0xB7"
}
},
"idiom" : "universal"
}
],

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -1,218 +1,344 @@
{
"images": [
"images" : [
{
"filename": "ios-panel-01-40.png",
"idiom": "iphone",
"scale": "2x",
"size": "20x20"
"filename" : "40.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"filename": "ios-panel-01-60.png",
"idiom": "iphone",
"scale": "3x",
"size": "20x20"
"filename" : "60.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename": "ios-panel-01-29.png",
"idiom": "iphone",
"scale": "1x",
"size": "29x29"
"filename" : "29.png",
"idiom" : "iphone",
"scale" : "1x",
"size" : "29x29"
},
{
"filename": "ios-panel-01-58.png",
"idiom": "iphone",
"scale": "2x",
"size": "29x29"
"filename" : "58.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"filename": "ios-panel-01-87.png",
"idiom": "iphone",
"scale": "3x",
"size": "29x29"
"filename" : "87.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"filename": "ios-panel-01-80.png",
"idiom": "iphone",
"scale": "2x",
"size": "40x40"
"filename" : "80.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"filename": "ios-panel-01-120.png",
"idiom": "iphone",
"scale": "3x",
"size": "40x40"
"filename" : "120.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename": "ios-panel-01-57.png",
"idiom": "iphone",
"scale": "1x",
"size": "57x57"
"filename" : "57.png",
"idiom" : "iphone",
"scale" : "1x",
"size" : "57x57"
},
{
"filename": "ios-panel-01-114.png",
"idiom": "iphone",
"scale": "2x",
"size": "57x57"
"filename" : "114.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "57x57"
},
{
"filename": "ios-panel-01-120.png",
"idiom": "iphone",
"scale": "2x",
"size": "60x60"
"filename" : "120.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename": "ios-panel-01-180.png",
"idiom": "iphone",
"scale": "3x",
"size": "60x60"
"filename" : "180.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename": "ios-panel-01-20.png",
"idiom": "ipad",
"scale": "1x",
"size": "20x20"
"filename" : "20.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"filename": "ios-panel-01-40.png",
"idiom": "ipad",
"scale": "2x",
"size": "20x20"
"filename" : "40.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"filename": "ios-panel-01-29.png",
"idiom": "ipad",
"scale": "1x",
"size": "29x29"
"filename" : "29.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"filename": "ios-panel-01-58.png",
"idiom": "ipad",
"scale": "2x",
"size": "29x29"
"filename" : "58.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"filename": "ios-panel-01-40.png",
"idiom": "ipad",
"scale": "1x",
"size": "40x40"
"filename" : "40.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"filename": "ios-panel-01-80.png",
"idiom": "ipad",
"scale": "2x",
"size": "40x40"
"filename" : "80.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"filename": "ios-panel-01-50.png",
"idiom": "ipad",
"scale": "1x",
"size": "50x50"
"filename" : "50.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "50x50"
},
{
"filename": "ios-panel-01-100.png",
"idiom": "ipad",
"scale": "2x",
"size": "50x50"
"filename" : "100.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "50x50"
},
{
"filename": "ios-panel-01-72.png",
"idiom": "ipad",
"scale": "1x",
"size": "72x72"
"filename" : "72.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "72x72"
},
{
"filename": "ios-panel-01-144.png",
"idiom": "ipad",
"scale": "2x",
"size": "72x72"
"filename" : "144.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "72x72"
},
{
"filename": "ios-panel-01-76.png",
"idiom": "ipad",
"scale": "1x",
"size": "76x76"
"filename" : "76.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename": "ios-panel-01-152.png",
"idiom": "ipad",
"scale": "2x",
"size": "76x76"
"filename" : "152.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename": "ios-panel-01-167.png",
"idiom": "ipad",
"scale": "2x",
"size": "83.5x83.5"
"filename" : "167.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename": "ios-panel-01-1024.png",
"idiom": "ios-marketing",
"scale": "1x",
"size": "1024x1024"
"filename" : "1024.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
},
{
"filename": "mac-panel-05-16.png",
"idiom": "mac",
"scale": "1x",
"size": "16x16"
"filename" : "16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename": "mac-panel-05-32.png",
"idiom": "mac",
"scale": "2x",
"size": "16x16"
"filename" : "32.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename": "mac-panel-05-32.png",
"idiom": "mac",
"scale": "1x",
"size": "32x32"
"filename" : "32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename": "mac-panel-05-64.png",
"idiom": "mac",
"scale": "2x",
"size": "32x32"
"filename" : "64.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename": "mac-panel-05-128.png",
"idiom": "mac",
"scale": "1x",
"size": "128x128"
"filename" : "128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename": "mac-panel-05-256.png",
"idiom": "mac",
"scale": "2x",
"size": "128x128"
"filename" : "256.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename": "mac-panel-05-256.png",
"idiom": "mac",
"scale": "1x",
"size": "256x256"
"filename" : "256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename": "mac-panel-05-512.png",
"idiom": "mac",
"scale": "2x",
"size": "256x256"
"filename" : "512.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename": "mac-panel-05-512.png",
"idiom": "mac",
"scale": "1x",
"size": "512x512"
"filename" : "512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename": "mac-panel-05-1024.png",
"idiom": "mac",
"scale": "2x",
"size": "512x512"
"filename" : "1024.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
},
{
"filename" : "48.png",
"idiom" : "watch",
"role" : "notificationCenter",
"scale" : "2x",
"size" : "24x24",
"subtype" : "38mm"
},
{
"filename" : "55.png",
"idiom" : "watch",
"role" : "notificationCenter",
"scale" : "2x",
"size" : "27.5x27.5",
"subtype" : "42mm"
},
{
"filename" : "58.png",
"idiom" : "watch",
"role" : "companionSettings",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "87.png",
"idiom" : "watch",
"role" : "companionSettings",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "watch",
"role" : "notificationCenter",
"scale" : "2x",
"size" : "33x33",
"subtype" : "45mm"
},
{
"filename" : "80.png",
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "40x40",
"subtype" : "38mm"
},
{
"filename" : "88.png",
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "44x44",
"subtype" : "40mm"
},
{
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "46x46",
"subtype" : "41mm"
},
{
"filename" : "100.png",
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "50x50",
"subtype" : "44mm"
},
{
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "51x51",
"subtype" : "45mm"
},
{
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "54x54",
"subtype" : "49mm"
},
{
"filename" : "172.png",
"idiom" : "watch",
"role" : "quickLook",
"scale" : "2x",
"size" : "86x86",
"subtype" : "38mm"
},
{
"filename" : "196.png",
"idiom" : "watch",
"role" : "quickLook",
"scale" : "2x",
"size" : "98x98",
"subtype" : "42mm"
},
{
"filename" : "216.png",
"idiom" : "watch",
"role" : "quickLook",
"scale" : "2x",
"size" : "108x108",
"subtype" : "44mm"
},
{
"idiom" : "watch",
"role" : "quickLook",
"scale" : "2x",
"size" : "117x117",
"subtype" : "45mm"
},
{
"idiom" : "watch",
"role" : "quickLook",
"scale" : "2x",
"size" : "129x129",
"subtype" : "49mm"
},
{
"filename" : "1024.png",
"idiom" : "watch-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info": {
"author": "xcode",
"version": 1
"info" : {
"author" : "xcode",
"version" : 1
}
}

Some files were not shown because too many files have changed in this diff Show more