Compare commits
23 Commits
v0.2.2
...
440130f782
| Author | SHA1 | Date | |
|---|---|---|---|
|
440130f782
|
|||
|
2a13ee2539
|
|||
|
8efbee1ba9
|
|||
|
bd5ea80aff
|
|||
|
c9b4c8bd71
|
|||
|
dfa92d8069
|
|||
| ef052e9f66 | |||
|
c53576a700
|
|||
| 3d74ec37e4 | |||
|
c4855ed3fb
|
|||
|
07ee03b6be
|
|||
|
fb2a62d984
|
|||
|
113103f368
|
|||
| f112002249 | |||
|
0c1d8be79f
|
|||
| e3caf0bba9 | |||
|
287cd741b4
|
|||
| 8b29105686 | |||
|
27c7367ef1
|
|||
| f9cef4b70d | |||
|
e3a3e61bce
|
|||
| bcec1f8f90 | |||
|
cf483de06b
|
@@ -4,7 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- '**'
|
||||
- "**"
|
||||
|
||||
jobs:
|
||||
detect-version-change:
|
||||
@@ -152,7 +152,6 @@ jobs:
|
||||
|
||||
echo "changelog_body_path=$BODY" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
- name: Commit updated CHANGELOG
|
||||
run: |
|
||||
git add CHANGELOG.md
|
||||
@@ -169,6 +168,8 @@ jobs:
|
||||
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
|
||||
echo "Tag v$VERSION already exists, skipping tag creation."
|
||||
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 push origin "v$VERSION"
|
||||
fi
|
||||
|
||||
@@ -42,3 +42,15 @@ jobs:
|
||||
|
||||
- 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
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -10,6 +10,7 @@
|
||||
"peacock.color": "#c75c5c",
|
||||
"exportall.config.folderListener": [
|
||||
"/src/utils",
|
||||
"/src/types"
|
||||
"/src/types",
|
||||
"/src/i18n"
|
||||
]
|
||||
}
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -2,6 +2,35 @@
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
74
README.md
74
README.md
@@ -1,53 +1,58 @@
|
||||
# systemd-timer
|
||||
|
||||

