65 Commits

Author SHA1 Message Date
38afcf210e chore(changelog): update changelog for v0.4.1
All checks were successful
Upload Assets / upload-assets (amd64, linux) (release) Successful in 28s
Upload Assets / upload-assets (arm64, linux) (release) Successful in 29s
2025-05-28 16:34:22 +00:00
28b18dc994 chore(version): bump version to 0.4.1
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / changelog-only (push) Has been skipped
CI / build (push) Successful in 7s
Auto Changelog & Release / release (push) Successful in 7s
2025-05-28 18:34:09 +02:00
ede012317b chore(changelog): update unreleased changelog 2025-05-28 16:34:01 +00:00
a22c156dd3 fix(tasks): add read permissions to build scripts
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
CI / build (push) Successful in 6s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 6s
- Allow read permissions in build scripts for both amd64 and arm64 targets
- Ensure compatibility with files required during the build process
2025-05-28 18:33:47 +02:00
d648d5a3f1 chore(changelog): update changelog for v0.4.0
All checks were successful
Upload Assets / upload-assets (amd64, linux) (release) Successful in 28s
Upload Assets / upload-assets (arm64, linux) (release) Successful in 27s
2025-05-28 16:17:26 +00:00
bb51982f6e chore(version): bump to 0.4.0
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / changelog-only (push) Has been skipped
CI / build (push) Successful in 6s
Auto Changelog & Release / release (push) Successful in 13s
2025-05-28 18:17:07 +02:00
3416610486 chore(changelog): update unreleased changelog 2025-05-28 16:15:06 +00:00
54d71ba3f0 style(i18n): add missing newline at EOF in JSON files
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 2s
CI / build (push) Successful in 7s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 6s
- Ensures JSON files adhere to standard formatting by adding a newline
  at the end of the file.
