Skip to content

.github/workflows/api_cd.yaml #138

.github/workflows/api_cd.yaml

.github/workflows/api_cd.yaml #138

Workflow file for this run

on:
workflow_dispatch:
env:
INFISICAL_ENV: prod
jobs:
compute-version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- run: git fetch --tags --force
- uses: ./.github/actions/doxxer_install
- id: version
run: |
VERSION=$(doxxer --config doxxer.api.toml next patch)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Computed version: $VERSION"
deploy:
needs: compute-version
runs-on: ubuntu-latest
timeout-minutes: 60
concurrency: api-fly-deploy
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@master
- uses: ./.github/actions/rust_install
with:
platform: linux
- uses: ./.github/actions/infisical_install
# Machine-identity OIDC login instead of a static service token: scopes
# live in the identity's editable project role, so secret reorganizations
# never require re-minting a token into GitHub secrets.
- name: Infisical OIDC login
run: |
set -euo pipefail
oidc_jwt=$(curl -sSf -H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}" | jq -r '.value')
infisical_token=$(infisical login --method=oidc-auth \
--machine-identity-id=7dde9440-cec4-4cf0-a13f-1588ba67feb2 \
--jwt="$oidc_jwt" --silent --plain)
echo "::add-mask::$infisical_token"
echo "INFISICAL_TOKEN=$infisical_token" >> "$GITHUB_ENV"
- name: Verify encrypted CloudSync
run: |
set -euo pipefail
python3 .github/scripts/test_verify_cloudsync_e2ee.py
cloudsync_secrets_json="$(mktemp)"
trap 'rm -f "$cloudsync_secrets_json"' EXIT
infisical export \
--token="$INFISICAL_TOKEN" \
--env="$INFISICAL_ENV" \
--projectId="$INFISICAL_PROJECT_ID" \
--path="/anarlog/cloudsync" \
--format=json \
--output-file="$cloudsync_secrets_json"
python3 .github/scripts/verify_cloudsync_e2ee.py "$cloudsync_secrets_json"
env:
INFISICAL_PROJECT_ID: ${{ secrets.INFISICAL_PROJECT_ID }}
- run: |
set -euo pipefail
ai_secrets_json="$(mktemp)"
cloudsync_secrets_json="$(mktemp)"
secrets_file="$(mktemp)"
trap 'rm -f "$ai_secrets_json" "$cloudsync_secrets_json" "$secrets_file"' EXIT
infisical export \
--token="$INFISICAL_TOKEN" \
--env="$INFISICAL_ENV" \
--projectId="$INFISICAL_PROJECT_ID" \
--path="/anarlog/ai" \
--format=json \
--output-file="$ai_secrets_json"
infisical export \
--token="$INFISICAL_TOKEN" \
--env="$INFISICAL_ENV" \
--projectId="$INFISICAL_PROJECT_ID" \
--path="/anarlog/cloudsync" \
--format=json \
--output-file="$cloudsync_secrets_json"
python3 - "$ai_secrets_json" "$cloudsync_secrets_json" "$secrets_file" <<'PY'
import json
import sys
cloudsync_runtime_keys = {
"ANARLOG_CLOUDSYNC_DATABASE_ID",
"ANARLOG_CLOUDSYNC_E2EE_DATABASE_ID",
"ANARLOG_CLOUDSYNC_PROTOCOL_MODE",
"ANARLOG_CLOUDSYNC_TOKEN_TTL_SECONDS",
"SQLITECLOUD_PROJECT_URL",
"SQLITECLOUD_TOKEN_ISSUER_API_KEY",
}
sources = (
(sys.argv[1], None),
(sys.argv[2], cloudsync_runtime_keys),
)
secrets_by_key = {}
cloudsync_keys = set()
for source_path, allowed_keys in sources:
with open(source_path) as source:
for secret in json.load(source):
key = secret["key"]
if allowed_keys is not None and key not in allowed_keys:
continue
value = secret.get("value", "")
if allowed_keys is cloudsync_runtime_keys and value:
cloudsync_keys.add(key)
if key in secrets_by_key and secrets_by_key[key] != value:
raise ValueError(f"Conflicting Infisical secret: {key}")
secrets_by_key[key] = value
missing_cloudsync_keys = sorted(cloudsync_runtime_keys - cloudsync_keys)
if missing_cloudsync_keys:
raise ValueError(
"Missing required CloudSync secrets: " + ", ".join(missing_cloudsync_keys)
)
with open(sys.argv[-1], "w") as target:
for key, value in secrets_by_key.items():
value = value.replace("\r\n", "\n").replace("\r", "\n").replace("\n", "\\n")
target.write(f"{key}={value}\n")
PY
if [ -s "$secrets_file" ]; then
flyctl secrets import --app hyprnote-ai --stage < "$secrets_file"
else
echo "No Infisical secrets exported; skipping Fly secret import."
fi
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
INFISICAL_PROJECT_ID: ${{ secrets.INFISICAL_PROJECT_ID }}
- run: flyctl deploy --config apps/api/fly.toml --dockerfile apps/api/Dockerfile --remote-only --build-arg APP_VERSION=${{ needs.compute-version.outputs.version }}
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
- if: failure()
run: timeout 60s flyctl logs --app hyprnote-ai --no-tail || true
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
tag:
needs: [compute-version, deploy]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: mathieudutour/github-tag-action@v6.2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
custom_tag: api_v${{ needs.compute-version.outputs.version }}
tag_prefix: ""