57 Commits

Author SHA1 Message Date
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
67f302c2e9 chore(changelog): add initial changelog file to document project updates
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / changelog-only (push) Successful in 1m59s
Auto Changelog & Release / release (push) Has been skipped
2025-05-21 03:02:20 +02:00
97dc3fe23a feat(cli): add command to generate systemd unit files
- Introduces a CLI tool for creating systemd .timer and .service units
- Adds options for configuring unit names, commands, scheduling, and more
- Supports dry-run mode and user-level unit file generation
2025-05-21 03:01:21 +02:00
569b14d574 test(generate): add unit tests for service and timer generation
- Introduces tests to validate service and timer unit generation
- Covers description, dependencies, environment variables, and logging
- Ensures generated units meet expected configurations
2025-05-21 03:01:12 +02:00
316f3af04e refactor(utils): update import path for TimerOptions
- Adjusts the import path for TimerOptions to align with the new module structure.
- Simplifies dependency management by consolidating imports.
2025-05-21 03:01:03 +02:00
428e84927f feat(utils): export utility functions for filesystem and naming 2025-05-21 03:00:13 +02:00
ba4b933f78 feat(types): add TimerOptions interface for timer configuration 2025-05-21 02:58:53 +02:00
d5a383a62c feat(cli): add entry point for CLI commands 2025-05-21 02:58:30 +02:00
9539fe0532 feat(utils): add function to derive sanitized job names
- Introduces a utility function to extract and sanitize job names
  from executable paths by removing paths, extensions, and special
  characters.
- Adds unit tests to validate function behavior with various inputs.
2025-05-21 02:58:05 +02:00
ef2ac416d9 test(utils): add unit tests for systemd file handling
- Add tests for writing .service and .timer files with `writeUnitFiles`
- Add tests for resolving target paths with `resolveUnitTargetPath`
- Ensure proper file creation, content validation, and path resolution
2025-05-21 02:57:53 +02:00
6608f48840 chore: add VSCode settings for color customizations and folder listener 2025-05-21 02:57:32 +02:00
23 changed files with 909 additions and 5 deletions

View File

@@ -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

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

15
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#d48282",
"activityBar.background": "#d48282",
"activityBar.foreground": "#15202b",
"activityBar.inactiveForeground": "#15202b99",
"activityBarBadge.background": "#b8e7b8",
"activityBarBadge.foreground": "#15202b"
},
"peacock.color": "#c75c5c",
"exportall.config.folderListener": [
"/src/utils",
"/src/types"
]
}

98
CHANGELOG.md Normal file
View File