2025-05-28 18:14:51 +02:00
c02da70902 feat(vscode): enable Deno support and configure JSON formatting
- Enables Deno integration in VS Code settings
- Sets the default JSON formatter to Deno's extension
- Activates format-on-save for improved workflow
2025-05-28 18:14:51 +02:00
9ad407e531 feat(ci): add CI workflow with format, lint, and test steps
- Introduces a CI workflow triggered on push, pull requests, and manual dispatch
- Includes steps for code formatting, linting, and testing using Deno
- Fails the workflow if any of the steps do not succeed
2025-05-28 18:14:51 +02:00
07730e5761 feat(tasks): add formatting, linting, and CI tasks
- Introduces new tasks for code formatting and linting
- Adds a CI task combining formatting, linting, and testing
2025-05-28 18:14:51 +02:00
1f79c1a15a chore(changelog): update unreleased changelog 2025-05-28 16:10:19 +00:00
440130f782 feat(tasks): include localization files in build commands
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 6s
- Add German and English localization files to build outputs
- Ensure compiled binaries include required resources for i18n support
2025-05-28 18:10:04 +02:00
2a13ee2539 refactor(cli): integrate i18n support across commands
- Centralize CLI text strings using the i18n module for localization
- Refactor `createCommand` and `createCli` to improve modularity
- Update logging and error messages to use translated strings
2025-05-28 18:09:52 +02:00
8efbee1ba9 test(i18n): add unit tests for localization functions
- Add tests for `loadLocale` to verify translations load correctly
- Add tests for `t` to ensure fallback behavior for missing keys
- Add tests for `getCurrentLanguage` to validate language detection logic
2025-05-28 18:09:24 +02:00
bd5ea80aff feat(i18n): add German and English translations for CLI tool
- Introduce localization files for German and English languages
- Provide translations for CLI tool options and error messages
2025-05-28 18:09:16 +02:00
c9b4c8bd71 feat(i18n): add i18n module for localization support
- Introduce functions to initialize and load locale files dynamically
- Add support for translation keys with placeholder replacements
- Default to English if locale files are missing or not found
- Determine system language using environment variables
2025-05-28 18:09:04 +02:00
dfa92d8069 chore(vscode): update folder listener with i18n directory
- Adds the /src/i18n directory to the folder listener configuration
- Ensures changes in the i18n folder are tracked as part of the setup
2025-05-28 18:08:37 +02:00
ef052e9f66 chore(changelog): update changelog for v0.3.1
All checks were successful
Upload Assets / upload-assets (arm64, linux) (release) Successful in 29s
Upload Assets / upload-assets (amd64, linux) (release) Successful in 43s
2025-05-28 12:40:53 +00:00
c53576a700 chore(version): bump version to 0.3.1
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / changelog-only (push) Has been skipped
Auto Changelog & Release / release (push) Successful in 8s
2025-05-28 14:40:42 +02:00
3d74ec37e4 chore(changelog): update unreleased changelog 2025-05-28 12:40:16 +00:00
c4855ed3fb style(workflows): fix formatting and whitespace issues
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 7s
- Standardize quotes in branch matching patterns
- Remove unnecessary trailing whitespace
- Add export of author and committer dates for consistent tags
- Ensure proper environment variable alignment
2025-05-28 14:39:42 +02:00
07ee03b6be chore(version): bump version to 0.3.0
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / changelog-only (push) Has been skipped
Auto Changelog & Release / release (push) Successful in 8s
2025-05-28 14:26:02 +02:00
fb2a62d984 docs(readme): expand CLI option descriptions for clarity
- Add detailed explanations for each CLI option in README files
- Improve consistency and formatting for better readability
- Highlight supported features and usage comprehensively
2025-05-28 14:26:02 +02:00
113103f368 feat(cli): add options for user, home, and working directory
- Add `--run-as` option to specify user for system-wide timers
- Add `--home` option to set the HOME environment variable
- Add `--cwd` option to define the working directory
- Update tests to validate new options and behavior
2025-05-28 14:26:02 +02:00
f112002249 chore(changelog): update unreleased changelog 2025-05-26 15:27:10 +00:00
0c1d8be79f chore(workflows): consolidate release sync into upload workflow
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 6s
- Remove separate workflow for syncing releases to GitHub
- Integrate release sync steps into the upload-assets workflow
- Simplify workflow management and reduce duplication
2025-05-26 17:27:00 +02:00
e3caf0bba9 chore(changelog): update changelog for v0.2.3
All checks were successful
Sync Release to GitHub / sync-github (release) Successful in 6s
Upload Assets / upload-assets (amd64, linux) (release) Successful in 13s
Upload Assets / upload-assets (arm64, linux) (release) Successful in 12s
2025-05-26 15:24:44 +00:00
287cd741b4 chore(version): bump version to 0.2.3
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 2s
Auto Changelog & Release / changelog-only (push) Has been skipped
Auto Changelog & Release / release (push) Successful in 8s
2025-05-26 17:24:28 +02:00
8b29105686 chore(changelog): update unreleased changelog 2025-05-26 15:22:28 +00:00
27c7367ef1 feat(workflows): add GitHub release synchronization workflow
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 2s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 6s
- Introduces a workflow to sync Gitea releases with GitHub
- Supports manual dispatch and reacts to release events
- Utilizes secrets for secure token management
2025-05-26 17:22:17 +02:00
f9cef4b70d chore(changelog): update unreleased changelog 2025-05-22 18:52:03 +00:00
e3a3e61bce docs(readme): update project time badge interval
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 29s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 32s
- Adjusts the project time badge interval from "today" to "any"
2025-05-22 20:50:59 +02:00
bcec1f8f90 chore(changelog): update unreleased changelog 2025-05-22 11:25:35 +00:00
cf483de06b docs: add Englisch README
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 4s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 6s
2025-05-22 13:25:23 +02:00
d9c9eda823 chore(changelog): update changelog for v0.2.2
All checks were successful
Upload Assets / upload-assets (amd64, linux) (release) Successful in 9s
Upload Assets / upload-assets (arm64, linux) (release) Successful in 9s
2025-05-22 08:59:11 +00:00
4737dbb60d chore(version): bump version to 0.2.2
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / changelog-only (push) Has been skipped
Auto Changelog & Release / release (push) Successful in 6s
2025-05-22 10:58:59 +02:00
f82249d10f chore(changelog): update changelog for v0.2.1
All checks were successful
Upload Assets / upload-assets (arm64, linux) (release) Successful in 11s
Upload Assets / upload-assets (amd64, linux) (release) Successful in 14s
2025-05-22 08:39:39 +00:00
d46ad2c2b4 chore(version): bump version to 0.2.1
Some checks failed
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / changelog-only (push) Has been skipped
Auto Changelog & Release / release (push) Failing after 7s
2025-05-22 10:39:24 +02:00
7911a7ed6f chore(changelog): update unreleased changelog 2025-05-22 08:38:13 +00:00
9853f854c9 docs(readme): update installation instructions with script
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 5s
- Add installation instructions using a shell script for convenience
- Highlight automatic platform detection and binary integrity verification
- Provide guidance for manual inspection and alternative installation options
2025-05-22 10:38:03 +02:00
a2c8ff0f84 chore(changelog): update unreleased changelog 2025-05-22 08:37:28 +00:00
0ca8ed94cc fix(install): enhance checksum validation with detailed comparison
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 4s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 6s
- Replaces `sha256sum` with a detailed checksum comparison using OpenSSL
- Improves error messaging by displaying both expected and actual hashes
2025-05-22 10:37:15 +02:00
227a0426b5 chore(changelog): update unreleased changelog 2025-05-22 08:35:32 +00:00
20d143035e fix(install): ensure compatibility with non-bash shells
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 5s
- Add conditional to enable fail-safe mode only for bash shells
2025-05-22 10:35:17 +02:00
7b5b855774 chore(changelog): update changelog for v0.2.0
All checks were successful
Upload Assets / upload-assets (arm64, linux) (release) Successful in 15s
Upload Assets / upload-assets (amd64, linux) (release) Successful in 17s
2025-05-22 08:31:49 +00:00
6fc6207da8 chore(version): update to 0.2.0
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 4s
Auto Changelog & Release / changelog-only (push) Has been skipped
Auto Changelog & Release / release (push) Successful in 32s
2025-05-22 10:21:55 +02:00
264b43c9a6 feat(scripts): add installation script for systemd-timer binary
- Introduces a Bash script to install the systemd-timer binary
- Detects OS and architecture to download the appropriate binary
- Verifies the binary using SHA256 checksum for security
- Supports installation with or without sudo privileges
2025-05-22 10:21:24 +02:00
118e4e5a86 feat(workflows): add matrix build and SHA256 generation for releases
- Introduce matrix strategy for building and uploading binaries
  for multiple architectures (amd64, arm64).