|
||||
- 
|
||||
- [Deutsche Version dieser Readme](README.DE.md)
|
||||
|
||||
Ein einfaches CLI-Tool zum schnellen Erzeugen von systemd `.service` und `.timer` Units – als Ersatz oder moderne Ergänzung zu klassischen `cron`-Jobs.
|
||||
A simple CLI tool for quickly generating systemd `.service` and `.timer` units — as a replacement or modern supplement to classic `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`
|
||||
- `--exec`
|
||||
- `--after`
|
||||
- `--environment`
|
||||
- `--output`
|
||||
- `--dry-run`
|
||||
- Getestet und typisiert mit Deno + Cliffy
|
||||
* Generates `.service` and `.timer` files via CLI
|
||||
* Supports `--user` timers (for `~/.config/systemd/user/`)
|
||||
* Optional logging (`StandardOutput/StandardError`)
|
||||
* Supports:
|
||||
* `--calendar`: Timer schedule (systemd `OnCalendar`)
|
||||
* `--exec`: Command to execute (`ExecStart`)
|
||||
* `--description`: Description for the unit
|
||||
* `--after`: `After=` dependencies in the service unit
|
||||
* `--environment`: Arbitrary `Environment=KEY=VALUE` entries
|
||||
* `--output`: Target directory for the generated unit files
|
||||
* `--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
|
||||
|
||||
|
||||
Du kannst `systemd-timer` direkt per Shell-Skript installieren:
|
||||
You can install `systemd-timer` directly via shell script:
|
||||
|
||||
```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`).
|
||||
The script automatically detects your platform (Linux `amd64` or `arm64`) and installs the appropriate binary to `/usr/local/bin`, if permitted (possibly using `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:
|
||||
**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
|
||||
```
|
||||
|
||||
Weitere Optionen und manuelle Installationswege findest du unter [`scripts/install.sh`](scripts/install.sh).
|
||||
Additional options and manual installation methods are available under [`scripts/install.sh`](scripts/install.sh).
|
||||
|
||||
---
|
||||
|
||||
## 📦 Beispiel
|
||||
## 📦 Example
|
||||
|
||||
```bash
|
||||
./systemd-timer create \
|
||||
@@ -58,11 +63,12 @@ Weitere Optionen und manuelle Installationswege findest du unter [`scripts/insta
|
||||
--logfile "/var/log/backup.log"
|
||||
```
|
||||
|
||||
Erzeugt:
|
||||
- `~/.config/systemd/user/backup.service`
|
||||
- `~/.config/systemd/user/backup.timer`
|
||||
This creates:
|
||||
|
||||
Anschließend aktivieren:
|
||||
* `~/.config/systemd/user/backup.service`
|
||||
* `~/.config/systemd/user/backup.timer`
|
||||
|
||||
Activate afterwards:
|
||||
|
||||
```bash
|
||||
systemctl --user daemon-reload
|
||||
@@ -71,7 +77,7 @@ systemctl --user enable --now backup.timer
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
## 🧪 Running Tests
|
||||
|
||||
```bash
|
||||
deno task test
|
||||
@@ -79,7 +85,7 @@ deno task test
|
||||
|
||||
---
|
||||
|
||||
## 🧰 Entwickeln
|
||||
## 🧰 Development
|
||||
|
||||
```bash
|
||||
deno task start create --exec "/bin/true" --calendar "daily" --dry-run
|
||||
@@ -87,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-write` (zum Schreiben von `.service`/`.timer`)
|
||||
* `--allow-env` (for `$HOME`)
|
||||
* `--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)
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"tasks": {
|
||||
"start": "deno run -A src/mod.ts",
|
||||
"test": "deno test -A --coverage **/__tests__/*.test.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"
|
||||
"build:amd64": "deno compile --target x86_64-unknown-linux-gnu --include VERSION --include src/i18n/de.json --include src/i18n/en.json --allow-env --allow-write --output dist/systemd-timer-linux-amd64 src/mod.ts",
|
||||
"build:arm64": "deno compile --target aarch64-unknown-linux-gnu --include VERSION --include src/i18n/de.json --include src/i18n/en.json --allow-env --allow-write --output dist/systemd-timer-linux-arm64 src/mod.ts"
|
||||
},
|
||||
"compilerOptions": {},
|
||||
"fmt": {
|
||||
|
||||
@@ -1,39 +1,54 @@
|
||||
import { Command } from '@cliffy/command';
|
||||
import { generateUnitFiles } from '../templates/unit-generator.ts';
|
||||
import { TimerOptions } from '../types/options.ts';
|
||||
import { t } from '../i18n/mod.ts';
|
||||
|
||||
export const createCommand = new Command()
|
||||
.description('Erzeugt eine systemd .service und .timer Unit')
|
||||
.option(
|
||||
'--name <name:string>',
|
||||
'Name der Unit-Dateien (optional, wird sonst aus dem Exec generiert)',
|
||||
)
|
||||
.option(
|
||||
'--exec <cmd:string>',
|
||||
'Kommando, das durch systemd ausgeführt werden soll',
|
||||
{ required: true },
|
||||
)
|
||||
.option('--calendar <time:string>', 'OnCalendar-Ausdruck für den Timer', {
|
||||
required: true,
|
||||
})
|
||||
.option('--description <desc:string>', 'Beschreibung des Timers')
|
||||
.option('--user', 'Erstellt die Unit als User-Timer')
|
||||
.option('--output <dir:string>', 'Zielverzeichnis der Unit-Dateien')
|
||||
.option(
|
||||
'--after <target:string>',
|
||||
'Optionales After= für die Service-Unit',
|
||||
{ collect: true },
|
||||
)
|
||||
.option(
|
||||
'--environment <env:string>',
|
||||
'Environment-Variablen im Format KEY=VALUE',
|
||||
{ collect: true },
|
||||
)
|
||||
.option(
|
||||
'--logfile <file:string>',
|
||||
'Dateipfad für Log-Ausgabe (stdout/stderr)',
|
||||
)
|
||||
.option('--dry-run', 'Gibt die Unit-Dateien nur aus, ohne sie zu schreiben')
|
||||
.action(async (options: TimerOptions) => {
|
||||
await generateUnitFiles(options);
|
||||
});
|
||||
export function createCommand() {
|
||||
return new Command()
|
||||
.description(t('cli_create_description'))
|
||||
.option(
|
||||
'--name <name:string>',
|
||||
t('option_name'),
|
||||
)
|
||||
.option(
|
||||
'--exec <cmd:string>',
|
||||
t('option_exec'),
|
||||
{ required: true },
|
||||
)
|
||||
.option('--calendar <time:string>', t('option_calendar'), {
|
||||
required: true,
|
||||
})
|
||||
.option('--description <desc:string>', t('option_description'))
|
||||
.option('--user', t('option_user'))
|
||||
.option(
|
||||
'--run-as <user:string>',
|
||||
t('option_run_as'),
|
||||
)
|
||||
.option(
|
||||
'--home <path:string>',
|
||||
t('option_home'),
|
||||
)
|
||||
.option(
|
||||
'--cwd <path:string>',
|
||||
t('option_cwd'),
|
||||
)
|
||||
.option('--output <dir:string>', t('option_output'))
|
||||
.option(
|
||||
'--after <target:string>',
|
||||
t('option_after'),
|
||||
{ collect: true },
|
||||
)
|
||||
.option(
|
||||
'--environment <env:string>',
|
||||
t('option_environment'),
|
||||
{ collect: true },
|
||||
)
|
||||
.option(
|
||||
'--logfile <file:string>',
|
||||
t('option_logfile'),
|
||||
)
|
||||
.option('--dry-run', t('option_dry_run'))
|
||||
.action(async (options: TimerOptions) => {
|
||||
await generateUnitFiles(options);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Command } from '@cliffy/command';
|
||||
import { createCommand } from './create.ts';
|
||||
import { getVersion } from '../utils/mod.ts';
|
||||
import { t } from '../i18n/mod.ts';
|
||||
import { createCommand } from './mod.ts';
|
||||
|
||||
await new Command()
|
||||
.name('systemd-timer')
|
||||
.version(await getVersion())
|
||||
.description('CLI Tool zum Erzeugen von systemd .timer und .service Units')
|
||||
.command('create', createCommand)
|
||||
.parse(Deno.args);
|
||||
export async function createCli() {
|
||||
return new Command()
|
||||
.name('systemd-timer')
|
||||
.version(await getVersion())
|
||||
.description(t('cli_description'))
|
||||
.command('create', createCommand());
|
||||
}
|
||||
|
||||
2
src/cli/mod.ts
Normal file
2
src/cli/mod.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { createCommand } from './create.ts';
|
||||
export { createCli } from './main.ts';
|
||||
32
src/i18n/__tests__/i18n.test.ts
Normal file
32
src/i18n/__tests__/i18n.test.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { assertEquals } from 'https://deno.land/std@0.224.0/assert/mod.ts';
|
||||
import { getCurrentLanguage, loadLocale, t } from '../mod.ts';
|
||||
|
||||
Deno.test('loadLocale lade Deutsche Übersetzung', async () => {
|
||||
await loadLocale('de');
|
||||
assertEquals(
|
||||
t('error_write_units'),
|
||||
'Fehler beim Schreiben der Units:',
|
||||
);
|
||||
assertEquals(
|
||||
t('unit_written_service', { path: '/tmp/service' }),
|
||||
'Service Unit geschrieben in: /tmp/service',
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test('t gibt den Schlüssel zurück, wenn dieser nicht gefunden wird', () => {
|
||||
const result = t('non_existent_key');
|
||||
assertEquals(result, 'non_existent_key');
|
||||
});
|
||||
|
||||
Deno.test('getCurrentLanguage gibt die Fallback Sprache zurück', () => {
|
||||
Deno.env.delete('SYSTEMD_TIMER_LANG');
|
||||
Deno.env.delete('LC_ALL');
|
||||
Deno.env.delete('LC_MESSAGES');
|
||||
Deno.env.delete('LANG');
|
||||
assertEquals(getCurrentLanguage(), 'en');
|
||||
});
|
||||
|
||||
Deno.test('getCurrentLanguage nutz die `SYSTEMD_TIMER_LANG`, wenn gesetzt', () => {
|
||||
Deno.env.set('SYSTEMD_TIMER_LANG', 'de_DE.UTF-8');
|
||||
assertEquals(getCurrentLanguage(), 'de');
|
||||
});
|
||||
22
src/i18n/de.json
Normal file
22
src/i18n/de.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"cli_description": "CLI-Tool zum Erzeugen von systemd .timer und .service Units",
|
||||
"cli_create_description": "Erzeugt eine systemd .service und .timer Unit",
|
||||
"option_name": "Name der Unit-Dateien (optional, wird sonst aus dem Exec generiert)",
|
||||
"option_exec": "Kommando, das durch systemd ausgeführt werden soll",
|
||||
"option_calendar": "OnCalendar-Ausdruck für den Timer",
|
||||
"option_description": "Beschreibung des Timers",
|
||||
"option_user": "Erstellt die Unit als User-Timer",
|
||||
"option_run_as": "Führe den systemweiten Timer als bestimmter Benutzer aus (setzt User= in der Service-Unit)",
|
||||
"option_home": "HOME-Variable für den Service setzen",
|
||||
"option_cwd": "Arbeitsverzeichnis (WorkingDirectory) für den Service-Prozess",
|
||||
"option_output": "Zielverzeichnis der Unit-Dateien",
|
||||
"option_after": "Optionales After= für die Service-Unit",
|
||||
"option_environment": "Environment-Variablen im Format KEY=VALUE",
|
||||
"option_logfile": "Dateipfad für Log-Ausgabe (stdout/stderr)",
|
||||
"option_dry_run": "Gibt die Unit-Dateien nur aus, ohne sie zu schreiben",
|
||||
"unit_written_service": "Service Unit geschrieben in: {path}",
|
||||
"unit_written_timer": "Timer Unit geschrieben in: {path}",
|
||||
"hint_header": "\nℹ️ Hinweis:",
|
||||
"error_write_units": "Fehler beim Schreiben der Units:",
|
||||
"rollback_failed": "Rollback fehlgeschlagen:"
|
||||
}
|
||||
22
src/i18n/en.json
Normal file
22
src/i18n/en.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"cli_description": "CLI tool for generating systemd .timer and .service units",
|
||||
"cli_create_description": "Generates a systemd .service and .timer unit",
|
||||
"option_name": "Name of the unit files (optional, otherwise derived from the exec command)",
|
||||
"option_exec": "Command to be executed by systemd",
|
||||
"option_calendar": "OnCalendar expression for the timer",
|
||||
"option_description": "Description of the timer",
|
||||
"option_user": "Creates the unit as a user timer",
|
||||
"option_run_as": "Runs the system-wide timer as a specific user (sets User= in the service unit)",
|
||||
"option_home": "Sets the HOME variable for the service",
|
||||
"option_cwd": "Working directory (WorkingDirectory) for the service process",
|
||||
"option_output": "Target directory for the unit files",
|
||||
"option_after": "Optional After= directive for the service unit",
|
||||
"option_environment": "Environment variables in the format KEY=VALUE",
|
||||
"option_logfile": "File path for log output (stdout/stderr)",
|
||||
"option_dry_run": "Only outputs the unit files without writing them",
|
||||
"unit_written_service": "Service unit written to: {path}",
|
||||
"unit_written_timer": "Timer unit written to: {path}",
|
||||
"hint_header": "\nℹ️ Note:",
|
||||
"error_write_units": "Error while writing unit files:",
|
||||
"rollback_failed": "Rollback failed:"
|
||||
}
|
||||
74
src/i18n/i18n.ts
Normal file
74
src/i18n/i18n.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Initializes the i18n module by loading
|
||||
* the appropriate locale file based on the system language.
|
||||
*/
|
||||
export async function initI18n(): Promise<void> {
|
||||
await loadLocale(getCurrentLanguage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains the loaded translations for the current language.
|
||||
* The keys represent i18n identifiers, the values are the localized strings.
|
||||
*/
|
||||
let translations: Record<string, string> = {};
|
||||
|
||||
/**
|
||||
* Loads the translation file for the specified locale.
|
||||
*
|
||||
* Expects a JSON file in the same directory named like `de.json` or `en.json`.
|
||||
* Falls back to English ('en') if the specified file does not exist.
|
||||
*
|
||||
* @param locale - The language code (e.g., 'de', 'en') to load
|
||||
* @returns Promise that resolves once the translations have been loaded
|
||||
*/
|
||||
export async function loadLocale(locale: string): Promise<void> {
|
||||
try {
|
||||
const localeUrl = new URL(`./${locale}.json`, import.meta.url);
|
||||
const file = await Deno.readTextFile(localeUrl);
|
||||
translations = JSON.parse(file);
|
||||
} catch (err) {
|
||||
if (err instanceof Deno.errors.NotFound) {
|
||||
console.warn(
|
||||
`Locale '${locale}' not found – falling back to 'en'.`,
|
||||
);
|
||||
if (locale !== 'en') {
|
||||
await loadLocale('en');
|
||||
}
|
||||
} else {
|
||||
console.error('Error loading translation file:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a given key using the loaded translation data.
|
||||
* Replaces placeholders in the format `{variable}` with the provided values.
|
||||
*
|
||||
* @param key - The i18n key (e.g., 'timer_created')
|
||||
* @param vars - Optional replacements for placeholders in the string
|
||||
* @returns The translated string, or the key itself if no translation is found
|
||||
*/
|
||||
export function t(key: string, vars: Record<string, string> = {}): string {
|
||||
let text = translations[key] ?? key;
|
||||
for (const [k, v] of Object.entries(vars)) {
|
||||
text = text.replace(`{${k}}`, v);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the current language from the system environment.
|
||||
*
|
||||
* Priority: SYSTEMD_TIMER_LANG > LC_ALL > LC_MESSAGES > LANG.
|
||||
*
|
||||
* @returns The language code (e.g., 'de', 'en'), defaults to 'en'
|
||||
*/
|
||||
export function getCurrentLanguage(): string {
|
||||
return (
|
||||
Deno.env.get('SYSTEMD_TIMER_LANG') ??
|
||||
Deno.env.get('LC_ALL') ??
|
||||
Deno.env.get('LC_MESSAGES') ??
|
||||
Deno.env.get('LANG') ??
|
||||
'en'
|
||||
).split('.')[0].split('_')[0].toLowerCase();
|
||||
}
|
||||
1
src/i18n/mod.ts
Normal file
1
src/i18n/mod.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { getCurrentLanguage, initI18n, loadLocale, t } from './i18n.ts';
|
||||
@@ -1,5 +1,8 @@
|
||||
import './cli/main.ts';
|
||||
import { createCli } from './cli/mod.ts';
|
||||
import { initI18n } from './i18n/mod.ts';
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// Entry Point for CLI
|
||||
// Delegates to src/cli/main.ts, which registers all CLI commands
|
||||
|
||||
await initI18n();
|
||||
await (await createCli()).parse(Deno.args);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
assert,
|
||||
assertStringIncludes,
|
||||
} from 'https://deno.land/std@0.224.0/assert/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, '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',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { t } from '../i18n/mod.ts';
|
||||
import { TimerOptions } from '../types/mod.ts';
|
||||
import { deriveNameFromExec, writeUnitFiles } from '../utils/mod.ts';
|
||||
|
||||
@@ -21,13 +22,13 @@ export async function generateUnitFiles(options: TimerOptions): Promise<void> {
|
||||
|
||||
if (result) {
|
||||
const { servicePath, timerPath } = result;
|
||||
console.log(`Service Unit geschrieben in: ${servicePath}`);
|
||||
console.log(`Timer Unit geschrieben in: ${timerPath}`);
|
||||
console.log(t('unit_written_service', { path: servicePath }));
|
||||
console.log(t('unit_written_timer', { path: timerPath }));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`\nℹ️ Hinweis:`);
|
||||
console.log(t('hint_header'));
|
||||
|
||||
if (options.user) {
|
||||
console.log(` systemctl --user daemon-reload`);
|
||||
@@ -51,14 +52,18 @@ export function generateUnits(name: string, options: TimerOptions): {
|
||||
`[Service]`,
|
||||
`Type=oneshot`,
|
||||
`ExecStart=${options.exec}`,
|
||||
...(options.cwd ? [`WorkingDirectory=${options.cwd}`] : []),
|
||||
...(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 timerParts = [
|
||||
@@ -70,7 +75,7 @@ export function generateUnits(name: string, options: TimerOptions): {
|
||||
`Persistent=true`,
|
||||
``,
|
||||
`[Install]`,
|
||||
`WantedBy=timers.target`,
|
||||
`WantedBy=${options.user ? 'default.target' : 'timers.target'}`,
|
||||
];
|
||||
|
||||
const timerUnit = timerParts.join('\n');
|
||||
|
||||
@@ -4,6 +4,9 @@ export interface TimerOptions {
|
||||
calendar: string;
|
||||
description?: string;
|
||||
user?: boolean;
|
||||
runAs?: string;
|
||||
home?: string;
|
||||
cwd?: string;
|
||||
output?: string;
|
||||
after?: string[];
|
||||
environment?: string[];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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 { TimerOptions } from '../types/mod.ts';
|
||||
import { t } from '../i18n/mod.ts';
|
||||
|
||||
export async function writeUnitFiles(
|
||||
name: string,
|
||||
@@ -28,9 +29,9 @@ export async function writeUnitFiles(
|
||||
await Deno.remove(timerPath);
|
||||
}
|
||||
} catch (rollbackError) {
|
||||
console.error('Rollback fehlgeschlagen:', rollbackError);
|
||||
console.error(t('rollback_failed'), rollbackError);
|
||||
}
|
||||
console.error('Fehler beim Schreiben der Units:', error);
|
||||
console.error(t('error_write_units'), error);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user