License the project; add root README, website, and parked release CI

- Code: GPL-3.0-or-later (LICENSE, SPDX headers on all .kt/.aidl).
  Specs in docs/: CC-BY-4.0 (docs/LICENSE). Rationale in build-status.md;
  server decided GPL (not AGPL).
- Root README for the public repo.
- web/: Cloudflare Worker site for echo-lot.app. /apk resolves the newest
  APK from the Gitea latest-release API at request time (edge-cached 5 min),
  so tagging a release is the only publish step. /fdroid, /source, and a
  manual DOWNLOAD_URL fallback are wrangler vars.
- .gitea/workflows/release.yml: tag-driven (v*) signed semver APK builds for
  the future production app in echolot-app/. Parked; the prober is
  deliberately not CI-built.
- Fix UserService.kt: drop the explicit secondary constructor that
  conflicted with the implicit primary (never compiled before — first
  local build caught it). Prober now builds: :app:assembleDebug OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mrambossek
2026-07-30 09:15:35 +02:00
co-authored by Claude Opus 5
parent e3840f54fe
commit 6ddf013dfe
28 changed files with 1659 additions and 5 deletions
+63
View File
@@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: 2026 Echolot contributors
// SPDX-License-Identifier: GPL-3.0-or-later
// Everything under public/ is served straight from the edge without invoking
// this Worker. The Worker exists for the short stable URLs (/apk, /fdroid,
// /source) — short enough for a QR code — and to resolve "/apk" to the newest
// release asset at request time, so tagging a release in Gitea is the only
// publish step. No site redeploy, no URL to update.
const STATIC_ROUTES = {
"/fdroid": "FDROID_URL",
"/source": "SOURCE_URL",
};
// Resolve the newest APK from the Gitea "latest release" API. Cached at the
// edge for 5 minutes so a release becomes visible quickly, while Gitea sees
// at most one API hit per POP per 5 min regardless of download traffic.
async function latestApkUrl(env) {
if (!env.GITEA_REPO_API) return null;
const res = await fetch(`${env.GITEA_REPO_API}/releases/latest`, {
headers: { Accept: "application/json", "User-Agent": "echolot-site" },
cf: { cacheTtl: 300, cacheEverything: true },
});
if (!res.ok) return null;
const rel = await res.json();
const apk = rel.assets?.find((a) => a.name?.endsWith(".apk"));
return apk?.browser_download_url ?? null;
}
function redirect(location) {
return new Response(null, {
status: 302,
headers: {
Location: location,
"Cache-Control": "no-store",
"Referrer-Policy": "no-referrer",
},
});
}
export default {
async fetch(request, env) {
const { pathname } = new URL(request.url);
const path = pathname.replace(/\/$/, "");
const fallback = new URL("/#install", request.url).toString();
if (path === "/apk" || path === "/download") {
// Order: live Gitea release → manual override → install section.
let target = null;
try {
target = await latestApkUrl(env);
} catch {
// Gitea unreachable — fall through rather than 500 on a download link.
}
return redirect(target || env.DOWNLOAD_URL || fallback);
}
const varName = STATIC_ROUTES[path];
if (varName) return redirect(env[varName] || fallback);
return env.ASSETS.fetch(request);
},
};