- Generate SHA256 checksum files for release binaries.
- Upload both binaries and their corresponding SHA256 files
  as release assets for better integrity verification.
2025-05-22 10:20:07 +02:00
01898a3a8e feat(tasks): add build tasks for amd64 and arm64 targets
- Introduces separate build tasks for amd64 and arm64 architectures
- Enables cross-compilation for better platform support
2025-05-22 10:19:02 +02:00
1a1ad66ab6 chore(changelog): update unreleased changelog 2025-05-21 07:51:29 +00:00
bd71b8ee14 fix(utils): handle file write failures with rollback
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 5s
- Add error handling and rollback logic for unit file writes
- Prevent partial file writes by removing created files on failure
- Update tests to reflect new return type and error handling
2025-05-21 09:51:16 +02:00
a76417ce1d chore(changelog): update unreleased changelog 2025-05-21 07:21:11 +00:00
a288dbc140 docs(readme): add project time badge
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 4s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 8s
2025-05-21 09:20:54 +02:00
c3a6957a9e chore(changelog): update changelog for v0.1.0
All checks were successful
Upload Assets / upload-assets (release) Successful in 15s
2025-05-21 01:48:51 +00:00
10060db8cb chore(version): add initial version file
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / changelog-only (push) Has been skipped
Auto Changelog & Release / release (push) Successful in 6s
- Introduces a VERSION file to track the project's version
- Sets the initial version to 0.1.0
2025-05-21 03:48:39 +02:00
283a0d3905 chore(changelog): update unreleased changelog 2025-05-21 01:47:52 +00:00
1012ca5378 feat(workflows): add release asset upload workflow
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 5s
- Introduces a workflow to upload release assets in Gitea
- Executes on published release events to associate artifacts
- Ensures artifacts match the release state using tagged checkout
2025-05-21 03:47:34 +02:00
6e00e89bb0 chore(tasks): include version file in build process
- Adds the `--include=VERSION` flag to ensure the version file is part
  of the build output for consistency.
