Compare commits
4 commits
main
...
trojan-sha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7cf38c382 | ||
|
|
6c6bfe5434 | ||
|
|
4d1f589280 | ||
|
|
d1638726ca |
10
.bazelignore
|
|
@ -1,10 +0,0 @@
|
|||
.build
|
||||
.derived-data
|
||||
.git
|
||||
.nscloud-cache
|
||||
Apple/build
|
||||
ci-artifacts
|
||||
dist
|
||||
publish
|
||||
release
|
||||
target
|
||||
|
|
@ -1 +0,0 @@
|
|||
9.1.0
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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."
|
||||
|
|
@ -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})."
|
||||
|
|
@ -10,10 +10,6 @@ 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
|
||||
|
|
@ -43,21 +39,8 @@ jobs:
|
|||
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
|
||||
uses: https://code.forgejo.org/actions/upload-artifact@v4
|
||||
with:
|
||||
name: burrow-release-${{ github.ref_name }}
|
||||
path: dist/*
|
||||
|
|
|
|||
12
.gitignore
vendored
|
|
@ -1,10 +1,6 @@
|
|||
# Xcode
|
||||
xcuserdata
|
||||
Apple/build/
|
||||
.derived-data/
|
||||
AuthKey_*.p8
|
||||
burrow-certs-pass.txt
|
||||
*.p12
|
||||
|
||||
# Swift
|
||||
Apple/Package/.swiftpm/
|
||||
|
|
@ -13,19 +9,11 @@ Apple/Package/.swiftpm/
|
|||
target/
|
||||
.env
|
||||
|
||||
# Bazel
|
||||
bazel-*
|
||||
|
||||
.DS_STORE
|
||||
.idea/
|
||||
|
||||
tmp/
|
||||
.cache/
|
||||
intake/
|
||||
dist/
|
||||
publish/
|
||||
release/
|
||||
ci-artifacts/
|
||||
|
||||
*.db
|
||||
*.sqlite3
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package net.burrow.android
|
||||
|
||||
object BurrowCore {
|
||||
init {
|
||||
System.loadLibrary("burrow_mobile_ffi")
|
||||
}
|
||||
|
||||
external fun version(): String
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
|
@ -1,3 +0,0 @@
|
|||
<resources>
|
||||
<string name="app_name">Burrow</string>
|
||||
</resources>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
@ -15,5 +15,7 @@
|
|||
<array>
|
||||
<string>$(APP_GROUP_IDENTIFIER)</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -10,20 +10,15 @@ 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
|
||||
|
||||
EXCLUDED_SOURCE_FILE_NAMES[sdk=macosx*] =
|
||||
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
|
||||
|
||||
SWIFT_INCLUDE_PATHS = $(inherited) $(PROJECT_DIR)/NetworkExtension
|
||||
|
|
|
|||
|
|
@ -1,29 +1,17 @@
|
|||
#if os(macOS)
|
||||
import AppKit
|
||||
import BurrowConfiguration
|
||||
import BurrowCore
|
||||
import BurrowUI
|
||||
import Sparkle
|
||||
import SwiftUI
|
||||
import libburrow
|
||||
|
||||
@main
|
||||
@MainActor
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
private var windowController: NSWindowController?
|
||||
private let updaterController = SPUStandardUpdaterController(
|
||||
startingUpdater: true,
|
||||
updaterDelegate: nil,
|
||||
userDriverDelegate: nil
|
||||
)
|
||||
private let logger = Logger.logger(for: AppDelegate.self)
|
||||
|
||||
private let quitItem: NSMenuItem = {
|
||||
let quitItem = NSMenuItem(
|
||||
title: "Quit Burrow",
|
||||
action: #selector(NSApplication.terminate(_:)),
|
||||
keyEquivalent: "q"
|
||||
)
|
||||
quitItem.target = NSApplication.shared
|
||||
quitItem.keyEquivalentModifierMask = .command
|
||||
return quitItem
|
||||
}()
|
||||
private let quitItem = AppDelegate.makeQuitItem()
|
||||
|
||||
private lazy var openItem: NSMenuItem = {
|
||||
let item = NSMenuItem(
|
||||
|
|
@ -36,16 +24,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
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)
|
||||
|
|
@ -61,7 +39,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
menu.items = [
|
||||
toggleItem,
|
||||
openItem,
|
||||
checkForUpdatesItem,
|
||||
.separator(),
|
||||
quitItem
|
||||
]
|
||||
|
|
@ -78,9 +55,85 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
}()
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
installMainMenu()
|
||||
startDaemon()
|
||||
statusItem.menu = menu
|
||||
}
|
||||
|
||||
private func startDaemon() {
|
||||
do {
|
||||
libburrow.spawnInProcess(
|
||||
socketPath: try Constants.socketURL.path(percentEncoded: false),
|
||||
databasePath: try Constants.databaseURL.path(percentEncoded: false)
|
||||
)
|
||||
} catch {
|
||||
logger.error("Failed to spawn daemon thread: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func installMainMenu() {
|
||||
let mainMenu = NSMenu(title: "Burrow")
|
||||
|
||||
let appItem = NSMenuItem(title: "Burrow", action: nil, keyEquivalent: "")
|
||||
let appMenu = NSMenu(title: "Burrow")
|
||||
|
||||
let aboutItem = NSMenuItem(
|
||||
title: "About Burrow",
|
||||
action: #selector(NSApplication.orderFrontStandardAboutPanel(_:)),
|
||||
keyEquivalent: ""
|
||||
)
|
||||
aboutItem.target = NSApplication.shared
|
||||
appMenu.addItem(aboutItem)
|
||||
appMenu.addItem(.separator())
|
||||
appMenu.addItem(Self.makeQuitItem())
|
||||
|
||||
appItem.submenu = appMenu
|
||||
mainMenu.addItem(appItem)
|
||||
|
||||
let editItem = NSMenuItem(title: "Edit", action: nil, keyEquivalent: "")
|
||||
editItem.submenu = makeEditMenu()
|
||||
mainMenu.addItem(editItem)
|
||||
|
||||
NSApplication.shared.mainMenu = mainMenu
|
||||
}
|
||||
|
||||
private static func makeQuitItem() -> NSMenuItem {
|
||||
let quitItem = NSMenuItem(
|
||||
title: "Quit Burrow",
|
||||
action: #selector(NSApplication.terminate(_:)),
|
||||
keyEquivalent: "q"
|
||||
)
|
||||
quitItem.target = NSApplication.shared
|
||||
quitItem.keyEquivalentModifierMask = .command
|
||||
return quitItem
|
||||
}
|
||||
|
||||
private func makeEditMenu() -> NSMenu {
|
||||
let editMenu = NSMenu(title: "Edit")
|
||||
|
||||
editMenu.addItem(NSMenuItem(title: "Undo", action: Selector(("undo:")), keyEquivalent: "z"))
|
||||
editMenu.addItem(NSMenuItem(title: "Redo", action: Selector(("redo:")), keyEquivalent: "Z"))
|
||||
editMenu.addItem(.separator())
|
||||
|
||||
editMenu.addItem(NSMenuItem(title: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x"))
|
||||
editMenu.addItem(NSMenuItem(title: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c"))
|
||||
editMenu.addItem(NSMenuItem(title: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v"))
|
||||
|
||||
let pastePlainItem = NSMenuItem(
|
||||
title: "Paste and Match Style",
|
||||
action: #selector(NSTextView.pasteAsPlainText(_:)),
|
||||
keyEquivalent: "V"
|
||||
)
|
||||
pastePlainItem.keyEquivalentModifierMask = [.command, .option, .shift]
|
||||
editMenu.addItem(pastePlainItem)
|
||||
|
||||
editMenu.addItem(NSMenuItem(title: "Delete", action: #selector(NSText.delete(_:)), keyEquivalent: ""))
|
||||
editMenu.addItem(.separator())
|
||||
editMenu.addItem(NSMenuItem(title: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a"))
|
||||
|
||||
return editMenu
|
||||
}
|
||||
|
||||
@objc
|
||||
private func openWindow() {
|
||||
if let window = windowController?.window {
|
||||
|
|
@ -91,9 +144,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
|
||||
let contentView = BurrowView()
|
||||
let hostingController = NSHostingController(rootView: contentView)
|
||||
if #available(macOS 13.0, *) {
|
||||
hostingController.sizingOptions = []
|
||||
}
|
||||
let window = NSWindow(contentViewController: hostingController)
|
||||
window.title = "Burrow"
|
||||
window.setContentSize(NSSize(width: 820, height: 720))
|
||||
window.contentMinSize = NSSize(width: 640, height: 560)
|
||||
window.styleMask.insert([.titled, .closable, .miniaturizable, .resizable])
|
||||
window.center()
|
||||
|
||||
|
|
@ -103,10 +160,20 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
windowController = controller
|
||||
NSApplication.shared.activate(ignoringOtherApps: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
private func checkForUpdates(_ sender: Any?) {
|
||||
updaterController.checkForUpdates(sender)
|
||||
@main
|
||||
enum BurrowApplication {
|
||||
@MainActor
|
||||
private static let delegate = AppDelegate()
|
||||
|
||||
@MainActor
|
||||
static func main() {
|
||||
let application = NSApplication.shared
|
||||
application.delegate = delegate
|
||||
application.setActivationPolicy(.accessory)
|
||||
application.finishLaunching()
|
||||
application.run()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23091" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24506" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23091"/>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24506"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
|
|
|
|||
|
|
@ -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,9 +16,9 @@
|
|||
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 */; };
|
||||
D0BCC60C2A09A0C200AD070D /* libburrow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D0BCC6032A09535900AD070D /* libburrow.a */; };
|
||||
D0BF09522C8E66F6000D8DEC /* BurrowConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */; };
|
||||
D0BF09552C8E66FD000D8DEC /* BurrowConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */; };
|
||||
D0D4E53A2C8D996F007F820A /* BurrowCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
|
|
@ -37,7 +36,6 @@
|
|||
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 +43,14 @@
|
|||
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 */; };
|
||||
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 */; };
|
||||
D0FA10012D10200100112233 /* Generated/burrow.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA10032D10200100112233 /* Generated/burrow.pb.swift */; };
|
||||
D0FA10022D10200100112233 /* Generated/burrow.grpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA10042D10200100112233 /* Generated/burrow.grpc.swift */; };
|
||||
D11000012F70000100112233 /* BurrowUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D11000042F70000100112233 /* BurrowUITests.swift */; };
|
||||
/* 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 */;
|
||||
|
|
@ -109,6 +100,13 @@
|
|||
remoteGlobalIDString = D0D4E5302C8D996F007F820A;
|
||||
remoteInfo = Core;
|
||||
};
|
||||
D11000022F70000100112233 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = D05B9F7129E39EEC008CB1F9;
|
||||
remoteInfo = App;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
|
|
@ -141,9 +139,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>"; };
|
||||
|
|
@ -191,18 +186,14 @@
|
|||
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>"; };
|
||||
D0FA10032D10200100112233 /* Generated/burrow.pb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Generated/burrow.pb.swift; sourceTree = "<group>"; };
|
||||
D0FA10042D10200100112233 /* Generated/burrow.grpc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Generated/burrow.grpc.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>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
D11000062F70000100112233 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D020F65029E4A697002790F6 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
|
@ -221,7 +212,7 @@
|
|||
D0BF09552C8E66FD000D8DEC /* BurrowConfiguration.framework in Frameworks */,
|
||||
D0F4FAD32C8DC79C0068730A /* BurrowCore.framework in Frameworks */,
|
||||
D0D4E5892C8D9C94007F820A /* BurrowUI.framework in Frameworks */,
|
||||
D0C0DE102FA0000100112233 /* Sparkle in Frameworks */,
|
||||
D0BCC60C2A09A0C200AD070D /* libburrow.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -242,11 +233,17 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0A11C102F90000100112233 /* DequeModule in Frameworks */,
|
||||
D0D4E5702C8D9C62007F820A /* BurrowCore.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D11000062F70000100112233 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
|
|
@ -329,14 +326,6 @@
|
|||
path = App;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D11000072F70000100112233 /* AppUITests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D11000042F70000100112233 /* BurrowUITests.swift */,
|
||||
);
|
||||
path = AppUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D0B98FD729FDDB57004E7149 /* libburrow */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -351,8 +340,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D0D4E4952C8D921A007F820A /* burrow.proto */,
|
||||
D0FA10032D10200100112233 /* burrow.pb.swift */,
|
||||
D0FA10042D10200100112233 /* burrow.grpc.swift */,
|
||||
D0FA10032D10200100112233 /* Generated/burrow.pb.swift */,
|
||||
D0FA10042D10200100112233 /* Generated/burrow.grpc.swift */,
|
||||
);
|
||||
path = Client;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -406,27 +395,17 @@
|
|||
path = Constants;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D11000072F70000100112233 /* AppUITests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D11000042F70000100112233 /* BurrowUITests.swift */,
|
||||
);
|
||||
path = AppUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* 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 +444,6 @@
|
|||
D020F65C29E4A697002790F6 /* PBXTargetDependency */,
|
||||
);
|
||||
name = App;
|
||||
packageProductDependencies = (
|
||||
D0C0DE122FA0000100112233 /* Sparkle */,
|
||||
);
|
||||
productName = Burrow;
|
||||
productReference = D05B9F7229E39EEC008CB1F9 /* Burrow.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
|
|
@ -512,7 +488,6 @@
|
|||
);
|
||||
name = UI;
|
||||
packageProductDependencies = (
|
||||
D0A11C0F2F90000100112233 /* DequeModule */,
|
||||
);
|
||||
productName = Core;
|
||||
productReference = D0D4E5582C8D9BF2007F820A /* BurrowUI.framework */;
|
||||
|
|
@ -536,6 +511,24 @@
|
|||
productReference = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
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";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
|
|
@ -546,19 +539,54 @@
|
|||
LastSwiftUpdateCheck = 1600;
|
||||
LastUpgradeCheck = 1520;
|
||||
TargetAttributes = {
|
||||
D11000082F70000100112233 = {
|
||||
CreatedOnToolsVersion = 16.0;
|
||||
TestTargetID = D05B9F7129E39EEC008CB1F9;
|
||||
};
|
||||
D020F65229E4A697002790F6 = {
|
||||
CreatedOnToolsVersion = 14.3;
|
||||
DevelopmentTeam = 2KPBQHRJ2D;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.ApplicationGroups.Mac = {
|
||||
enabled = 1;
|
||||
};
|
||||
com.apple.NetworkExtensions = {
|
||||
enabled = 1;
|
||||
};
|
||||
com.apple.Sandbox = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
D05B9F7129E39EEC008CB1F9 = {
|
||||
CreatedOnToolsVersion = 14.3;
|
||||
DevelopmentTeam = 2KPBQHRJ2D;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.ApplicationGroups.Mac = {
|
||||
enabled = 1;
|
||||
};
|
||||
com.apple.ApplicationGroups.iOS = {
|
||||
enabled = 1;
|
||||
};
|
||||
com.apple.AssociatedDomains = {
|
||||
enabled = 1;
|
||||
};
|
||||
com.apple.NetworkExtensions = {
|
||||
enabled = 1;
|
||||
};
|
||||
com.apple.NetworkExtensions.iOS = {
|
||||
enabled = 1;
|
||||
};
|
||||
com.apple.Sandbox = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
D0D4E5302C8D996F007F820A = {
|
||||
CreatedOnToolsVersion = 16.0;
|
||||
};
|
||||
D11000082F70000100112233 = {
|
||||
CreatedOnToolsVersion = 16.0;
|
||||
TestTargetID = D05B9F7129E39EEC008CB1F9;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = D05B9F6D29E39EEC008CB1F9 /* Build configuration list for PBXProject "Burrow" */;
|
||||
|
|
@ -576,8 +604,6 @@
|
|||
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 = "";
|
||||
|
|
@ -594,19 +620,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;
|
||||
};
|
||||
|
|
@ -617,6 +635,13 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D11000092F70000100112233 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
|
|
@ -665,14 +690,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 +711,8 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0FA10012D10200100112233 /* burrow.pb.swift in Sources */,
|
||||
D0FA10022D10200100112233 /* burrow.grpc.swift in Sources */,
|
||||
D0FA10012D10200100112233 /* Generated/burrow.pb.swift in Sources */,
|
||||
D0FA10022D10200100112233 /* Generated/burrow.grpc.swift in Sources */,
|
||||
D0F7598D2C8DB3DA00126CF3 /* Client.swift in Sources */,
|
||||
D0D4E56B2C8D9C2F007F820A /* Logging.swift in Sources */,
|
||||
);
|
||||
|
|
@ -728,14 +745,17 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D110000A2F70000100112233 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D11000012F70000100112233 /* BurrowUITests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
D110000B2F70000100112233 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D05B9F7129E39EEC008CB1F9 /* App */;
|
||||
targetProxy = D11000022F70000100112233 /* PBXContainerItemProxy */;
|
||||
};
|
||||
D020F65C29E4A697002790F6 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D020F65229E4A697002790F6 /* NetworkExtension */;
|
||||
|
|
@ -775,27 +795,19 @@
|
|||
isa = PBXTargetDependency;
|
||||
productRef = D0F759892C8DB34200126CF3 /* GRPC */;
|
||||
};
|
||||
D110000B2F70000100112233 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D05B9F7129E39EEC008CB1F9 /* App */;
|
||||
targetProxy = D11000022F70000100112233 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* 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 */;
|
||||
buildSettings = {
|
||||
DEVELOPMENT_TEAM = 2KPBQHRJ2D;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
|
@ -803,6 +815,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D020F66229E4A6E5002790F6 /* NetworkExtension.xcconfig */;
|
||||
buildSettings = {
|
||||
DEVELOPMENT_TEAM = 2KPBQHRJ2D;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
|
@ -810,6 +823,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D020F64029E4A1FF002790F6 /* Compiler.xcconfig */;
|
||||
buildSettings = {
|
||||
DEVELOPMENT_TEAM = 2KPBQHRJ2D;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
|
@ -817,6 +831,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D020F64029E4A1FF002790F6 /* Compiler.xcconfig */;
|
||||
buildSettings = {
|
||||
DEVELOPMENT_TEAM = 2KPBQHRJ2D;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
|
@ -824,6 +839,10 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D020F64929E4A34B002790F6 /* App.xcconfig */;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 2KPBQHRJ2D;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
|
@ -831,6 +850,10 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D020F64929E4A34B002790F6 /* App.xcconfig */;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 2KPBQHRJ2D;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
|
@ -876,18 +899,23 @@
|
|||
};
|
||||
name = Release;
|
||||
};
|
||||
D110000C2F70000100112233 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D11000052F70000100112233 /* UITests.xcconfig */;
|
||||
buildSettings = {
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
D110000D2F70000100112233 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D11000052F70000100112233 /* UITests.xcconfig */;
|
||||
buildSettings = {
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* 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 = (
|
||||
|
|
@ -942,6 +970,15 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D110000E2F70000100112233 /* Build configuration list for PBXNativeTarget "BurrowUITests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
D110000C2F70000100112233 /* Debug */,
|
||||
D110000D2F70000100112233 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
|
|
@ -961,14 +998,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 +1006,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,21 +1050,11 @@
|
|||
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 */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D0C0DE112FA0000100112233 /* XCRemoteSwiftPackageReference "Sparkle" */;
|
||||
productName = Sparkle;
|
||||
};
|
||||
D0F7597D2C8DB30500126CF3 /* CGRPCZlib */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D0D4E4822C8D8EF6007F820A /* XCRemoteSwiftPackageReference "grpc-swift" */;
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -16,6 +16,13 @@ public enum Constants {
|
|||
try groupContainerURL.appending(component: "burrow.sock", directoryHint: .notDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
public static var packetTunnelSocketURL: URL {
|
||||
get throws {
|
||||
try groupContainerURL.appending(component: "burrow-packet-tunnel.sock", directoryHint: .notDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
public static var databaseURL: URL {
|
||||
get throws {
|
||||
try groupContainerURL.appending(component: "burrow.db", directoryHint: .notDirectory)
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
DEVELOPMENT_TEAM = P6PV2R9443
|
||||
APP_BUNDLE_IDENTIFIER = com.hackclub.burrow
|
||||
DEVELOPMENT_TEAM = 2KPBQHRJ2D
|
||||
APP_BUNDLE_IDENTIFIER = com.tianruichen.burrow
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -108,6 +108,83 @@ public struct Burrow_TailnetLoginStatusResponse: Sendable {
|
|||
public init() {}
|
||||
}
|
||||
|
||||
public struct Burrow_ProxySubscriptionImportRequest: Sendable {
|
||||
public var url: String = ""
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct Burrow_ProxySubscriptionNodePreview: Sendable {
|
||||
public var ordinal: Int32 = 0
|
||||
public var name: String = ""
|
||||
public var `protocol`: String = ""
|
||||
public var server: String = ""
|
||||
public var port: Int32 = 0
|
||||
public var warnings: [String] = []
|
||||
public var runtimeSupported: Bool = false
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct Burrow_ProxySubscriptionRejectedEntry: Sendable {
|
||||
public var ordinal: Int32 = 0
|
||||
public var reason: String = ""
|
||||
public var scheme: String = ""
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct Burrow_ProxySubscriptionPreviewResponse: Sendable {
|
||||
public var suggestedName: String = ""
|
||||
public var detectedFormat: String = ""
|
||||
public var compatibleCount: Int32 = 0
|
||||
public var rejectedCount: Int32 = 0
|
||||
public var node: [Burrow_ProxySubscriptionNodePreview] = []
|
||||
public var rejected: [Burrow_ProxySubscriptionRejectedEntry] = []
|
||||
public var warnings: [String] = []
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct Burrow_ProxySubscriptionApplyRequest: Sendable {
|
||||
public var id: Int32 = 0
|
||||
public var url: String = ""
|
||||
public var name: String = ""
|
||||
public var selectedOrdinal: Int32 = 0
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct Burrow_ProxySubscriptionApplyResponse: Sendable {
|
||||
public var id: Int32 = 0
|
||||
public var importedCount: Int32 = 0
|
||||
public var selectedOrdinal: Int32 = 0
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct Burrow_ProxySubscriptionSelectRequest: Sendable {
|
||||
public var id: Int32 = 0
|
||||
public var selectedOrdinal: Int32 = 0
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct Burrow_ProxySubscriptionSelectResponse: Sendable {
|
||||
public var id: Int32 = 0
|
||||
public var selectedOrdinal: Int32 = 0
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct Burrow_TunnelPacket: Sendable {
|
||||
public var payload = Data()
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
|
@ -417,6 +494,239 @@ extension Burrow_TunnelPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
|
|||
}
|
||||
}
|
||||
|
||||
extension Burrow_ProxySubscriptionImportRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = "burrow.ProxySubscriptionImportRequest"
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "url")
|
||||
]
|
||||
|
||||
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.url)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if !self.url.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.url, fieldNumber: 1)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
}
|
||||
|
||||
extension Burrow_ProxySubscriptionNodePreview: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = "burrow.ProxySubscriptionNodePreview"
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "ordinal"),
|
||||
2: .same(proto: "name"),
|
||||
3: .same(proto: "protocol"),
|
||||
4: .same(proto: "server"),
|
||||
5: .same(proto: "port"),
|
||||
6: .same(proto: "warnings"),
|
||||
7: .standard(proto: "runtime_supported"),
|
||||
]
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularInt32Field(value: &self.ordinal)
|
||||
case 2: try decoder.decodeSingularStringField(value: &self.name)
|
||||
case 3: try decoder.decodeSingularStringField(value: &self.`protocol`)
|
||||
case 4: try decoder.decodeSingularStringField(value: &self.server)
|
||||
case 5: try decoder.decodeSingularInt32Field(value: &self.port)
|
||||
case 6: try decoder.decodeRepeatedStringField(value: &self.warnings)
|
||||
case 7: try decoder.decodeSingularBoolField(value: &self.runtimeSupported)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if self.ordinal != 0 { try visitor.visitSingularInt32Field(value: self.ordinal, fieldNumber: 1) }
|
||||
if !self.name.isEmpty { try visitor.visitSingularStringField(value: self.name, fieldNumber: 2) }
|
||||
if !self.`protocol`.isEmpty { try visitor.visitSingularStringField(value: self.`protocol`, fieldNumber: 3) }
|
||||
if !self.server.isEmpty { try visitor.visitSingularStringField(value: self.server, fieldNumber: 4) }
|
||||
if self.port != 0 { try visitor.visitSingularInt32Field(value: self.port, fieldNumber: 5) }
|
||||
if !self.warnings.isEmpty { try visitor.visitRepeatedStringField(value: self.warnings, fieldNumber: 6) }
|
||||
if self.runtimeSupported { try visitor.visitSingularBoolField(value: self.runtimeSupported, fieldNumber: 7) }
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
}
|
||||
|
||||
extension Burrow_ProxySubscriptionRejectedEntry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = "burrow.ProxySubscriptionRejectedEntry"
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "ordinal"),
|
||||
2: .same(proto: "reason"),
|
||||
3: .same(proto: "scheme"),
|
||||
]
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularInt32Field(value: &self.ordinal)
|
||||
case 2: try decoder.decodeSingularStringField(value: &self.reason)
|
||||
case 3: try decoder.decodeSingularStringField(value: &self.scheme)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if self.ordinal != 0 { try visitor.visitSingularInt32Field(value: self.ordinal, fieldNumber: 1) }
|
||||
if !self.reason.isEmpty { try visitor.visitSingularStringField(value: self.reason, fieldNumber: 2) }
|
||||
if !self.scheme.isEmpty { try visitor.visitSingularStringField(value: self.scheme, fieldNumber: 3) }
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
}
|
||||
|
||||
extension Burrow_ProxySubscriptionPreviewResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = "burrow.ProxySubscriptionPreviewResponse"
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .standard(proto: "suggested_name"),
|
||||
2: .standard(proto: "detected_format"),
|
||||
3: .standard(proto: "compatible_count"),
|
||||
4: .standard(proto: "rejected_count"),
|
||||
5: .same(proto: "node"),
|
||||
6: .same(proto: "rejected"),
|
||||
7: .same(proto: "warnings"),
|
||||
]
|
||||
|
||||
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.suggestedName)
|
||||
case 2: try decoder.decodeSingularStringField(value: &self.detectedFormat)
|
||||
case 3: try decoder.decodeSingularInt32Field(value: &self.compatibleCount)
|
||||
case 4: try decoder.decodeSingularInt32Field(value: &self.rejectedCount)
|
||||
case 5: try decoder.decodeRepeatedMessageField(value: &self.node)
|
||||
case 6: try decoder.decodeRepeatedMessageField(value: &self.rejected)
|
||||
case 7: try decoder.decodeRepeatedStringField(value: &self.warnings)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if !self.suggestedName.isEmpty { try visitor.visitSingularStringField(value: self.suggestedName, fieldNumber: 1) }
|
||||
if !self.detectedFormat.isEmpty { try visitor.visitSingularStringField(value: self.detectedFormat, fieldNumber: 2) }
|
||||
if self.compatibleCount != 0 { try visitor.visitSingularInt32Field(value: self.compatibleCount, fieldNumber: 3) }
|
||||
if self.rejectedCount != 0 { try visitor.visitSingularInt32Field(value: self.rejectedCount, fieldNumber: 4) }
|
||||
if !self.node.isEmpty { try visitor.visitRepeatedMessageField(value: self.node, fieldNumber: 5) }
|
||||
if !self.rejected.isEmpty { try visitor.visitRepeatedMessageField(value: self.rejected, fieldNumber: 6) }
|
||||
if !self.warnings.isEmpty { try visitor.visitRepeatedStringField(value: self.warnings, fieldNumber: 7) }
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
}
|
||||
|
||||
extension Burrow_ProxySubscriptionApplyRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = "burrow.ProxySubscriptionApplyRequest"
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "id"),
|
||||
2: .same(proto: "url"),
|
||||
3: .same(proto: "name"),
|
||||
4: .standard(proto: "selected_ordinal"),
|
||||
]
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularInt32Field(value: &self.id)
|
||||
case 2: try decoder.decodeSingularStringField(value: &self.url)
|
||||
case 3: try decoder.decodeSingularStringField(value: &self.name)
|
||||
case 4: try decoder.decodeSingularInt32Field(value: &self.selectedOrdinal)
|
||||
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.url.isEmpty { try visitor.visitSingularStringField(value: self.url, fieldNumber: 2) }
|
||||
if !self.name.isEmpty { try visitor.visitSingularStringField(value: self.name, fieldNumber: 3) }
|
||||
if self.selectedOrdinal != 0 { try visitor.visitSingularInt32Field(value: self.selectedOrdinal, fieldNumber: 4) }
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
}
|
||||
|
||||
extension Burrow_ProxySubscriptionApplyResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = "burrow.ProxySubscriptionApplyResponse"
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "id"),
|
||||
2: .standard(proto: "imported_count"),
|
||||
3: .standard(proto: "selected_ordinal"),
|
||||
]
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularInt32Field(value: &self.id)
|
||||
case 2: try decoder.decodeSingularInt32Field(value: &self.importedCount)
|
||||
case 3: try decoder.decodeSingularInt32Field(value: &self.selectedOrdinal)
|
||||
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.importedCount != 0 { try visitor.visitSingularInt32Field(value: self.importedCount, fieldNumber: 2) }
|
||||
if self.selectedOrdinal != 0 { try visitor.visitSingularInt32Field(value: self.selectedOrdinal, fieldNumber: 3) }
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
}
|
||||
|
||||
extension Burrow_ProxySubscriptionSelectRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = "burrow.ProxySubscriptionSelectRequest"
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "id"),
|
||||
2: .standard(proto: "selected_ordinal"),
|
||||
]
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularInt32Field(value: &self.id)
|
||||
case 2: try decoder.decodeSingularInt32Field(value: &self.selectedOrdinal)
|
||||
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.selectedOrdinal != 0 { try visitor.visitSingularInt32Field(value: self.selectedOrdinal, fieldNumber: 2) }
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
}
|
||||
|
||||
extension Burrow_ProxySubscriptionSelectResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = "burrow.ProxySubscriptionSelectResponse"
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "id"),
|
||||
2: .standard(proto: "selected_ordinal"),
|
||||
]
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularInt32Field(value: &self.id)
|
||||
case 2: try decoder.decodeSingularInt32Field(value: &self.selectedOrdinal)
|
||||
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.selectedOrdinal != 0 { try visitor.visitSingularInt32Field(value: self.selectedOrdinal, fieldNumber: 2) }
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
}
|
||||
|
||||
public struct TailnetClient: Client, GRPCClient {
|
||||
public let channel: GRPCChannel
|
||||
public var defaultCallOptions: CallOptions
|
||||
|
|
@ -487,6 +797,52 @@ public struct TailnetClient: Client, GRPCClient {
|
|||
}
|
||||
}
|
||||
|
||||
public struct ProxySubscriptionClient: Client, GRPCClient {
|
||||
public let channel: GRPCChannel
|
||||
public var defaultCallOptions: CallOptions
|
||||
|
||||
public init(channel: any GRPCChannel) {
|
||||
self.channel = channel
|
||||
self.defaultCallOptions = .init()
|
||||
}
|
||||
|
||||
public func previewImport(
|
||||
_ request: Burrow_ProxySubscriptionImportRequest,
|
||||
callOptions: CallOptions? = nil
|
||||
) async throws -> Burrow_ProxySubscriptionPreviewResponse {
|
||||
try await self.performAsyncUnaryCall(
|
||||
path: "/burrow.ProxySubscriptions/PreviewImport",
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: []
|
||||
)
|
||||
}
|
||||
|
||||
public func applyImport(
|
||||
_ request: Burrow_ProxySubscriptionApplyRequest,
|
||||
callOptions: CallOptions? = nil
|
||||
) async throws -> Burrow_ProxySubscriptionApplyResponse {
|
||||
try await self.performAsyncUnaryCall(
|
||||
path: "/burrow.ProxySubscriptions/ApplyImport",
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: []
|
||||
)
|
||||
}
|
||||
|
||||
public func selectNode(
|
||||
_ request: Burrow_ProxySubscriptionSelectRequest,
|
||||
callOptions: CallOptions? = nil
|
||||
) async throws -> Burrow_ProxySubscriptionSelectResponse {
|
||||
try await self.performAsyncUnaryCall(
|
||||
path: "/burrow.ProxySubscriptions/SelectNode",
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: []
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public struct TunnelPacketClient: Client, GRPCClient {
|
||||
public let channel: GRPCChannel
|
||||
public var defaultCallOptions: CallOptions
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ public enum Burrow_NetworkType: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
public typealias RawValue = Int
|
||||
case wireGuard // = 0
|
||||
case tailnet // = 1
|
||||
case proxySubscription // = 3
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
public init() {
|
||||
|
|
@ -35,6 +36,7 @@ public enum Burrow_NetworkType: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
switch rawValue {
|
||||
case 0: self = .wireGuard
|
||||
case 1: self = .tailnet
|
||||
case 3: self = .proxySubscription
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +45,7 @@ public enum Burrow_NetworkType: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
switch self {
|
||||
case .wireGuard: return 0
|
||||
case .tailnet: return 1
|
||||
case .proxySubscription: return 3
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
|
@ -51,6 +54,7 @@ public enum Burrow_NetworkType: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
public static let allCases: [Burrow_NetworkType] = [
|
||||
.wireGuard,
|
||||
.tailnet,
|
||||
.proxySubscription,
|
||||
]
|
||||
|
||||
}
|
||||
|
|
@ -217,6 +221,8 @@ public struct Burrow_TunnelConfigurationResponse: Sendable {
|
|||
|
||||
public var routes: [String] = []
|
||||
|
||||
public var excludedRoutes: [String] = []
|
||||
|
||||
public var dnsServers: [String] = []
|
||||
|
||||
public var searchDomains: [String] = []
|
||||
|
|
@ -236,6 +242,7 @@ extension Burrow_NetworkType: SwiftProtobuf._ProtoNameProviding {
|
|||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
0: .same(proto: "WireGuard"),
|
||||
1: .same(proto: "Tailnet"),
|
||||
3: .same(proto: "ProxySubscription"),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -544,6 +551,7 @@ extension Burrow_TunnelConfigurationResponse: SwiftProtobuf.Message, SwiftProtob
|
|||
4: .standard(proto: "dns_servers"),
|
||||
5: .standard(proto: "search_domains"),
|
||||
6: .standard(proto: "include_default_route"),
|
||||
7: .standard(proto: "excluded_routes"),
|
||||
]
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -558,6 +566,7 @@ extension Burrow_TunnelConfigurationResponse: SwiftProtobuf.Message, SwiftProtob
|
|||
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) }()
|
||||
case 7: try { try decoder.decodeRepeatedStringField(value: &self.excludedRoutes) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -573,6 +582,9 @@ extension Burrow_TunnelConfigurationResponse: SwiftProtobuf.Message, SwiftProtob
|
|||
if !self.routes.isEmpty {
|
||||
try visitor.visitRepeatedStringField(value: self.routes, fieldNumber: 3)
|
||||
}
|
||||
if !self.excludedRoutes.isEmpty {
|
||||
try visitor.visitRepeatedStringField(value: self.excludedRoutes, fieldNumber: 7)
|
||||
}
|
||||
if !self.dnsServers.isEmpty {
|
||||
try visitor.visitRepeatedStringField(value: self.dnsServers, fieldNumber: 4)
|
||||
}
|
||||
|
|
@ -589,6 +601,7 @@ extension Burrow_TunnelConfigurationResponse: SwiftProtobuf.Message, SwiftProtob
|
|||
if lhs.addresses != rhs.addresses {return false}
|
||||
if lhs.mtu != rhs.mtu {return false}
|
||||
if lhs.routes != rhs.routes {return false}
|
||||
if lhs.excludedRoutes != rhs.excludedRoutes {return false}
|
||||
if lhs.dnsServers != rhs.dnsServers {return false}
|
||||
if lhs.searchDomains != rhs.searchDomains {return false}
|
||||
if lhs.includeDefaultRoute != rhs.includeDefaultRoute {return false}
|
||||
|
|
|
|||
|
|
@ -9,3 +9,5 @@ CODE_SIGN_ENTITLEMENTS = NetworkExtension/NetworkExtension-iOS.entitlements
|
|||
CODE_SIGN_ENTITLEMENTS[sdk=macosx*] = NetworkExtension/NetworkExtension-macOS.entitlements
|
||||
|
||||
SWIFT_INCLUDE_PATHS = $(inherited) $(PROJECT_DIR)/NetworkExtension
|
||||
|
||||
OTHER_LDFLAGS = $(inherited) -framework SystemConfiguration
|
||||
|
|
|
|||
|
|
@ -28,13 +28,13 @@ final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
|||
get throws { try _client.get() }
|
||||
}
|
||||
private let _client: Result<TunnelClient, Swift.Error> = Result {
|
||||
try TunnelClient.unix(socketURL: Constants.socketURL)
|
||||
try TunnelClient.unix(socketURL: Constants.packetTunnelSocketURL)
|
||||
}
|
||||
|
||||
override init() {
|
||||
do {
|
||||
libburrow.spawnInProcess(
|
||||
socketPath: try Constants.socketURL.path(percentEncoded: false),
|
||||
socketPath: try Constants.packetTunnelSocketURL.path(percentEncoded: false),
|
||||
databasePath: try Constants.databaseURL.path(percentEncoded: false)
|
||||
)
|
||||
} catch {
|
||||
|
|
@ -54,6 +54,11 @@ final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
|||
guard let settings = configuration?.settings else {
|
||||
throw Error.missingTunnelConfiguration
|
||||
}
|
||||
if let configuration {
|
||||
logger.log(
|
||||
"Applying tunnel configuration addresses=\(configuration.addresses.joined(separator: ","), privacy: .public) routes=\(configuration.routes.joined(separator: ","), privacy: .public) excludedRoutes=\(configuration.excludedRoutes.joined(separator: ","), privacy: .public) dns=\(configuration.dnsServers.joined(separator: ","), privacy: .public) includeDefaultRoute=\(configuration.includeDefaultRoute, privacy: .public)"
|
||||
)
|
||||
}
|
||||
try await setTunnelNetworkSettings(settings)
|
||||
try startPacketBridge()
|
||||
logger.log("Started tunnel with network settings: \(settings)")
|
||||
|
|
@ -88,15 +93,22 @@ extension PacketTunnelProvider {
|
|||
private func startPacketBridge() throws {
|
||||
stopPacketBridge()
|
||||
|
||||
let packetClient = TunnelPacketClient.unix(socketURL: try Constants.socketURL)
|
||||
let packetClient = TunnelPacketClient.unix(socketURL: try Constants.packetTunnelSocketURL)
|
||||
let call = packetClient.makeTunnelPacketsCall()
|
||||
self.packetCall = call
|
||||
|
||||
inboundPacketTask = Task { [weak self] in
|
||||
guard let self else { return }
|
||||
var packetCount = 0
|
||||
var byteCount = 0
|
||||
do {
|
||||
for try await packet in call.responseStream {
|
||||
let payload = packet.payload
|
||||
packetCount += 1
|
||||
byteCount += payload.count
|
||||
if packetCount == 1 || packetCount % 1024 == 0 {
|
||||
self.logger.log("Tunnel packet receive progress packets=\(packetCount, privacy: .public) bytes=\(byteCount, privacy: .public) lastBytes=\(payload.count, privacy: .public)")
|
||||
}
|
||||
self.packetFlow.writePackets(
|
||||
[payload],
|
||||
withProtocols: [Self.protocolNumber(for: payload)]
|
||||
|
|
@ -111,10 +123,17 @@ extension PacketTunnelProvider {
|
|||
outboundPacketTask = Task { [weak self] in
|
||||
guard let self else { return }
|
||||
defer { call.requestStream.finish() }
|
||||
var packetCount = 0
|
||||
var byteCount = 0
|
||||
do {
|
||||
while !Task.isCancelled {
|
||||
let packets = await self.readPacketsBatch()
|
||||
for (payload, _) in packets {
|
||||
packetCount += 1
|
||||
byteCount += payload.count
|
||||
if packetCount == 1 || packetCount % 1024 == 0 {
|
||||
self.logger.log("Tunnel packet send progress packets=\(packetCount, privacy: .public) bytes=\(byteCount, privacy: .public) lastBytes=\(payload.count, privacy: .public)")
|
||||
}
|
||||
var packet = Burrow_TunnelPacket()
|
||||
packet.payload = payload
|
||||
try await call.requestStream.send(packet)
|
||||
|
|
@ -128,6 +147,7 @@ extension PacketTunnelProvider {
|
|||
}
|
||||
|
||||
private func stopPacketBridge() {
|
||||
logger.log("Stopping tunnel packet bridge")
|
||||
inboundPacketTask?.cancel()
|
||||
inboundPacketTask = nil
|
||||
outboundPacketTask?.cancel()
|
||||
|
|
@ -165,6 +185,9 @@ extension Burrow_TunnelConfigurationResponse {
|
|||
let parsedRoutes = routes.compactMap(ParsedTunnelRoute.init(rawValue:))
|
||||
var ipv4Routes = parsedRoutes.compactMap(\.ipv4Route)
|
||||
var ipv6Routes = parsedRoutes.compactMap(\.ipv6Route)
|
||||
let parsedExcludedRoutes = excludedRoutes.compactMap(ParsedTunnelRoute.init(rawValue:))
|
||||
let excludedIPv4Routes = parsedExcludedRoutes.compactMap(\.ipv4Route)
|
||||
let excludedIPv6Routes = parsedExcludedRoutes.compactMap(\.ipv6Route)
|
||||
if includeDefaultRoute {
|
||||
ipv4Routes.append(.default())
|
||||
ipv6Routes.append(.default())
|
||||
|
|
@ -180,6 +203,9 @@ extension Burrow_TunnelConfigurationResponse {
|
|||
if !ipv4Routes.isEmpty {
|
||||
ipv4Settings.includedRoutes = ipv4Routes
|
||||
}
|
||||
if !excludedIPv4Routes.isEmpty {
|
||||
ipv4Settings.excludedRoutes = excludedIPv4Routes
|
||||
}
|
||||
settings.ipv4Settings = ipv4Settings
|
||||
}
|
||||
if !ipv6Addresses.isEmpty {
|
||||
|
|
@ -190,6 +216,9 @@ extension Burrow_TunnelConfigurationResponse {
|
|||
if !ipv6Routes.isEmpty {
|
||||
ipv6Settings.includedRoutes = ipv6Routes
|
||||
}
|
||||
if !excludedIPv6Routes.isEmpty {
|
||||
ipv6Settings.excludedRoutes = excludedIPv6Routes
|
||||
}
|
||||
settings.ipv6Settings = ipv6Settings
|
||||
}
|
||||
if !dnsServers.isEmpty {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# This is a build script. It is run by Xcode as a build step.
|
||||
# The type of build is described in various environment variables set by Xcode.
|
||||
|
||||
export PATH="${PATH}:${HOME}/.cargo/bin:/opt/homebrew/bin:/usr/local/bin:/etc/profiles/per-user/${USER}/bin"
|
||||
export PATH="${PATH}:${HOME}/.cargo/bin:${HOME}/.nix-profile/bin:/opt/homebrew/bin:/usr/local/bin:/etc/profiles/per-user/${USER}/bin:/run/current-system/sw/bin:/nix/var/nix/profiles/default/bin"
|
||||
|
||||
if ! [[ -x "$(command -v cargo)" ]]; then
|
||||
echo 'error: Unable to find cargo'
|
||||
|
|
@ -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
|
||||
|
|
@ -86,19 +65,11 @@ fi
|
|||
if [[ -x "$(command -v rustup)" ]]; then
|
||||
CARGO_PATH="$(dirname "$(rustup which cargo)"):/usr/bin"
|
||||
else
|
||||
CARGO_PATH="$(dirname "$(command -v cargo)"):/usr/bin"
|
||||
CARGO_PATH="$(dirname "$(readlink -f "$(command -v 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
|
||||
CARGO_PATH="$(dirname $PROTOC):$CARGO_PATH"
|
||||
PROTOC="$(readlink -f "$(command -v 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.
|
||||
|
|
|
|||
|
|
@ -1,15 +1,6 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x31",
|
||||
"green" : "0x7A",
|
||||
"red" : "0xB7"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/100.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/1024.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/114.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/120.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/128.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/144.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/152.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/16.png
Normal file
|
After Width: | Height: | Size: 684 B |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/167.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/172.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/180.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/196.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/20.png
Normal file
|
After Width: | Height: | Size: 927 B |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/216.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/256.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/29.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/32.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/40.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/48.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/50.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/512.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/55.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/57.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/58.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/60.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/64.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/72.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/76.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/80.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/87.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
Apple/UI/Assets.xcassets/AppIcon.appiconset/88.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
|
|
@ -1,214 +1,340 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename": "ios-panel-01-40.png",
|
||||
"filename" : "40.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-60.png",
|
||||
"filename" : "60.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-29.png",
|
||||
"filename" : "29.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-58.png",
|
||||
"filename" : "58.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-87.png",
|
||||
"filename" : "87.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-80.png",
|
||||
"filename" : "80.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-120.png",
|
||||
"filename" : "120.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-57.png",
|
||||
"filename" : "57.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x",
|
||||
"size" : "57x57"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-114.png",
|
||||
"filename" : "114.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "57x57"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-120.png",
|
||||
"filename" : "120.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-180.png",
|
||||
"filename" : "180.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-20.png",
|
||||
"filename" : "20.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-40.png",
|
||||
"filename" : "40.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-29.png",
|
||||
"filename" : "29.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-58.png",
|
||||
"filename" : "58.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-40.png",
|
||||
"filename" : "40.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-80.png",
|
||||
"filename" : "80.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-50.png",
|
||||
"filename" : "50.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "50x50"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-100.png",
|
||||
"filename" : "100.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "50x50"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-72.png",
|
||||
"filename" : "72.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "72x72"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-144.png",
|
||||
"filename" : "144.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "72x72"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-76.png",
|
||||
"filename" : "76.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-152.png",
|
||||
"filename" : "152.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-167.png",
|
||||
"filename" : "167.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"filename": "ios-panel-01-1024.png",
|
||||
"filename" : "1024.png",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"filename": "mac-panel-05-16.png",
|
||||
"filename" : "16.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename": "mac-panel-05-32.png",
|
||||
"filename" : "32.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename": "mac-panel-05-32.png",
|
||||
"filename" : "32.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename": "mac-panel-05-64.png",
|
||||
"filename" : "64.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename": "mac-panel-05-128.png",
|
||||
"filename" : "128.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename": "mac-panel-05-256.png",
|
||||
"filename" : "256.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename": "mac-panel-05-256.png",
|
||||
"filename" : "256.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename": "mac-panel-05-512.png",
|
||||
"filename" : "512.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename": "mac-panel-05-512.png",
|
||||
"filename" : "512.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename": "mac-panel-05-1024.png",
|
||||
"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" : {
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 485 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 1,011 B |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |