Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32659ff0b9 | ||
|
|
3f37e6f047 |
@@ -0,0 +1,94 @@
|
|||||||
|
# Baut die Android-APK und hängt sie an ein Gitea-Release.
|
||||||
|
#
|
||||||
|
# Ausgelöst durch ein Etikett der Form v1.2.3. Voraussetzungen und die
|
||||||
|
# Einrichtung der Geheimnisse stehen in Android/RELEASE.md.
|
||||||
|
name: APK veröffentlichen
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
apk:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Quelltext holen
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# Der Versionszähler kommt aus der Anzahl der Commits; dafür wird
|
||||||
|
# der ganze Verlauf gebraucht, nicht nur der letzte Stand.
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Java einrichten
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: "17"
|
||||||
|
|
||||||
|
- name: Android SDK einrichten
|
||||||
|
uses: android-actions/setup-android@v3
|
||||||
|
|
||||||
|
- name: Signaturschlüssel bereitlegen
|
||||||
|
env:
|
||||||
|
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
|
||||||
|
run: |
|
||||||
|
# Ohne hinterlegten Schlüssel wird mit dem Debug-Schlüssel signiert.
|
||||||
|
# Der Build sagt das selbst deutlich; hier nur der Hinweis im Protokoll.
|
||||||
|
if [ -z "$KEYSTORE_BASE64" ]; then
|
||||||
|
echo "Kein Schlüssel hinterlegt – es wird mit dem Debug-Schlüssel signiert."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "$KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/release.jks"
|
||||||
|
echo "KEYSTORE_FILE=$RUNNER_TEMP/release.jks" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: APK bauen
|
||||||
|
env:
|
||||||
|
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||||
|
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||||
|
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
VERSION="${GITHUB_REF_NAME#v}"
|
||||||
|
# Die Anzahl der Commits steigt verlässlich und erfüllt damit, was
|
||||||
|
# Android vom Versionszähler verlangt: er darf nie kleiner werden.
|
||||||
|
CODE="$(git rev-list --count HEAD)"
|
||||||
|
echo "Version $VERSION (Zähler $CODE)"
|
||||||
|
cd Android
|
||||||
|
chmod +x gradlew
|
||||||
|
./gradlew :app:assembleRelease \
|
||||||
|
-PversionName="$VERSION" \
|
||||||
|
-PversionCode="$CODE" --no-daemon
|
||||||
|
mv app/build/outputs/apk/release/app-release.apk \
|
||||||
|
"../CamperMonitor-$VERSION.apk"
|
||||||
|
|
||||||
|
- name: An das Release hängen
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
VERSION="${GITHUB_REF_NAME#v}"
|
||||||
|
API="$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/releases"
|
||||||
|
AUTH="Authorization: token $TOKEN"
|
||||||
|
|
||||||
|
# Wurde das Release schon von Hand angelegt, wird es genommen;
|
||||||
|
# sonst hier erzeugt. Beides muss gehen, sonst scheitert der Ablauf
|
||||||
|
# je nachdem, in welcher Reihenfolge man vorgeht.
|
||||||
|
ID="$(curl -sf -H "$AUTH" "$API/tags/$GITHUB_REF_NAME" \
|
||||||
|
| sed -n 's/.*"id":\([0-9]*\).*/\1/p' | head -1 || true)"
|
||||||
|
|
||||||
|
if [ -z "$ID" ]; then
|
||||||
|
ID="$(curl -sf -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"tag_name\":\"$GITHUB_REF_NAME\",\"name\":\"$GITHUB_REF_NAME\"}" \
|
||||||
|
"$API" | sed -n 's/.*"id":\([0-9]*\).*/\1/p' | head -1)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$ID" ]; then
|
||||||
|
echo "Release konnte weder gefunden noch angelegt werden." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl -sf -X POST -H "$AUTH" \
|
||||||
|
-F "attachment=@CamperMonitor-$VERSION.apk" \
|
||||||
|
"$API/$ID/assets?name=CamperMonitor-$VERSION.apk" > /dev/null
|
||||||
|
|
||||||
|
echo "CamperMonitor-$VERSION.apk hängt an Release $GITHUB_REF_NAME."
|
||||||
@@ -6,3 +6,8 @@ local.properties
|
|||||||
.idea/
|
.idea/
|
||||||
captures/
|
captures/
|
||||||
.cxx/
|
.cxx/
|
||||||
|
|
||||||
|
# Signaturschlüssel und Passwörter gehören nirgends ins Repository.
|
||||||
|
keystore.properties
|
||||||
|
*.jks
|
||||||
|
*.keystore
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
# Eine APK veröffentlichen
|
||||||
|
|
||||||
|
## Vorweg: die APK gehört nicht ins Repository
|
||||||
|
|
||||||
|
Ein Release-Anhang in Gitea liegt **neben** dem Repository, nicht darin. Gitea
|
||||||
|
legt ihn in seinem Anhang-Speicher ab; im Git-Verlauf taucht er nicht auf.
|
||||||
|
|
||||||
|
Das ist auch gut so. Eine APK ist eine 11-MB-Binärdatei, die sich bei jedem
|
||||||
|
Build komplett ändert. Läge sie im Repository, wüchse der Verlauf mit jeder
|
||||||
|
Version um 11 MB — und zwar **unwiderruflich**: Git vergisst nichts, ein
|
||||||
|
späteres Löschen bringt den Platz nicht zurück. Nach zehn Versionen wäre das
|
||||||
|
Auschecken des Projekts ein 110-MB-Download, obwohl der Quelltext selbst nur
|
||||||
|
wenige hundert Kilobyte hat.
|
||||||
|
|
||||||
|
Deshalb: bauen, an ein Release hängen, fertig. `.gitignore` sorgt dafür, dass
|
||||||
|
die gebauten Dateien gar nicht erst versehentlich hineinrutschen.
|
||||||
|
|
||||||
|
## Signieren
|
||||||
|
|
||||||
|
Android installiert **keine unsignierte APK** — auch nicht per Sideload. Das
|
||||||
|
hat nichts mit Googles Sperren zu tun, sondern damit, dass das System ohne
|
||||||
|
Signatur nicht feststellen kann, ob ein Update vom selben Absender stammt wie
|
||||||
|
die Erstinstallation.
|
||||||
|
|
||||||
|
Ohne eigenen Schlüssel signiert der Build mit dem Android-Debug-Schlüssel und
|
||||||
|
sagt das beim Bauen deutlich. Das funktioniert, hat aber einen Haken: Dieser
|
||||||
|
Schlüssel wird pro Rechner erzeugt. Baust du die nächste Version auf einem
|
||||||
|
anderen Rechner, hat sie eine andere Signatur, und Android verweigert das
|
||||||
|
Update — es bliebe nur Deinstallieren und neu einrichten.
|
||||||
|
|
||||||
|
### Einen eigenen Schlüssel anlegen
|
||||||
|
|
||||||
|
Einmalig. Das Passwort tippst du dabei selbst.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
keytool -genkeypair -v \
|
||||||
|
-keystore ~/camper-monitor.jks \
|
||||||
|
-alias camper -keyalg RSA -keysize 4096 -validity 10000
|
||||||
|
```
|
||||||
|
|
||||||
|
Danach `Android/keystore.properties` anlegen (die Datei ist in `.gitignore`,
|
||||||
|
sie landet nicht im Repository):
|
||||||
|
|
||||||
|
```properties
|
||||||
|
storeFile=/Users/DEINNAME/camper-monitor.jks
|
||||||
|
storePassword=DEIN_PASSWORT
|
||||||
|
keyAlias=camper
|
||||||
|
keyPassword=DEIN_PASSWORT
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Diesen Schlüssel aufbewahren.** Geht er verloren, lässt sich für alle, die
|
||||||
|
> die App installiert haben, nie wieder ein Update ausliefern. Eine Kopie an
|
||||||
|
> einen zweiten Ort, getrennt vom Rechner.
|
||||||
|
|
||||||
|
## Wenn Gitea die APK nicht annimmt
|
||||||
|
|
||||||
|
Zwei Dinge, die leicht zusammenfallen:
|
||||||
|
|
||||||
|
**Der Quelltext als `.zip` und `.tar.gz`** hängt an *jedem* Gitea-Release
|
||||||
|
automatisch dran. Gitea erzeugt beides aus dem Etikett; es ist nichts, was
|
||||||
|
versehentlich hochgeladen wurde, und es lässt sich auch nicht abschalten. Es
|
||||||
|
stört nur optisch und kostet keinen Speicher — die Archive werden erst beim
|
||||||
|
Anklicken erzeugt.
|
||||||
|
|
||||||
|
**Die APK abgelehnt** wird dagegen aus einem handfesten Grund: Gitea nimmt nur
|
||||||
|
Dateitypen an, die in seiner Liste stehen, und `.apk` steht dort nicht. Die
|
||||||
|
Liste dieser Instanz lässt sich abfragen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s https://git.home.fritob.de/api/v1/settings/attachment
|
||||||
|
```
|
||||||
|
|
||||||
|
Kommt dort eine `allowed_types`-Liste ohne `.apk` zurück, ist das die Ursache.
|
||||||
|
Die Grösse ist es nicht — die Grenze liegt bei 100 MiB, die APK bei 11.
|
||||||
|
|
||||||
|
### Der richtige Weg: `.apk` erlauben
|
||||||
|
|
||||||
|
In der `app.ini` des Servers (bei Docker meist
|
||||||
|
`/data/gitea/conf/app.ini`, sonst `/etc/gitea/app.ini`):
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[attachment]
|
||||||
|
ALLOWED_TYPES = .avif,.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip,.apk
|
||||||
|
|
||||||
|
[repository.release]
|
||||||
|
ALLOWED_TYPES = .apk,.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Beide Abschnitte, weil je nach Gitea-Fassung mal der eine, mal der andere für
|
||||||
|
Release-Anhänge zuständig ist. Danach Gitea neu starten.
|
||||||
|
|
||||||
|
Wer es sich einfach machen will, schreibt in beide Schlüssel `*/*` — dann ist
|
||||||
|
alles erlaubt. Auf einer Instanz im eigenen Netz ist das vertretbar; öffentlich
|
||||||
|
wäre es das nicht, weil dann auch beliebige ausführbare Dateien hochladbar sind.
|
||||||
|
|
||||||
|
### Ohne Serverzugriff: die Paket-Registry
|
||||||
|
|
||||||
|
Die nimmt beliebige Dateien an, ganz ohne Typenliste, und ist auf dieser
|
||||||
|
Instanz eingeschaltet. Der Preis: Die Datei landet unter *Packages* statt am
|
||||||
|
Release.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Android/upload-package.sh 1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Skript liest den Zugangsschlüssel aus `~/.gitea-token` und gibt ihn
|
||||||
|
nirgends aus. Ein Token mit dem Recht `write:package` genügt; anzulegen unter
|
||||||
|
*Einstellungen → Anwendungen → Zugriffstoken*.
|
||||||
|
|
||||||
|
## Von Hand veröffentlichen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source Android/env.sh
|
||||||
|
cd Android
|
||||||
|
./gradlew :app:assembleRelease -PversionName=1.0.0 -PversionCode=1
|
||||||
|
```
|
||||||
|
|
||||||
|
Die APK liegt dann unter
|
||||||
|
`Android/app/build/outputs/apk/release/app-release.apk`.
|
||||||
|
|
||||||
|
In Gitea: **Releases → New Release**, ein Etikett wie `v1.0.0` wählen, und die
|
||||||
|
APK unter *Attachments* hochladen. Fertig — genau das ist das „Wie sage ich
|
||||||
|
Gitea, dass die APK dort hinein soll".
|
||||||
|
|
||||||
|
## Von selbst veröffentlichen
|
||||||
|
|
||||||
|
`.gitea/workflows/release.yml` nimmt das ab: Ein Etikett hochschieben genügt.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag v1.0.0
|
||||||
|
git push origin v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Ablauf baut die APK, legt das Release an, falls es noch nicht besteht, und
|
||||||
|
hängt die Datei an. Die Versionsnummer kommt aus dem Etikett, der
|
||||||
|
Versionszähler aus der Anzahl der Commits — so steigt er verlässlich.
|
||||||
|
|
||||||
|
### Voraussetzungen
|
||||||
|
|
||||||
|
1. **Ein Runner.** Actions brauchen in Gitea einen registrierten Runner mit dem
|
||||||
|
Etikett `ubuntu-latest`. Unter *Site Administration → Actions → Runners*
|
||||||
|
nachsehen. Ist keiner da, bleibt der Weg von Hand — der tut es genauso.
|
||||||
|
2. **Actions eingeschaltet**, im Repository unter *Settings → Advanced*.
|
||||||
|
3. **Der Schlüssel als Geheimnis**, falls mit eigenem Schlüssel signiert werden
|
||||||
|
soll. Unter *Settings → Actions → Secrets* anlegen:
|
||||||
|
|
||||||
|
| Name | Inhalt |
|
||||||
|
| ------------------ | ------------------------------------------ |
|
||||||
|
| `KEYSTORE_BASE64` | `base64 -i ~/camper-monitor.jks \| pbcopy` |
|
||||||
|
| `KEYSTORE_PASSWORD`| das Passwort des Schlüsselspeichers |
|
||||||
|
| `KEY_ALIAS` | `camper` |
|
||||||
|
| `KEY_PASSWORD` | das Passwort des Schlüssels |
|
||||||
|
|
||||||
|
Fehlen sie, baut der Ablauf trotzdem — dann eben mit dem Debug-Schlüssel,
|
||||||
|
und die APK heisst entsprechend.
|
||||||
@@ -1,9 +1,32 @@
|
|||||||
|
import java.util.Properties
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
id("org.jetbrains.kotlin.plugin.compose")
|
id("org.jetbrains.kotlin.plugin.compose")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Angaben zum Signaturschlüssel.
|
||||||
|
*
|
||||||
|
* Sie stehen in `Android/keystore.properties` - einer Datei, die bewusst nicht
|
||||||
|
* im Repository liegt. Fehlt sie, wird stattdessen aus der Umgebung gelesen;
|
||||||
|
* so kann ein Bauknecht sie aus seinen Geheimnissen setzen, ohne dass ein
|
||||||
|
* Passwort je im Verlauf landet.
|
||||||
|
*/
|
||||||
|
val signing = Properties().apply {
|
||||||
|
val file = rootProject.file("keystore.properties")
|
||||||
|
if (file.exists()) {
|
||||||
|
file.inputStream().use { load(it) }
|
||||||
|
} else {
|
||||||
|
System.getenv("KEYSTORE_FILE")?.let { setProperty("storeFile", it) }
|
||||||
|
System.getenv("KEYSTORE_PASSWORD")?.let { setProperty("storePassword", it) }
|
||||||
|
System.getenv("KEY_ALIAS")?.let { setProperty("keyAlias", it) }
|
||||||
|
System.getenv("KEY_PASSWORD")?.let { setProperty("keyPassword", it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val hasSigningKey = signing.getProperty("storeFile") != null
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "de.fritob.campermonitor"
|
namespace = "de.fritob.campermonitor"
|
||||||
compileSdk = 35
|
compileSdk = 35
|
||||||
@@ -13,13 +36,35 @@ android {
|
|||||||
// Android 8: älter lohnt nicht, dort fehlt zu viel an Bluetooth LE.
|
// Android 8: älter lohnt nicht, dort fehlt zu viel an Bluetooth LE.
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 1
|
versionCode = (findProperty("versionCode") as String?)?.toInt() ?: 1
|
||||||
versionName = "1.0"
|
// Beim Bauen aus einem Etikett heraus überschreibbar:
|
||||||
|
// ./gradlew :app:assembleRelease -PversionName=1.2.0
|
||||||
|
versionName = (findProperty("versionName") as String?) ?: "1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
if (hasSigningKey) {
|
||||||
|
create("release") {
|
||||||
|
storeFile = file(signing.getProperty("storeFile"))
|
||||||
|
storePassword = signing.getProperty("storePassword")
|
||||||
|
keyAlias = signing.getProperty("keyAlias")
|
||||||
|
keyPassword = signing.getProperty("keyPassword")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
|
// Ohne eigenen Schlüssel wird mit dem Debug-Schlüssel signiert.
|
||||||
|
// Unsigniert wäre die APK schlicht nicht installierbar - Android
|
||||||
|
// weist sie ab, auch beim Sideloading. Der Hinweis unten sagt, was
|
||||||
|
// das für Folgen hat.
|
||||||
|
signingConfig = if (hasSigningKey) {
|
||||||
|
signingConfigs.getByName("release")
|
||||||
|
} else {
|
||||||
|
signingConfigs.getByName("debug")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,3 +104,20 @@ dependencies {
|
|||||||
// Die Ablage der Geräte und Profile nutzt org.json aus Android selbst -
|
// Die Ablage der Geräte und Profile nutzt org.json aus Android selbst -
|
||||||
// eine weitere Bibliothek lohnt für eine Handvoll Felder nicht.
|
// eine weitere Bibliothek lohnt für eine Handvoll Felder nicht.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sichtbar machen, womit signiert wurde - eine mit dem Debug-Schlüssel
|
||||||
|
// signierte Release-APK sieht sonst aus wie eine richtige.
|
||||||
|
tasks.matching { it.name == "assembleRelease" }.configureEach {
|
||||||
|
doLast {
|
||||||
|
if (!hasSigningKey) {
|
||||||
|
logger.lifecycle(
|
||||||
|
"\n ACHTUNG: mit dem Debug-Schlüssel signiert." +
|
||||||
|
"\n Installierbar, aber an diesen Rechner gebunden: auf einem anderen" +
|
||||||
|
"\n entsteht ein anderer Debug-Schlüssel, und ein Update liesse sich" +
|
||||||
|
"\n nicht mehr über die alte Installation legen." +
|
||||||
|
"\n Für einen eigenen Schlüssel: Android/keystore.properties anlegen." +
|
||||||
|
"\n Anleitung in Android/RELEASE.md.\n"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Executable
+41
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Lädt die gebaute APK in Gitas Paket-Registry.
|
||||||
|
#
|
||||||
|
# Der Weg über die Registry umgeht die Typenliste für Release-Anhänge, die
|
||||||
|
# `.apk` nicht enthält. Siehe RELEASE.md.
|
||||||
|
#
|
||||||
|
# Android/upload-package.sh 1.0.0
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
VERSION="${1:?Aufruf: upload-package.sh <version>, z. B. 1.0.0}"
|
||||||
|
SERVER="${GITEA_SERVER:-https://git.home.fritob.de}"
|
||||||
|
OWNER="${GITEA_OWNER:-fritob}"
|
||||||
|
TOKEN_FILE="${GITEA_TOKEN_FILE:-$HOME/.gitea-token}"
|
||||||
|
|
||||||
|
APK="$(dirname "$0")/app/build/outputs/apk/release/app-release.apk"
|
||||||
|
[ -f "$APK" ] || {
|
||||||
|
echo "Keine APK gefunden. Erst bauen:" >&2
|
||||||
|
echo " source Android/env.sh && cd Android && ./gradlew :app:assembleRelease -PversionName=$VERSION" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ -f "$TOKEN_FILE" ] || {
|
||||||
|
echo "Kein Zugangsschlüssel in $TOKEN_FILE." >&2
|
||||||
|
echo "In Gitea unter Einstellungen -> Anwendungen einen mit 'write:package' anlegen," >&2
|
||||||
|
echo "dann: printf %s 'DEIN_TOKEN' > $TOKEN_FILE && chmod 600 $TOKEN_FILE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
NAME="CamperMonitor-$VERSION.apk"
|
||||||
|
URL="$SERVER/api/packages/$OWNER/generic/camper-monitor/$VERSION/$NAME"
|
||||||
|
|
||||||
|
# Der Schlüssel geht nur an curl und steht nirgends in der Ausgabe.
|
||||||
|
CODE="$(curl -s -o /dev/null -w '%{http_code}' \
|
||||||
|
-H "Authorization: token $(cat "$TOKEN_FILE")" \
|
||||||
|
--upload-file "$APK" "$URL")"
|
||||||
|
|
||||||
|
case "$CODE" in
|
||||||
|
201) echo "Hochgeladen: $URL" ;;
|
||||||
|
409) echo "Version $VERSION liegt dort schon. Erst löschen oder Version erhöhen." >&2; exit 1 ;;
|
||||||
|
401|403) echo "Abgelehnt (HTTP $CODE) - fehlt dem Token das Recht 'write:package'?" >&2; exit 1 ;;
|
||||||
|
*) echo "Fehlgeschlagen, HTTP $CODE" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user