2025-05-21 03:47:24 +02:00
403e047c0c feat(cli): use dynamic version retrieval
- Replace hardcoded version with dynamic retrieval using `getVersion`
- Improves maintainability by avoiding manual version updates
2025-05-21 03:47:14 +02:00
56fb554f13 feat(utils): add version retrieval utility
- Introduces a function to retrieve the application version
- Returns 'dev' if the version file is missing and 'unknown' for other errors
- Exports the new utility for use in other modules
2025-05-21 03:47:02 +02:00
8ed98cc998 chore(changelog): update unreleased changelog 2025-05-21 01:13:51 +00:00
f81bb53353 feat(generator): add systemctl usage instructions
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 6s
- Log systemctl commands for user and root configurations
- Help users reload and enable timers more easily
2025-05-21 03:13:27 +02:00
db1f56c539 docs: add README for systemd-timer CLI tool
- Introduces a README file describing the systemd-timer tool
- Highlights features, installation steps, and usage examples
- Provides details on testing, development, and required permissions
2025-05-21 03:10:54 +02:00
e1cd5dfd35 docs: add MIT license file 2025-05-21 03:09:40 +02:00
27 changed files with 868 additions and 66 deletions

46
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,46 @@
name: CI
on:
workflow_dispatch:
push:
branches:
- main
pull_request:
types:
- opened
- synchronize
- reopened
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: denoland/setup-deno@v1
with:
deno-version: v2.x
- name: Format
id: format
continue-on-error: true
run: deno task fmt
- name: Lint
id: lint
continue-on-error: true
run: deno task lint
- name: Test
id: test
continue-on-error: true
run: deno task test
- name: Fail if any step failed
if: |
steps.format.outcome != 'success' ||
steps.lint.outcome != 'success' ||
steps.test.outcome != 'success'
run: |
echo "::error::One or more steps failed"
exit 1

View File

@@ -4,7 +4,7 @@ on:
push: push:
branches: branches:
- main - main
- '**' - "**"
jobs: jobs:
detect-version-change: detect-version-change:
@@ -125,7 +125,7 @@ jobs:
if: steps.restore-cliff.outputs.cache-hit != 'true' if: steps.restore-cliff.outputs.cache-hit != 'true'
run: | run: |
cargo install git-cliff --version "${{ steps.cliff_version.outputs.version }}" --features gitea cargo install git-cliff --version "${{ steps.cliff_version.outputs.version }}" --features gitea
- name: Generate changelog for release and tag - name: Generate changelog for release and tag
id: generate-changelog id: generate-changelog
run: | run: |
@@ -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,12 +168,14 @@ 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
- name: Create Gitea release - name: Create Gitea release
env: env:
RELEASE_PUBLISH_TOKEN: ${{ secrets.RELEASE_PUBLISH_TOKEN }} RELEASE_PUBLISH_TOKEN: ${{ secrets.RELEASE_PUBLISH_TOKEN }}
run: | run: |
VERSION=${{ steps.version.outputs.value }} VERSION=${{ steps.version.outputs.value }}

View File

@@ -0,0 +1,56 @@
name: Upload Assets
on:
release:
types: [published]
jobs:
upload-assets:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- target: linux
arch: amd64
- target: linux
arch: arm64
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
fetch-depth: 0
- name: Get Release ID from tag
run: .gitea/scripts/get-release-id.sh "${{ github.event.release.tag_name }}"
- uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Build ${{ matrix.target }}-${{ matrix.arch }}
run: deno task build:${{ matrix.arch }}
- name: Generate SHA256 for ${{ matrix.target }}-${{ matrix.arch }}
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

