20 Commits

Author SHA1 Message Date
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
13 changed files with 289 additions and 11 deletions

View File

@@ -0,0 +1,44 @@
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

View File

@@ -2,10 +2,30 @@
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.2.0](https://git.0xmax42.io/maxp/systemd-timer/compare/v0.1.0..v0.2.0) - 2025-05-22
### 🚀 Features ### 🚀 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 +36,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 +48,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.

91
README.md Normal file
View File

@@ -0,0 +1,91 @@
# systemd-timer
![Project time](https://waka.0xmax42.io/api/badge/0XMax42/interval:today/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`
- `--exec`
- `--after`
- `--environment`
- `--output`
- `--dry-run`
- Getestet und typisiert mit Deno + Cliffy
---
## 🛠️ Installation
```bash
git clone https://git.0xmax42.io/maxp/systemd-timer.git
cd systemd-timer
deno task build
# Binary liegt nun unter ./systemd-timer
./systemd-timer --help
```
---
## 📦 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)

1
VERSION Normal file
View File

@@ -0,0 +1 @@
0.2.0

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": {

48
scripts/install.sh Normal file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -euo pipefail
# === 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}"
# === Optional: SHA256-Check ===
curl -fsSL "${DOWNLOAD_URL}.sha256" -o "${TMP_FILE}.sha256"
echo "$(cat ${TMP_FILE}.sha256) ${TMP_FILE}" | sha256sum -c -
# === 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,9 +1,10 @@
import { Command } from '@cliffy/command'; import { Command } from '@cliffy/command';
import { createCommand } from './create.ts'; import { createCommand } from './create.ts';
import { getVersion } from '../utils/mod.ts';
await new Command() await new Command()
.name('systemd-timer') .name('systemd-timer')
.version('0.1.0') .version(await getVersion())
.description('CLI Tool zum Erzeugen von systemd .timer und .service Units') .description('CLI Tool zum Erzeugen von systemd .timer und .service Units')
.command('create', createCommand) .command('create', createCommand)
.parse(Deno.args); .parse(Deno.args);

View File

@@ -12,14 +12,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(`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`);
}
} }
} }

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,4 +1,4 @@
import { ensureDir } from 'https://deno.land/std@0.224.0/fs/mod.ts'; import { ensureDir, exists } from 'https://deno.land/std@0.224.0/fs/mod.ts';
import { join } from 'https://deno.land/std@0.224.0/path/mod.ts'; import { join } from 'https://deno.land/std@0.224.0/path/mod.ts';
import { TimerOptions } from '../types/mod.ts'; import { TimerOptions } from '../types/mod.ts';
@@ -7,7 +7,7 @@ export async function writeUnitFiles(
serviceContent: string, serviceContent: string,
timerContent: string, timerContent: string,
options: TimerOptions, options: TimerOptions,
): Promise<{ servicePath: string; timerPath: string }> { ): Promise<{ servicePath: string; timerPath: string } | undefined> {
const basePath = resolveUnitTargetPath(options); const basePath = resolveUnitTargetPath(options);
await ensureDir(basePath); await ensureDir(basePath);
@@ -15,8 +15,24 @@ export async function writeUnitFiles(
const servicePath = join(basePath, `${name}.service`); const servicePath = join(basePath, `${name}.service`);
const timerPath = join(basePath, `${name}.timer`); const timerPath = join(basePath, `${name}.timer`);
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('Rollback fehlgeschlagen:', rollbackError);
}
console.error('Fehler beim Schreiben der 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';
}
}