Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
ef052e9f66 | |||
c53576a700
|
|||
3d74ec37e4 | |||
c4855ed3fb
|
|||
07ee03b6be
|
|||
fb2a62d984
|
|||
113103f368
|
|||
f112002249 | |||
0c1d8be79f
|
|||
e3caf0bba9 | |||
287cd741b4
|
|||
8b29105686 | |||
27c7367ef1
|
|||
f9cef4b70d | |||
e3a3e61bce
|
|||
bcec1f8f90 | |||
cf483de06b
|
|||
d9c9eda823 | |||
4737dbb60d
|
|||
f82249d10f | |||
d46ad2c2b4
|
|||
7911a7ed6f | |||
9853f854c9
|
|||
a2c8ff0f84 | |||
0ca8ed94cc
|
|||
227a0426b5 | |||
20d143035e
|
|||
7b5b855774 | |||
6fc6207da8
|
|||
264b43c9a6
|
|||
118e4e5a86
|
|||
01898a3a8e
|
|||
1a1ad66ab6 | |||
bd71b8ee14
|
|||
a76417ce1d | |||
a288dbc140
|
@@ -4,7 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- '**'
|
- "**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
detect-version-change:
|
detect-version-change:
|
||||||
@@ -152,7 +152,6 @@ jobs:
|
|||||||
|
|
||||||
echo "changelog_body_path=$BODY" >> $GITHUB_OUTPUT
|
echo "changelog_body_path=$BODY" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
|
||||||
- name: Commit updated CHANGELOG
|
- name: Commit updated CHANGELOG
|
||||||
run: |
|
run: |
|
||||||
git add CHANGELOG.md
|
git add CHANGELOG.md
|
||||||
@@ -169,6 +168,8 @@ jobs:
|
|||||||
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
|
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
|
||||||
echo "Tag v$VERSION already exists, skipping tag creation."
|
echo "Tag v$VERSION already exists, skipping tag creation."
|
||||||
else
|
else
|
||||||
|
export GIT_AUTHOR_DATE="$(date --iso-8601=seconds)"
|
||||||
|
export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
|
||||||
git tag -a "v$VERSION" -F "${{ steps.generate-changelog.outputs.changelog_body_path }}" --cleanup=verbatim
|
git tag -a "v$VERSION" -F "${{ steps.generate-changelog.outputs.changelog_body_path }}" --cleanup=verbatim
|
||||||
git push origin "v$VERSION"
|
git push origin "v$VERSION"
|
||||||
fi
|
fi
|
||||||
|
@@ -1,38 +1,27 @@
|
|||||||
# ========================
|
|
||||||
# 📦 Upload Assets Template
|
|
||||||
# ========================
|
|
||||||
# Dieser Workflow wird automatisch ausgelöst, wenn ein Release
|
|
||||||
# in Gitea veröffentlicht wurde (event: release.published).
|
|
||||||
#
|
|
||||||
# Er dient dem Zweck, Release-Artefakte (wie z. B. Binary-Dateien,
|
|
||||||
# Changelogs oder Build-Zips) nachträglich mit dem Release zu verknüpfen.
|
|
||||||
#
|
|
||||||
# Voraussetzung: Zwei Shell-Skripte liegen im Projekt:
|
|
||||||
# - .gitea/scripts/get-release-id.sh → ermittelt Release-ID per Tag
|
|
||||||
# - .gitea/scripts/upload-asset.sh → lädt Datei als Release-Asset hoch
|
|
||||||
#
|
|
||||||
# --------------------------------------
|
|
||||||
|
|
||||||
name: Upload Assets
|
name: Upload Assets
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [published] # Nur bei Veröffentlichung eines Releases (nicht bei Entwürfen)
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
upload-assets:
|
upload-assets:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- target: linux
|
||||||
|
arch: amd64
|
||||||
|
- target: linux
|
||||||
|
arch: arm64
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# 📥 Checke den Stand des Repos aus, exakt auf dem veröffentlichten Tag
|
|
||||||
# So ist garantiert, dass die Artefakte dem Zustand des Releases entsprechen.
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.release.tag_name }} # z. B. "v1.2.3"
|
ref: ${{ github.event.release.tag_name }}
|
||||||
fetch-depth: 0 # vollständige Git-Historie (für z. B. git-cliff, logs etc.)
|
fetch-depth: 0
|
||||||
|
|
||||||
# 🆔 Hole die Release-ID basierend auf dem Tag
|
|
||||||
# Die ID wird als Umgebungsvariable RELEASE_ID über $GITHUB_ENV verfügbar gemacht.
|
|
||||||
- name: Get Release ID from tag
|
- name: Get Release ID from tag
|
||||||
run: .gitea/scripts/get-release-id.sh "${{ github.event.release.tag_name }}"
|
run: .gitea/scripts/get-release-id.sh "${{ github.event.release.tag_name }}"
|
||||||
|
|
||||||
@@ -40,8 +29,28 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
deno-version: v2.x
|
deno-version: v2.x
|
||||||
|
|
||||||
- name: Build application
|
- name: Build ${{ matrix.target }}-${{ matrix.arch }}
|
||||||
run: deno task build
|
run: deno task build:${{ matrix.arch }}
|
||||||
|
|
||||||
- name: Upload CHANGELOG.md as RELEASE-NOTES.md
|
- name: Generate SHA256 for ${{ matrix.target }}-${{ matrix.arch }}
|
||||||
run: .gitea/scripts/upload-asset.sh ./dist/systemd-timer systemd-timer
|
run: |
|
||||||
|
FILE="./dist/systemd-timer-${{ matrix.target }}-${{ matrix.arch }}"
|
||||||
|
sha256sum "$FILE" > "$FILE.sha256"
|
||||||
|
|
||||||
|
- name: Upload binary for ${{ matrix.target }}-${{ matrix.arch }}
|
||||||
|
run: .gitea/scripts/upload-asset.sh ./dist/systemd-timer-${{ matrix.target }}-${{ matrix.arch }} systemd-timer-${{ matrix.target }}-${{ matrix.arch }}
|
||||||
|
|
||||||
|
- name: Upload SHA256 for ${{ matrix.target }}-${{ matrix.arch }}
|
||||||
|
run: .gitea/scripts/upload-asset.sh ./dist/systemd-timer-${{ matrix.target }}-${{ matrix.arch }}.sha256 systemd-timer-${{ matrix.target }}-${{ matrix.arch }}.sha256
|
||||||
|
|
||||||
|
- name: Run Releases Sync Action
|
||||||
|
uses: https://git.0xmax42.io/actions/releases-sync@main
|
||||||
|
with:
|
||||||
|
gitea_token: ${{ secrets.RELEASE_PUBLISH_TOKEN }}
|
||||||
|
gitea_url: https://git.0xmax42.io
|
||||||
|
gitea_owner: maxp
|
||||||
|
gitea_repo: systemd-timer
|
||||||
|
tag_name: ${{ github.event.release.tag_name }}
|
||||||
|
github_token: ${{ secrets.SYNC_GITHUB_TOKEN }}
|
||||||
|
github_owner: 0xmax42
|
||||||
|
github_repo: systemd-timer
|
||||||
|
56
CHANGELOG.md
56
CHANGELOG.md
@@ -2,6 +2,62 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [0.3.1](https://git.0xmax42.io/maxp/systemd-timer/compare/v0.2.3..v0.3.1) - 2025-05-28
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- *(cli)* Add options for user, home, and working directory - ([113103f](https://git.0xmax42.io/maxp/systemd-timer/commit/113103f368ead3014165cc708f016a04749f59be))
|
||||||
|
|
||||||
|
### 📚 Documentation
|
||||||
|
|
||||||
|
- *(readme)* Expand CLI option descriptions for clarity - ([fb2a62d](https://git.0xmax42.io/maxp/systemd-timer/commit/fb2a62d984615caa4035fd5c1e8e64d245499e47))
|
||||||
|
|
||||||
|
### 🎨 Styling
|
||||||
|
|
||||||
|
- *(workflows)* Fix formatting and whitespace issues - ([c4855ed](https://git.0xmax42.io/maxp/systemd-timer/commit/c4855ed3fbc0ada208690f90932710983daef392))
|
||||||
|
|
||||||
|
### ⚙️ Miscellaneous Tasks
|
||||||
|
|
||||||
|
- *(workflows)* Consolidate release sync into upload workflow - ([0c1d8be](https://git.0xmax42.io/maxp/systemd-timer/commit/0c1d8be79f0cc331db9029beb46384659f465f6e))
|
||||||
|
|
||||||
|
## [0.2.3](https://git.0xmax42.io/maxp/systemd-timer/compare/v0.2.2..v0.2.3) - 2025-05-26
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- *(workflows)* Add GitHub release synchronization workflow - ([27c7367](https://git.0xmax42.io/maxp/systemd-timer/commit/27c7367ef1799428cc5a491b25036f77b65758af))
|
||||||
|
|
||||||
|
### 📚 Documentation
|
||||||
|
|
||||||
|
- *(readme)* Update project time badge interval - ([e3a3e61](https://git.0xmax42.io/maxp/systemd-timer/commit/e3a3e61bce0e62c2397bbc5bde3eff81b915c94a))
|
||||||
|
- Add Englisch README - ([cf483de](https://git.0xmax42.io/maxp/systemd-timer/commit/cf483de06b555599052b1d9f97ee98e9233e5a86))
|
||||||
|
|
||||||
|
## [0.2.2](https://git.0xmax42.io/maxp/systemd-timer/compare/v0.2.0..v0.2.2) - 2025-05-22
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- *(install)* Enhance checksum validation with detailed comparison - ([0ca8ed9](https://git.0xmax42.io/maxp/systemd-timer/commit/0ca8ed94ccc4b9fe4ccac331957f01f852999094))
|
||||||
|
- *(install)* Ensure compatibility with non-bash shells - ([20d1430](https://git.0xmax42.io/maxp/systemd-timer/commit/20d143035ec6893f680b68dc4a2f6319ca7a5b81))
|
||||||
|
|
||||||
|
### 📚 Documentation
|
||||||
|
|
||||||
|
- *(readme)* Update installation instructions with script - ([9853f85](https://git.0xmax42.io/maxp/systemd-timer/commit/9853f854c991d87b12cd4fb5e19fce55e7246024))
|
||||||
|
|
||||||
|
## [0.2.0](https://git.0xmax42.io/maxp/systemd-timer/compare/v0.1.0..v0.2.0) - 2025-05-22
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- *(scripts)* Add installation script for systemd-timer binary - ([264b43c](https://git.0xmax42.io/maxp/systemd-timer/commit/264b43c9a667d344e27cca4ac2f17d7a4a25bffc))
|
||||||
|
- *(workflows)* Add matrix build and SHA256 generation for releases - ([118e4e5](https://git.0xmax42.io/maxp/systemd-timer/commit/118e4e5a867a42c0d79efcc3b2a4db188affedec))
|
||||||
|
- *(tasks)* Add build tasks for amd64 and arm64 targets - ([01898a3](https://git.0xmax42.io/maxp/systemd-timer/commit/01898a3a8e094dfbbf981ab6f1cf38d52f60ef5d))
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- *(utils)* Handle file write failures with rollback - ([bd71b8e](https://git.0xmax42.io/maxp/systemd-timer/commit/bd71b8ee14a1856f1adaaaea198c8467b1a00d24))
|
||||||
|
|
||||||
|
### 📚 Documentation
|
||||||
|
|
||||||
|
- *(readme)* Add project time badge - ([a288dbc](https://git.0xmax42.io/maxp/systemd-timer/commit/a288dbc140fefbc46745f70cdcd71148802fdabf))
|
||||||
|
|
||||||
## [0.1.0] - 2025-05-21
|
## [0.1.0] - 2025-05-21
|
||||||
|
|
||||||
### 🚀 Features
|
### 🚀 Features
|
||||||
|
107
README.DE.md
Normal file
107
README.DE.md
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# systemd-timer
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Ein einfaches CLI-Tool zum schnellen Erzeugen von systemd `.service` und `.timer` Units – als Ersatz oder moderne Ergänzung zu klassischen `cron`-Jobs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Features
|
||||||
|
|
||||||
|
- Erzeugt `.service` und `.timer` Dateien per CLI
|
||||||
|
- Unterstützt `--user` Timer (für `~/.config/systemd/user/`)
|
||||||
|
- Optionales Logging (`StandardOutput/StandardError`)
|
||||||
|
- Unterstützt:
|
||||||
|
- `--calendar`: Zeitplan für den Timer (systemd `OnCalendar`)
|
||||||
|
- `--exec`: Auszuführendes Kommando (`ExecStart`)
|
||||||
|
- `--description`: Beschreibung für die Unit
|
||||||
|
- `--after`: `After=`-Abhängigkeiten in der Service-Unit
|
||||||
|
- `--environment`: Beliebige `Environment=KEY=VALUE` Einträge
|
||||||
|
- `--output`: Zielverzeichnis für die generierten Unit-Dateien
|
||||||
|
- `--run-as`: Setzt `User=` in der Service-Unit (nur systemweite Timer)
|
||||||
|
- `--home`: Setzt `Environment=HOME=…`
|
||||||
|
- `--cwd`: Arbeitsverzeichnis des Prozesses (`WorkingDirectory`)
|
||||||
|
- `--dry-run`: Gibt nur die generierten Inhalte aus, ohne sie zu schreiben
|
||||||
|
- Getestet und typisiert mit **Deno** + **Cliffy**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Installation
|
||||||
|
|
||||||
|
|
||||||
|
Du kannst `systemd-timer` direkt per Shell-Skript installieren:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://git.0xmax42.io/maxp/systemd-timer/raw/branch/main/scripts/install.sh | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Skript erkennt automatisch deine Plattform (Linux `amd64` oder `arm64`) und installiert die passende Binary nach `/usr/local/bin`, sofern dies erlaubt ist (ggf. mit `sudo`).
|
||||||
|
|
||||||
|
**Hinweis:**
|
||||||
|
- Für die Installation ist eine funktionierende Internetverbindung notwendig.
|
||||||
|
- Die Integrität der Binary wird mittels SHA256-Prüfsumme verifiziert.
|
||||||
|
- Du kannst das Skript vor der Ausführung auch manuell inspizieren:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://git.0xmax42.io/maxp/systemd-timer/raw/branch/main/scripts/install.sh -o install.sh
|
||||||
|
less install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Weitere Optionen und manuelle Installationswege findest du unter [`scripts/install.sh`](scripts/install.sh).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Beispiel
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./systemd-timer create \
|
||||||
|
--exec "/usr/local/bin/backup.sh" \
|
||||||
|
--calendar "Mon..Fri 02:00" \
|
||||||
|
--description "Backup Job" \
|
||||||
|
--user \
|
||||||
|
--logfile "/var/log/backup.log"
|
||||||
|
```
|
||||||
|
|
||||||
|
Erzeugt:
|
||||||
|
- `~/.config/systemd/user/backup.service`
|
||||||
|
- `~/.config/systemd/user/backup.timer`
|
||||||
|
|
||||||
|
Anschließend aktivieren:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user enable --now backup.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Tests ausführen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
deno task test
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧰 Entwickeln
|
||||||
|
|
||||||
|
```bash
|
||||||
|
deno task start create --exec "/bin/true" --calendar "daily" --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔒 Rechte / Flags
|
||||||
|
|
||||||
|
Das Tool benötigt beim Ausführen bzw. Kompilieren:
|
||||||
|
|
||||||
|
- `--allow-env` (für `$HOME`)
|
||||||
|
- `--allow-write` (zum Schreiben von `.service`/`.timer`)
|
||||||
|
|
||||||
|
Beim Entwickeln wird meist `-A` (allow all) verwendet.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Lizenz
|
||||||
|
|
||||||
|
[MIT License](LICENSE)
|
82
README.md
82
README.md
@@ -1,39 +1,58 @@
|
|||||||
# systemd-timer
|
# systemd-timer
|
||||||
|
|
||||||
Ein einfaches CLI-Tool zum schnellen Erzeugen von systemd `.service` und `.timer` Units – als Ersatz oder moderne Ergänzung zu klassischen `cron`-Jobs.
|
- 
|
||||||
|
- [Deutsche Version dieser Readme](README.DE.md)
|
||||||
|
|
||||||
|
A simple CLI tool for quickly generating systemd `.service` and `.timer` units — as a replacement or modern supplement to classic `cron` jobs.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 Features
|
## 🚀 Features
|
||||||
|
|
||||||
- Erzeugt `.service` und `.timer` Dateien per CLI
|
* Generates `.service` and `.timer` files via CLI
|
||||||
- Unterstützt `--user` Timer (für `~/.config/systemd/user/`)
|
* Supports `--user` timers (for `~/.config/systemd/user/`)
|
||||||
- Optionales Logging (`StandardOutput/StandardError`)
|
* Optional logging (`StandardOutput/StandardError`)
|
||||||
- Unterstützt:
|
* Supports:
|
||||||
- `--calendar`
|
* `--calendar`: Timer schedule (systemd `OnCalendar`)
|
||||||
- `--exec`
|
* `--exec`: Command to execute (`ExecStart`)
|
||||||
- `--after`
|
* `--description`: Description for the unit
|
||||||
- `--environment`
|
* `--after`: `After=` dependencies in the service unit
|
||||||
- `--output`
|
* `--environment`: Arbitrary `Environment=KEY=VALUE` entries
|
||||||
- `--dry-run`
|
* `--output`: Target directory for the generated unit files
|
||||||
- Getestet und typisiert mit Deno + Cliffy
|
* `--run-as`: Sets `User=` in the service unit (only for system-level timers)
|
||||||
|
* `--home`: Sets `Environment=HOME=…`
|
||||||
|
* `--cwd`: Working directory for the process (`WorkingDirectory`)
|
||||||
|
* `--dry-run`: Outputs unit content without writing to disk
|
||||||
|
* Tested and fully typed with **Deno** + **Cliffy**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛠️ Installation
|
## 🛠️ Installation
|
||||||
|
|
||||||
```bash
|
You can install `systemd-timer` directly via shell script:
|
||||||
git clone https://git.0xmax42.io/maxp/systemd-timer.git
|
|
||||||
cd systemd-timer
|
|
||||||
deno task build
|
|
||||||
|
|
||||||
# Binary liegt nun unter ./systemd-timer
|
```bash
|
||||||
./systemd-timer --help
|
curl -fsSL https://git.0xmax42.io/maxp/systemd-timer/raw/branch/main/scripts/install.sh | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The script automatically detects your platform (Linux `amd64` or `arm64`) and installs the appropriate binary to `/usr/local/bin`, if permitted (possibly using `sudo`).
|
||||||
|
|
||||||
|
**Note:**
|
||||||
|
|
||||||
|
* A working internet connection is required for installation.
|
||||||
|
* The integrity of the binary is verified using a SHA256 checksum.
|
||||||
|
* You can manually inspect the script before execution:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://git.0xmax42.io/maxp/systemd-timer/raw/branch/main/scripts/install.sh -o install.sh
|
||||||
|
less install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional options and manual installation methods are available under [`scripts/install.sh`](scripts/install.sh).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📦 Beispiel
|
## 📦 Example
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./systemd-timer create \
|
./systemd-timer create \
|
||||||
@@ -44,11 +63,12 @@ deno task build
|
|||||||
--logfile "/var/log/backup.log"
|
--logfile "/var/log/backup.log"
|
||||||
```
|
```
|
||||||
|
|
||||||
Erzeugt:
|
This creates:
|
||||||
- `~/.config/systemd/user/backup.service`
|
|
||||||
- `~/.config/systemd/user/backup.timer`
|
|
||||||
|
|
||||||
Anschließend aktivieren:
|
* `~/.config/systemd/user/backup.service`
|
||||||
|
* `~/.config/systemd/user/backup.timer`
|
||||||
|
|
||||||
|
Activate afterwards:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
systemctl --user daemon-reload
|
systemctl --user daemon-reload
|
||||||
@@ -57,7 +77,7 @@ systemctl --user enable --now backup.timer
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🧪 Tests ausführen
|
## 🧪 Running Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
deno task test
|
deno task test
|
||||||
@@ -65,7 +85,7 @@ deno task test
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🧰 Entwickeln
|
## 🧰 Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
deno task start create --exec "/bin/true" --calendar "daily" --dry-run
|
deno task start create --exec "/bin/true" --calendar "daily" --dry-run
|
||||||
@@ -73,17 +93,17 @@ deno task start create --exec "/bin/true" --calendar "daily" --dry-run
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔒 Rechte / Flags
|
## 🔒 Permissions / Flags
|
||||||
|
|
||||||
Das Tool benötigt beim Ausführen bzw. Kompilieren:
|
The tool requires the following permissions when running or compiling:
|
||||||
|
|
||||||
- `--allow-env` (für `$HOME`)
|
* `--allow-env` (for `$HOME`)
|
||||||
- `--allow-write` (zum Schreiben von `.service`/`.timer`)
|
* `--allow-write` (to write `.service`/`.timer` files)
|
||||||
|
|
||||||
Beim Entwickeln wird meist `-A` (allow all) verwendet.
|
During development, usually `-A` (allow all) is used.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📝 Lizenz
|
## 📝 License
|
||||||
|
|
||||||
[MIT License](LICENSE)
|
[MIT License](LICENSE)
|
||||||
|
@@ -2,7 +2,8 @@
|
|||||||
"tasks": {
|
"tasks": {
|
||||||
"start": "deno run -A src/mod.ts",
|
"start": "deno run -A src/mod.ts",
|
||||||
"test": "deno test -A --coverage **/__tests__/*.test.ts",
|
"test": "deno test -A --coverage **/__tests__/*.test.ts",
|
||||||
"build": "deno compile --include=VERSION --allow-env --allow-write --output dist/systemd-timer src/mod.ts"
|
"build:amd64": "deno compile --target x86_64-unknown-linux-gnu --include=VERSION --allow-env --allow-write --output dist/systemd-timer-linux-amd64 src/mod.ts",
|
||||||
|
"build:arm64": "deno compile --target aarch64-unknown-linux-gnu --include=VERSION --allow-env --allow-write --output dist/systemd-timer-linux-arm64 src/mod.ts"
|
||||||
},
|
},
|
||||||
"compilerOptions": {},
|
"compilerOptions": {},
|
||||||
"fmt": {
|
"fmt": {
|
||||||
|
63
scripts/install.sh
Normal file
63
scripts/install.sh
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Fail-safe bash mode (only if bash is used)
|
||||||
|
if [ -n "$BASH_VERSION" ]; then
|
||||||
|
set -euo pipefail
|
||||||
|
else
|
||||||
|
set -eu
|
||||||
|
fi
|
||||||
|
|
||||||
|
# === Konfiguration ===
|
||||||
|
REPO_URL="https://git.0xmax42.io/maxp/systemd-timer/releases/download/latest"
|
||||||
|
BINARY_NAME="systemd-timer"
|
||||||
|
INSTALL_PATH="/usr/local/bin"
|
||||||
|
|
||||||
|
# === Systemarchitektur erkennen ===
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
case "$ARCH" in
|
||||||
|
x86_64) ARCH="amd64" ;;
|
||||||
|
aarch64 | arm64) ARCH="arm64" ;;
|
||||||
|
*) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
OS=$(uname -s)
|
||||||
|
case "$OS" in
|
||||||
|
Linux) OS="linux" ;;
|
||||||
|
*) echo "Unsupported OS: $OS" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# === Download-URL zusammensetzen ===
|
||||||
|
BINARY_FILE="${BINARY_NAME}-${OS}-${ARCH}"
|
||||||
|
DOWNLOAD_URL="${REPO_URL}/${BINARY_FILE}"
|
||||||
|
|
||||||
|
echo "📦 Installing ${BINARY_NAME} for ${OS}/${ARCH}..."
|
||||||
|
echo "🌐 Downloading from: ${DOWNLOAD_URL}"
|
||||||
|
|
||||||
|
# === Binary herunterladen ===
|
||||||
|
TMP_FILE=$(mktemp)
|
||||||
|
curl -fsSL "${DOWNLOAD_URL}" -o "${TMP_FILE}"
|
||||||
|
chmod +x "${TMP_FILE}"
|
||||||
|
|
||||||
|
# === SHA256-Check ===
|
||||||
|
TMP_HASH=$(mktemp)
|
||||||
|
curl -fsSL "${DOWNLOAD_URL}.sha256" -o "$TMP_HASH"
|
||||||
|
EXPECTED_HASH=$(cut -d ' ' -f1 "$TMP_HASH")
|
||||||
|
ACTUAL_HASH=$(openssl dgst -sha256 "$TMP_FILE" | awk '{print $2}')
|
||||||
|
|
||||||
|
if [ "$EXPECTED_HASH" != "$ACTUAL_HASH" ]; then
|
||||||
|
echo "⚠️ Checksum mismatch!"
|
||||||
|
echo "Expected: $EXPECTED_HASH"
|
||||||
|
echo "Actual: $ACTUAL_HASH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# === Installation ===
|
||||||
|
echo "🚀 Installing to ${INSTALL_PATH}/${BINARY_NAME}"
|
||||||
|
if [ -w "$INSTALL_PATH" ]; then
|
||||||
|
install -m 755 "${TMP_FILE}" "${INSTALL_PATH}/${BINARY_NAME}"
|
||||||
|
else
|
||||||
|
sudo install -m 755 "${TMP_FILE}" "${INSTALL_PATH}/${BINARY_NAME}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Installation complete: $(command -v ${BINARY_NAME})"
|
||||||
|
"${BINARY_NAME}" --version || true
|
@@ -18,6 +18,18 @@ export const createCommand = new Command()
|
|||||||
})
|
})
|
||||||
.option('--description <desc:string>', 'Beschreibung des Timers')
|
.option('--description <desc:string>', 'Beschreibung des Timers')
|
||||||
.option('--user', 'Erstellt die Unit als User-Timer')
|
.option('--user', 'Erstellt die Unit als User-Timer')
|
||||||
|
.option(
|
||||||
|
'--run-as <user:string>',
|
||||||
|
'Führe den systemweiten Timer als bestimmter Benutzer aus (setzt User= in der Service-Unit)',
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
'--home <path:string>',
|
||||||
|
'HOME-Variable für den Service setzen',
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
'--cwd <path:string>',
|
||||||
|
'Arbeitsverzeichnis (WorkingDirectory) für den Service-Prozess',
|
||||||
|
)
|
||||||
.option('--output <dir:string>', 'Zielverzeichnis der Unit-Dateien')
|
.option('--output <dir:string>', 'Zielverzeichnis der Unit-Dateien')
|
||||||
.option(
|
.option(
|
||||||
'--after <target:string>',
|
'--after <target:string>',
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
assert,
|
||||||
assertStringIncludes,
|
assertStringIncludes,
|
||||||
} from 'https://deno.land/std@0.224.0/assert/mod.ts';
|
} from 'https://deno.land/std@0.224.0/assert/mod.ts';
|
||||||
import { TimerOptions } from '../../types/mod.ts';
|
import { TimerOptions } from '../../types/mod.ts';
|
||||||
@@ -53,3 +54,63 @@ Deno.test('generateUnits berücksichtigt environment und logfile', () => {
|
|||||||
assertStringIncludes(serviceUnit, 'StandardOutput=append:/var/log/job.log');
|
assertStringIncludes(serviceUnit, 'StandardOutput=append:/var/log/job.log');
|
||||||
assertStringIncludes(serviceUnit, 'StandardError=append:/var/log/job.log');
|
assertStringIncludes(serviceUnit, 'StandardError=append:/var/log/job.log');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test('generateUnits berücksichtigt runAs', () => {
|
||||||
|
const opts: TimerOptions = {
|
||||||
|
exec: '/bin/true',
|
||||||
|
calendar: 'daily',
|
||||||
|
runAs: 'myuser',
|
||||||
|
};
|
||||||
|
const { serviceUnit } = generateUnits('job', opts);
|
||||||
|
|
||||||
|
assertStringIncludes(serviceUnit, 'User=myuser');
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('generateUnits berücksichtigt home', () => {
|
||||||
|
const opts: TimerOptions = {
|
||||||
|
exec: '/bin/true',
|
||||||
|
calendar: 'daily',
|
||||||
|
home: '/home/myuser',
|
||||||
|
};
|
||||||
|
const { serviceUnit } = generateUnits('job', opts);
|
||||||
|
|
||||||
|
assertStringIncludes(serviceUnit, 'Environment=HOME=/home/myuser');
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('generateUnits berücksichtigt cwd', () => {
|
||||||
|
const opts: TimerOptions = {
|
||||||
|
exec: '/bin/true',
|
||||||
|
calendar: 'daily',
|
||||||
|
cwd: '/srv/app',
|
||||||
|
};
|
||||||
|
const { serviceUnit } = generateUnits('job', opts);
|
||||||
|
|
||||||
|
assertStringIncludes(serviceUnit, 'WorkingDirectory=/srv/app');
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('generateUnits verwendet default.target bei User-Timern', () => {
|
||||||
|
const opts: TimerOptions = {
|
||||||
|
exec: '/bin/true',
|
||||||
|
calendar: 'daily',
|
||||||
|
user: true,
|
||||||
|
};
|
||||||
|
const { timerUnit } = generateUnits('job', opts);
|
||||||
|
|
||||||
|
assertStringIncludes(timerUnit, 'WantedBy=default.target');
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('generateUnits ignoriert runAs bei --user', () => {
|
||||||
|
const opts = {
|
||||||
|
exec: '/bin/true',
|
||||||
|
calendar: 'daily',
|
||||||
|
user: true,
|
||||||
|
runAs: 'should-not-appear',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { serviceUnit } = generateUnits('job', opts);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
!serviceUnit.includes('User=should-not-appear'),
|
||||||
|
'User= sollte bei --user nicht enthalten sein',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@@ -12,14 +12,20 @@ export async function generateUnitFiles(options: TimerOptions): Promise<void> {
|
|||||||
console.log(`\n===== ${name}.timer =====`);
|
console.log(`\n===== ${name}.timer =====`);
|
||||||
console.log(timerUnit);
|
console.log(timerUnit);
|
||||||
} else {
|
} else {
|
||||||
const { servicePath, timerPath } = await writeUnitFiles(
|
const result = await writeUnitFiles(
|
||||||
name,
|
name,
|
||||||
serviceUnit,
|
serviceUnit,
|
||||||
timerUnit,
|
timerUnit,
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
console.log(`Service unit written to: ${servicePath}`);
|
|
||||||
console.log(`Timer unit written to: ${timerPath}`);
|
if (result) {
|
||||||
|
const { servicePath, timerPath } = result;
|
||||||
|
console.log(`Service Unit geschrieben in: ${servicePath}`);
|
||||||
|
console.log(`Timer Unit geschrieben in: ${timerPath}`);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`\nℹ️ Hinweis:`);
|
console.log(`\nℹ️ Hinweis:`);
|
||||||
|
|
||||||
@@ -45,14 +51,18 @@ export function generateUnits(name: string, options: TimerOptions): {
|
|||||||
`[Service]`,
|
`[Service]`,
|
||||||
`Type=oneshot`,
|
`Type=oneshot`,
|
||||||
`ExecStart=${options.exec}`,
|
`ExecStart=${options.exec}`,
|
||||||
|
...(options.cwd ? [`WorkingDirectory=${options.cwd}`] : []),
|
||||||
...(options.environment?.map((e) => `Environment=${e}`) ?? []),
|
...(options.environment?.map((e) => `Environment=${e}`) ?? []),
|
||||||
|
...(options.home ? [`Environment=HOME=${options.home}`] : []),
|
||||||
|
...(options.logfile
|
||||||
|
? [
|
||||||
|
`StandardOutput=append:${options.logfile}`,
|
||||||
|
`StandardError=append:${options.logfile}`,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(options.runAs && !options.user ? [`User=${options.runAs}`] : []),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (options.logfile) {
|
|
||||||
unitParts.push(`StandardOutput=append:${options.logfile}`);
|
|
||||||
unitParts.push(`StandardError=append:${options.logfile}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const serviceUnit = unitParts.join('\n');
|
const serviceUnit = unitParts.join('\n');
|
||||||
|
|
||||||
const timerParts = [
|
const timerParts = [
|
||||||
@@ -64,7 +74,7 @@ export function generateUnits(name: string, options: TimerOptions): {
|
|||||||
`Persistent=true`,
|
`Persistent=true`,
|
||||||
``,
|
``,
|
||||||
`[Install]`,
|
`[Install]`,
|
||||||
`WantedBy=timers.target`,
|
`WantedBy=${options.user ? 'default.target' : 'timers.target'}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
const timerUnit = timerParts.join('\n');
|
const timerUnit = timerParts.join('\n');
|
||||||
|
@@ -4,6 +4,9 @@ export interface TimerOptions {
|
|||||||
calendar: string;
|
calendar: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
user?: boolean;
|
user?: boolean;
|
||||||
|
runAs?: string;
|
||||||
|
home?: string;
|
||||||
|
cwd?: string;
|
||||||
output?: string;
|
output?: string;
|
||||||
after?: string[];
|
after?: string[];
|
||||||
environment?: string[];
|
environment?: string[];
|
||||||
|
@@ -24,7 +24,7 @@ Deno.test('writeUnitFiles schreibt .service und .timer korrekt', async () => {
|
|||||||
serviceContent,
|
serviceContent,
|
||||||
timerContent,
|
timerContent,
|
||||||
options as TimerOptions,
|
options as TimerOptions,
|
||||||
);
|
) as { servicePath: string; timerPath: string };
|
||||||
|
|
||||||
// Überprüfe Pfade
|
// Überprüfe Pfade
|
||||||
assertEquals(servicePath, join(tmp, 'test-backup.service'));
|
assertEquals(servicePath, join(tmp, 'test-backup.service'));
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { ensureDir } from 'https://deno.land/std@0.224.0/fs/mod.ts';
|
import { ensureDir, exists } from 'https://deno.land/std@0.224.0/fs/mod.ts';
|
||||||
import { join } from 'https://deno.land/std@0.224.0/path/mod.ts';
|
import { join } from 'https://deno.land/std@0.224.0/path/mod.ts';
|
||||||
import { TimerOptions } from '../types/mod.ts';
|
import { TimerOptions } from '../types/mod.ts';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ export async function writeUnitFiles(
|
|||||||
serviceContent: string,
|
serviceContent: string,
|
||||||
timerContent: string,
|
timerContent: string,
|
||||||
options: TimerOptions,
|
options: TimerOptions,
|
||||||
): Promise<{ servicePath: string; timerPath: string }> {
|
): Promise<{ servicePath: string; timerPath: string } | undefined> {
|
||||||
const basePath = resolveUnitTargetPath(options);
|
const basePath = resolveUnitTargetPath(options);
|
||||||
|
|
||||||
await ensureDir(basePath);
|
await ensureDir(basePath);
|
||||||
@@ -15,8 +15,24 @@ export async function writeUnitFiles(
|
|||||||
const servicePath = join(basePath, `${name}.service`);
|
const servicePath = join(basePath, `${name}.service`);
|
||||||
const timerPath = join(basePath, `${name}.timer`);
|
const timerPath = join(basePath, `${name}.timer`);
|
||||||
|
|
||||||
|
try {
|
||||||
await Deno.writeTextFile(servicePath, serviceContent);
|
await Deno.writeTextFile(servicePath, serviceContent);
|
||||||
await Deno.writeTextFile(timerPath, timerContent);
|
await Deno.writeTextFile(timerPath, timerContent);
|
||||||
|
} catch (error) {
|
||||||
|
// Rollback: Remove any files that were written
|
||||||
|
try {
|
||||||
|
if (await exists(servicePath)) {
|
||||||
|
await Deno.remove(servicePath);
|
||||||
|
}
|
||||||
|
if (await exists(timerPath)) {
|
||||||
|
await Deno.remove(timerPath);
|
||||||
|
}
|
||||||
|
} catch (rollbackError) {
|
||||||
|
console.error('Rollback fehlgeschlagen:', rollbackError);
|
||||||
|
}
|
||||||
|
console.error('Fehler beim Schreiben der Units:', error);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
return { servicePath, timerPath };
|
return { servicePath, timerPath };
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user