@@ -0,0 +1,98 @@
# Changelog
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
### 🚀 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))
- *(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))
- *(cli)* Add entry point for CLI commands - ([d5a383a](https://git.0xmax42.io/maxp/systemd-timer/commit/d5a383a62c965b60de7429ac1cb89f02639935f6))
- *(utils)* Add function to derive sanitized job names - ([9539fe0](https://git.0xmax42.io/maxp/systemd-timer/commit/9539fe053245e9fea10ceda0e46fe61e9de80797))
### 🚜 Refactor
- *(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
- *(generate)* Add unit tests for service and timer generation - ([569b14d](https://git.0xmax42.io/maxp/systemd-timer/commit/569b14d57432589107a0f33e52881b605c5f79f9))
- *(utils)* Add unit tests for systemd file handling - ([ef2ac41](https://git.0xmax42.io/maxp/systemd-timer/commit/ef2ac416d92f59efe3390317af46e943549adc47))
### ⚙️ 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))
- *(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))
- *(gitignore)* Add dist/ and .env files to ignore list - ([2da372d](https://git.0xmax42.io/maxp/systemd-timer/commit/2da372d20dd0e023feb7e2da391dd0971da6a73d))
- *(gitignore)* Add common build and coverage directories - ([2990af3](https://git.0xmax42.io/maxp/systemd-timer/commit/2990af3628b036c1d61daaf3d8efd3d2f0d4b761))

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.3.1

View File

@@ -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 --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
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

51
src/cli/create.ts Normal file
View File

@@ -0,0 +1,51 @@
import { Command } from '@cliffy/command';
import { generateUnitFiles } from '../templates/unit-generator.ts';
import { TimerOptions } from '../types/options.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(
'--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(
'--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);
});

10
src/cli/main.ts Normal file
View File

@@ -0,0 +1,10 @@
import { Command } from '@cliffy/command';
import { createCommand } from './create.ts';
import { getVersion } from '../utils/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);

5
src/mod.ts Normal file
View File

@@ -0,0 +1,5 @@
import './cli/main.ts';
// ────────────────────────────────────────────────
// Entry Point for CLI
// Delegates to src/cli/main.ts, which registers all CLI commands

View File

@@ -0,0 +1,116 @@
import {
assert,
assertStringIncludes,
} from 'https://deno.land/std@0.224.0/assert/mod.ts';
import { TimerOptions } from '../../types/mod.ts';
import { generateUnits } from '../unit-generator.ts';
Deno.test('generateUnits erzeugt Basis-Service und Timer korrekt', () => {
const opts: TimerOptions = {
exec: '/usr/bin/my-script',
calendar: 'daily',
};
const { serviceUnit, timerUnit } = generateUnits('myjob', opts);
assertStringIncludes(serviceUnit, 'ExecStart=/usr/bin/my-script');
assertStringIncludes(timerUnit, 'OnCalendar=daily');
assertStringIncludes(timerUnit, '[Install]');
});
Deno.test('generateUnits setzt Beschreibung aus Option', () => {
const opts: TimerOptions = {
exec: '/bin/true',
calendar: 'daily',
description: 'Meine Unit',
};
const { serviceUnit } = generateUnits('job', opts);
assertStringIncludes(serviceUnit, 'Description=Meine Unit');
});
Deno.test('generateUnits berücksichtigt after=', () => {
const opts: TimerOptions = {
exec: '/bin/true',
calendar: 'daily',
after: ['network-online.target', 'docker.service'],
};
const { serviceUnit } = generateUnits('job', opts);
assertStringIncludes(serviceUnit, 'After=network-online.target');
assertStringIncludes(serviceUnit, 'After=docker.service');
});
Deno.test('generateUnits berücksichtigt environment und logfile', () => {
const opts: TimerOptions = {
exec: '/bin/true',
calendar: 'daily',
environment: ['FOO=bar', 'DEBUG=1'],
logfile: '/var/log/job.log',
};
const { serviceUnit } = generateUnits('job', opts);
assertStringIncludes(serviceUnit, 'Environment=FOO=bar');
assertStringIncludes(serviceUnit, 'Environment=DEBUG=1');
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',
);
});

View File

@@ -0,0 +1,83 @@
import { TimerOptions } from '../types/mod.ts';
import { deriveNameFromExec, writeUnitFiles } from '../utils/mod.ts';
export async function generateUnitFiles(options: TimerOptions): Promise<void> {
const name = options.name || deriveNameFromExec(options.exec);
const { serviceUnit, timerUnit } = generateUnits(name, options);
if (options.dryRun) {
console.log(`===== ${name}.service =====`);
console.log(serviceUnit);
console.log(`\n===== ${name}.timer =====`);
console.log(timerUnit);
} else {
const result = await writeUnitFiles(
name,
serviceUnit,
timerUnit,
options,
);
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:`);
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`);
}
}
}
export function generateUnits(name: string, options: TimerOptions): {
serviceUnit: string;
timerUnit: string;
} {
const unitParts = [
`[Unit]`,
`Description=${options.description ?? name}`,
...(options.after?.map((a) => `After=${a}`) ?? []),
``,
`[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}`] : []),
];
const serviceUnit = unitParts.join('\n');
const timerParts = [
`[Unit]`,
`Description=Timer for ${name}`,
``,
`[Timer]`,
`OnCalendar=${options.calendar}`,
`Persistent=true`,
``,
`[Install]`,
`WantedBy=${options.user ? 'default.target' : 'timers.target'}`,
];
const timerUnit = timerParts.join('\n');
return { serviceUnit, timerUnit };
}

1
src/types/mod.ts Normal file
View File

@@ -0,0 +1 @@
export type { TimerOptions } from './options.ts';

15
src/types/options.ts Normal file
View File

@@ -0,0 +1,15 @@
export interface TimerOptions {
name?: string;
exec: string;
calendar: string;
description?: string;
user?: boolean;
runAs?: string;
home?: string;
cwd?: string;
output?: string;
after?: string[];
environment?: string[];
logfile?: string;
dryRun?: boolean;
}

View File

@@ -0,0 +1,59 @@
import {
assertEquals,
assertExists,
assertStringIncludes,
} from 'https://deno.land/std@0.224.0/assert/mod.ts';
import { join } from 'https://deno.land/std@0.224.0/path/mod.ts';
import { resolveUnitTargetPath, writeUnitFiles } from '../mod.ts';
import { TimerOptions } from '../../types/options.ts';
Deno.test('writeUnitFiles schreibt .service und .timer korrekt', async () => {
const tmp = await Deno.makeTempDir({ prefix: 'test-units-' });
const options = {
output: tmp,
user: false,
};
const name = 'test-backup';
const serviceContent = '[Service]\nExecStart=/bin/true';
const timerContent = '[Timer]\nOnCalendar=daily';
const { servicePath, timerPath } = await writeUnitFiles(
name,
serviceContent,
timerContent,
options as TimerOptions,
) as { servicePath: string; timerPath: string };
// Überprüfe Pfade
assertEquals(servicePath, join(tmp, 'test-backup.service'));
assertEquals(timerPath, join(tmp, 'test-backup.timer'));
// Existieren Dateien?
assertExists(await Deno.stat(servicePath));
assertExists(await Deno.stat(timerPath));
// Enthält die Datei den erwarteten Inhalt?
const readService = await Deno.readTextFile(servicePath);
const readTimer = await Deno.readTextFile(timerPath);
assertStringIncludes(readService, 'ExecStart=/bin/true');
assertStringIncludes(readTimer, 'OnCalendar=daily');
});
Deno.test('resolveUnitTargetPath mit --output', () => {
const result = resolveUnitTargetPath({ output: '/tmp/units', user: false });
assertEquals(result, '/tmp/units');
});
Deno.test('resolveUnitTargetPath mit --user ohne output', () => {
Deno.env.set('HOME', '/home/maxp');
const result = resolveUnitTargetPath({ output: undefined, user: true });
assertEquals(result, '/home/maxp/.config/systemd/user');
});
Deno.test('resolveUnitTargetPath ohne output und ohne user', () => {
const result = resolveUnitTargetPath({ output: undefined, user: false });
assertEquals(result, '/etc/systemd/system');
});

View File

@@ -0,0 +1,19 @@
import { assertEquals } from 'https://deno.land/std@0.224.0/assert/mod.ts';
import { deriveNameFromExec } from '../mod.ts';
Deno.test('deriveNameFromExec - entfernt Pfad, Endung und Sonderzeichen', () => {
const tests: Array<[string, string]> = [
['/usr/local/bin/backup.sh', 'backup'],
['/usr/bin/python3 /home/user/myscript.py', 'python3'],
['./my-job.ts', 'my-job'],
['node ./tools/start.js', 'node'],
['/bin/custom-script.rb', 'custom-script'],
[' /usr/bin/something-strange!.bin ', 'something-strange'],
['weird:name?.sh', 'weird-name'],
['', 'job'],
];
for (const [input, expected] of tests) {
assertEquals(deriveNameFromExec(input), expected);
}
});

46
src/utils/fs.ts Normal file
View File

@@ -0,0 +1,46 @@
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';
export async function writeUnitFiles(
name: string,
serviceContent: string,
timerContent: string,
options: TimerOptions,
): Promise<{ servicePath: string; timerPath: string } | undefined> {
const basePath = resolveUnitTargetPath(options);
await ensureDir(basePath);
const servicePath = join(basePath, `${name}.service`);
const timerPath = join(basePath, `${name}.timer`);
try {
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('Rollback fehlgeschlagen:', rollbackError);
}
console.error('Fehler beim Schreiben der Units:', error);
return undefined;
}
return { servicePath, timerPath };
}
export function resolveUnitTargetPath(
options: Pick<TimerOptions, 'output' | 'user'>,
): string {
if (options.output) return options.output;
if (options.user) return `${Deno.env.get('HOME')}/.config/systemd/user`;
return '/etc/systemd/system';
}

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

@@ -0,0 +1,12 @@
export function deriveNameFromExec(exec: string): string {
const parts = exec.trim().split(' ');
const base = parts[0].split('/').pop() || 'job';
// remove the file extension
const withoutExt = base.replace(/\.(sh|py|ts|js|pl|rb|exe|bin)$/, '');
// replace illegal chars, then trim leading/trailing hyphens
return withoutExt
.replaceAll(/[^a-zA-Z0-9_-]/g, '-')
.replace(/^-+|-+$/g, '');
}

3
src/utils/mod.ts Normal file
View File

@@ -0,0 +1,3 @@
export { resolveUnitTargetPath, writeUnitFiles } from './fs.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';
}
}