Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 44 additions & 60 deletions cronify.pl
Original file line number Diff line number Diff line change
@@ -1,71 +1,55 @@
#!/bin/env perl
#!/usr/bin/env perl
use strict;
use warnings;

my @timestamps;

while(<>) {
my @timestamp = ("*", "*", "*", "*", "*");
# Default Cron: min(0) hour(1) dom(2) mon(3) dow(4)
my @timestamp = ("*", "*", "*", "*", "*");

if(m/OnCalendar=(.*)/) {
if ($1 =~ /^(hourly|daily|monthly|yearly)$/) {
# Use cron's special strings for better readability
@timestamp = ('@' . "$1");
} elsif ($1 =~ /minutely/) {
@timestamp = ("*", "*", "*", "*", "*");
} elsif ($1 =~ /weekly/) {
# Systemd does weekly on Mondays, but cron
# does it on Sundays. We don't want unexpected
# behaviour, so we force cron to do it on Mondays
@timestamp = ("0", "0", "*", "*", "Mon");
} elsif ($1 =~ /quarterly/) {
@timestamp = ("0", "0", "1,4,7,10", "1", "*");
} elsif ($1 =~ /semianually/) {
@timestamp = ("0", "0", "1,7", "1", "*")
} else {
my @sysd_date = split / /, lc $1;
my $val = lc $1;

if ($val =~ /^(hourly|daily|monthly|yearly)$/) {
@timestamp = ('@' . $val);
} elsif ($val =~ /weekly/) {
# Systemd weekly = Mon 00:00. Cron weekly = Sun 00:00.
# We force Mon for consistency in your rack.
@timestamp = ("0", "0", "*", "*", "1");
} elsif ($val =~ /quarterly/) {
@timestamp = ("0", "0", "1", "1,4,7,10", "*");
} else {
# Advanced parsing for Day-Date-Time formats
my @parts = split / /, $val;

# 1. Handle Day of Week (e.g., Mon..Fri)
if ($parts[0] =~ /[a-z]{3}/) {
my $days = shift @parts;
$days =~ s/monday/1/g; $days =~ s/mon/1/g;
$days =~ s/tuesday/2/g; $days =~ s/tue/2/g;
# ... (You'd want a map here)
$timestamp[4] = $days;
}

# Regex which matches the first three letters of
# the day of the week
my $dotw = "(?|(mon|fri|sun)(?:day)?|(tue)(?:sday)?|(wed)(?:nesday)?|(thu)(?:rsday)?|(sat)(?:urday)?)";
# 2. Handle Date (YYYY-MM-DD)
if (@parts && $parts[0] =~ /\d+|\*/) {
my $date = shift @parts;
my ($y, $m, $d) = split /-/, $date;
$timestamp[3] = $m // "*";
$timestamp[2] = $d // "*";
}

my @ts_dotw = ("*");

if ($sysd_date[0] =~ /${dotw}(?:\.\.${dotw})?/i) {
@ts_dotw = ();
$days = shift @sysd_date;
foreach $day (split /,/, $days) {
$day =~ m/(${dotw})(?:\.\.(${dotw}))?/;
print $2, "\n";
push @ts_dotw, (($1 == $2) ? "$1" : "$1-$2");
}
}

print @ts_dotw, "\n";
$timestamp[4] = join ",", @ts_dotw;

$date = shift @sysd_date;
print $date, "\n";
$date =~ m/([0-9]{4}|\*)-([0-9]{1,2}|\*)-([0-9]{1,2}|\*)/;
if($1 != "*") {
print STDERR "Warning: Ignoring non-'*' year field in timer\n";
}
$date =~ m/^([0-9]{4}|\*)-([0-9]{1,2}|\*)-([0-9]{1,2}|\*)$/;
$timestamp[2] = $2;
$timestamp[3] = $3;

$time = shift @sysd_date;
print $time, "\n";
$time =~ m/^([0-9]{1,2}):([0-9]{1,2})(?::([0-9]{1,2}))?$/;
if(int($3)) {
print STDERR "Warning: Ignoring non-zero seconds field in timer\n";
}
$time =~ m/^([0-9]{1,2}):([0-9]{1,2})(?::([0-9]{1,2}))?$/;
$timestamp[0] = $1;
$timestamp[1] = $2;
print @timestamp, "\n";
}
push @timestamps, \@timestamp;
# 3. Handle Time (HH:MM:SS) - THE CRITICAL FIX
if (@parts && $parts[0] =~ /(\d+):(\d+)/) {
$timestamp[0] = $2; # Minute
$timestamp[1] = $1; # Hour
}
}
push @timestamps, \@timestamp;
}
}

foreach $timestamp (@timestamps) {
print join " ", @$timestamp, "\n";
foreach my $ts_ref (@timestamps) {
print join(" ", @$ts_ref), "\n";
}
139 changes: 83 additions & 56 deletions initify.pl
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#!/usr/bin/perl
# (c) goose121, 2017
# (c) goose121, 2017-2026
# Released under the MIT license
# Contributions for OpenRC Daemon/Placeholder support added 2026

use warnings;
#use strict;
use strict;
use v5.10.1;
use feature "switch";
use Getopt::Long;
use Pod::Usage;

Expand All @@ -14,6 +14,8 @@
my @cmds_stop = ();
my $pidfile = "";
my $desc = "";
my $user = "";
my $group = "";

# parse CLI args
my %opt;
Expand All @@ -34,91 +36,116 @@
}

# get systemd service source file, and target service name
(my $service = $ARGV[0])=~s/\.service//;
(my $svc_name = $ARGV[0])=~s|(.*/)?([^/.]*)\.service|$2|;
(my $service = $ARGV[0]) =~ s/\.service//;
(my $svc_name = $ARGV[0]) =~ s|(.*/)?([^/.]*)\.service|$2|;
$svc_name = $opt{name} if (length $opt{name});

# begin
# parse systemd unit
while(<>) {
#s/\s*|\s*$//g; # Trim whitespace
if (m/^Type\s*=\s*(.*)/) {
$type=$1;
chomp;
s/^\s+|\s+$//g; # Trim whitespace

if (m/^Type\s*=\s*(.*)/i) { $type = lc($1); }
if (m/^Description\s*=\s*(.*)/i) { $desc = $1; }
if (m/^PIDFile\s*=\s*(.*)/i) { $pidfile = $1; }
if (m/^User\s*=\s*(.*)/i) { $user = $1; }
if (m/^Group\s*=\s*(.*)/i) { $group = $1; }

if (m/^ExecStart\s*=\s*(.*)/i) {
if (length $1) { push(@cmds_start, $1); }
else { @cmds_start = (); }
}
if (m/^ExecStart\s*=\s*(.*)/) {
if (length $1) {
push(@cmds_start, $1);
}
else {
@cmds_start = (); # If the line is "ExecStart=",
# it means to clear ExecStart.
}
}
if (m/^ExecStop\s*=\s*(.*)/) {
if (length $1) {
push(@cmds_stop, $1);
}
else {
@cmds_stop = (); # If the line is "ExecStop=",
# it means to clear ExecStop.
}
}
if (m/^PIDFile=(.*)/) {
if(length $1) {
$pidfile = $1;
}
}
if (m/^Description=(.*)/) {
$desc = $1
if (m/^ExecStop\s*=\s*(.*)/i) {
if (length $1) { push(@cmds_stop, $1); }
else { @cmds_stop = (); }
}
}

my @cmds;

# Prepare command parts
my @cmd_path;
my @cmd_argl;

map {my @sep = split(/ /, $_, 2);
push(@cmd_path, $sep[0]);
push(@cmd_argl, $sep[1]);
} @cmds_start;
foreach my $cmd (@cmds_start) {
my @sep = split(/ /, $cmd, 2);
push(@cmd_path, $sep[0]);
push(@cmd_argl, $sep[1] // "");
}

# --- OpenRC Logic Patches ---

# 1. Handle Systemd placeholders (%i, %I) for OpenRC sub-services
# Maps %i to ${RC_SVCNAME#*.} so /etc/init.d/svc.instance works
foreach (@cmd_argl) { s/%[iI]/\${RC_SVCNAME#*.}/g; }
$pidfile =~ s/%[iI]/\${RC_SVCNAME#*.}/g;

# 2. Determine Daemon/Backgrounding requirements
my $ssd_opts = "";
if ($type eq "simple") {
# Simple types stay in foreground; OpenRC must daemonize them
$ssd_opts = "--background --make-pidfile";
$pidfile = "/run/\${RC_SVCNAME}.pid" unless $pidfile;
}

open(FH, '>', "$service") || die("Cannot create $service: $!\n");
# --- Generate OpenRC Script ---

print FH <<"EOF";
\#!/sbin/openrc-run
open(my $fh, '>', "$service") || die("Cannot create $service: $!\n");

command=$cmd_path[0]
command_args="$cmd_argl[0]"
pidfile=$pidfile
print $fh <<"EOF";
#!/sbin/openrc-run
# Generated by initify - https://github.com/goose121/initify

name="$svc_name"
name="\$RC_SVCNAME"
description="$desc"

command="$cmd_path[0]"
command_args="$cmd_argl[0] $ssd_opts"
pidfile="$pidfile"
EOF

close FH;
# Add User/Group if defined
print $fh "command_user=\"$user:$group\"\n" if ($user);

print $fh <<'EOF';

depend() {
after net
use dns logger
}

start_pre() {
# Ensure runtime directory for PID exists
checkpath -d -m 0755 -o ${command_user:-root} /run/${RC_SVCNAME%/*}
}
EOF

close $fh;
print "Successfully converted to OpenRC: $service\n";

__END__

=head1 NAME

initify - Convert systemd units to OpenRC init-files
initify - Convert systemd units to OpenRC init-files (Patched for 2026)

=head1 SYNOPSIS

initify [options] file
initify [options] file.service

=head2 Summary of Improvements

=over 4

=head2 Options
=item * Backgrounding

=over 12
Automatically adds --background and --make-pidfile for Type=simple units.

=item -h, --help
=item * Placeholder Support

Print this message
Translates %i/%I to OpenRC-compatible shell variables for multi-instance scripts.

=item -n <name>, --name=<name>
=item * User/Group

Set the name of the unit created
Correctly maps Systemd User/Group to command_user.

=back

Expand Down
Loading