Android app (TWA) in android/, built with podman; serve assetlinks.json for it
This commit is contained in:
parent
44708d5f3c
commit
7900bf5b3e
21 changed files with 313 additions and 0 deletions
22
README.md
22
README.md
|
|
@ -163,6 +163,28 @@ rsvg-convert -w 512 -h 512 icon-maskable.svg -o icon-maskable-512.png
|
||||||
rsvg-convert -w 180 -h 180 icon-maskable.svg -o apple-touch-icon.png
|
rsvg-convert -w 180 -h 180 icon-maskable.svg -o apple-touch-icon.png
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Android app
|
||||||
|
|
||||||
|
`android/` is the site as an Android app: a Trusted Web Activity, which opens the public site in
|
||||||
|
the phone's Chrome, full screen, with no code of its own (Google's `androidbrowserhelper` does it
|
||||||
|
all). Build it with podman:
|
||||||
|
|
||||||
|
```
|
||||||
|
android/build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
That builds an image with JDK 17, the Android SDK and Gradle, and writes a signed
|
||||||
|
`android/out/simpleadmin-web.apk`. The signing key is kept outside the repo, in
|
||||||
|
`~/.config/simpleadmin-web/android/` (`release.jks` and its `password`), and made on the first run.
|
||||||
|
Back it up: an APK signed with another key won't install over the old one. Then:
|
||||||
|
|
||||||
|
- Set `SAW_ANDROID_PACKAGE` and `SAW_ANDROID_CERT_SHA256` (the fingerprint `build.sh` prints) so
|
||||||
|
the panel serves `/.well-known/assetlinks.json`. Without it, Chrome shows its URL bar over the app.
|
||||||
|
- Bump `versionCode` in `android/app/build.gradle` for every APK you hand out.
|
||||||
|
- The site and start page are at the top of `android/app/build.gradle`; never change `applicationId`.
|
||||||
|
|
||||||
|
The app needs Chrome (or another browser with TWA support) on the phone.
|
||||||
|
|
||||||
## Limitations and caveats
|
## Limitations and caveats
|
||||||
|
|
||||||
- **Tested against a real MySQL 8.4** with SimpleAdmin 1.8.2b's migrations applied, and a fake game
|
- **Tested against a real MySQL 8.4** with SimpleAdmin 1.8.2b's migrations applied, and a fake game
|
||||||
|
|
|
||||||
6
android/.gitignore
vendored
Normal file
6
android/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
app/build/
|
||||||
|
out/
|
||||||
|
local.properties
|
||||||
|
*.jks
|
||||||
17
android/Containerfile
Normal file
17
android/Containerfile
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Build image for the Android app: JDK 17, the Android SDK and Gradle. See build.sh.
|
||||||
|
FROM docker.io/library/eclipse-temurin:17-jdk
|
||||||
|
|
||||||
|
ARG CMDLINE_TOOLS=15859902
|
||||||
|
ARG GRADLE=8.14.3
|
||||||
|
ENV ANDROID_HOME=/opt/android-sdk
|
||||||
|
ENV PATH=/opt/gradle/bin:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends unzip && rm -rf /var/lib/apt/lists/*
|
||||||
|
RUN curl -fsSLo /tmp/tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS}_latest.zip \
|
||||||
|
&& mkdir -p $ANDROID_HOME/cmdline-tools && unzip -q /tmp/tools.zip -d /tmp \
|
||||||
|
&& mv /tmp/cmdline-tools $ANDROID_HOME/cmdline-tools/latest && rm /tmp/tools.zip
|
||||||
|
RUN yes | sdkmanager --licenses >/dev/null && sdkmanager "platforms;android-36" "build-tools;35.0.0" "platform-tools" >/dev/null
|
||||||
|
RUN curl -fsSLo /tmp/gradle.zip https://services.gradle.org/distributions/gradle-${GRADLE}-bin.zip \
|
||||||
|
&& unzip -q /tmp/gradle.zip -d /opt && mv /opt/gradle-${GRADLE} /opt/gradle && rm /tmp/gradle.zip
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
53
android/app/build.gradle
Normal file
53
android/app/build.gradle
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
plugins {
|
||||||
|
id "com.android.application"
|
||||||
|
}
|
||||||
|
|
||||||
|
// The site the app opens. It must serve /.well-known/assetlinks.json naming this app's package and
|
||||||
|
// signing key, or Chrome shows its URL bar over the app (see SAW_ANDROID_* in example.env).
|
||||||
|
def siteURL = "https://cs2.zio.sh"
|
||||||
|
def startPath = "/"
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace "sh.zio.cs2"
|
||||||
|
compileSdk 36
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// Never change applicationId once installed anywhere: Android treats it as a different app.
|
||||||
|
applicationId "sh.zio.cs2"
|
||||||
|
minSdk 24
|
||||||
|
targetSdk 36
|
||||||
|
// Bump versionCode for every APK you hand out, or Android refuses to update over the old one.
|
||||||
|
versionCode 2
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
resValue "string", "launch_url", siteURL + startPath
|
||||||
|
resValue "string", "asset_statements",
|
||||||
|
"[{\\\"relation\\\": [\\\"delegate_permission/common.handle_all_urls\\\"], \\\"target\\\": {\\\"namespace\\\": \\\"web\\\", \\\"site\\\": \\\"" + siteURL + "\\\"}}]"
|
||||||
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
storeFile file(System.getenv("KEYSTORE_FILE") ?: "/keystore/release.jks")
|
||||||
|
storePassword System.getenv("KEYSTORE_PASSWORD")
|
||||||
|
keyAlias "release"
|
||||||
|
keyPassword System.getenv("KEYSTORE_PASSWORD")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt")
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "com.google.androidbrowserhelper:androidbrowserhelper:2.7.3"
|
||||||
|
}
|
||||||
80
android/app/src/main/AndroidManifest.xml
Normal file
80
android/app/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- A Trusted Web Activity: the app is the panel, opened full screen in the phone's Chrome.
|
||||||
|
androidbrowserhelper's LauncherActivity does all of it; there is no code of our own. -->
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:manageSpaceActivity="com.google.androidbrowserhelper.trusted.ManageDataLauncherActivity"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@android:style/Theme.Translucent.NoTitleBar">
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="asset_statements"
|
||||||
|
android:resource="@string/asset_statements" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.google.androidbrowserhelper.trusted.LauncherActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.customtabs.trusted.DEFAULT_URL"
|
||||||
|
android:value="@string/launch_url" />
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.customtabs.trusted.STATUS_BAR_COLOR"
|
||||||
|
android:resource="@color/theme" />
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.customtabs.trusted.NAVIGATION_BAR_COLOR"
|
||||||
|
android:resource="@color/background" />
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.customtabs.trusted.SPLASH_IMAGE_DRAWABLE"
|
||||||
|
android:resource="@drawable/splash" />
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.customtabs.trusted.SPLASH_SCREEN_BACKGROUND_COLOR"
|
||||||
|
android:resource="@color/background" />
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.customtabs.trusted.FILE_PROVIDER_AUTHORITY"
|
||||||
|
android:value="sh.zio.cs2.fileprovider" />
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<!-- LauncherActivity uses these library activities by name, so they must be declared here
|
||||||
|
(as Bubblewrap's template does), or R8 renames them and the app crashes on launch.
|
||||||
|
ManageDataLauncherActivity is "Manage space" in Android's app info: the site's data. -->
|
||||||
|
<activity
|
||||||
|
android:name="com.google.androidbrowserhelper.trusted.ManageDataLauncherActivity"
|
||||||
|
android:enabled="true"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="false">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.customtabs.trusted.MANAGE_SPACE_URL"
|
||||||
|
android:value="@string/launch_url" />
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity android:name="com.google.androidbrowserhelper.trusted.FocusActivity" />
|
||||||
|
<!-- Used when the phone has no browser that supports TWAs. -->
|
||||||
|
<activity
|
||||||
|
android:name="com.google.androidbrowserhelper.trusted.WebViewFallbackActivity"
|
||||||
|
android:configChanges="orientation|screenSize" />
|
||||||
|
|
||||||
|
<!-- Hands the splash image to Chrome while the panel loads. -->
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="sh.zio.cs2.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/filepaths" />
|
||||||
|
</provider>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 8 KiB |
BIN
android/app/src/main/res/drawable-nodpi/splash.png
Normal file
BIN
android/app/src/main/res/drawable-nodpi/splash.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
6
android/app/src/main/res/values/colors.xml
Normal file
6
android/app/src/main/res/values/colors.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- The panel's colours: the top bar (theme) and page background in styles.css. -->
|
||||||
|
<resources>
|
||||||
|
<color name="theme">#1B1E25</color>
|
||||||
|
<color name="background">#14161B</color>
|
||||||
|
</resources>
|
||||||
4
android/app/src/main/res/values/strings.xml
Normal file
4
android/app/src/main/res/values/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">zio.sh</string>
|
||||||
|
</resources>
|
||||||
4
android/app/src/main/res/xml/filepaths.xml
Normal file
4
android/app/src/main/res/xml/filepaths.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths>
|
||||||
|
<files-path name="twa_splash" path="twa_splash/" />
|
||||||
|
</paths>
|
||||||
3
android/build.gradle
Normal file
3
android/build.gradle
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
plugins {
|
||||||
|
id "com.android.application" version "8.13.2" apply false
|
||||||
|
}
|
||||||
37
android/build.sh
Executable file
37
android/build.sh
Executable file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Builds the signed release APK into out/ with podman (no local JDK or Android SDK needed).
|
||||||
|
#
|
||||||
|
# The signing key lives outside the repo, in $KEYSTORE_DIR (default ~/.config/simpleadmin-web/android),
|
||||||
|
# and is created on the first run. Back it up: an APK signed with a different key can't update an
|
||||||
|
# installed one, and the panel's assetlinks.json names the key's fingerprint.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
here="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
keydir="${KEYSTORE_DIR:-$HOME/.config/simpleadmin-web/android}"
|
||||||
|
image=simpleadmin-web-android-build
|
||||||
|
|
||||||
|
podman build -q -t "$image" "$here" >/dev/null
|
||||||
|
|
||||||
|
mkdir -p "$keydir"
|
||||||
|
chmod 700 "$keydir"
|
||||||
|
if [[ ! -f "$keydir/release.jks" ]]; then
|
||||||
|
echo "Creating a signing key in $keydir"
|
||||||
|
(umask 077 && openssl rand -hex 24 >"$keydir/password")
|
||||||
|
podman run --rm -v "$keydir":/keystore:Z "$image" keytool -genkeypair -noprompt \
|
||||||
|
-keystore /keystore/release.jks -storetype PKCS12 -storepass "$(cat "$keydir/password")" \
|
||||||
|
-alias release -keyalg RSA -keysize 4096 -validity 10000 -dname "CN=zio.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman run --rm \
|
||||||
|
-v "$here":/src:Z \
|
||||||
|
-v "$keydir":/keystore:ro,Z \
|
||||||
|
-v simpleadmin-web-android-gradle:/root/.gradle \
|
||||||
|
-e KEYSTORE_PASSWORD="$(cat "$keydir/password")" \
|
||||||
|
"$image" gradle --no-daemon -q assembleRelease
|
||||||
|
|
||||||
|
mkdir -p "$here/out"
|
||||||
|
cp "$here/app/build/outputs/apk/release/app-release.apk" "$here/out/simpleadmin-web.apk"
|
||||||
|
echo "APK: $here/out/simpleadmin-web.apk"
|
||||||
|
echo "Signing key SHA-256 (for SAW_ANDROID_CERT_SHA256):"
|
||||||
|
podman run --rm -v "$keydir":/keystore:ro,Z "$image" keytool -list -keystore /keystore/release.jks \
|
||||||
|
-storepass "$(cat "$keydir/password")" -alias release | sed -n 's/.*SHA-256): //p'
|
||||||
2
android/gradle.properties
Normal file
2
android/gradle.properties
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.gradle.jvmargs=-Xmx2g
|
||||||
|
android.useAndroidX=true
|
||||||
16
android/settings.gradle
Normal file
16
android/settings.gradle
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rootProject.name = "simpleadmin-web"
|
||||||
|
include ":app"
|
||||||
|
|
@ -25,6 +25,12 @@ SAW_SA_CONFIG=/data/CS2-SimpleAdmin.json
|
||||||
# Rank labels, colours and public descriptions. Built-in defaults cover the #rank/* groups.
|
# Rank labels, colours and public descriptions. Built-in defaults cover the #rank/* groups.
|
||||||
# SAW_SITE_CONFIG=/data/site.json
|
# SAW_SITE_CONFIG=/data/site.json
|
||||||
|
|
||||||
|
# The Android app (android/build.sh), for /.well-known/assetlinks.json. Without these the app still
|
||||||
|
# works, but Chrome shows its URL bar over it. build.sh prints the fingerprint; several are
|
||||||
|
# comma-separated (e.g. the Play signing key as well).
|
||||||
|
# SAW_ANDROID_PACKAGE=sh.zio.cs2
|
||||||
|
# SAW_ANDROID_CERT_SHA256=AB:CD:...
|
||||||
|
|
||||||
# SAW_LISTEN=:8080
|
# SAW_LISTEN=:8080
|
||||||
# SAW_POLL_SECONDS=5
|
# SAW_POLL_SECONDS=5
|
||||||
# SAW_TIMEZONE=UTC (defaults to SimpleAdmin's Timezone setting)
|
# SAW_TIMEZONE=UTC (defaults to SimpleAdmin's Timezone setting)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The site installs as an app (a PWA): a web app manifest, plus the service worker in
|
// The site installs as an app (a PWA): a web app manifest, plus the service worker in
|
||||||
|
|
@ -62,6 +63,34 @@ func (s *Server) manifest(staff bool) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// assetLinks tells Chrome the Android app belongs to this site, so it opens the site full screen
|
||||||
|
// (a Trusted Web Activity) instead of with its URL bar showing.
|
||||||
|
func (s *Server) assetLinks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var certs []string
|
||||||
|
for _, c := range s.cfg.AndroidCerts {
|
||||||
|
if c = strings.ToUpper(strings.TrimSpace(c)); c != "" {
|
||||||
|
certs = append(certs, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.cfg.AndroidPackage == "" || len(certs) == 0 {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type target struct {
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
|
PackageName string `json:"package_name"`
|
||||||
|
Fingerprints []string `json:"sha256_cert_fingerprints"`
|
||||||
|
}
|
||||||
|
type statement struct {
|
||||||
|
Relation []string `json:"relation"`
|
||||||
|
Target target `json:"target"`
|
||||||
|
}
|
||||||
|
writeJSON(w, http.StatusOK, []statement{{
|
||||||
|
Relation: []string{"delegate_permission/common.handle_all_urls"},
|
||||||
|
Target: target{Namespace: "android_app", PackageName: s.cfg.AndroidPackage, Fingerprints: certs},
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
// serviceWorker serves sw.js from the root, since a worker only controls pages under its own path.
|
// serviceWorker serves sw.js from the root, since a worker only controls pages under its own path.
|
||||||
func serviceWorker(static fs.FS) http.HandlerFunc {
|
func serviceWorker(static fs.FS) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
||||||
|
|
@ -44,3 +44,19 @@ func TestPWA(t *testing.T) {
|
||||||
}
|
}
|
||||||
get("/static/offline.html")
|
get("/static/offline.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAssetLinks(t *testing.T) {
|
||||||
|
serve := func(cfg Config) *httptest.ResponseRecorder {
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
(&Server{cfg: cfg, site: defaultSite()}).Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/.well-known/assetlinks.json", nil))
|
||||||
|
return rec
|
||||||
|
}
|
||||||
|
if rec := serve(Config{AndroidCerts: []string{""}}); rec.Code != http.StatusNotFound {
|
||||||
|
t.Errorf("unset: status %d", rec.Code)
|
||||||
|
}
|
||||||
|
rec := serve(Config{AndroidPackage: "sh.zio.cs2", AndroidCerts: []string{" aa:bb ", "", "CC:DD"}})
|
||||||
|
want := `[{"relation":["delegate_permission/common.handle_all_urls"],"target":{"namespace":"android_app","package_name":"sh.zio.cs2","sha256_cert_fingerprints":["AA:BB","CC:DD"]}}]`
|
||||||
|
if got := strings.TrimSpace(rec.Body.String()); rec.Code != http.StatusOK || got != want {
|
||||||
|
t.Errorf("status %d, body %s", rec.Code, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,10 @@ type Config struct {
|
||||||
// Limits used when SimpleAdmin's config file isn't available.
|
// Limits used when SimpleAdmin's config file isn't available.
|
||||||
MaxBanDuration int
|
MaxBanDuration int
|
||||||
MaxMuteDuration int
|
MaxMuteDuration int
|
||||||
|
// The Android app (android/ in this repo), for /.well-known/assetlinks.json: its package and
|
||||||
|
// the SHA-256 fingerprints of the keys it's signed with. Empty serves no assetlinks.json.
|
||||||
|
AndroidPackage string
|
||||||
|
AndroidCerts []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
|
@ -84,6 +88,7 @@ func (s *Server) Handler() http.Handler {
|
||||||
mux.HandleFunc("GET /manifest.webmanifest", s.manifest(false))
|
mux.HandleFunc("GET /manifest.webmanifest", s.manifest(false))
|
||||||
mux.HandleFunc("GET /admin.webmanifest", s.manifest(true))
|
mux.HandleFunc("GET /admin.webmanifest", s.manifest(true))
|
||||||
mux.HandleFunc("GET /sw.js", serviceWorker(static))
|
mux.HandleFunc("GET /sw.js", serviceWorker(static))
|
||||||
|
mux.HandleFunc("GET /.well-known/assetlinks.json", s.assetLinks)
|
||||||
|
|
||||||
mux.HandleFunc("GET /auth/login", s.login)
|
mux.HandleFunc("GET /auth/login", s.login)
|
||||||
mux.HandleFunc("GET /auth/callback", s.callback)
|
mux.HandleFunc("GET /auth/callback", s.callback)
|
||||||
|
|
|
||||||
2
main.go
2
main.go
|
|
@ -160,6 +160,8 @@ func run() error {
|
||||||
ServerAddress: joinAddr,
|
ServerAddress: joinAddr,
|
||||||
MaxBanDuration: maxBan,
|
MaxBanDuration: maxBan,
|
||||||
MaxMuteDuration: maxMute,
|
MaxMuteDuration: maxMute,
|
||||||
|
AndroidPackage: env("SAW_ANDROID_PACKAGE", ""),
|
||||||
|
AndroidCerts: strings.Split(env("SAW_ANDROID_CERT_SHA256", ""), ","),
|
||||||
}, st, rc, poller, sacfg)
|
}, st, rc, poller, sacfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue