Compare commits
No commits in common. "ad197ecf905328329ca924629ea4a9de1f318021" and "b7f13d8fac9de7d592bf8fe9f566cce2d3f3ef20" have entirely different histories.
ad197ecf90
...
b7f13d8fac
15
.gitattributes
vendored
|
|
@ -1,15 +0,0 @@
|
||||||
# Zeilenenden nicht anfassen – einige Dateien liegen bewusst mit CRLF vor.
|
|
||||||
* -text
|
|
||||||
|
|
||||||
# Dateien, die nicht ins Release-ZIP gehoeren.
|
|
||||||
# `git archive` (siehe .github/workflows/release.yml) wertet export-ignore aus.
|
|
||||||
/.gitattributes export-ignore
|
|
||||||
/.gitignore export-ignore
|
|
||||||
/.dockerignore export-ignore
|
|
||||||
/.github export-ignore
|
|
||||||
/docs export-ignore
|
|
||||||
/tests export-ignore
|
|
||||||
/tools export-ignore
|
|
||||||
/phpunit.xml.dist export-ignore
|
|
||||||
/phpstan.neon export-ignore
|
|
||||||
/composer.lock export-ignore
|
|
||||||
152
.github/workflows/release.yml
vendored
|
|
@ -1,152 +0,0 @@
|
||||||
name: Release-Paket
|
|
||||||
|
|
||||||
# Bei jedem Merge nach main entsteht ein installierbares ZIP und landet als
|
|
||||||
# Vorab-Release "latest-main" im Repository. Wird ein Tag v* gepusht, wird
|
|
||||||
# daraus ein regulaeres Release mit derselben Mechanik.
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
tags: [ 'v*' ]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
package:
|
|
||||||
name: ZIP bauen und veröffentlichen
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Version und Dateinamen bestimmen
|
|
||||||
id: meta
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
VERSION="$(tr -d ' \r\n' < VERSION)"
|
|
||||||
SHORT_SHA="$(git rev-parse --short HEAD)"
|
|
||||||
BUILD_DATE="$(date -u +%Y-%m-%d)"
|
|
||||||
|
|
||||||
if [ "${GITHUB_REF_TYPE:-branch}" = "tag" ]; then
|
|
||||||
TAG="${GITHUB_REF_NAME}"
|
|
||||||
NAME="unifi-voucher-tool-${TAG}"
|
|
||||||
TITLE="Version ${TAG}"
|
|
||||||
PRERELEASE="false"
|
|
||||||
else
|
|
||||||
TAG="latest-main"
|
|
||||||
NAME="unifi-voucher-tool-${VERSION}+${BUILD_DATE}.${SHORT_SHA}"
|
|
||||||
TITLE="Aktueller Stand von main – ${VERSION} (${SHORT_SHA})"
|
|
||||||
PRERELEASE="true"
|
|
||||||
fi
|
|
||||||
|
|
||||||
{
|
|
||||||
echo "version=${VERSION}"
|
|
||||||
echo "short_sha=${SHORT_SHA}"
|
|
||||||
echo "build_date=${BUILD_DATE}"
|
|
||||||
echo "tag=${TAG}"
|
|
||||||
echo "name=${NAME}"
|
|
||||||
echo "title=${TITLE}"
|
|
||||||
echo "prerelease=${PRERELEASE}"
|
|
||||||
} >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: ZIP erzeugen
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
mkdir -p dist
|
|
||||||
# git archive wertet die export-ignore-Regeln aus .gitattributes aus,
|
|
||||||
# docs/, tests/, tools/ und CI-Dateien bleiben also draußen.
|
|
||||||
git archive --format=zip -9 \
|
|
||||||
--prefix="unifi-voucher-tool/" \
|
|
||||||
-o "dist/${{ steps.meta.outputs.name }}.zip" HEAD
|
|
||||||
cd dist
|
|
||||||
sha256sum "${{ steps.meta.outputs.name }}.zip" > "${{ steps.meta.outputs.name }}.zip.sha256"
|
|
||||||
ls -lh
|
|
||||||
|
|
||||||
- name: Inhalt kurz prüfen
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
# Ein paar Dateien muessen enthalten sein, sonst ist das Paket kaputt.
|
|
||||||
for required in \
|
|
||||||
unifi-voucher-tool/index.php \
|
|
||||||
unifi-voucher-tool/install.php \
|
|
||||||
unifi-voucher-tool/database.sql \
|
|
||||||
unifi-voucher-tool/assets/global.css \
|
|
||||||
unifi-voucher-tool/assets/vendor/inter/inter.css \
|
|
||||||
unifi-voucher-tool/includes/Ui.php
|
|
||||||
do
|
|
||||||
if ! unzip -l "dist/${{ steps.meta.outputs.name }}.zip" | grep -q "$required"; then
|
|
||||||
echo "Fehlt im Paket: $required" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "Paket vollständig."
|
|
||||||
|
|
||||||
- name: Als Build-Artefakt sichern
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
continue-on-error: true # Artefakt-Speicher ist optional
|
|
||||||
with:
|
|
||||||
name: ${{ steps.meta.outputs.name }}
|
|
||||||
path: dist/*
|
|
||||||
retention-days: 30
|
|
||||||
|
|
||||||
- name: Release anlegen bzw. auffrischen
|
|
||||||
env:
|
|
||||||
# Forgejo stellt den Token automatisch bereit; FORGEJO_TOKEN
|
|
||||||
# (persönlicher Token) dient als Ausweichweg.
|
|
||||||
TOKEN: ${{ secrets.GITHUB_TOKEN || secrets.FORGEJO_TOKEN }}
|
|
||||||
API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
|
||||||
TAG: ${{ steps.meta.outputs.tag }}
|
|
||||||
NAME: ${{ steps.meta.outputs.name }}
|
|
||||||
TITLE: ${{ steps.meta.outputs.title }}
|
|
||||||
PRERELEASE: ${{ steps.meta.outputs.prerelease }}
|
|
||||||
VERSION: ${{ steps.meta.outputs.version }}
|
|
||||||
SHORT_SHA: ${{ steps.meta.outputs.short_sha }}
|
|
||||||
BUILD_DATE: ${{ steps.meta.outputs.build_date }}
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
if [ -z "${TOKEN:-}" ]; then
|
|
||||||
echo "Kein Token vorhanden – Release wird übersprungen." >&2
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
AUTH="Authorization: token ${TOKEN}"
|
|
||||||
|
|
||||||
# Rollendes Vorab-Release durch ein frisches ersetzen, damit der
|
|
||||||
# Download-Link stabil bleibt und auf den aktuellen Stand zeigt.
|
|
||||||
if [ "$TAG" = "latest-main" ]; then
|
|
||||||
OLD_ID="$(curl -sf -H "$AUTH" "${API}/releases/tags/${TAG}" \
|
|
||||||
| grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 || true)"
|
|
||||||
if [ -n "${OLD_ID:-}" ]; then
|
|
||||||
curl -sf -X DELETE -H "$AUTH" "${API}/releases/${OLD_ID}" || true
|
|
||||||
curl -sf -X DELETE -H "$AUTH" "${API}/tags/${TAG}" || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Release-Text bewusst ohne Anfuehrungszeichen und Backslashes,
|
|
||||||
# damit er ohne jq direkt in den JSON-Body passt (\n bleibt literal).
|
|
||||||
BODY="Automatisch gebaut aus Commit ${SHORT_SHA}.\n\n"
|
|
||||||
BODY="${BODY}| | |\n|---|---|\n"
|
|
||||||
BODY="${BODY}| Version | ${VERSION} |\n"
|
|
||||||
BODY="${BODY}| Commit | ${SHORT_SHA} |\n"
|
|
||||||
BODY="${BODY}| Gebaut am | ${BUILD_DATE} |\n\n"
|
|
||||||
BODY="${BODY}**Neuinstallation:** ZIP entpacken, Dateien auf den Webserver legen, install.php aufrufen.\n\n"
|
|
||||||
BODY="${BODY}**Update einer bestehenden Installation:** config.php, uploads/ und updater/storage/ nicht ueberschreiben "
|
|
||||||
BODY="${BODY}- oder gleich den eingebauten Updater unter Administration, System-Update verwenden.\n\n"
|
|
||||||
BODY="${BODY}Pruefsumme: siehe beigelegte .sha256-Datei."
|
|
||||||
|
|
||||||
RELEASE_ID="$(curl -sf -X POST -H "$AUTH" -H 'Content-Type: application/json' \
|
|
||||||
-d "{\"tag_name\":\"${TAG}\",\"target_commitish\":\"${GITHUB_SHA}\",\"name\":\"${TITLE}\",\"body\":\"${BODY}\",\"draft\":false,\"prerelease\":${PRERELEASE}}" \
|
|
||||||
"${API}/releases" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)"
|
|
||||||
|
|
||||||
if [ -z "${RELEASE_ID:-}" ]; then
|
|
||||||
echo "Release konnte nicht angelegt werden." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
for file in "dist/${NAME}.zip" "dist/${NAME}.zip.sha256"; do
|
|
||||||
curl -sf -X POST -H "$AUTH" \
|
|
||||||
-F "attachment=@${file}" \
|
|
||||||
"${API}/releases/${RELEASE_ID}/assets?name=$(basename "$file")" > /dev/null
|
|
||||||
echo "Angehängt: $(basename "$file")"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Release ${TITLE} steht bereit: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG}"
|
|
||||||
25
Readme.md
|
|
@ -519,10 +519,6 @@ vendor/bin/phpunit # 29 Tests (Crypto, TOTP, API-Keys, Upload, Ui)
|
||||||
vendor/bin/phpstan analyse # Level 5
|
vendor/bin/phpstan analyse # Level 5
|
||||||
```
|
```
|
||||||
|
|
||||||
Die Versionsnummer steht in der Datei **`VERSION`** im Projektstamm. Sie wird
|
|
||||||
im Admin-Bereich unten in der Seitenleiste angezeigt und benennt das
|
|
||||||
Release-Paket – für eine neue Version also dort (und im Badge oben) anheben.
|
|
||||||
|
|
||||||
Die Pipeline (`.github/workflows/ci.yml`) führt zusätzlich einen
|
Die Pipeline (`.github/workflows/ci.yml`) führt zusätzlich einen
|
||||||
Syntax-Check über alle PHP-Dateien aus und prüft, ob `lang/de.php` und
|
Syntax-Check über alle PHP-Dateien aus und prüft, ob `lang/de.php` und
|
||||||
`lang/en.php` dieselben Schlüssel enthalten und jeder im Code verwendete
|
`lang/en.php` dieselben Schlüssel enthalten und jeder im Code verwendete
|
||||||
|
|
@ -544,27 +540,6 @@ git clone https://git.loheide.cloud/friloo/Unifi-Voucher-Tool.git
|
||||||
git clone ssh://git@git.loheide.cloud:2222/friloo/Unifi-Voucher-Tool.git
|
git clone ssh://git@git.loheide.cloud:2222/friloo/Unifi-Voucher-Tool.git
|
||||||
```
|
```
|
||||||
|
|
||||||
### Fertige Pakete
|
|
||||||
|
|
||||||
Jeder Merge nach `main` erzeugt automatisch ein installierbares ZIP
|
|
||||||
(`.github/workflows/release.yml`) und hängt es an das rollende Vorab-Release
|
|
||||||
**`latest-main`**:
|
|
||||||
|
|
||||||
**<https://git.loheide.cloud/friloo/Unifi-Voucher-Tool/releases>**
|
|
||||||
|
|
||||||
Das Paket enthält nur die Laufzeit-Dateien – `docs/`, `tests/`, `tools/` und die
|
|
||||||
CI-Konfiguration bleiben draußen (rund 1,4 MB). Wird ein Tag `v*` gepusht,
|
|
||||||
entsteht daraus ein reguläres Release mit derselben Mechanik.
|
|
||||||
|
|
||||||
> Beim **Update einer bestehenden Installation** `config.php`, `uploads/` und
|
|
||||||
> `updater/storage/` nicht überschreiben – oder gleich den eingebauten Updater
|
|
||||||
> verwenden, der genau diese Pfade schützt.
|
|
||||||
|
|
||||||
Voraussetzung ist ein registrierter **Forgejo-Actions-Runner**; ohne Runner
|
|
||||||
bleiben die Workflows in der Warteschlange stehen.
|
|
||||||
|
|
||||||
### Mitwirken
|
|
||||||
|
|
||||||
Fehlerberichte und Änderungsvorschläge laufen über die **Issues** und **Pull
|
Fehlerberichte und Änderungsvorschläge laufen über die **Issues** und **Pull
|
||||||
Requests** dort. Für die Kommandozeile eignet sich [`tea`](https://gitea.com/gitea/tea),
|
Requests** dort. Für die Kommandozeile eignet sich [`tea`](https://gitea.com/gitea/tea),
|
||||||
die Gitea-/Forgejo-CLI:
|
die Gitea-/Forgejo-CLI:
|
||||||
|
|
|
||||||
1
VERSION
|
|
@ -1 +0,0 @@
|
||||||
2.6.0
|
|
||||||
|
Before Width: | Height: | Size: 392 KiB After Width: | Height: | Size: 390 KiB |
|
Before Width: | Height: | Size: 385 KiB After Width: | Height: | Size: 384 KiB |
|
Before Width: | Height: | Size: 321 KiB After Width: | Height: | Size: 320 KiB |
|
Before Width: | Height: | Size: 315 KiB After Width: | Height: | Size: 313 KiB |
|
Before Width: | Height: | Size: 311 KiB After Width: | Height: | Size: 309 KiB |
|
Before Width: | Height: | Size: 331 KiB After Width: | Height: | Size: 330 KiB |
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 278 KiB |
|
Before Width: | Height: | Size: 285 KiB After Width: | Height: | Size: 285 KiB |
|
Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 416 KiB |
|
|
@ -116,21 +116,6 @@ class Ui
|
||||||
. '</style>';
|
. '</style>';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Version aus der Datei VERSION im Projektstamm.
|
|
||||||
* Damit tragen Oberfläche und Release-Paket dieselbe Nummer.
|
|
||||||
*/
|
|
||||||
public static function version(): string
|
|
||||||
{
|
|
||||||
static $version = null;
|
|
||||||
if ($version === null) {
|
|
||||||
$file = self::root() . '/VERSION';
|
|
||||||
$version = is_file($file) ? trim((string)file_get_contents($file)) : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $version;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Entwicklerhinweis – bewusst an einer Stelle gepflegt. */
|
/** Entwicklerhinweis – bewusst an einer Stelle gepflegt. */
|
||||||
public const CREDIT_NAME = 'Loheide.eu';
|
public const CREDIT_NAME = 'Loheide.eu';
|
||||||
public const CREDIT_URL = 'https://loheide.eu';
|
public const CREDIT_URL = 'https://loheide.eu';
|
||||||
|
|
@ -138,16 +123,11 @@ class Ui
|
||||||
/**
|
/**
|
||||||
* Dezenter Hinweis auf den Entwickler, wie er im Seitenfuß erscheint.
|
* Dezenter Hinweis auf den Entwickler, wie er im Seitenfuß erscheint.
|
||||||
*/
|
*/
|
||||||
public static function credit(bool $withVersion = false): string
|
public static function credit(): string
|
||||||
{
|
{
|
||||||
$label = function_exists('__') ? __('credit_by') : 'Entwickelt von';
|
$label = function_exists('__') ? __('credit_by') : 'Entwickelt von';
|
||||||
|
|
||||||
$prefix = '';
|
return '<p class="app-credit">' . htmlspecialchars($label) . ' '
|
||||||
if ($withVersion && self::version() !== '') {
|
|
||||||
$prefix = 'v' . htmlspecialchars(self::version()) . ' · ';
|
|
||||||
}
|
|
||||||
|
|
||||||
return '<p class="app-credit">' . $prefix . htmlspecialchars($label) . ' '
|
|
||||||
. '<a href="' . self::CREDIT_URL . '" target="_blank" rel="noopener">'
|
. '<a href="' . self::CREDIT_URL . '" target="_blank" rel="noopener">'
|
||||||
. self::CREDIT_NAME . '</a></p>';
|
. self::CREDIT_NAME . '</a></p>';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ foreach ($navGroups as $items) {
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?= Ui::credit(true) ?>
|
<?= Ui::credit() ?>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
|
|
||||||