Mobile Repos, CI/CD & Security (all free-tier)¶
Decided by Hari 2026-06-11: OSS-first three-tier chain, everything on GitHub Free.
Repo chain & flow¶
BelledonneCommunications/linphone-iphone (upstream — Belledonne OSS)
│ fetch + merge their updates
▼
astradial/astradial-ios-oss PUBLIC ← PRIMARY DEV REPO (branch: astradial)
│ merge oss → private overlay all app code lands here first
▼
astradial/astradial-ios PRIVATE ← mirror + private-only overlay
(branch: private-main)
Every change goes OSS → private. The public repo is where features are developed and where ALL enforcement lives (free for public repos). The private repo is a downstream overlay for anything that must never be public (internal tooling; never credentials — see below) plus a backup mirror of astradial.
Local remotes (single clone at ~/AstradialDevelopment/Mobile App/astradial-ios): upstream = Belledonne · oss = public · origin = private.
Routines¶
| Action | Command |
|---|---|
| Daily feature work | branch off astradial → PR into astradial on oss (CI + gitleaks gate) |
| Sync from Belledonne | git fetch upstream && git merge upstream/master into astradial, fix, push oss |
| Update private mirror | git push origin astradial && git branch -f private-main astradial && git push origin private-main (plus private-only commits cherry-picked on top) |
| Release | tag on oss astradial; TestFlight built from that tag |
CI/CD (GitHub Actions — free, public repo only)¶
.github/workflows/ci.yml, gated with if: github.repository == 'astradial/astradial-ios-oss' so the private mirror consumes zero Actions minutes (macOS runners bill 10× on private; public repos are unlimited and free):
- Secret scan (gitleaks) — every push/PR, full-history fetch, custom
.gitleaks.toml(allowlists only Linphone's public placeholder plist). - Build (iOS Simulator) —
xcodebuildonmacos-15with the newest runner Xcode, signing disabled. ⚠️ If GitHub's hosted Xcode is ever too old for the iOS-26 SDK features we use, the fallback is a free self-hosted runner on Hari's Mac (Settings → Actions → Runners → New self-hosted runner).
Security controls (all free)¶
| Control | Where | Status |
|---|---|---|
| GitHub secret scanning + push protection | oss (public = free) | ✅ enabled via API |
| gitleaks CI gate | oss | ✅ workflow |
Branch protection on astradial (require both CI checks, no force-push/delete) | oss | apply after first push (API 404s on a branch that doesn't exist yet) — command below |
Real GoogleService-Info.plist uncommittable | local clone | ✅ git update-index --skip-worktree — file swaps never appear in git status |
.gitignore for .env*, *-sa-key.json, *credentials* | repo | ✅ |
CODEOWNERS (@harisuryaa), PR template with no-secrets checklist, dependabot (SPM + Actions weekly), SECURITY.md | repo | ✅ |
| Branch protection on private repo | — | ❌ requires paid plan → not needed; rules live on oss, private is a mirror |
Credential model recap: the app has NO API keys (Firebase login → platform JWT). The only sensitive local file is the Astradial GoogleService-Info.plist, which is triple-guarded (skip-worktree + gitleaks + GitHub push protection). Linphone's placeholder remains in git history intentionally — it has been public in Belledonne's repo for years.
One-time commands still to run (Hari)¶
# 1. First push of our branches to the public OSS repo
cd "~/AstradialDevelopment/Mobile App/astradial-ios"
git push oss astradial master
gh repo edit astradial/astradial-ios-oss --default-branch astradial
# 2. Then protect the branch (requires it to exist):
gh api -X PUT repos/astradial/astradial-ios-oss/branches/astradial/protection \
-F 'required_status_checks[strict]=true' \
-f 'required_status_checks[contexts][]=Build (iOS Simulator)' \
-f 'required_status_checks[contexts][]=Secret scan (gitleaks)' \
-F enforce_admins=false -F 'allow_force_pushes=false' -F 'allow_deletions=false' \
--input /dev/null 2>/dev/null || true # or ask Claude to run it
Android (astradial-android) — clean split¶
Created June 2026. Same OSS-first idea as iOS, but with a clean split: the public repo is a standalone native-Samsung dialer with no Firebase/auth/ analytics; the private repo overlays them. (iOS instead keeps all code on OSS and hides only the secret plist.)
BelledonneCommunications/linphone-android (upstream — Belledonne OSS)
│ git fetch upstream && merge upstream/master
▼
astradial/astradial-android-oss PUBLIC ← dialer dev repo (branch: astradial)
│ git merge oss/astradial → private-main NO Firebase / analytics
▼
astradial/astradial-android PRIVATE ← OSS dialer + overlay (branch: private-main)
Firebase sign-in + MD Analytics + Tickets
Local remotes (single clone ~/AstradialDevelopment/Mobile App/astradial-android): upstream = Belledonne · oss = public · origin = private. Flow is strictly one-way OSS → private (never git push oss private-main).
How the split works (no product flavors needed)¶
Linphone's app/build.gradle.kts already gates Firebase on file presence: firebaseCloudMessagingAvailable = File(".../google-services.json").exists(). So:
- OSS: delete
app/google-services.json→ google-services plugin not applied, Firebase/Crashlytics off.firebase-messagingstays on the classpath, so nothing fails to compile; FCM is simply inert. - Private: add the real Astradial
app/google-services.json(gitignored +skip-worktree) → Firebase on. Overlay code (Analytics/Tickets/sign-in) lives in private-only paths so Belledonne merges don't conflict.
Routines¶
| Action | Command |
|---|---|
| Daily dialer work | branch off astradial → PR into astradial on oss (CI + TruffleHog gate) |
| Sync from Belledonne | git fetch upstream && git merge upstream/master into astradial, fix, push oss |
| Update private overlay | git checkout private-main && git merge oss/astradial (+ overlay commits on top) → git push origin private-main |
| Release | tag on oss astradial; Play build from that tag |
CI/CD (GitHub Actions — free, public repo only)¶
.github/workflows/ci.yml, gated if: github.repository == 'astradial/astradial-android-oss' so the private mirror burns zero minutes:
- Secret scan (TruffleHog) — every push/PR, full-history.
- Build —
./gradlew :app:assembleDebug(→assembleOssDebugonce a flavor is added) onubuntu-latestwith JDK 21 (Linux Android builds are free/unlimited on public repos; far cheaper than iOS macOS runners). Needs SDK platform 37 (sdkmanager "platforms;android-37.0" "build-tools;37.0.0") and resolves the Linphone SDK AAR from Maven.
Security controls (all free)¶
| Control | Where | Status |
|---|---|---|
| GitHub secret scanning + push protection | oss (public = free) | ✅ enabled via API at creation (org default is OFF — must set explicitly) |
| TruffleHog CI gate | oss | ✅ workflow |
| Dependabot alerts + security updates (gradle + actions) | oss | ✅ enabled via API |
Branch protection on astradial (require both checks, no force-push/delete) | oss | apply after first push — command below |
Real google-services.json uncommittable | private clone | git update-index --skip-worktree app/google-services.json + .gitignore |
Signing keystore (*.jks, keystore.properties passwords) | private/local only | .gitignore *.jks/*.keystore; skip-worktree keystore.properties; CI debug unsigned |
READ_CONTACTS dropped (org-directory contacts) | manifest (M2) | privacy + smaller Play data-safety surface |
Audit (June 2026): upstream commits its own google-services.json (Belledonne project linphone-android-8a563) and an empty placeholder keystore.properties — both already public for years, so the fork inherits nothing sensitive. The Android Firebase API key is a client id shipped in every APK, not a secret — protect it with Firebase Security Rules + API-key restriction (package + signing SHA-256) + App Check, not by hiding the file. SIP transport is UDP only (accepted cleartext-on-wire risk; see decisions). The old astradial-softphone-android repo was secret-scanned (gitleaks) — clean (only false positives in the public sqlite3.c amalgamation).
One-time commands still to run (Hari)¶
# 1. First push of our branches to the public OSS repo (publishing new Astradial work)
cd "~/AstradialDevelopment/Mobile App/astradial-android"
git push oss astradial
# 2. Then protect the branch (requires it to exist):
gh api -X PUT repos/astradial/astradial-android-oss/branches/astradial/protection \
-F 'required_status_checks[strict]=true' \
-f 'required_status_checks[contexts][]=Build (assembleDebug)' \
-f 'required_status_checks[contexts][]=Secret scan (TruffleHog)' \
-F enforce_admins=false -F 'allow_force_pushes=false' -F 'allow_deletions=false' \
--input /dev/null 2>/dev/null || true # or ask Claude to run it