Compare commits
37
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f890c89f1c
|
||
|
|
0dbe24e7d9
|
||
|
|
51a800802d
|
||
|
|
9819dfce5b
|
||
|
|
d974f1b7ac
|
||
|
|
9858ee959d
|
||
|
|
d726bab972
|
||
|
|
851cfaeda8
|
||
|
|
9c4a1e1c95
|
||
|
|
fc798b39a3
|
||
|
|
2bde2baf66
|
||
|
|
24f127c629
|
||
|
|
b4bf33ebd9
|
||
|
|
83ae3ad3cc
|
||
|
|
b525a6c954
|
||
|
|
18854200c0
|
||
|
|
7e644e52c6
|
||
|
|
fd0ddf6aeb
|
||
|
|
c0ad5987df
|
||
|
|
52d9bb6880
|
||
|
|
d8ce9849b8
|
||
|
|
f04663803c
|
||
|
|
a1fac95722
|
||
|
|
020e2f3b1a
|
||
|
|
aa9e1da74f
|
||
|
|
cc1643cb8b
|
||
|
|
c2d21e93c8
|
||
|
|
c02644b48d
|
||
|
|
a1d22ce71f
|
||
|
|
10dc412566
|
||
|
|
68c1b07b23
|
||
|
|
25e076ba18
|
||
|
|
a0ffe731bf
|
||
|
|
9a2a1730dd
|
||
|
|
851c863b24
|
||
|
|
0d811cfb75
|
||
|
|
bd374b95fc
|
@@ -0,0 +1,15 @@
|
||||
[registry]
|
||||
default = "gitea"
|
||||
|
||||
[registries.gitea]
|
||||
index = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/" # Sparse index
|
||||
# index = "https://gitea.creative-dragonslayer.de/Rust-Crates/_cargo-index.git" # Git
|
||||
|
||||
[net]
|
||||
git-fetch-with-cli = true
|
||||
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
linker = "aarch64-linux-gnu-gcc"
|
||||
|
||||
[target.i686-unknown-linux-gnu]
|
||||
linker = "i686-linux-gnu-gcc"
|
||||
@@ -19,6 +19,12 @@ jobs:
|
||||
toolchain: stable
|
||||
cache: false
|
||||
|
||||
- name: Install Cross-Compilation Toolchains & Dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu gcc-i686-linux-gnu
|
||||
rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu i686-unknown-linux-gnu
|
||||
|
||||
- name: Install Packaging Tools (Prebuilt Binaries)
|
||||
run: |
|
||||
mkdir -p ~/.cargo/bin
|
||||
@@ -30,21 +36,43 @@ jobs:
|
||||
run: |
|
||||
cargo test
|
||||
|
||||
- name: Build Release Binary
|
||||
- name: Build Release Binaries
|
||||
run: |
|
||||
cargo build --release
|
||||
cargo build --release --target x86_64-unknown-linux-gnu
|
||||
cargo build --release --target aarch64-unknown-linux-gnu
|
||||
cargo build --release --target i686-unknown-linux-gnu
|
||||
|
||||
- name: Build Debian Package (.deb)
|
||||
- name: Determine Build Number
|
||||
id: build_num
|
||||
env:
|
||||
GITEA_URL: ${{ gitea.server_url || github.server_url }}
|
||||
REPO: ${{ gitea.repository || github.repository }}
|
||||
REPO_OWNER: ${{ gitea.repository_owner || github.repository_owner }}
|
||||
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
|
||||
run: |
|
||||
cargo deb
|
||||
BUILD_NUM=$(python3 scripts/get-build-number.py)
|
||||
echo "build_number=${BUILD_NUM}" >> $GITHUB_OUTPUT
|
||||
echo "BUILD_NUMBER=${BUILD_NUM}" >> $GITHUB_ENV
|
||||
echo "Ermittelte Build-Nummer: ${BUILD_NUM}"
|
||||
|
||||
- name: Build Fedora / RPM Package (.rpm)
|
||||
- name: Build Debian Packages (.deb)
|
||||
run: |
|
||||
cargo generate-rpm
|
||||
cargo deb --target x86_64-unknown-linux-gnu --deb-revision "${BUILD_NUMBER}" --no-build
|
||||
cargo deb --target aarch64-unknown-linux-gnu --deb-revision "${BUILD_NUMBER}" --no-build
|
||||
cargo deb --target i686-unknown-linux-gnu --deb-revision "${BUILD_NUMBER}" --no-build
|
||||
|
||||
- name: Build Arch Linux Package (.pkg.tar.zst)
|
||||
- name: Build Fedora / RPM Packages (.rpm)
|
||||
run: |
|
||||
python3 scripts/package-arch.py
|
||||
mkdir -p target/generate-rpm
|
||||
cargo generate-rpm --target x86_64-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
|
||||
cargo generate-rpm --target aarch64-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
|
||||
cargo generate-rpm --target i686-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
|
||||
|
||||
- name: Build Arch Linux Packages (.pkg.tar.zst)
|
||||
run: |
|
||||
python3 scripts/package-arch.py --target x86_64-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
|
||||
python3 scripts/package-arch.py --target aarch64-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
|
||||
python3 scripts/package-arch.py --target i686-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
|
||||
|
||||
- name: Publish Packages to Gitea Package Registry
|
||||
env:
|
||||
@@ -76,7 +104,7 @@ jobs:
|
||||
curl -f -s -S -X PUT \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
--upload-file "$pkg" \
|
||||
"${GITEA_URL}/api/packages/${REPO_OWNER}/arch/stable/upload"
|
||||
"${GITEA_URL}/api/packages/${REPO_OWNER}/arch/stable"
|
||||
done
|
||||
|
||||
- name: Create Gitea Release and Upload Assets
|
||||
|
||||
@@ -19,6 +19,12 @@ jobs:
|
||||
toolchain: stable
|
||||
cache: false
|
||||
|
||||
- name: Install Cross-Compilation Toolchains & Dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu gcc-i686-linux-gnu
|
||||
rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu i686-unknown-linux-gnu
|
||||
|
||||
- name: Install Packaging Tools (Prebuilt Binaries)
|
||||
run: |
|
||||
mkdir -p ~/.cargo/bin
|
||||
@@ -30,21 +36,43 @@ jobs:
|
||||
run: |
|
||||
cargo test
|
||||
|
||||
- name: Build Release Binary
|
||||
- name: Build Release Binaries
|
||||
run: |
|
||||
cargo build --release
|
||||
cargo build --release --target x86_64-unknown-linux-gnu
|
||||
cargo build --release --target aarch64-unknown-linux-gnu
|
||||
cargo build --release --target i686-unknown-linux-gnu
|
||||
|
||||
- name: Build Debian Package (.deb)
|
||||
- name: Determine Build Number
|
||||
id: build_num
|
||||
env:
|
||||
GITEA_URL: ${{ gitea.server_url || github.server_url }}
|
||||
REPO: ${{ gitea.repository || github.repository }}
|
||||
REPO_OWNER: ${{ gitea.repository_owner || github.repository_owner }}
|
||||
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
|
||||
run: |
|
||||
cargo deb
|
||||
BUILD_NUM=$(python3 scripts/get-build-number.py)
|
||||
echo "build_number=${BUILD_NUM}" >> $GITHUB_OUTPUT
|
||||
echo "BUILD_NUMBER=${BUILD_NUM}" >> $GITHUB_ENV
|
||||
echo "Ermittelte Build-Nummer: ${BUILD_NUM}"
|
||||
|
||||
- name: Build Fedora / RPM Package (.rpm)
|
||||
- name: Build Debian Packages (.deb)
|
||||
run: |
|
||||
cargo generate-rpm
|
||||
cargo deb --target x86_64-unknown-linux-gnu --deb-revision "${BUILD_NUMBER}" --no-build
|
||||
cargo deb --target aarch64-unknown-linux-gnu --deb-revision "${BUILD_NUMBER}" --no-build
|
||||
cargo deb --target i686-unknown-linux-gnu --deb-revision "${BUILD_NUMBER}" --no-build
|
||||
|
||||
- name: Build Arch Linux Package (.pkg.tar.zst)
|
||||
- name: Build Fedora / RPM Packages (.rpm)
|
||||
run: |
|
||||
python3 scripts/package-arch.py
|
||||
mkdir -p target/generate-rpm
|
||||
cargo generate-rpm --target x86_64-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
|
||||
cargo generate-rpm --target aarch64-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
|
||||
cargo generate-rpm --target i686-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
|
||||
|
||||
- name: Build Arch Linux Packages (.pkg.tar.zst)
|
||||
run: |
|
||||
python3 scripts/package-arch.py --target x86_64-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
|
||||
python3 scripts/package-arch.py --target aarch64-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
|
||||
python3 scripts/package-arch.py --target i686-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
|
||||
|
||||
- name: Publish Packages to Gitea Package Registry
|
||||
env:
|
||||
@@ -76,7 +104,7 @@ jobs:
|
||||
curl -f -s -S -X PUT \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
--upload-file "$pkg" \
|
||||
"${GITEA_URL}/api/packages/${REPO_OWNER}/arch/testing/upload"
|
||||
"${GITEA_URL}/api/packages/${REPO_OWNER}/arch/testing"
|
||||
done
|
||||
|
||||
- name: Create Gitea Pre-Release and Upload Assets
|
||||
|
||||
Generated
+1
@@ -3,6 +3,7 @@
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
||||
@@ -17,7 +17,7 @@ lädt diese herunter, startet betroffene Services neu und bereinigt ungenutzte Z
|
||||
Konstrukte oder bedingten Nicht-Linux-Kompilierungen (`#[cfg(target_os = ...)]`) hinzugefügt werden.
|
||||
- **Root-Rechte**: Die Anwendung erfordert Root-Rechte zur Verwaltung von Docker und Compose-Services. Fehlen diese,
|
||||
eskaliert sie via `sudo`.
|
||||
- **Keine Unicode-Emojis im Logging**: Das Logging-Modul (`src/log.rs`) verwendet einheitliche ASCII-Präfixe (`[!]`
|
||||
- **Keine Unicode-Emojis im Logging**: Das Logging-Modul (`logger-ctdra`) verwendet einheitliche ASCII-Präfixe (`[!]`
|
||||
Error, `[?]` Warn, `[i]` Info, `[d]` Debug).
|
||||
- **Lizenz**: GNU General Public License v3.0 or later (`GPL-3.0-or-later`).
|
||||
|
||||
@@ -32,45 +32,42 @@ DockerUpdate/
|
||||
│ ├── testing.yaml # CI/CD für testing-Branch (Tests, Testing-Packages & Preview-Release)
|
||||
│ └── main.yaml # CI/CD für main-Branch (Tests, Stable-Packages & Release)
|
||||
├── scripts/
|
||||
│ └── package-arch.py # Hilfsskript zur Arch-Linux-Paketerstellung (.pkg.tar.zst)
|
||||
│ ├── get-build-number.py # Ermittelt dynamisch die nächste Build-Nummer für Pakete
|
||||
│ └── package-arch.py # Hilfsskript zur Arch-Linux-Paketerstellung (.pkg.tar.zst)
|
||||
├── Cargo.toml # Rust-Manifest, Abhängigkeiten und Metadaten für deb / generate-rpm / arch
|
||||
├── Cargo.lock
|
||||
├── LICENSE # GPL-3.0 Lizenztext
|
||||
├── README.md # Projektdokumentation für Endanwender und Administratoren
|
||||
├── AGENTS.md # Richtlinien und Dokumentation für Agenten
|
||||
├── tests/
|
||||
│ ├── config.rs # Tests für Konfigurationsdatenstrukturen
|
||||
│ └── updater.rs # Tests für Updater-Hilfsfunktionen und Compose-Parsing
|
||||
└── src/
|
||||
├── main.rs # Einstiegspunkt, Root-Prüfung, Ausführung
|
||||
├── program.rs # Hilfsfunktion zur Programmnamensermittlung
|
||||
├── sudo.rs # Root-Prüfung (geteuid == 0) und Re-Exec via sudo
|
||||
├── config.rs # Konfigurationsverwaltung (confy / TOML)
|
||||
├── log.rs # Threadsicheres Logging (Terminal + Datei im Temp-Verzeichnis)
|
||||
├── lib.rs # Bibliotheksexporte für Module und Tests
|
||||
├── config.rs # Konfigurationsdatenstrukturen (config-ctdra / TOML)
|
||||
└── updater.rs # Scan-, Pull-, Restart- und Bereinigungslogik für Compose-Apps
|
||||
```
|
||||
|
||||
### Modulverantwortlichkeiten
|
||||
|
||||
- **`src/main.rs`**:
|
||||
Initialer Startpunkt. Prüft mit `sudo::is_run_as_root()`, ob Root-Rechte vorliegen. Wenn nicht, wird mit
|
||||
`sudo::run_as_root()` neu gestartet. Ruft anschließend `updater::run_updates()` auf.
|
||||
Initialer Startpunkt. Prüft mit `sudo_ctdra::is_run_as_root()`, ob Root-Rechte vorliegen. Wenn nicht, wird mit
|
||||
`sudo_ctdra::run_as_root()` neu gestartet. Initialisiert das Logging über `logger-ctdra` anhand der Konfiguration und
|
||||
ruft anschließend `updater::run_updates()` auf.
|
||||
- **`src/updater.rs`**:
|
||||
- Sucht in Unterverzeichnissen von `apps_dir` nach Compose-Dateien (`docker-compose.yaml`, `docker-compose.yml`,
|
||||
`compose.yaml`, `compose.yml`).
|
||||
- Parst Services via `serde_yaml` und filtert mit `is_latest_image()` nach Services mit `:latest`-Images oder ohne
|
||||
- Parst Services via `serde-saphyr` und filtert mit `is_latest_image()` nach Services mit `:latest`-Images oder ohne
|
||||
expliziten Tag.
|
||||
- Führt `docker pull` für jedes gefundene Image aus und vergleicht Vorher/Nachher-Image-IDs via
|
||||
`docker image inspect`.
|
||||
- Startet bei Änderungen Services neu (`docker compose up -d` mit Fallback auf `docker-compose up -d`).
|
||||
- Führt nach Updates `docker image prune -f` aus.
|
||||
- **`src/config.rs`**:
|
||||
- Lädt und speichert die Konfiguration (`AppConfig`, `General` mit `apps_dir` und `log_level`).
|
||||
- Standardpfade: `/etc/docker-update/config.toml` (Root) bzw. `~/.config/docker-update/config.toml` (Benutzer).
|
||||
- **`src/log.rs`**:
|
||||
- Formatiert Logmeldungen nach Schema `[Präfix][Zeitstempel][LEVEL][Tag]: Nachricht`.
|
||||
- Gibt auf Konsole aus und schreibt zusätzlich in tagesbasierte Logdateien im Temp-Verzeichnis
|
||||
(`/tmp/docker-update-.../log-YYYY-MM-DD.log`).
|
||||
- **`src/sudo.rs`**:
|
||||
- `is_run_as_root()`: Nutzt `libc::geteuid() == 0`.
|
||||
- `run_as_root()`: Führt `sudo <args>` per `exec` aus.
|
||||
- Definiert die Konfigurationsdatenstrukturen (`AppConfig`, `General` mit `apps_dir` und `log_level`).
|
||||
- Lädt und verwaltet die Konfiguration über `config-ctdra` (Standardpfade: `/etc/docker-update/config.toml` bzw.
|
||||
`~/.config/docker-update/config.toml`).
|
||||
- **`.gitea/workflows/`**:
|
||||
- `testing.yaml`: Führt bei Push auf `testing` Tests und Builds durch, veröffentlicht Pakete in der Gitea Package
|
||||
Registry
|
||||
|
||||
Generated
+153
-28
@@ -2,12 +2,59 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "annotate-snippets"
|
||||
version = "0.12.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"memchr",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "arraydeque"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "config-ctdra"
|
||||
version = "1.0.4"
|
||||
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
|
||||
checksum = "f7b3e25b95d4cdb6fc856f2af0106d4558b882c2f10798db46181e51827749a4"
|
||||
dependencies = [
|
||||
"confy",
|
||||
"program-ctdra",
|
||||
"serde",
|
||||
"sudo-ctdra",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "confy"
|
||||
version = "2.0.0"
|
||||
@@ -29,13 +76,31 @@ checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||
|
||||
[[package]]
|
||||
name = "docker-update"
|
||||
version = "0.1.2"
|
||||
version = "1.2.0"
|
||||
dependencies = [
|
||||
"confy",
|
||||
"libc 1.0.0-alpha.4",
|
||||
"config-ctdra",
|
||||
"logger-ctdra",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"time",
|
||||
"serde-saphyr",
|
||||
"sudo-ctdra",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs_io"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fba3fe847045ecff794b9c138293a80db914678c453ad63fbf0c6a9eb6e00b22"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -55,6 +120,16 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "granit-parser"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ccd1be9ebf2bd5520dbfcfb72b70ef492f654061299a097056f3e73410e38ec"
|
||||
dependencies = [
|
||||
"arraydeque",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
@@ -80,12 +155,6 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@@ -104,12 +173,43 @@ version = "1.0.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d0f24f33af482526a4e3f9b47f0abb2c6377a1713c8aa4a8106994689a4cfa5"
|
||||
|
||||
[[package]]
|
||||
name = "logger-ctdra"
|
||||
version = "1.0.3"
|
||||
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
|
||||
checksum = "af9ea239d80163c80b12cae5f74c0c2824199921dedafba74678e96a11a8b15e"
|
||||
dependencies = [
|
||||
"program-ctdra",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "nohash-hasher"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
@@ -134,6 +234,12 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "program-ctdra"
|
||||
version = "1.0.0"
|
||||
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
|
||||
checksum = "54f20af67f90bf6d10697dd77bb4beb41dafad7fe0fb46aacfc81ad45a8e48bd"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
@@ -143,12 +249,6 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
@@ -159,6 +259,23 @@ dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-saphyr"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1ec1f5cac0eb96063c64b28705255a7ed6e7d77f95c1d25e9f8b8c928006ce1"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
"base64",
|
||||
"encoding_rs_io",
|
||||
"granit-parser",
|
||||
"nohash-hasher",
|
||||
"num-traits",
|
||||
"serde_core",
|
||||
"smallvec",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
@@ -189,16 +306,18 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.34+deprecated"
|
||||
name = "smallvec"
|
||||
version = "1.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||
|
||||
[[package]]
|
||||
name = "sudo-ctdra"
|
||||
version = "1.0.0"
|
||||
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
|
||||
checksum = "2c2746122bbe36f821de70db3ca4ad24edfb77118fc6a6bc7693082b50d23e5f"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
"libc 1.0.0-alpha.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -310,10 +429,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.11"
|
||||
name = "unicode-width"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
@@ -414,3 +533,9 @@ name = "winnow"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "docker-update"
|
||||
version = "0.1.2"
|
||||
version = "1.2.0"
|
||||
edition = "2024"
|
||||
authors = ['DragonSlayer_14']
|
||||
readme = "README.md"
|
||||
@@ -9,11 +9,11 @@ repository = "https://gitea.creative-dragonslayer.de/Linuxapps/DockerUpdate"
|
||||
description = "Automatisches Aktualisieren von Docker-Compose-Anwendungen in /var/apps mit :latest-Images"
|
||||
|
||||
[dependencies]
|
||||
time = { version="0.3.41", features = ["formatting", "macros", "local-offset"] }
|
||||
serde = { version="1.0.219", features = ["derive"] }
|
||||
serde_yaml = "0.9"
|
||||
confy = "2.0.0"
|
||||
libc = "1.0.0-alpha.1"
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
serde-saphyr = "1.1.0"
|
||||
logger-ctdra = { version = "1.0.3", registry = "gitea" }
|
||||
sudo-ctdra = { version = "1.0.0", registry = "gitea" }
|
||||
config-ctdra = { version = "1.0.4", registry = "gitea" }
|
||||
|
||||
[profile.release]
|
||||
debug = "none"
|
||||
|
||||
@@ -29,7 +29,7 @@ auf neuere Versionen prüft, aktualisiert und neu startet.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- **Betriebssystem**: Linux (x86_64, aarch64)
|
||||
- **Betriebssystem**: Linux (amd64 / x86_64, arm64 / aarch64, i386 / i686)
|
||||
- **Laufzeit-Abhängigkeiten**:
|
||||
- `docker` (inkl. Docker Compose Plugin `docker compose` oder Standalone `docker-compose`)
|
||||
- `sudo`
|
||||
@@ -130,14 +130,36 @@ zypper install docker-update
|
||||
|
||||
### 3. Arch Linux (`pacman`)
|
||||
|
||||
### Installiere den GPG-Key für das Repository
|
||||
|
||||
Key herunterladen:
|
||||
|
||||
```bash
|
||||
wget https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/arch/repository.key
|
||||
```
|
||||
|
||||
ID des Keys anzeigen lassen:
|
||||
|
||||
```bash
|
||||
gpg --show-keys repository.key
|
||||
```
|
||||
|
||||
Key zu pacman hinzufügen und signieren:
|
||||
|
||||
```
|
||||
pacman-key --add repository.key
|
||||
pacman-key --lsign-key {key id}
|
||||
```
|
||||
|
||||
{key id} = ID aus dem vorherigen Schritt
|
||||
|
||||
#### Stable (Produktiv)
|
||||
|
||||
Füge das Repository in `/etc/pacman.conf` ein:
|
||||
|
||||
```ini
|
||||
[docker-update-stable]
|
||||
[stable]
|
||||
SigLevel = Optional TrustAll
|
||||
Server = https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/arch/stable/x86_64
|
||||
Server = https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/arch/stable/$arch
|
||||
```
|
||||
|
||||
Installiere das Paket anschließend:
|
||||
@@ -151,9 +173,9 @@ sudo pacman -Sy docker-update
|
||||
Füge das Testing-Repository in `/etc/pacman.conf` ein:
|
||||
|
||||
```ini
|
||||
[docker-update-testing]
|
||||
[testing]
|
||||
SigLevel = Optional TrustAll
|
||||
Server = https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/arch/testing/x86_64
|
||||
Server = https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/arch/testing/$arch
|
||||
```
|
||||
|
||||
Installiere das Paket:
|
||||
@@ -246,16 +268,19 @@ Die Paketkonfiguration und Abhängigkeiten (`sudo`, `docker.io | docker-ce`) sin
|
||||
|
||||
Die Paketmetadaten und Abhängigkeiten für Arch Linux sind direkt in `Cargo.toml` unter `[package.metadata.arch]`
|
||||
hinterlegt. Das Paket kann nach dem Kompilieren des Release-Builds über das Skript `scripts/package-arch.py` (analog zu
|
||||
CI/CD) erzeugt
|
||||
werden:
|
||||
CI/CD) erzeugt werden (optional mit `--target <TRIPLE>` für Cross-Builds):
|
||||
|
||||
```bash
|
||||
# Release kompilieren
|
||||
cargo build --release
|
||||
|
||||
# Arch Linux-Paket (.pkg.tar.zst) erstellen
|
||||
# Arch Linux-Paket (.pkg.tar.zst) erstellen (Standard: Host/x86_64)
|
||||
python3 scripts/package-arch.py
|
||||
|
||||
# Optional für spezifische Zielarchitektur:
|
||||
# python3 scripts/package-arch.py --target aarch64-unknown-linux-gnu
|
||||
# python3 scripts/package-arch.py --target i686-unknown-linux-gnu
|
||||
|
||||
# Installation des erstellten Pakets
|
||||
sudo pacman -U target/arch/docker-update-*.pkg.tar.zst
|
||||
```
|
||||
@@ -275,6 +300,11 @@ cargo install cargo-generate-rpm
|
||||
cargo build --release
|
||||
cargo generate-rpm
|
||||
|
||||
# Optional für spezifische Zielarchitektur (z.B. Cross-Builds):
|
||||
# mkdir -p target/generate-rpm
|
||||
# cargo generate-rpm --target aarch64-unknown-linux-gnu -o target/generate-rpm
|
||||
# cargo generate-rpm --target i686-unknown-linux-gnu -o target/generate-rpm
|
||||
|
||||
# Installation des erstellten RPM-Pakets
|
||||
sudo dnf install target/generate-rpm/docker-update-*.rpm
|
||||
```
|
||||
|
||||
Executable
+164
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Ermittelt automatisch die nächste Build-Nummer (Revision / Release / pkgrel)
|
||||
für eine gegebene Paket-Version (z. B. 1.0.2 -> wenn 1.0.2-1 existiert, wird 2 zurückgegeben).
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
|
||||
def get_cargo_pkg_info():
|
||||
try:
|
||||
metadata = json.loads(subprocess.check_output(["cargo", "metadata", "--format-version", "1", "--no-deps"]))
|
||||
pkg = metadata["packages"][0]
|
||||
name = pkg.get("name", "docker-update")
|
||||
version = pkg.get("version", "1.0.0")
|
||||
repository = pkg.get("repository", "")
|
||||
return name, version, repository
|
||||
except Exception:
|
||||
# Fallback auf einfaches Parsen der Cargo.toml
|
||||
name = "docker-update"
|
||||
version = "1.0.0"
|
||||
repository = ""
|
||||
if os.path.exists("Cargo.toml"):
|
||||
with open("Cargo.toml", "r") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith("name ="):
|
||||
name = line.split("=", 1)[1].strip().strip('"\'')
|
||||
elif line.startswith("version ="):
|
||||
version = line.split("=", 1)[1].strip().strip('"\'')
|
||||
elif line.startswith("repository ="):
|
||||
repository = line.split("=", 1)[1].strip().strip('"\'')
|
||||
return name, version, repository
|
||||
|
||||
|
||||
def parse_repo_info(repo_url):
|
||||
"""Extrahiert Server-URL, Owner und Repo-Name aus einer Repository-URL."""
|
||||
if not repo_url:
|
||||
return None, None, None
|
||||
parsed = urllib.parse.urlparse(repo_url)
|
||||
server_url = f"{parsed.scheme}://{parsed.netloc}"
|
||||
path_parts = [p for p in parsed.path.strip("/").split("/") if p]
|
||||
if len(path_parts) >= 2:
|
||||
owner = path_parts[0]
|
||||
repo_name = path_parts[1]
|
||||
if repo_name.endswith(".git"):
|
||||
repo_name = repo_name[:-4]
|
||||
return server_url, f"{owner}/{repo_name}", owner
|
||||
return server_url, None, None
|
||||
|
||||
|
||||
def query_existing_build_numbers(name, version, gitea_url, repo, owner, token=None):
|
||||
build_nums = set()
|
||||
pattern = re.compile(rf'(?:{re.escape(name)}[_-]|^v?){re.escape(version)}-(\d+)')
|
||||
|
||||
headers = {"User-Agent": "docker-update-build-resolver"}
|
||||
if token:
|
||||
headers["Authorization"] = f"token {token}"
|
||||
|
||||
# 1. Gitea Releases API
|
||||
if gitea_url and repo:
|
||||
try:
|
||||
url = f"{gitea_url.rstrip('/')}/api/v1/repos/{repo}/releases?limit=50"
|
||||
req = urllib.request.Request(url, headers=headers)
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
releases = json.loads(resp.read().decode())
|
||||
for rel in releases:
|
||||
tag = rel.get("tag_name", "")
|
||||
m = pattern.search(tag)
|
||||
if m:
|
||||
build_nums.add(int(m.group(1)))
|
||||
for asset in rel.get("assets", []):
|
||||
asset_name = asset.get("name", "")
|
||||
m = pattern.search(asset_name)
|
||||
if m:
|
||||
build_nums.add(int(m.group(1)))
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"[Hinweis] Konnte Gitea Releases nicht abfragen: {e}\n")
|
||||
|
||||
# 2. Gitea Packages API (falls Token vorhanden)
|
||||
if gitea_url and owner and token:
|
||||
try:
|
||||
url = f"{gitea_url.rstrip('/')}/api/v1/packages/{owner}?limit=50"
|
||||
req = urllib.request.Request(url, headers=headers)
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
packages = json.loads(resp.read().decode())
|
||||
for pkg in packages:
|
||||
pkg_ver = pkg.get("version", "")
|
||||
m = pattern.search(pkg_ver)
|
||||
if m:
|
||||
build_nums.add(int(m.group(1)))
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"[Hinweis] Konnte Gitea Packages nicht abfragen: {e}\n")
|
||||
|
||||
# 3. Lokales Target-Verzeichnis prüfen
|
||||
target_dirs = ["target/debian", "target/generate-rpm", "target/arch"]
|
||||
for t_dir in target_dirs:
|
||||
if os.path.isdir(t_dir):
|
||||
for file in os.listdir(t_dir):
|
||||
m = pattern.search(file)
|
||||
if m:
|
||||
build_nums.add(int(m.group(1)))
|
||||
|
||||
return build_nums
|
||||
|
||||
|
||||
def get_next_build_number(version=None, gitea_url=None, repo=None, owner=None, token=None, explicit_build_num=None):
|
||||
if explicit_build_num is not None:
|
||||
return int(explicit_build_num)
|
||||
|
||||
env_build_num = os.environ.get("BUILD_NUMBER") or os.environ.get("BUILD_NUM") or os.environ.get("PKGREL")
|
||||
if env_build_num:
|
||||
try:
|
||||
return int(env_build_num)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
pkg_name, pkg_version, pkg_repo = get_cargo_pkg_info()
|
||||
if not version:
|
||||
version = pkg_version
|
||||
|
||||
default_server, default_repo, default_owner = parse_repo_info(pkg_repo)
|
||||
|
||||
gitea_url = gitea_url or os.environ.get("GITEA_URL") or os.environ.get("GITHUB_SERVER_URL") or default_server
|
||||
repo = repo or os.environ.get("REPO") or os.environ.get("GITHUB_REPOSITORY") or default_repo
|
||||
owner = owner or os.environ.get("REPO_OWNER") or os.environ.get("GITHUB_REPOSITORY_OWNER") or default_owner
|
||||
token = token or os.environ.get("TOKEN") or os.environ.get("GITEA_TOKEN") or os.environ.get("PACKAGE_TOKEN") or os.environ.get("RELEASE_TOKEN")
|
||||
|
||||
existing_nums = query_existing_build_numbers(pkg_name, version, gitea_url, repo, owner, token)
|
||||
if existing_nums:
|
||||
return max(existing_nums) + 1
|
||||
return 1
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Ermittelt die nächste Build-Nummer für die Paketierung.")
|
||||
parser.add_argument("--version", help="Paket-Version (Standard: aus Cargo.toml)")
|
||||
parser.add_argument("--gitea-url", help="Gitea Basis-URL")
|
||||
parser.add_argument("--repo", help="Repository (z. B. Owner/Repo)")
|
||||
parser.add_argument("--owner", help="Repository Owner / Organisation")
|
||||
parser.add_argument("--token", help="API-Token")
|
||||
parser.add_argument("--build-number", type=int, help="Explizite Build-Nummer erzwingen")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
build_num = get_next_build_number(
|
||||
version=args.version,
|
||||
gitea_url=args.gitea_url,
|
||||
repo=args.repo,
|
||||
owner=args.owner,
|
||||
token=args.token,
|
||||
explicit_build_num=args.build_number,
|
||||
)
|
||||
print(build_num)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+79
-4
@@ -1,12 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
TARGET_ARCH_MAP = {
|
||||
"x86_64-unknown-linux-gnu": "x86_64",
|
||||
"x86_64-unknown-linux-musl": "x86_64",
|
||||
"x86_64": "x86_64",
|
||||
"amd64": "x86_64",
|
||||
"aarch64-unknown-linux-gnu": "aarch64",
|
||||
"aarch64-unknown-linux-musl": "aarch64",
|
||||
"aarch64": "aarch64",
|
||||
"arm64": "aarch64",
|
||||
"i686-unknown-linux-gnu": "i686",
|
||||
"i686-unknown-linux-musl": "i686",
|
||||
"i686": "i686",
|
||||
"i386": "i686",
|
||||
}
|
||||
|
||||
def main():
|
||||
|
||||
def resolve_pkgrel(version=None, default="1"):
|
||||
# 1. Environment Variable
|
||||
env_pkgrel = os.environ.get("BUILD_NUMBER") or os.environ.get("BUILD_NUM") or os.environ.get("PKGREL")
|
||||
if env_pkgrel:
|
||||
return str(env_pkgrel)
|
||||
|
||||
# 2. get-build-number.py falls vorhanden
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
getter_path = os.path.join(script_dir, "get-build-number.py")
|
||||
if os.path.exists(getter_path):
|
||||
try:
|
||||
spec = importlib.util.spec_from_file_location("get_build_number", getter_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return str(module.get_next_build_number(version=version))
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"[Hinweis] Konnte get-build-number nicht ausführen: {e}\n")
|
||||
|
||||
return str(default)
|
||||
|
||||
|
||||
def build_package(target_triple=None, target_arch=None, pkgrel=None):
|
||||
metadata = json.loads(subprocess.check_output(["cargo", "metadata", "--format-version", "1", "--no-deps"]))
|
||||
pkg = metadata["packages"][0]
|
||||
name = pkg["name"]
|
||||
@@ -18,8 +57,34 @@ def main():
|
||||
author = authors[0] if authors else "Unknown"
|
||||
|
||||
arch_meta = pkg.get("metadata", {}).get("arch", {})
|
||||
pkgrel = arch_meta.get("pkgrel", "1")
|
||||
arch = arch_meta.get("arch", "x86_64")
|
||||
default_pkgrel = arch_meta.get("pkgrel", "1")
|
||||
if not pkgrel:
|
||||
pkgrel = resolve_pkgrel(version=version, default=default_pkgrel)
|
||||
default_arch = arch_meta.get("arch", "x86_64")
|
||||
|
||||
if target_arch:
|
||||
arch = TARGET_ARCH_MAP.get(target_arch, target_arch)
|
||||
elif target_triple:
|
||||
arch = TARGET_ARCH_MAP.get(target_triple, default_arch)
|
||||
else:
|
||||
arch = default_arch
|
||||
|
||||
candidate_paths = []
|
||||
if target_triple:
|
||||
candidate_paths.append(f"target/{target_triple}/release/{name}")
|
||||
candidate_paths.append(f"target/release/{name}")
|
||||
|
||||
bin_path = None
|
||||
for p in candidate_paths:
|
||||
if os.path.exists(p):
|
||||
bin_path = p
|
||||
break
|
||||
|
||||
if not bin_path:
|
||||
raise FileNotFoundError(
|
||||
f"Keine kompilierte Binary für {name} gefunden. Gesuchte Pfade: {candidate_paths}"
|
||||
)
|
||||
|
||||
depends = arch_meta.get("depends", ["gcc-libs", "glibc", "sudo", "docker-ce-cli", "docker-compose-plugin"])
|
||||
optdepends = arch_meta.get("optdepends", ["docker-compose: Unterstützung für Docker Compose v1"])
|
||||
|
||||
@@ -31,7 +96,7 @@ def main():
|
||||
os.makedirs(doc_dir, exist_ok=True)
|
||||
os.makedirs(lic_dir, exist_ok=True)
|
||||
|
||||
subprocess.run(["install", "-m", "755", f"target/release/{name}", f"{bin_dir}/{name}"], check=True)
|
||||
subprocess.run(["install", "-m", "755", bin_path, f"{bin_dir}/{name}"], check=True)
|
||||
if os.path.exists("LICENSE"):
|
||||
subprocess.run(["install", "-m", "644", "LICENSE", f"{lic_dir}/LICENSE"], check=True)
|
||||
if os.path.exists("README.md"):
|
||||
@@ -67,5 +132,15 @@ def main():
|
||||
print(f"Arch-Paket erfolgreich erstellt: {output_file}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Erstellt Arch Linux-Pakete (.pkg.tar.zst)")
|
||||
parser.add_argument("--target", help="Rust Target-Triple (z.B. x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, i686-unknown-linux-gnu)")
|
||||
parser.add_argument("--arch", help="Architektur (z.B. x86_64, aarch64, i686)")
|
||||
parser.add_argument("--pkgrel", help="Release-/Build-Nummer (z.B. 1, 2, ...)")
|
||||
args = parser.parse_args()
|
||||
|
||||
build_package(target_triple=args.target, target_arch=args.arch, pkgrel=args.pkgrel)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
+7
-63
@@ -1,16 +1,13 @@
|
||||
//! Modul für die Konfigurationsverwaltung der Anwendung.
|
||||
//!
|
||||
//! Dieses Modul stellt die Funktionalität zum Laden, Speichern und Verwalten
|
||||
//! der Anwendungskonfiguration bereit. Die Konfiguration wird in einer Datei gespeichert
|
||||
//! und beim Programmstart automatisch geladen.
|
||||
//! Dieses Modul stellt die Datenstrukturen für die Anwendungskonfiguration bereit.
|
||||
//! Das Laden, Speichern und Cachen erfolgt über das Crate `config-ctdra`.
|
||||
|
||||
use crate::program;
|
||||
use crate::sudo::is_run_as_root;
|
||||
use config_ctdra::get_config as get_cached_config;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// Hauptkonfigurationsstruktur der Anwendung.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct AppConfig {
|
||||
/// Allgemeine Einstellungen
|
||||
pub general: General,
|
||||
@@ -25,7 +22,7 @@ impl Default for AppConfig {
|
||||
}
|
||||
|
||||
/// Allgemeine Konfigurationseinstellungen.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct General {
|
||||
/// Log-Level für die Anwendung (error, warn, info, debug)
|
||||
pub log_level: String,
|
||||
@@ -42,60 +39,7 @@ impl Default for General {
|
||||
}
|
||||
}
|
||||
|
||||
static CONFIG_NAME: &str = "config";
|
||||
static CONFIG: OnceLock<AppConfig> = OnceLock::new();
|
||||
|
||||
/// Gibt die aktuelle Konfiguration zurück.
|
||||
///
|
||||
/// Lädt die Konfiguration beim ersten Aufruf und speichert sie zwischen.
|
||||
/// Nachfolgende Aufrufe geben die gespeicherte Konfiguration zurück.
|
||||
/// Gibt die globale Konfiguration zurück.
|
||||
pub fn get_config() -> &'static AppConfig {
|
||||
CONFIG.get_or_init(|| {
|
||||
load_config()
|
||||
})
|
||||
get_cached_config::<AppConfig>()
|
||||
}
|
||||
|
||||
/// Modifiziert die aktuelle Konfiguration mit der übergebenen Mutator-Funktion.
|
||||
///
|
||||
/// Die Funktion lädt die Konfiguration neu von der Festplatte, wendet die Mutator-Funktion an
|
||||
/// und speichert die geänderte Konfiguration anschließend wieder.
|
||||
#[allow(dead_code)]
|
||||
pub fn modify_config<F>(mutator: F)
|
||||
where
|
||||
F: FnOnce(&mut AppConfig),
|
||||
{
|
||||
// Immer frisch von Disk laden, damit Änderungen konsistent sind
|
||||
let mut cfg = load_config();
|
||||
mutator(&mut cfg);
|
||||
save_config(cfg);
|
||||
}
|
||||
|
||||
/// Lädt die Konfiguration aus der Konfigurationsdatei.
|
||||
fn load_config() -> AppConfig {
|
||||
if is_run_as_root() {
|
||||
return confy::load_path(format!("/etc/{}/{}.toml", program::program_name(), CONFIG_NAME)).unwrap_or_default();
|
||||
}
|
||||
confy::load(&*program::program_name(), CONFIG_NAME).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Speichert die übergebene Konfiguration in der Konfigurationsdatei.
|
||||
#[allow(dead_code)]
|
||||
fn save_config(config: AppConfig) {
|
||||
if is_run_as_root() {
|
||||
let _ = confy::store_path(format!("/etc/{}/{}.toml", program::program_name(), CONFIG_NAME), config);
|
||||
return;
|
||||
}
|
||||
let _ = confy::store(&*program::program_name(), CONFIG_NAME, config);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_default_config() {
|
||||
let config = AppConfig::default();
|
||||
assert_eq!(config.general.apps_dir, "/var/apps");
|
||||
assert_eq!(config.general.log_level, "info");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
//! DockerUpdate Bibliotheksexporte
|
||||
|
||||
pub mod config;
|
||||
pub mod updater;
|
||||
-254
@@ -1,254 +0,0 @@
|
||||
//! Einfaches, threadsicheres Logging-Modul.
|
||||
//!
|
||||
//! Merkmale:
|
||||
//! - Ausgabe auf Standard-Streams (stdout/stderr) und zusätzlich in eine Logdatei im temporären Verzeichnis.
|
||||
//! - Nachrichtenvorlage: `[DD.MM.YYYY HH:MM:SS.mmm][LEVEL][TAG]: <Text>` mit Präfix ([!]/[?]/[i]/[d]).
|
||||
//! - Der aktuell verwendete Schweregradfilter ist statisch (`LOG_LEVEL`) und wird zur Laufzeit nicht geändert.
|
||||
//! - Dateihandle wird lazily initialisiert und zwischengespeichert (`OnceLock`).
|
||||
//!
|
||||
//! Hinweise:
|
||||
//! - Bei einem leeren `tag` wird ein leerer Tag-Abschnitt erzeugt.
|
||||
//! - Die Logdatei wird unterhalb eines prozessspezifischen Ordners im System-Temp-Verzeichnis abgelegt und nach Datum
|
||||
//! benannt (z. B. `log-2025-08-18.log`). Es wird im Append-Modus geschrieben.
|
||||
//!
|
||||
//! Beispiel (ohne Ausführung in Doctests):
|
||||
//! ```rust,no_run
|
||||
//! use crate::log::{log, LogLevel};
|
||||
//!
|
||||
//! log("startup", "Dienst wird initialisiert...", LogLevel::Info);
|
||||
//! log("db", "Verbindung unterbrochen!", LogLevel::Warn);
|
||||
//! log("core", "Unerwarteter Fehler", LogLevel::Error);
|
||||
//! ```
|
||||
|
||||
use std::env;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fs::{File, OpenOptions, create_dir_all};
|
||||
use std::io::Write;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
use crate::config;
|
||||
use crate::program;
|
||||
use time::{OffsetDateTime, macros::format_description};
|
||||
|
||||
/// Schweregrade für Logeinträge in aufsteigender Detailtiefe.
|
||||
///
|
||||
/// Die Reihenfolge bestimmt den Filter: Nur Einträge mit `log_level <= LOG_LEVEL` werden ausgegeben.
|
||||
///
|
||||
/// Anzeige (Display):
|
||||
/// - `Error` -> `ERROR`
|
||||
/// - `Warn` -> `WARN`
|
||||
/// - `Info` -> `INFO`
|
||||
/// - `Debug` -> `DEBUG`
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
||||
pub enum LogLevel {
|
||||
/// Kritische Fehler, gehen zusätzlich auf `stderr`.
|
||||
Error = 1,
|
||||
/// Wichtige Warnungen über potenzielle Probleme.
|
||||
Warn = 2,
|
||||
/// Allgemeine Betriebsinformationen.
|
||||
Info = 3,
|
||||
/// Ausführliche Diagnoseausgaben für die Entwicklung.
|
||||
Debug = 4,
|
||||
}
|
||||
|
||||
impl Display for LogLevel {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LogLevel::Error => write!(f, "ERROR"),
|
||||
LogLevel::Warn => write!(f, "WARN"),
|
||||
LogLevel::Info => write!(f, "INFO"),
|
||||
LogLevel::Debug => write!(f, "DEBUG"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for LogLevel {
|
||||
type Error = LogLevel;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self> {
|
||||
match value.to_lowercase().as_str() {
|
||||
"error" => Ok(LogLevel::Error),
|
||||
"warn" => Ok(LogLevel::Warn),
|
||||
"info" => Ok(LogLevel::Info),
|
||||
"debug" => Ok(LogLevel::Debug),
|
||||
_ => Err(LogLevel::Info),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Globaler Schweregradfilter für Ausgabe.
|
||||
static LOG_LEVEL: OnceLock<LogLevel> = OnceLock::new();
|
||||
|
||||
/// Lazy-initialisiertes Handle zur Logdatei; kann `None` sein, falls das Öffnen fehlschlug.
|
||||
static LOG_FILE: OnceLock<Mutex<Option<File>>> = OnceLock::new();
|
||||
|
||||
/// Protokolliert eine Nachricht abhängig vom angegebenen Schweregrad.
|
||||
///
|
||||
/// Verhalten:
|
||||
/// - Wenn `log_level` größer als der globale Filter ist, wird nichts ausgegeben.
|
||||
/// - `Error`-Meldungen gehen nach `stderr`, alle anderen nach `stdout`.
|
||||
/// - Zusätzlich wird in eine tägliche Logdatei im Temp-Verzeichnis geschrieben (wenn erfolgreich geöffnet).
|
||||
///
|
||||
/// Parameter:
|
||||
/// - `tag`: Kurzer Kontext (z. B. Modulname).
|
||||
/// - `message`: Die eigentliche Nachricht (eine Zeile).
|
||||
/// - `log_level`: Schweregrad der Nachricht.
|
||||
///
|
||||
/// Thread-Sicherheit:
|
||||
/// - Dateischreibzugriffe sind über `Mutex` serialisiert.
|
||||
///
|
||||
/// Beispiel:
|
||||
/// ```rust,no_run
|
||||
/// # use crate::log::{log, LogLevel};
|
||||
/// log("http", "Server gestartet auf Port 8080", LogLevel::Info);
|
||||
/// ```
|
||||
pub fn log(tag: &str, message: &str, log_level: LogLevel) {
|
||||
if log_level <= get_log_level() {
|
||||
let message: String = format_message(tag, message, &log_level);
|
||||
|
||||
if log_level == LogLevel::Error {
|
||||
eprintln!("{}", message);
|
||||
} else {
|
||||
println!("{}", message);
|
||||
}
|
||||
|
||||
let file_lock = get_or_init_log_file();
|
||||
if let Ok(mut guard) = file_lock.lock() {
|
||||
if let Some(f) = guard.as_mut() {
|
||||
let _ = writeln!(f, "{}", message);
|
||||
let _ = f.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Liefert das einmalig initialisierte Log-Level für die Filterung.
|
||||
///
|
||||
/// Ermittelt das konfigurierte Log-Level aus der Config-Datei.
|
||||
/// Wenn ungültig oder nicht vorhanden, wird LogLevel::Info als Fallback verwendet.
|
||||
///
|
||||
/// Rückgabe:
|
||||
/// - `LogLevel`: Das zu verwendende Log-Level als enum-Wert.
|
||||
fn get_log_level() -> LogLevel {
|
||||
*LOG_LEVEL.get_or_init(|| {
|
||||
let log_level = &config::get_config().general.log_level;
|
||||
LogLevel::try_from(log_level.to_string()).unwrap_or(LogLevel::Info)
|
||||
})
|
||||
}
|
||||
|
||||
/// Formatiert eine Lognachricht mit Zeitstempel, Level, Tag und Präfix.
|
||||
///
|
||||
/// Format:
|
||||
/// - Datum/Zeit lokal (Fallback: UTC) im Format `DD.MM.YYYY HH:MM:SS.mmm`.
|
||||
/// - Level als Text (`ERROR`, `WARN`, `INFO`, `DEBUG`).
|
||||
/// - Tag in eckigen Klammern; bei leerem Tag entsteht ein leeres `[]`.
|
||||
/// - Präfix pro Level: `[!]` (Error), `[?]` (Warn), `[i]` (Info), `[d]` (Debug).
|
||||
///
|
||||
/// Beispielausgabe:
|
||||
/// - `[i][18.08.2025 14:23:45.012][INFO][init]: Fertig`
|
||||
///
|
||||
/// Hinweis:
|
||||
/// - Diese Funktion formatiert nur; sie führt keinen I/O aus.
|
||||
fn format_message(tag: &str, message: &str, log_level: &LogLevel) -> String {
|
||||
let mut prefix: String = String::new();
|
||||
|
||||
let now_local: OffsetDateTime =
|
||||
OffsetDateTime::now_local().unwrap_or(OffsetDateTime::now_utc());
|
||||
let fmt =
|
||||
format_description!("[day].[month].[year] [hour]:[minute]:[second].[subsecond digits:3]");
|
||||
|
||||
if tag != "" {
|
||||
prefix = format!("[{}]", tag);
|
||||
}
|
||||
|
||||
let message = format!(
|
||||
"[{}][{}]{}: {}",
|
||||
now_local.format(fmt).unwrap(),
|
||||
log_level.to_string(),
|
||||
prefix,
|
||||
message
|
||||
);
|
||||
|
||||
match log_level {
|
||||
&LogLevel::Error => format!("[!]{}", message),
|
||||
&LogLevel::Warn => format!("[?]{}", message),
|
||||
&LogLevel::Info => format!("[i]{}", message),
|
||||
&LogLevel::Debug => format!("[d]{}", message),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialisiert das Logdatei-Handle beim ersten Aufruf und liefert eine Referenz darauf.
|
||||
///
|
||||
/// Rückgabe:
|
||||
/// - `&'static Mutex<Option<File>>`: Das Mutex schützt den optionalen Dateihandler.
|
||||
/// `None` bedeutet, dass das Öffnen fehlgeschlagen ist (z. B. fehlende Rechte).
|
||||
fn get_or_init_log_file() -> &'static Mutex<Option<File>> {
|
||||
LOG_FILE.get_or_init(|| {
|
||||
let file = open_log_file().ok();
|
||||
Mutex::new(file)
|
||||
})
|
||||
}
|
||||
|
||||
/// Öffnet (und erstellt bei Bedarf) die tagesbasierte Logdatei im Temp-Verzeichnis.
|
||||
///
|
||||
/// Pfadaufbau:
|
||||
/// - Basis: `std::env::temp_dir()`
|
||||
/// - Unterordner: `<programmname>-<konstante-uuid>`
|
||||
/// - Datei: `log-YYYY-MM-DD.log` im Append-Modus
|
||||
///
|
||||
/// Rückgabe:
|
||||
/// - `Ok(File)`, wenn der Ordner erstellt/gefunden und die Datei geöffnet/angelegt werden konnte.
|
||||
/// - `Err(std::io::Error)`, wenn ein I/O-Fehler auftrat.
|
||||
///
|
||||
/// Fehler:
|
||||
/// - Gibt I/O-Fehler unverändert weiter (z. B. beim Erstellen des Ordners oder Öffnen der Datei).
|
||||
fn open_log_file() -> std::io::Result<File> {
|
||||
let program = program::program_name();
|
||||
let rand = "13692bbf-a93b-43e9-9cc6-f05f94a8cfb6";
|
||||
|
||||
let mut dir = env::temp_dir();
|
||||
dir.push(format!("{}-{}", program, rand));
|
||||
create_dir_all(&dir)?;
|
||||
|
||||
let today_fmt = format_description!("[year]-[month]-[day]");
|
||||
let date_str = OffsetDateTime::now_local()
|
||||
.unwrap_or(OffsetDateTime::now_utc())
|
||||
.format(today_fmt)
|
||||
.unwrap_or_else(|_| "0000-00-00".to_string());
|
||||
|
||||
let file_name = format!("log-{}.log", date_str);
|
||||
dir.push(file_name);
|
||||
|
||||
OpenOptions::new().create(true).append(true).open(dir)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_format_message_prefixes() {
|
||||
let msg_info = format_message("test", "Info message", &LogLevel::Info);
|
||||
assert!(msg_info.starts_with("[i]"));
|
||||
assert!(msg_info.contains("[INFO][test]: Info message"));
|
||||
|
||||
let msg_err = format_message("test", "Error message", &LogLevel::Error);
|
||||
assert!(msg_err.starts_with("[!]"));
|
||||
assert!(msg_err.contains("[ERROR][test]: Error message"));
|
||||
|
||||
let msg_warn = format_message("test", "Warn message", &LogLevel::Warn);
|
||||
assert!(msg_warn.starts_with("[?]"));
|
||||
assert!(msg_warn.contains("[WARN][test]: Warn message"));
|
||||
|
||||
let msg_debug = format_message("test", "Debug message", &LogLevel::Debug);
|
||||
assert!(msg_debug.starts_with("[d]"));
|
||||
assert!(msg_debug.contains("[DEBUG][test]: Debug message"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_message_empty_tag() {
|
||||
let msg = format_message("", "No tag message", &LogLevel::Info);
|
||||
assert!(msg.starts_with("[i]"));
|
||||
assert!(msg.contains("[INFO]: No tag message"));
|
||||
}
|
||||
}
|
||||
+24
-12
@@ -1,19 +1,31 @@
|
||||
mod program;
|
||||
mod sudo;
|
||||
mod log;
|
||||
mod config;
|
||||
mod updater;
|
||||
|
||||
use crate::log::{log, LogLevel};
|
||||
use docker_update::config::get_config;
|
||||
use docker_update::updater;
|
||||
use logger_ctdra::{LogLevel, log};
|
||||
use std::process::exit;
|
||||
use sudo_ctdra::{is_run_as_root, run_as_root};
|
||||
|
||||
fn main() {
|
||||
if !sudo::is_run_as_root() {
|
||||
log("auth", "Root-Rechte erforderlich. Starte mit sudo neu...", LogLevel::Info);
|
||||
sudo::run_as_root();
|
||||
return;
|
||||
if !is_run_as_root() {
|
||||
log(
|
||||
"auth",
|
||||
"Root-Rechte erforderlich. Starte mit sudo neu...",
|
||||
LogLevel::Info,
|
||||
);
|
||||
let err = run_as_root();
|
||||
log(
|
||||
"auth",
|
||||
&format!("Fehler beim Ausführen von 'sudo': {}", err),
|
||||
LogLevel::Error,
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let config = get_config();
|
||||
if let Ok(level) = LogLevel::try_from(config.general.log_level.as_str()) {
|
||||
logger_ctdra::init(level);
|
||||
}
|
||||
|
||||
log("main", "docker-update gestartet.", LogLevel::Info);
|
||||
updater::run_updates();
|
||||
log("main", "docker-update beendet.", LogLevel::Info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
use std::env;
|
||||
|
||||
/// Liefert den Programmnamen (Dateistamm der aktuellen ausführbaren Datei).
|
||||
///
|
||||
/// Rückgabe:
|
||||
/// - Dateistamm der aktuellen Executable als `String`.
|
||||
/// - Fallback `"app"`, wenn der Name nicht ermittelt werden kann.
|
||||
pub(crate) fn program_name() -> String {
|
||||
env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.file_stem().map(|s| s.to_string_lossy().to_string()))
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or_else(|| "app".to_string())
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
use crate::log::{LogLevel, log};
|
||||
use std::env;
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::process::{Command, exit};
|
||||
|
||||
/// Prüft, ob das Programm mit Root-/Administrator-Rechten ausgeführt wird.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `true` - Das Programm läuft mit erhöhten Rechten
|
||||
/// * `false` - Das Programm läuft mit normalen Benutzerrechten
|
||||
pub fn is_run_as_root() -> bool {
|
||||
unsafe { libc::geteuid() == 0 }
|
||||
}
|
||||
|
||||
/// Startet das Programm mit Root-Rechten über `sudo` neu.
|
||||
///
|
||||
/// Diese Funktion versucht das Programm mit erhöhten Rechten neu zu starten.
|
||||
pub fn run_as_root() {
|
||||
let commandline_args: Vec<String> = env::args().collect();
|
||||
let err = Command::new("sudo").args(&commandline_args).exec();
|
||||
log(
|
||||
"auth",
|
||||
&format!("Fehler beim Ausführen von 'sudo': {}", err),
|
||||
LogLevel::Error,
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
+2
-74
@@ -1,5 +1,5 @@
|
||||
use crate::config;
|
||||
use crate::log::{LogLevel, log};
|
||||
use logger_ctdra::{LogLevel, log};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
@@ -66,7 +66,7 @@ pub fn parse_compose_services(compose_path: &Path) -> Result<Vec<(String, String
|
||||
)
|
||||
})?;
|
||||
|
||||
let compose: ComposeFile = serde_yaml::from_str(&content).map_err(|e| {
|
||||
let compose: ComposeFile = serde_saphyr::from_str(&content).map_err(|e| {
|
||||
format!(
|
||||
"Fehler beim Parsen der YAML-Datei '{}': {}",
|
||||
compose_path.display(),
|
||||
@@ -411,75 +411,3 @@ pub fn run_updates() {
|
||||
LogLevel::Info,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
#[test]
|
||||
fn test_is_latest_image() {
|
||||
assert!(is_latest_image("nginx"));
|
||||
assert!(is_latest_image("nginx:latest"));
|
||||
assert!(is_latest_image("nginx:LATEST"));
|
||||
assert!(is_latest_image("gitea/gitea"));
|
||||
assert!(is_latest_image("gitea/gitea:latest"));
|
||||
assert!(is_latest_image("localhost:5000/my-app"));
|
||||
assert!(is_latest_image("localhost:5000/my-app:latest"));
|
||||
|
||||
assert!(!is_latest_image("nginx:alpine"));
|
||||
assert!(!is_latest_image("gitea/gitea:1.20"));
|
||||
assert!(!is_latest_image("postgres:15-alpine"));
|
||||
assert!(!is_latest_image("localhost:5000/my-app:v1.0"));
|
||||
assert!(!is_latest_image("nginx@sha256:1234567890abcdef"));
|
||||
assert!(!is_latest_image(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_compose_services() {
|
||||
let temp_dir = std::env::temp_dir().join("docker_update_test_compose");
|
||||
let _ = fs::create_dir_all(&temp_dir);
|
||||
let compose_path = temp_dir.join("docker-compose.yaml");
|
||||
|
||||
let yaml_content = r#"
|
||||
services:
|
||||
gitea:
|
||||
image: gitea/gitea:latest
|
||||
ports:
|
||||
- "3000:3000"
|
||||
db:
|
||||
image: postgres:15
|
||||
environment:
|
||||
POSTGRES_DB: gitea
|
||||
"#;
|
||||
let mut file = File::create(&compose_path).unwrap();
|
||||
file.write_all(yaml_content.as_bytes()).unwrap();
|
||||
|
||||
let services = parse_compose_services(&compose_path).unwrap();
|
||||
assert_eq!(services.len(), 2);
|
||||
|
||||
let gitea_svc = services.iter().find(|(s, _)| s == "gitea").unwrap();
|
||||
assert_eq!(gitea_svc.1, "gitea/gitea:latest");
|
||||
|
||||
let db_svc = services.iter().find(|(s, _)| s == "db").unwrap();
|
||||
assert_eq!(db_svc.1, "postgres:15");
|
||||
|
||||
let _ = fs::remove_dir_all(&temp_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_compose_file() {
|
||||
let temp_dir = std::env::temp_dir().join("docker_update_test_find");
|
||||
let _ = fs::create_dir_all(&temp_dir);
|
||||
|
||||
assert_eq!(find_compose_file(&temp_dir), None);
|
||||
|
||||
let compose_path = temp_dir.join("docker-compose.yaml");
|
||||
File::create(&compose_path).unwrap();
|
||||
|
||||
assert_eq!(find_compose_file(&temp_dir), Some(compose_path));
|
||||
|
||||
let _ = fs::remove_dir_all(&temp_dir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
use docker_update::config::AppConfig;
|
||||
|
||||
#[test]
|
||||
fn test_default_config() {
|
||||
let config = AppConfig::default();
|
||||
assert_eq!(config.general.apps_dir, "/var/apps");
|
||||
assert_eq!(config.general.log_level, "info");
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use docker_update::updater::{find_compose_file, is_latest_image, parse_compose_services};
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
|
||||
#[test]
|
||||
fn test_is_latest_image() {
|
||||
assert!(is_latest_image("nginx"));
|
||||
assert!(is_latest_image("nginx:latest"));
|
||||
assert!(is_latest_image("nginx:LATEST"));
|
||||
assert!(is_latest_image("gitea/gitea"));
|
||||
assert!(is_latest_image("gitea/gitea:latest"));
|
||||
assert!(is_latest_image("localhost:5000/my-app"));
|
||||
assert!(is_latest_image("localhost:5000/my-app:latest"));
|
||||
|
||||
assert!(!is_latest_image("nginx:alpine"));
|
||||
assert!(!is_latest_image("gitea/gitea:1.20"));
|
||||
assert!(!is_latest_image("postgres:15-alpine"));
|
||||
assert!(!is_latest_image("localhost:5000/my-app:v1.0"));
|
||||
assert!(!is_latest_image("nginx@sha256:1234567890abcdef"));
|
||||
assert!(!is_latest_image(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_compose_services() {
|
||||
let temp_dir = std::env::temp_dir().join("docker_update_test_compose");
|
||||
let _ = fs::create_dir_all(&temp_dir);
|
||||
let compose_path = temp_dir.join("docker-compose.yaml");
|
||||
|
||||
let yaml_content = r#"
|
||||
services:
|
||||
gitea:
|
||||
image: gitea/gitea:latest
|
||||
ports:
|
||||
- "3000:3000"
|
||||
db:
|
||||
image: postgres:15
|
||||
environment:
|
||||
POSTGRES_DB: gitea
|
||||
"#;
|
||||
let mut file = File::create(&compose_path).unwrap();
|
||||
file.write_all(yaml_content.as_bytes()).unwrap();
|
||||
|
||||
let services = parse_compose_services(&compose_path).unwrap();
|
||||
assert_eq!(services.len(), 2);
|
||||
|
||||
let gitea_svc = services.iter().find(|(s, _)| s == "gitea").unwrap();
|
||||
assert_eq!(gitea_svc.1, "gitea/gitea:latest");
|
||||
|
||||
let db_svc = services.iter().find(|(s, _)| s == "db").unwrap();
|
||||
assert_eq!(db_svc.1, "postgres:15");
|
||||
|
||||
let _ = fs::remove_dir_all(&temp_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_compose_file() {
|
||||
let temp_dir = std::env::temp_dir().join("docker_update_test_find");
|
||||
let _ = fs::create_dir_all(&temp_dir);
|
||||
|
||||
assert_eq!(find_compose_file(&temp_dir), None);
|
||||
|
||||
let compose_path = temp_dir.join("docker-compose.yaml");
|
||||
File::create(&compose_path).unwrap();
|
||||
|
||||
assert_eq!(find_compose_file(&temp_dir), Some(compose_path));
|
||||
|
||||
let _ = fs::remove_dir_all(&temp_dir);
|
||||
}
|
||||
Reference in New Issue
Block a user