EquiTrail — Developer Reference
Keep this file updated. Claude reads this at the start of each session.
Last updated: 2026-05-31
Project
| Item |
Value |
| App name |
EquiTrail |
| Package (Android) |
com.nossie.equitrail |
| Bundle ID (iOS) |
com.nossie.equitrail |
| Current version |
3.5.0+50 (phone) · 1050 (watch) |
| Azure DevOps repo |
https://dev.azure.com/nossie/equitrail/_git/equitrail |
| Git user |
Paul Tierlier |
| Git email |
nossiej@gmail.com |
| Working directory |
/Users/nossie/app/equitrail |
| Flutter SDK |
3.44 / Dart 3.12 |
Key File Locations (ALL in workdir — never in Downloads)
| File |
Path |
Purpose |
| Android keystore |
android/equitrail.jks |
Release signing |
| Android key properties |
android/key.properties |
Keystore passwords |
| Play Console service account |
android/play_service_account.json |
API upload (gitignored) |
| Firebase Android |
android/app/google-services.json |
Firebase config (gitignored) |
| Firebase iOS |
ios/Runner/GoogleService-Info.plist |
Firebase config (gitignored) |
| Credentials/env |
credentials.env |
All passwords/keys (gitignored) |
| App constants |
lib/core/constants/app_constants.dart |
API keys, URLs, constants |
android/key.properties content
storeFile=../equitrail.jks
storePassword=VYdUKs9MbUH5mFeegSMB
keyAlias=equitrail
keyPassword=VYdUKs9MbUH5mFeegSMB
Build Commands
Android phone AAB (release)
cd /Users/nossie/app/equitrail
bash scripts/build_release.sh --aab-only
# Output: build/app/outputs/bundle/release/app-release.aab
Android wearable AAB (release)
cd /Users/nossie/app/equitrail/android
./gradlew :wearable:bundleRelease
# Output: android/wearable/build/outputs/bundle/release/wearable-release.aab
# Then sign: jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 \
# -keystore equitrail.jks -storepass VYdUKs9MbUH5mFeegSMB \
# wearable/build/outputs/bundle/release/wearable-release.aab equitrail
iOS IPA (release)
cd /Users/nossie/app/equitrail
flutter build ipa --release
# Output: build/ios/ipa/equitrail.ipa
# Deploy to device: flutter install --device-id=00008030-0002196E11E3402E
Upload to Play Console
Upload script
cd /Users/nossie/app/equitrail
python3 scripts/play_upload.py \
--package com.nossie.equitrail \
--aab build/app/outputs/bundle/release/app-release.aab \
--track internal \
--key android/play_service_account.json
# Watch (wear:internal track)
python3 scripts/play_upload.py \
--package com.nossie.equitrail \
--aab android/wearable/build/outputs/bundle/release/wearable-release.aab \
--track wear:internal \
--key android/play_service_account.json
Or use the google-api-python-client directly
# Key: android/play_service_account.json
# Project: fleet-furnace-497817
# Package: com.nossie.equitrail
# Internal track: 'internal'
# Wear track: 'wear:internal'
Devices
| Device |
ID |
OS |
Connection |
| iPhone SE (iNossie) |
00008030-0002196E11E3402E |
iOS 26.5 |
Wireless |
| Galaxy Watch 6 |
(via Play Store wear:internal) |
Wear OS 3 |
Via phone |
Deploy iOS to iPhone
flutter install --device-id=00008030-0002196E11E3402E
# OR for release build:
flutter run --release --device-id=00008030-0002196E11E3402E
Monitoring
| Service |
URL |
Notes |
| Uptime Kuma |
http://192.168.1.111:3001 |
Synology NAS, Portainer stack |
| EquiTrail Dashboard (terminal) |
python3 scripts/dashboard.py |
Mac local, reads credentials.env |
| EquiTrail Dashboard (web) |
python3 scripts/dashboard_web.py → localhost:8088 |
Mac local |
| Azure DevOps Wiki |
https://dev.azure.com/nossie/equitrail/_wiki/wikis/EquiTrail-Wiki |
8 pages |
| Integration tests |
bash scripts/run_tests.sh |
Boots Pixel_9, writes docs/bugtracker.md |
| Backup verify |
bash scripts/verify_backup.sh |
Monthly check |
Uptime Kuma setup (first time)
- Open http://192.168.1.111:3001 → create admin account
- Settings → Backup → Import → upload
docker/uptime-kuma-monitors.json
- Creates 8 monitors: website, routing, Firebase, Discord API, Discord bot, Oracle SSH, admin dashboard
Infrastructure Locations
| Tool/Service |
Where it runs |
Notes |
| Portainer |
Local network only (Mac/NAS) |
NOT on Oracle — never deploy Oracle services via Portainer |
| Uptime Kuma |
Local network (Portainer stack) |
Monitors external services |
| Docker |
Oracle Cloud (100.126.14.49) |
Plain Docker — deploy via docker compose over SSH |
| GraphHopper routing |
Oracle Cloud |
systemd service |
| Discord bot |
Oracle Cloud |
systemd user service equitrail-bot |
| Firebase |
Google Cloud europe-west4 |
Auth + Firestore + Functions |
| Website |
Plesk (equitrail.horse) |
FTP deploy only |
| Backup target |
Oracle Cloud |
~/equitrail-backup/ via rsync |
⚠️ Portainer is local only. Any Docker workload for Oracle must be deployed via docker compose over SSH (Tailscale), not via Portainer UI.
Cloud Infrastructure (ALL europe-west4 — NEVER us-central1)
| Service |
Region |
URL |
| Firebase Auth + Firestore + Functions |
europe-west4 |
firebase.google.com |
| GraphHopper routing server |
Oracle Cloud NL (Amsterdam) |
routing.equitrail.horse |
| Website hosting |
Plesk (equitrail.horse) |
136.144.178.202 |
| Backup server |
Oracle Cloud 100.126.14.49 |
~/equitrail-backup/ |
Routing server SSH
# ALWAYS use Tailscale IP — NEVER the public IP
ssh equitrail@100.126.14.49
# Public IP: 152.70.54.93 (blocked via UFW, never use directly)
# Routing API key: fFN-dJNv9x5SQ37yOCVyg67SP9ASksy5r0yjp5KPUyg
Deploy a Docker service to Oracle (NOT via Portainer)
# Copy compose file to server, then run over SSH
scp docker/some-stack.yml equitrail@100.126.14.49:~/
ssh equitrail@100.126.14.49 "docker compose -f some-stack.yml up -d"
Wearable Version Convention
- Phone versionCode = N
- Watch versionCode = N + 1000
- Current: phone=45, watch=1045
Firebase
| Item |
Value |
| Project ID |
equitrail |
| Android App ID |
1:967800454287:android:845eaa6d9ef6d6f3a544fa |
| iOS App ID |
1:967800454287:ios:b441f335f874398ca544fa |
| Package (correct, OLD was wrong) |
com.nossie.equitrail (NOT com.equitrail.equitrail) |
| SHA-1 (Play App Signing) |
In Firebase console |
App Store Connect (iOS TestFlight)
- Team ID:
SAF6DV6MH7
- Bundle ID:
com.nossie.equitrail
- Apple ID: nossiej@gmail.com
- Privacy policy URL: https://equitrail.horse/privacy
- Setup guide:
docs/apple-developer-testflight-setup.md
Azure DevOps Boards
| Item |
Value |
| Org |
https://dev.azure.com/nossie |
| Project |
equitrail |
| PAT name |
equitrail-code |
| PAT |
in credentials.env as AZURE_DEVOPS_PAT |
| Board |
https://dev.azure.com/nossie/equitrail/_boards/board/t/equitrail%20Team/Issues |
| Backlog |
https://dev.azure.com/nossie/equitrail/_backlogs/backlog/equitrail%20Team/Epics |
| Setup script |
scripts/setup_devops_board.py |
Sprints
| Sprint |
Dates |
Focus |
| Sprint 1 |
2026-06-01 → 2026-06-14 |
Hotfixes, Watch test, Apple enrollment |
| Sprint 2 - Social Discovery |
2026-06-15 → 2026-06-28 |
Rider discovery map, iOS TestFlight |
| Sprint 3 — Monetisation |
2026-07-01 → 2026-07-14 |
Google Play Billing, App Store IAP |
| Sprint 4 — Platform Expansion |
2026-07-15 → 2026-07-28 |
Apple Watch, push notifications, security |
Epics (#6–#16)
🐛 Bugs · 🐴 Tracking · 🌐 Social · 👥 Rider Discovery · 💰 Monetisation · 📱 iOS · ⌚ Galaxy Watch · 🍎 Apple Watch · 🗺️ Navigation · 🔒 Security · 🌍 Website
Work item types (Basic process)
Epic → Issue (User Story) → Task
Commit linking
Use #<id> in commit messages to link commits to work items. Example: fix: GPS crash on pause #33
Key Architecture Files
| File |
Purpose |
lib/router.dart |
All app routes |
lib/main.dart |
App entry point, Hive box registrations |
lib/shared/widgets/scaffold_with_bottom_nav.dart |
Bottom nav bar |
lib/shared/l10n/app_localizations.dart |
NL/EN/FR/DE translations |
lib/core/constants/app_constants.dart |
All constants, API URLs |
lib/core/services/calorie_service.dart |
Calorie calculation |
lib/core/services/routing_service.dart |
GraphHopper routing |
lib/core/services/weather_service.dart |
wttr.in weather |
android/wearable/ |
Galaxy Watch Wear OS app |
Hive Box Names & Type IDs
| Box |
Key |
Type ID |
| Settings |
settings |
— |
| Horses |
horses |
0 |
| Rides |
rides |
1 |
| TrackPoints |
track_points |
2 |
| Routes |
route_library |
— |
Important Rules (from user)
- ALL cloud infrastructure in europe-west4 / Netherlands — NEVER us-central1
- SSH to routing server ONLY via Tailscale (100.126.14.49), NEVER public IP
- Never commit:
credentials.env, google-services.json, GoogleService-Info.plist, *.jks, play_service_account.json
- After every significant change: update CHANGELOG, README, ARCHITECTURE, SECURITY
- All features must work on both Android AND iOS
- Service account, keys, all credentials live in the workdir (not in Downloads or elsewhere)
Website (equitrail.horse)
- Host: 136.144.178.202 (Plesk)
- SFTP user:
equitrail.horse_8u2g2uuryqe
- SFTP pass: in credentials.env
- Remote dir:
/httpdocs
- Deploy:
bash scripts/deploy_website.sh
- Preview auth: user=
equiteam pass=Paard2026!
- Privacy policy:
/privacy (bypasses auth via .htaccess)
Discord
| Item |
Value |
| Bot |
EquiTrail#3566 (1510599021276692641) |
| Server |
1510601145465438289 |
| Token |
in credentials.env as DISCORD_BOT_TOKEN |
| Invite |
https://discord.gg/sRcJuTQzyZ (permanent) — credentials.env + AppConstants.discordInvite |
| Bot files |
/home/equitrail/equitrail-bot/ on Oracle server · discord_bot/ in repo |
| Restart |
ssh equitrail@100.126.14.49 "systemctl --user restart equitrail-bot" |
| Logs |
ssh equitrail@100.126.14.49 "journalctl --user -u equitrail-bot -f" |
| RouteHelper |
Merged into main bot — handles #share-routes GPX uploads + /route /gpx |
| GPX storage |
/home/equitrail/equitrail-bot/routes/ + metadata.json |
Website
| Item |
Value |
| Host |
equitrail.horse (Plesk, IP 136.144.178.202) |
| SFTP user |
in credentials.env as WEBSITE_SFTP_USER |
| SFTP pass |
in credentials.env as WEBSITE_SFTP_PASS |
| Remote dir |
/httpdocs |
| Deploy cmd |
lftp ftp://equitrail.horse -u "$WEBSITE_FTP_USER,$WEBSITE_FTP_PASS" -e "set ftp:ssl-force false; put FILE -o /httpdocs/FILE; quit" |
| ⚠️ Protocol |
FTP only — SFTP does NOT work on this Plesk server |
| Full deploy |
bash scripts/deploy_website.sh |
| Single file |
bash scripts/deploy_website.sh --file terms.html |
| PRO page |
equitrail.horse/pro — NL/EN/DE/FR pricing comparison |
| Privacy |
equitrail.horse/privacy — bypasses .htaccess auth |
PRO Pricing
| Channel |
Monthly |
Annual |
Commission |
| Website |
€10.00 |
€90.00 |
0% |
| Google Play |
€11.99 |
€109.99 |
15% |
| App Store (SBP) |
€11.99 |
€109.99 |
15% |
| App Store (standard) |
€14.99 |
€129.99 |
30% |
Apple SBP: apply at developer.apple.com/app-store/small-business-program/ (< $1M revenue, qualifies)
Play Console Upload History
| Date |
Phone versionCode |
Watch versionCode |
Track |
| 2026-05-28 |
43 |
1042 |
internal / wear:internal |
| 2026-05-31 |
45 |
1045 |
internal / wear:internal |
| 2026-05-31 |
46 |
— |
internal — cloud sync bugfixes |
| 2026-05-31 |
47 |
— |
internal — rider discovery, roles, Discord badge |
| 2026-05-31 |
48 |
— |
internal — Discord invite, RouteHelper, PRO pricing page |
| 2026-06-01 |
49 |
— |
internal — grey screen fix, copyright headers, IP protection |
| 2026-06-01 |
50 |
— |
internal — hidden badges, in-app notifications, GPX request, block/report, health disclaimer |