10
.vscode/settings.json vendored
View File

@@ -10,6 +10,12 @@
"peacock.color": "#c75c5c", "peacock.color": "#c75c5c",
"exportall.config.folderListener": [ "exportall.config.folderListener": [
"/src/utils", "/src/utils",
"/src/types" "/src/types",
] "/src/i18n"
],
"deno.enable": true,
"[json]": {
"editor.defaultFormatter": "denoland.vscode-deno"
},
"editor.formatOnSave": true
} }

View File

@@ -2,10 +2,103 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [unreleased] ## [0.4.1](https://git.0xmax42.io/maxp/systemd-timer/compare/v0.4.0..v0.4.1) - 2025-05-28
### 🐛 Bug Fixes
- *(tasks)* Add read permissions to build scripts - ([a22c156](https://git.0xmax42.io/maxp/systemd-timer/commit/a22c156dd3d2cf4a24f0eed699f7dfabfae3837a))
## [0.4.0](https://git.0xmax42.io/maxp/systemd-timer/compare/v0.3.1..v0.4.0) - 2025-05-28
### 🚀 Features ### 🚀 Features
- *(vscode)* Enable Deno support and configure JSON formatting - ([c02da70](https://git.0xmax42.io/maxp/systemd-timer/commit/c02da709028e1fbb175d5091fbd9d3ed2940cdcd))
- *(ci)* Add CI workflow with format, lint, and test steps - ([9ad407e](https://git.0xmax42.io/maxp/systemd-timer/commit/9ad407e531270445d9657402fa3e826a7dabd880))
- *(tasks)* Add formatting, linting, and CI tasks - ([07730e5](https://git.0xmax42.io/maxp/systemd-timer/commit/07730e576180be3f6a16b0fda6c6554a86844eee))
- *(tasks)* Include localization files in build commands - ([440130f](https://git.0xmax42.io/maxp/systemd-timer/commit/440130f782b1fc51053164410ead29397b867892))
- *(i18n)* Add German and English translations for CLI tool - ([bd5ea80](https://git.0xmax42.io/maxp/systemd-timer/commit/bd5ea80aff5092118920ea897af6c3f5f9fb2a3b))
- *(i18n)* Add i18n module for localization support - ([c9b4c8b](https://git.0xmax42.io/maxp/systemd-timer/commit/c9b4c8bd71029976fe900b40a2297b52200a216b))
### 🚜 Refactor
- *(cli)* Integrate i18n support across commands - ([2a13ee2](https://git.0xmax42.io/maxp/systemd-timer/commit/2a13ee2539d96d161a9ee398629fa79822d856f2))
### 🎨 Styling
- *(i18n)* Add missing newline at EOF in JSON files - ([54d71ba](https://git.0xmax42.io/maxp/systemd-timer/commit/54d71ba3f00ced25313036d9f10f6fb01feba52a))
### 🧪 Testing
- *(i18n)* Add unit tests for localization functions - ([8efbee1](https://git.0xmax42.io/maxp/systemd-timer/commit/8efbee1ba9b4fc564f5a32fcbc101ff256c5555b))
### ⚙️ Miscellaneous Tasks
- *(vscode)* Update folder listener with i18n directory - ([dfa92d8](https://git.0xmax42.io/maxp/systemd-timer/commit/dfa92d80694b5b104c26e131d1ee7c5cf69ad94c))
## [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
### 🚀 Features
- *(workflows)* Add release asset upload workflow - ([1012ca5](https://git.0xmax42.io/maxp/systemd-timer/commit/1012ca53781c36131a8b7aa43a9134f7b8565599))
- *(cli)* Use dynamic version retrieval - ([403e047](https://git.0xmax42.io/maxp/systemd-timer/commit/403e047c0c376229244a5605d5c52eb1699acd4a))
- *(utils)* Add version retrieval utility - ([56fb554](https://git.0xmax42.io/maxp/systemd-timer/commit/56fb554f132a53d74b2e9a1a02cc973c5420e73c))
- *(generator)* Add systemctl usage instructions - ([f81bb53](https://git.0xmax42.io/maxp/systemd-timer/commit/f81bb533536810fc34656d572369b94ab669a181))
- *(cli)* Add command to generate systemd unit files - ([97dc3fe](https://git.0xmax42.io/maxp/systemd-timer/commit/97dc3fe23acf2c35053aced7b34918bab7778c35)) - *(cli)* Add command to generate systemd unit files - ([97dc3fe](https://git.0xmax42.io/maxp/systemd-timer/commit/97dc3fe23acf2c35053aced7b34918bab7778c35))
- *(utils)* Export utility functions for filesystem and naming - ([428e849](https://git.0xmax42.io/maxp/systemd-timer/commit/428e84927f8a9a379fa014ea763dd61115be34d6)) - *(utils)* Export utility functions for filesystem and naming - ([428e849](https://git.0xmax42.io/maxp/systemd-timer/commit/428e84927f8a9a379fa014ea763dd61115be34d6))
- *(types)* Add TimerOptions interface for timer configuration - ([ba4b933](https://git.0xmax42.io/maxp/systemd-timer/commit/ba4b933f78c48a52b1c199fe28dc82d7ebabd7fe)) - *(types)* Add TimerOptions interface for timer configuration - ([ba4b933](https://git.0xmax42.io/maxp/systemd-timer/commit/ba4b933f78c48a52b1c199fe28dc82d7ebabd7fe))
@@ -16,6 +109,11 @@ All notable changes to this project will be documented in this file.
- *(utils)* Update import path for TimerOptions - ([316f3af](https://git.0xmax42.io/maxp/systemd-timer/commit/316f3af04ef7fe4c08963cfe3ad7780ed3bc262c)) - *(utils)* Update import path for TimerOptions - ([316f3af](https://git.0xmax42.io/maxp/systemd-timer/commit/316f3af04ef7fe4c08963cfe3ad7780ed3bc262c))
### 📚 Documentation
- Add README for systemd-timer CLI tool - ([db1f56c](https://git.0xmax42.io/maxp/systemd-timer/commit/db1f56c539309b8a02adff114d765c725ac5ff8a))
- Add MIT license file - ([e1cd5df](https://git.0xmax42.io/maxp/systemd-timer/commit/e1cd5dfd353c7cd7ca770daae5fc40405e461d1d))
### 🧪 Testing ### 🧪 Testing
- *(generate)* Add unit tests for service and timer generation - ([569b14d](https://git.0xmax42.io/maxp/systemd-timer/commit/569b14d57432589107a0f33e52881b605c5f79f9)) - *(generate)* Add unit tests for service and timer generation - ([569b14d](https://git.0xmax42.io/maxp/systemd-timer/commit/569b14d57432589107a0f33e52881b605c5f79f9))
@@ -23,6 +121,7 @@ All notable changes to this project will be documented in this file.
### ⚙️ Miscellaneous Tasks ### ⚙️ Miscellaneous Tasks
- *(tasks)* Include version file in build process - ([6e00e89](https://git.0xmax42.io/maxp/systemd-timer/commit/6e00e89bb086672b9c3276ffeebcb1ded28c836f))
- Add VSCode settings for color customizations and folder listener - ([6608f48](https://git.0xmax42.io/maxp/systemd-timer/commit/6608f488405adefc7993f47a137a824e5de62154)) - Add VSCode settings for color customizations and folder listener - ([6608f48](https://git.0xmax42.io/maxp/systemd-timer/commit/6608f488405adefc7993f47a137a824e5de62154))
- *(config)* Add deno configuration and lockfile - ([0b72050](https://git.0xmax42.io/maxp/systemd-timer/commit/0b720500e0fe34db087b3277c38fa6bb07875e80)) - *(config)* Add deno configuration and lockfile - ([0b72050](https://git.0xmax42.io/maxp/systemd-timer/commit/0b720500e0fe34db087b3277c38fa6bb07875e80))
- Add automated release workflow and scripts for version management - ([a058e7b](https://git.0xmax42.io/maxp/systemd-timer/commit/a058e7b6838d41a98f3269db9a9d1e31f752121f)) - Add automated release workflow and scripts for version management - ([a058e7b](https://git.0xmax42.io/maxp/systemd-timer/commit/a058e7b6838d41a98f3269db9a9d1e31f752121f))

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 0xMax42
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

107
README.DE.md Normal file
View File

@@ -0,0 +1,107 @@
# systemd-timer
![Project time](https://waka.0xmax42.io/api/badge/0XMax42/interval:any/project:systemd-timer?label=Project%20time)
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)

109
README.md Normal file
View File

@@ -0,0 +1,109 @@
# systemd-timer
- ![Project time](https://waka.0xmax42.io/api/badge/0XMax42/interval:any/project:systemd-timer?label=Project%20time)
- [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
* 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
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
```
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).
---
## 📦 Example
```bash
./systemd-timer create \
--exec "/usr/local/bin/backup.sh" \
--calendar "Mon..Fri 02:00" \
--description "Backup Job" \
--user \
--logfile "/var/log/backup.log"
```
This creates:
* `~/.config/systemd/user/backup.service`
* `~/.config/systemd/user/backup.timer`
Activate afterwards:
```bash
systemctl --user daemon-reload
systemctl --user enable --now backup.timer
```
---
## 🧪 Running Tests
```bash
deno task test
```
---
## 🧰 Development
```bash
deno task start create --exec "/bin/true" --calendar "daily" --dry-run
```
---
## 🔒 Permissions / Flags
The tool requires the following permissions when running or compiling:
* `--allow-env` (for `$HOME`)
* `--allow-write` (to write `.service`/`.timer` files)
During development, usually `-A` (allow all) is used.
---
## 📝 License
[MIT License](LICENSE)

1
VERSION Normal file
View File

@@ -0,0 +1 @@
0.4.1

View File

@@ -2,7 +2,11 @@
"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 --allow-env --allow-write --output dist/systemd-timer src/mod.ts" "fmt": "deno fmt --check",
"lint": "deno lint",
"ci": "deno task fmt && deno task lint && deno task test", // For local CI checks
"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 --allow-read --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 --allow-read --output dist/systemd-timer-linux-arm64 src/mod.ts"
}, },
"compilerOptions": {}, "compilerOptions": {},
"fmt": { "fmt": {

63
scripts/install.sh Normal file
View 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

View File

@@ -1,39 +1,54 @@
import { Command } from '@cliffy/command'; import { Command } from '@cliffy/command';
import { generateUnitFiles } from '../templates/unit-generator.ts'; import { generateUnitFiles } from '../templates/unit-generator.ts';
import { TimerOptions } from '../types/options.ts'; import { TimerOptions } from '../types/options.ts';
import { t } from '../i18n/mod.ts';
export const createCommand = new Command() export function createCommand() {
.description('Erzeugt eine systemd .service und .timer Unit') return new Command()
.option( .description(t('cli_create_description'))
'--name <name:string>', .option(
'Name der Unit-Dateien (optional, wird sonst aus dem Exec generiert)', '--name <name:string>',
) t('option_name'),
.option( )
'--exec <cmd:string>', .option(
'Kommando, das durch systemd ausgeführt werden soll', '--exec <cmd:string>',
{ required: true }, t('option_exec'),
) { required: true },
.option('--calendar <time:string>', 'OnCalendar-Ausdruck für den Timer', { )
required: true, .option('--calendar <time:string>', t('option_calendar'), {
}) required: true,
.option('--description <desc:string>', 'Beschreibung des Timers') })
.option('--user', 'Erstellt die Unit als User-Timer') .option('--description <desc:string>', t('option_description'))
.option('--output <dir:string>', 'Zielverzeichnis der Unit-Dateien') .option('--user', t('option_user'))
.option( .option(
'--after <target:string>', '--run-as <user:string>',
'Optionales After= für die Service-Unit', t('option_run_as'),
{ collect: true }, )
) .option(
.option( '--home <path:string>',
'--environment <env:string>', t('option_home'),
'Environment-Variablen im Format KEY=VALUE', )
{ collect: true }, .option(
) '--cwd <path:string>',
.option( t('option_cwd'),
'--logfile <file:string>', )
'Dateipfad für Log-Ausgabe (stdout/stderr)', .option('--output <dir:string>', t('option_output'))
) .option(
.option('--dry-run', 'Gibt die Unit-Dateien nur aus, ohne sie zu schreiben') '--after <target:string>',
.action(async (options: TimerOptions) => { t('option_after'),
await generateUnitFiles(options); { 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);
});
}

View File

@@ -1,9 +1,12 @@
import { Command } from '@cliffy/command'; 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() export async function createCli() {
.name('systemd-timer') return new Command()
.version('0.1.0') .name('systemd-timer')
.description('CLI Tool zum Erzeugen von systemd .timer und .service Units') .version(await getVersion())
.command('create', createCommand) .description(t('cli_description'))
.parse(Deno.args); .command('create', createCommand());
}

2
src/cli/mod.ts Normal file
View File

@@ -0,0 +1,2 @@
export { createCommand } from './create.ts';
export { createCli } from './main.ts';

View 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
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
export { getCurrentLanguage, initI18n, loadLocale, t } from './i18n.ts';

View File

@@ -1,5 +1,8 @@
import './cli/main.ts'; import { createCli } from './cli/mod.ts';
import { initI18n } from './i18n/mod.ts';
// ──────────────────────────────────────────────── // ────────────────────────────────────────────────
// Entry Point for CLI // Entry Point for CLI
// Delegates to src/cli/main.ts, which registers all CLI commands
await initI18n();
await (await createCli()).parse(Deno.args);

View File

@@ -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',
);
});

View File

@@ -1,3 +1,4 @@
import { t } from '../i18n/mod.ts';
import { TimerOptions } from '../types/mod.ts'; import { TimerOptions } from '../types/mod.ts';
import { deriveNameFromExec, writeUnitFiles } from '../utils/mod.ts'; import { deriveNameFromExec, writeUnitFiles } from '../utils/mod.ts';
@@ -12,14 +13,30 @@ 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(t('unit_written_service', { path: servicePath }));
console.log(t('unit_written_timer', { path: timerPath }));
} else {
return;
}
console.log(t('hint_header'));
if (options.user) {
console.log(` systemctl --user daemon-reload`);
console.log(` systemctl --user enable --now ${name}.timer`);
} else {
console.log(` sudo systemctl daemon-reload`);
console.log(` sudo systemctl enable --now ${name}.timer`);
}
} }
} }
@@ -35,14 +52,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 = [
@@ -54,7 +75,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');

View File

@@ -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[];

View File

@@ -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'));

View File

@@ -1,13 +1,14 @@
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';
import { t } from '../i18n/mod.ts';
export async function writeUnitFiles( export async function writeUnitFiles(
name: string, name: string,
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 +16,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`);
await Deno.writeTextFile(servicePath, serviceContent); try {
await Deno.writeTextFile(timerPath, timerContent); await Deno.writeTextFile(servicePath, serviceContent);
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(t('rollback_failed'), rollbackError);
}
console.error(t('error_write_units'), error);
return undefined;
}
return { servicePath, timerPath }; return { servicePath, timerPath };
} }

View File

@@ -1,2 +1,3 @@
export { resolveUnitTargetPath, writeUnitFiles } from './fs.ts'; export { resolveUnitTargetPath, writeUnitFiles } from './fs.ts';
export { deriveNameFromExec } from './misc.ts'; export { deriveNameFromExec } from './misc.ts';
export { getVersion } from './version.ts';

12
src/utils/version.ts Normal file
View File

@@ -0,0 +1,12 @@
export async function getVersion(): Promise<string> {
try {
const versionUrl = new URL('../../VERSION', import.meta.url);
const version = await Deno.readTextFile(versionUrl);
return version.trim();
} catch (err) {
if (err instanceof Deno.errors.NotFound) {
return 'dev';
}
return 'unknown';
}
}