Compare commits
14 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ce57bedf9 | ||
|
|
bfc777c18b | ||
|
|
a2b732317e | ||
|
|
5ebf741427 | ||
|
|
4ae30ee8e6 | ||
|
|
72e5f89b49 | ||
|
|
d003748bb1 | ||
|
|
6048eafc0d | ||
|
|
562a7de7c0 | ||
|
|
8b12001fa6 | ||
|
|
383ff8a5cf | ||
|
|
65914144d1 | ||
|
|
a8780d5236 | ||
|
|
2dcf5a2aad |
13 changed files with 484 additions and 2 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -52,7 +52,8 @@ stats.json
|
||||||
|
|
||||||
# Local/generated assets & incidental lockfiles (do not commit)
|
# Local/generated assets & incidental lockfiles (do not commit)
|
||||||
/assets/
|
/assets/
|
||||||
/resources/
|
/resources/*
|
||||||
|
!/resources/splash.png
|
||||||
/bun.lock
|
/bun.lock
|
||||||
cvm-server/bun.lock
|
cvm-server/bun.lock
|
||||||
push-service/bun.lock
|
push-service/bun.lock
|
||||||
|
|
|
||||||
74
AGENTS.md
74
AGENTS.md
|
|
@ -2228,7 +2228,7 @@ session with **no other memory of this project**. So:
|
||||||
instead of showing the error boundary.
|
instead of showing the error boundary.
|
||||||
- **Baselines** (verified 2026-10-29): `npm test` → **0 errors, 0 warnings**,
|
- **Baselines** (verified 2026-10-29): `npm test` → **0 errors, 0 warnings**,
|
||||||
**156 tests**, build ok. The gate also type-checks the CVM server
|
**156 tests**, build ok. The gate also type-checks the CVM server
|
||||||
(`tsc -p cvm-server/tsconfig.json`). `package.json` version **1.7.60**.
|
(`tsc -p cvm-server/tsconfig.json`). `package.json` version **1.7.61**.
|
||||||
- ✅ Lint is **clean**: `no-explicit-any` = 0 · `react-hooks/exhaustive-deps` = 0
|
- ✅ Lint is **clean**: `no-explicit-any` = 0 · `react-hooks/exhaustive-deps` = 0
|
||||||
· `no-unused-vars` = 0 · `react-refresh` = 0. Non-component exports were
|
· `no-unused-vars` = 0 · `react-refresh` = 0. Non-component exports were
|
||||||
**split into their own modules** (`contexts/language.ts`, `lib/sortOptions.ts`,
|
**split into their own modules** (`contexts/language.ts`, `lib/sortOptions.ts`,
|
||||||
|
|
@ -2415,6 +2415,75 @@ Rules:
|
||||||
- Any time the deploy output isn't verified by the smoke test, run
|
- Any time the deploy output isn't verified by the smoke test, run
|
||||||
`node scripts/smoke-test.mjs` manually before shipping.
|
`node scripts/smoke-test.mjs` manually before shipping.
|
||||||
|
|
||||||
|
### Distribution without GitHub
|
||||||
|
> **Owner decision (2026-10-30): no external identities.** Distribution must
|
||||||
|
> stay self-hosted and Nostr-based. Do **not** create or ask for GitLab,
|
||||||
|
> Codeberg, GitHub, email or F-Droid-main accounts. The local Forgejo admin
|
||||||
|
> account and the self-hosted F-Droid repo key are infrastructure credentials,
|
||||||
|
> not third-party identities. F-Droid main and IzzyOnDroid are therefore
|
||||||
|
> **out of scope**.
|
||||||
|
|
||||||
|
- **Self-hosted Forgejo:** `https://git.skate-spots.org/skatespots/SkateSpots`
|
||||||
|
(public source, registration disabled, admin credentials only on the relay
|
||||||
|
host). It runs on `185.18.221.2` behind Caddy; do not commit its credentials.
|
||||||
|
- **Latest release (Obtanium HTML source):**
|
||||||
|
`https://git.skate-spots.org/skatespots/SkateSpots/releases/latest`.
|
||||||
|
The page exposes one signed `.apk`; Obtanium may need **Override Source →
|
||||||
|
HTML** because the host is self-hosted, not Codeberg.
|
||||||
|
- **Static download page:** `public/download/index.html` → `https://skate-spots.org/download/`.
|
||||||
|
- **Forgejo release helper:** `scripts/publish-forgejo.sh` (uses `FORGEJO_TOKEN`
|
||||||
|
or `FORGEJO_PASSWORD`; `scripts/release.sh` calls it automatically when either
|
||||||
|
is set). It uploads the APK and checksum, but does not push git history.
|
||||||
|
- **Self-hosted F-Droid repo:** `https://fdroid.skate-spots.org/repo`
|
||||||
|
(`org.skatespots.app`; currently 1.7.61). It is a **third-party repo**, not
|
||||||
|
F-Droid main. Repo signing fingerprint:
|
||||||
|
`88:B1:71:1C:24:02:89:FB:B2:A7:FC:C1:B3:1F:BB:9D:60:F5:7F:19:18:E0:3D:62:2D:35:F2:3D:E1:84:46:75`.
|
||||||
|
The developer APK signature is unchanged; the repo key only signs indexes.
|
||||||
|
A backup of the repo keystore/config is in
|
||||||
|
`~/.config/skatespots/fdroid-repo/` (mode 600).
|
||||||
|
- **F-Droid main/IzzyOnDroid:** **out of scope by owner decision.** Both require
|
||||||
|
external GitLab/Codeberg identities and review; do not submit, create accounts,
|
||||||
|
or present them as live. The self-hosted F-Droid repo above is the supported
|
||||||
|
F-Droid-family channel.
|
||||||
|
- **No GitHub required.** Keep Zapstore, Forgejo, the self-hosted F-Droid repo,
|
||||||
|
and the static page in sync for each release.
|
||||||
|
|
||||||
|
### Future release / distribution checklist
|
||||||
|
1. Bump `package.json`; update the top of `release-notes.txt`; run
|
||||||
|
`scripts/release.sh patch --yes --deploy`.
|
||||||
|
2. Set `FORGEJO_TOKEN` (or `FORGEJO_PASSWORD`) to upload the APK/checksum to the
|
||||||
|
latest Forgejo release; the helper is `scripts/publish-forgejo.sh`.
|
||||||
|
3. **Push source/tags to Forgejo.** The local machine's DNS for
|
||||||
|
`git.skate-spots.org` is intermittent, so the reliable path is: create a git
|
||||||
|
bundle locally, `scp` it to the relay, clone there, and push to
|
||||||
|
`http://127.0.0.1:3000/skatespots/SkateSpots.git` with the host admin
|
||||||
|
credentials. Do not put the password in a committed remote URL.
|
||||||
|
4. Add the APK to `/home/ubuntu/fdroid/repo/`, update the F-Droid metadata
|
||||||
|
changelog, run `fdroid update` in `/home/ubuntu/fdroid`, then rsync `repo/`
|
||||||
|
to `/var/www/fdroid/repo/`.
|
||||||
|
5. Update `public/download/index.html` with the new version/checksum and deploy
|
||||||
|
the web page.
|
||||||
|
6. Verify: latest Forgejo release exposes one `.apk`; static page 200;
|
||||||
|
`https://fdroid.skate-spots.org/repo/index-v2.json` lists the new
|
||||||
|
`versionCode`; Obtanium still resolves the latest release.
|
||||||
|
7. Keep Forgejo/F-Droid credentials and the F-Droid repo keystore out of git.
|
||||||
|
Local backups: `~/.config/skatespots/forgejo-admin.txt` and
|
||||||
|
`~/.config/skatespots/fdroid-repo/` (mode 600).
|
||||||
|
|
||||||
|
### Nostr-only distribution (no external identities)
|
||||||
|
- **Zapstore** is the Nostr-native app store/update channel; keep publishing
|
||||||
|
release events there.
|
||||||
|
- **Obtanium** is URL-based, not an account: users add the latest Forgejo
|
||||||
|
release URL (HTML source if needed) or the self-hosted F-Droid repo.
|
||||||
|
- **F-Droid main and IzzyOnDroid do not support Nostr identities.** They are
|
||||||
|
intentionally not pursued. Do not create or request GitLab/Codeberg/email
|
||||||
|
accounts, and do not claim those stores are live.
|
||||||
|
- The local Forgejo admin account, the relay, and the F-Droid repo signing key
|
||||||
|
are self-hosted infrastructure, not third-party identities.
|
||||||
|
- The F-Droid recipe remains in `distribution/fdroiddata/` only as an unused
|
||||||
|
technical reference. It was locally test-built at `5ebf741`, but must not be
|
||||||
|
submitted.
|
||||||
|
|
||||||
### Deploy
|
### Deploy
|
||||||
- **Web (auto)**: a systemd user timer (`skatespots-deploy.timer`) runs `scripts/auto-deploy.sh` every ~3 min. When `origin/main` changes it builds and runs `netlify deploy --prod --dir=dist`. It deploys **only committed code** (skips if tracked files are modified) and never mutates a dirty tree. **Doc/scripts-only commits are skipped** (matches `AGENTS.md`/`*.md`/`scripts/`/`cvm-server/`/`skills/`) — Netlify bills **15 credits per production deploy**, so don't spend them on non-site changes. One-time setup: `netlify login` + `netlify link`. Logs: `journalctl --user -u skatespots-deploy`. Manual run: `scripts/auto-deploy.sh`.
|
- **Web (auto)**: a systemd user timer (`skatespots-deploy.timer`) runs `scripts/auto-deploy.sh` every ~3 min. When `origin/main` changes it builds and runs `netlify deploy --prod --dir=dist`. It deploys **only committed code** (skips if tracked files are modified) and never mutates a dirty tree. **Doc/scripts-only commits are skipped** (matches `AGENTS.md`/`*.md`/`scripts/`/`cvm-server/`/`skills/`) — Netlify bills **15 credits per production deploy**, so don't spend them on non-site changes. One-time setup: `netlify login` + `netlify link`. Logs: `journalctl --user -u skatespots-deploy`. Manual run: `scripts/auto-deploy.sh`.
|
||||||
- ⚠️ **Deploy throttle (2026-09-28):** production deploys are capped to one per **24h** by default (after a week of 68 deploys ≈ **1,020 credits** — a third of the monthly allowance). Batching a day's commits into one 15-credit deploy is the single biggest credit lever. Override with `SKATESPOTS_DEPLOY_MIN_INTERVAL=<seconds>` (`0` disables); deploy on demand with **`scripts/auto-deploy.sh --force`**. A throttled tick deliberately does **not** record the deployed SHA, so the pending change still deploys on a later tick; a **failed** deploy doesn't set the clock, so it retries immediately.
|
- ⚠️ **Deploy throttle (2026-09-28):** production deploys are capped to one per **24h** by default (after a week of 68 deploys ≈ **1,020 credits** — a third of the monthly allowance). Batching a day's commits into one 15-credit deploy is the single biggest credit lever. Override with `SKATESPOTS_DEPLOY_MIN_INTERVAL=<seconds>` (`0` disables); deploy on demand with **`scripts/auto-deploy.sh --force`**. A throttled tick deliberately does **not** record the deployed SHA, so the pending change still deploys on a later tick; a **failed** deploy doesn't set the clock, so it retries immediately.
|
||||||
|
|
@ -2799,6 +2868,9 @@ Rules:
|
||||||
1600px WebP when smaller (GIF/undecodable formats untouched), cutting
|
1600px WebP when smaller (GIF/undecodable formats untouched), cutting
|
||||||
quota/storage/upload time. APK sha256 `8f11c229…`, versionCode 10760.
|
quota/storage/upload time. APK sha256 `8f11c229…`, versionCode 10760.
|
||||||
Web live at `6c9b6fa`.
|
Web live at `6c9b6fa`.
|
||||||
|
- **1.7.61 (2026-10-30):** Cordn setup timeline now checks prepared device
|
||||||
|
/sent request and spins on the approval step while waiting. APK sha256
|
||||||
|
`db0279f7…`, versionCode 10761. Web live at `50c95d0`.
|
||||||
|
|
||||||
### Relay archive (dataset backup)
|
### Relay archive (dataset backup)
|
||||||
- **Transfer content between keys (`scripts/transfer-content.mjs`, 2026-10-04):**
|
- **Transfer content between keys (`scripts/transfer-content.mjs`, 2026-10-04):**
|
||||||
|
|
|
||||||
97
distribution/README.md
Normal file
97
distribution/README.md
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
# Distribution without GitHub
|
||||||
|
|
||||||
|
SkateSpots is published from a self-hosted **Forgejo** instance. No GitHub
|
||||||
|
account is required.
|
||||||
|
|
||||||
|
## Current release source
|
||||||
|
|
||||||
|
- Forge: <https://git.skate-spots.org/skatespots/SkateSpots>
|
||||||
|
- Latest release: <https://git.skate-spots.org/skatespots/SkateSpots/releases/latest>
|
||||||
|
- Signed APK (1.7.61):
|
||||||
|
<https://git.skate-spots.org/skatespots/SkateSpots/releases/download/v1.7.61/SkateSpots-1.7.61.apk>
|
||||||
|
- SHA-256:
|
||||||
|
`db0279f759efd9fa05d9d1bed57a3f5856777e6ccb208d3ed78b15398f266fba`
|
||||||
|
|
||||||
|
The APK is signed with the same key as the Zapstore build. Installing an
|
||||||
|
F-Droid-signed build over it would require uninstalling SkateSpots and losing
|
||||||
|
the local key/settings. Obtanium and direct APK updates keep the install.
|
||||||
|
|
||||||
|
## Obtanium
|
||||||
|
|
||||||
|
Obtanium cannot use a Zapstore URL as a source. Use the self-hosted Forgejo
|
||||||
|
release page instead:
|
||||||
|
|
||||||
|
1. Open Obtanium → **Add App**.
|
||||||
|
2. Paste:
|
||||||
|
`https://git.skate-spots.org/skatespots/SkateSpots/releases/latest`
|
||||||
|
3. If Obtanium does not recognise the self-hosted host, select **Override
|
||||||
|
Source → HTML**.
|
||||||
|
4. Set version extraction if desired:
|
||||||
|
`SkateSpots-([0-9.]+)\.apk`, match group `1`.
|
||||||
|
|
||||||
|
The release page contains exactly one `.apk`, so Obtanium can follow the latest
|
||||||
|
release without a custom store listing.
|
||||||
|
|
||||||
|
## F-Droid family
|
||||||
|
|
||||||
|
### Self-hosted F-Droid repo (live now)
|
||||||
|
|
||||||
|
A signed third-party F-Droid repository is published here:
|
||||||
|
|
||||||
|
- Repo: <https://fdroid.skate-spots.org/repo>
|
||||||
|
- App ID: `org.skatespots.app`
|
||||||
|
- Repo signing fingerprint:
|
||||||
|
`88:B1:71:1C:24:02:89:FB:B2:A7:FC:C1:B3:1F:BB:9D:60:F5:7F:19:18:E0:3D:62:2D:35:F2:3D:E1:84:46:75`
|
||||||
|
|
||||||
|
Users can add this URL in F-Droid, or add it in Obtanium as an **F-Droid Third
|
||||||
|
Party Repo**. This is not F-Droid main: it is a self-hosted, signed repository
|
||||||
|
of the developer-signed APK. It keeps the existing install and signing key.
|
||||||
|
|
||||||
|
The repository metadata and APK are served from the relay host. The repo index
|
||||||
|
is signed with a repository key stored on that host; the APK itself keeps the
|
||||||
|
SkateSpots developer signature.
|
||||||
|
|
||||||
|
### No external store identities
|
||||||
|
|
||||||
|
**Owner decision:** distribution stays self-hosted and Nostr-based. No GitLab,
|
||||||
|
Codeberg, GitHub, email or F-Droid-main accounts are created or requested.
|
||||||
|
|
||||||
|
F-Droid main and IzzyOnDroid both require external identities and review, so
|
||||||
|
they are intentionally out of scope. The self-hosted repo above is the supported
|
||||||
|
F-Droid-family channel.
|
||||||
|
|
||||||
|
`distribution/fdroiddata/` and `fastlane/` are kept as an unused technical
|
||||||
|
reference only. A fresh local clone at `5ebf741` built the unsigned APK through
|
||||||
|
the recipe's `npm ci → android:init → android:sync → assembleRelease` path, but
|
||||||
|
the recipe must not be submitted.
|
||||||
|
|
||||||
|
## Release checklist
|
||||||
|
|
||||||
|
1. `npm test`
|
||||||
|
2. `scripts/release.sh patch --yes --deploy` (Zapstore + web)
|
||||||
|
3. Push the release tag and `main` to Forgejo.
|
||||||
|
4. Upload the APK through the helper:
|
||||||
|
`FORGEJO_TOKEN=... scripts/publish-forgejo.sh <version>`
|
||||||
|
(or set `FORGEJO_TOKEN` before `release.sh`; the APK upload is then automatic)
|
||||||
|
5. Copy the signed APK into the F-Droid repo on the relay host, add a
|
||||||
|
`metadata/org.skatespots.app.yml` changelog entry, run `fdroid update`, and
|
||||||
|
sync `repo/` to `/var/www/fdroid/repo/`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/ubuntu/fdroid
|
||||||
|
cp /path/to/SkateSpots-<version>.apk repo/
|
||||||
|
. /home/ubuntu/fdroid-venv/bin/activate
|
||||||
|
fdroid update
|
||||||
|
sudo rsync -a --delete repo/ /var/www/fdroid/repo/
|
||||||
|
sudo chown -R caddy:caddy /var/www/fdroid
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Verify the public latest-release page contains the new `.apk`, and that
|
||||||
|
`https://fdroid.skate-spots.org/repo/index-v2.json` lists the new
|
||||||
|
`versionCode`.
|
||||||
|
7. Update the static download page if the displayed version/checksum changes:
|
||||||
|
`public/download/index.html`.
|
||||||
|
|
||||||
|
The Forgejo instance runs on the same host as the relay. It exposes public
|
||||||
|
read access and HTTPS through Caddy; registration is disabled and administrative
|
||||||
|
credentials are kept on the host, not in this repository.
|
||||||
51
distribution/fdroiddata/org.skatespots.app.yml
Normal file
51
distribution/fdroiddata/org.skatespots.app.yml
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
Categories:
|
||||||
|
- Sports & Health
|
||||||
|
License: AGPL-3.0-only
|
||||||
|
AuthorName: SkateSpots
|
||||||
|
WebSite: https://skate-spots.org
|
||||||
|
SourceCode: https://git.skate-spots.org/skatespots/SkateSpots
|
||||||
|
IssueTracker: https://git.skate-spots.org/skatespots/SkateSpots/issues
|
||||||
|
Changelog: https://git.skate-spots.org/skatespots/SkateSpots/releases
|
||||||
|
|
||||||
|
AutoName: SkateSpots
|
||||||
|
Summary: Local-first skate map, crews, events and marketplace
|
||||||
|
Description: |-
|
||||||
|
SkateSpots is a local-first skating map for finding spots, crews, events and
|
||||||
|
gear. Your keys stay on your device, your messages stay in encrypted crew
|
||||||
|
chats and the app does not use analytics or sell your data.
|
||||||
|
|
||||||
|
Find skate spots on the map, add new ones, organise sessions with your crew,
|
||||||
|
follow events and browse the community marketplace. The Android app bundles
|
||||||
|
the same web app for fast shell startup and native camera, location, push and
|
||||||
|
NFC support where available.
|
||||||
|
|
||||||
|
SkateSpots is self-hosted and built on open protocols. You can run your own
|
||||||
|
relay and Blossom storage instead of relying on a central service.
|
||||||
|
|
||||||
|
RepoType: git
|
||||||
|
Repo: https://git.skate-spots.org/skatespots/SkateSpots.git
|
||||||
|
|
||||||
|
Builds:
|
||||||
|
- versionName: 1.7.61
|
||||||
|
versionCode: 10761
|
||||||
|
commit: 5ebf741427303be0b0feeff5ce1d8420e2c0ddea
|
||||||
|
subdir: .
|
||||||
|
prebuild:
|
||||||
|
- npm ci
|
||||||
|
- npm run android:init
|
||||||
|
- npm run android:sync
|
||||||
|
# F-Droid signs the APK itself. Remove the developer signing config so
|
||||||
|
# Gradle emits an unsigned release APK instead of requiring the keystore.
|
||||||
|
- sed -i '/signingConfig signingConfigs.release/d' android/app/build.gradle
|
||||||
|
build:
|
||||||
|
- cd android && ./gradlew assembleRelease --no-daemon
|
||||||
|
output: android/app/build/outputs/apk/release/app-release-unsigned.apk
|
||||||
|
# The generated Android project is intentionally not committed. This
|
||||||
|
# recipe recreates it with Capacitor before Gradle runs. F-Droid will sign
|
||||||
|
# the resulting APK with its own key. The F-Droid buildserver supplies the
|
||||||
|
# JDK/Android SDK; do not install packages with sudo in a recipe.
|
||||||
|
|
||||||
|
AutoUpdateMode: Version
|
||||||
|
UpdateCheckMode: Tags
|
||||||
|
CurrentVersion: 1.7.61
|
||||||
|
CurrentVersionCode: 10761
|
||||||
1
fastlane/metadata/android/en-US/changelogs/10761.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/10761.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Cordn setup now shows visible progress: the device-key step is checked once prepared, and the approval step shows a spinner while waiting. The APK uses the same signing key as the Zapstore release.
|
||||||
15
fastlane/metadata/android/en-US/full_description.txt
Normal file
15
fastlane/metadata/android/en-US/full_description.txt
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
SkateSpots is a local-first skating map for finding spots, crews, events and
|
||||||
|
gear. Your keys stay on your device, your messages stay in encrypted crew chats
|
||||||
|
and the app does not use analytics or sell your data.
|
||||||
|
|
||||||
|
Find skate spots on the map, add new ones, organise sessions with your crew,
|
||||||
|
follow events and browse the community marketplace. The Android app bundles the
|
||||||
|
same web app for fast offline shell startup and native camera, location, push
|
||||||
|
and NFC support where available.
|
||||||
|
|
||||||
|
SkateSpots is self-hosted and built on open protocols. You can run your own
|
||||||
|
relay and Blossom storage instead of relying on a central service.
|
||||||
|
|
||||||
|
Lightning payments are optional and self-custodial. Transport and driver
|
||||||
|
profiles are self-attested; SkateSpots does not verify a licence, insurance or
|
||||||
|
vehicle roadworthiness.
|
||||||
BIN
fastlane/metadata/android/en-US/images/icon.png
Normal file
BIN
fastlane/metadata/android/en-US/images/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Local-first skate map, crews, events and marketplace.
|
||||||
1
fastlane/metadata/android/en-US/title.txt
Normal file
1
fastlane/metadata/android/en-US/title.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
SkateSpots
|
||||||
127
public/download/index.html
Normal file
127
public/download/index.html
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||||
|
<meta name="color-scheme" content="dark" />
|
||||||
|
<title>SkateSpots APK</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Download the signed SkateSpots Android APK. No account, no store, no tracking."
|
||||||
|
/>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg: #090b0c;
|
||||||
|
--panel: #121719;
|
||||||
|
--line: #273033;
|
||||||
|
--text: #f2f5f4;
|
||||||
|
--muted: #9aa7a5;
|
||||||
|
--teal: #56e0c0;
|
||||||
|
--amber: #f5b83d;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100dvh;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 24px;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 20% 0%, rgba(86, 224, 192, 0.12), transparent 42%),
|
||||||
|
var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font: 15px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
width: min(100%, 560px);
|
||||||
|
padding: 28px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 18px;
|
||||||
|
background: color-mix(in srgb, var(--panel) 92%, transparent);
|
||||||
|
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
.eyebrow {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
color: var(--teal);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
h1 { margin: 0; font-size: clamp(28px, 7vw, 38px); line-height: 1.02; }
|
||||||
|
p { margin: 14px 0 0; color: var(--muted); }
|
||||||
|
a.download {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--teal);
|
||||||
|
color: #06201b;
|
||||||
|
font-weight: 800;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a.download:focus-visible { outline: 3px solid var(--amber); outline-offset: 3px; }
|
||||||
|
a.download span:last-child { font-size: 20px; }
|
||||||
|
.meta {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 18px;
|
||||||
|
padding-top: 18px;
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
color: var(--text);
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.links { display: flex; flex-wrap: wrap; gap: 10px 18px; margin-top: 16px; }
|
||||||
|
.links a { color: var(--amber); font-weight: 700; text-decoration: none; }
|
||||||
|
.links a:hover { text-decoration: underline; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<p class="eyebrow">SkateSpots · Android</p>
|
||||||
|
<h1>Get the signed APK.</h1>
|
||||||
|
<p>
|
||||||
|
Local-first skating map, crews, events and marketplace. No store account
|
||||||
|
required. This is the same signing key as the Zapstore and Obtanium
|
||||||
|
releases, so it updates over an existing install.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="download"
|
||||||
|
href="https://git.skate-spots.org/skatespots/SkateSpots/releases/download/v1.7.61/SkateSpots-1.7.61.apk"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
<span>Download SkateSpots-1.7.61.apk · 56 MB</span>
|
||||||
|
<span aria-hidden="true">↓</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="meta">
|
||||||
|
<span>Requires Android 7.0 or newer.</span>
|
||||||
|
<span>SHA-256: <code>db0279f759efd9fa05d9d1bed57a3f5856777e6ccb208d3ed78b15398f266fba</code></span>
|
||||||
|
<span>Only install APKs from a source you trust. Android will ask you to allow installs from your browser.</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="links">
|
||||||
|
<a href="https://git.skate-spots.org/skatespots/SkateSpots/releases">All releases</a>
|
||||||
|
<a href="https://git.skate-spots.org/skatespots/SkateSpots">Source code</a>
|
||||||
|
<a href="https://skate-spots.org">Open the web app</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="meta">
|
||||||
|
<span><strong>Obtanium:</strong> add the latest release page above as the app source, or select <em>Override Source → HTML</em> if it is not recognised.</span>
|
||||||
|
<span><strong>F-Droid repo:</strong> <a href="https://fdroid.skate-spots.org/repo">https://fdroid.skate-spots.org/repo</a> (third-party self-hosted repo; app ID <code>org.skatespots.app</code>).</span>
|
||||||
|
<span>Repo signing fingerprint: <code>88:B1:71:1C:24:02:89:FB:B2:A7:FC:C1:B3:1F:BB:9D:60:F5:7F:19:18:E0:3D:62:2D:35:F2:3D:E1:84:46:75</code></span>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
resources/splash.png
Normal file
BIN
resources/splash.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
108
scripts/publish-forgejo.sh
Executable file
108
scripts/publish-forgejo.sh
Executable file
|
|
@ -0,0 +1,108 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Publish a built APK to the self-hosted Forgejo release page.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# FORGEJO_TOKEN=... scripts/publish-forgejo.sh 1.7.62 [path/to.apk]
|
||||||
|
# FORGEJO_PASSWORD=... FORGEJO_USER=skatespots scripts/publish-forgejo.sh 1.7.62
|
||||||
|
#
|
||||||
|
# This uploads the APK and its .sha256 file. It does not push the git tag or
|
||||||
|
# source; do that separately (the Forgejo repo is a mirror, not an auto-push
|
||||||
|
# remote). Keep credentials in your shell/environment, never in this repo.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
VERSION="${1:?usage: publish-forgejo.sh <version> [apk]}"
|
||||||
|
APK="${2:-android/app/build/outputs/apk/release/app-release.apk}"
|
||||||
|
TAG="${FORGEJO_TAG:-v$VERSION}"
|
||||||
|
BASE="${FORGEJO_URL:-https://git.skate-spots.org}"
|
||||||
|
OWNER="${FORGEJO_OWNER:-skatespots}"
|
||||||
|
REPO="${FORGEJO_REPO:-SkateSpots}"
|
||||||
|
API="$BASE/api/v1/repos/$OWNER/$REPO"
|
||||||
|
|
||||||
|
command -v curl >/dev/null || { echo "curl is required" >&2; exit 1; }
|
||||||
|
command -v python3 >/dev/null || { echo "python3 is required" >&2; exit 1; }
|
||||||
|
[ -f "$APK" ] || { echo "APK not found: $APK" >&2; exit 1; }
|
||||||
|
|
||||||
|
AUTH=()
|
||||||
|
if [ -n "${FORGEJO_TOKEN:-}" ]; then
|
||||||
|
AUTH=(-H "Authorization: token $FORGEJO_TOKEN")
|
||||||
|
elif [ -n "${FORGEJO_PASSWORD:-}" ]; then
|
||||||
|
AUTH=(-u "${FORGEJO_USER:-skatespots}:$FORGEJO_PASSWORD")
|
||||||
|
else
|
||||||
|
echo "set FORGEJO_TOKEN (preferred) or FORGEJO_PASSWORD" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$tmpdir"' EXIT
|
||||||
|
|
||||||
|
sha="$(sha256sum "$APK" | awk '{print $1}')"
|
||||||
|
body="$tmpdir/release.md"
|
||||||
|
cat >"$body" <<EOF
|
||||||
|
## SkateSpots $VERSION
|
||||||
|
|
||||||
|
Signed Android release. SHA-256: \`$sha\`
|
||||||
|
|
||||||
|
- Source: $BASE/$OWNER/$REPO
|
||||||
|
- APK signing key: same as the Zapstore release.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
python3 - "$TAG" "$VERSION" "$body" >"$tmpdir/payload.json" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
tag, version, body_path = sys.argv[1:]
|
||||||
|
print(json.dumps({
|
||||||
|
"tag_name": tag,
|
||||||
|
"name": version,
|
||||||
|
"body": Path(body_path).read_text(),
|
||||||
|
"draft": False,
|
||||||
|
"prerelease": False,
|
||||||
|
}))
|
||||||
|
PY
|
||||||
|
|
||||||
|
create_code="$(curl -sS -o "$tmpdir/release.json" -w '%{http_code}' \
|
||||||
|
"${AUTH[@]}" -H 'Content-Type: application/json' \
|
||||||
|
-X POST "$API/releases" --data-binary @"$tmpdir/payload.json" || true)"
|
||||||
|
|
||||||
|
if [ "$create_code" = "201" ]; then
|
||||||
|
release_id="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["id"])' "$tmpdir/release.json")"
|
||||||
|
elif [ "$create_code" = "409" ] || [ "$create_code" = "422" ]; then
|
||||||
|
curl -fsS "${AUTH[@]}" "$API/releases/tags/$TAG" -o "$tmpdir/release.json"
|
||||||
|
release_id="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["id"])' "$tmpdir/release.json")"
|
||||||
|
else
|
||||||
|
echo "Forgejo release creation failed (HTTP $create_code)" >&2
|
||||||
|
cat "$tmpdir/release.json" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sha_file="$tmpdir/SkateSpots-$VERSION.apk.sha256"
|
||||||
|
printf '%s SkateSpots-%s.apk\n' "$sha" "$VERSION" >"$sha_file"
|
||||||
|
|
||||||
|
existing_assets="$(python3 - "$tmpdir/release.json" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
data = json.load(open(sys.argv[1]))
|
||||||
|
print("\n".join(asset["name"] for asset in data.get("assets", [])))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
for file in "$APK" "$sha_file"; do
|
||||||
|
name="$(basename "$file")"
|
||||||
|
if printf '%s\n' "$existing_assets" | grep -Fqx "$name"; then
|
||||||
|
echo "already present: $name"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
code="$(curl -sS -o "$tmpdir/asset.json" -w '%{http_code}' \
|
||||||
|
"${AUTH[@]}" -X POST "$API/releases/$release_id/assets?name=$name" \
|
||||||
|
-F "attachment=@$file" || true)"
|
||||||
|
case "$code" in
|
||||||
|
201|202) echo "uploaded $name" ;;
|
||||||
|
*) echo "warning: upload of $name returned HTTP $code" >&2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "release: $BASE/$OWNER/$REPO/releases/tag/$TAG"
|
||||||
|
echo "latest: $BASE/$OWNER/$REPO/releases/latest"
|
||||||
|
|
@ -126,6 +126,14 @@ for _ in 1 2 3 4 5 6; do
|
||||||
done
|
done
|
||||||
[ "$FOUND" -eq 1 ] || log "warning: the release event did not appear on the relay yet (it usually propagates shortly)"
|
[ "$FOUND" -eq 1 ] || log "warning: the release event did not appear on the relay yet (it usually propagates shortly)"
|
||||||
|
|
||||||
|
if [ -n "${FORGEJO_TOKEN:-}" ] || [ -n "${FORGEJO_PASSWORD:-}" ]; then
|
||||||
|
log "publishing the APK to the self-hosted Forgejo release page…"
|
||||||
|
scripts/publish-forgejo.sh "$VERSION" "$APK" || \
|
||||||
|
log "warning: Forgejo upload failed — the Zapstore release is still valid"
|
||||||
|
else
|
||||||
|
log "Forgejo: skipped (set FORGEJO_TOKEN or FORGEJO_PASSWORD to publish there)"
|
||||||
|
fi
|
||||||
|
|
||||||
log "committing the release…"
|
log "committing the release…"
|
||||||
git add package.json package-lock.json release-notes.txt
|
git add package.json package-lock.json release-notes.txt
|
||||||
git commit -m "release: $VERSION" >/dev/null
|
git commit -m "release: $VERSION" >/dev/null
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue