Add scheduled command module

This commit is contained in:
Yan Lin 2025-10-04 21:58:01 +02:00
parent cc0077a5ae
commit 479535c294
3 changed files with 123 additions and 142 deletions

View file

@ -5,6 +5,7 @@
../home-default.nix
../../../modules/syncthing.nix
../../../modules/tex.nix
../../../modules/scheduled-commands.nix
];
# hs-specific home configuration
@ -13,21 +14,28 @@
programs.yt-dlp-custom = {
enable = true;
downloadDir = "/mnt/storage/Media/web";
subscriptions = {
enable = true;
feeds = [
"https://www.youtube.com/feeds/videos.xml?channel_id=UCm22FAXZMw1BaWeFszZxUKw" # Kitboga
"https://www.youtube.com/feeds/videos.xml?channel_id=UCYwVxWpjeKFWwu8TML-Te9A" # JCS - Criminal Psychology
"https://www.youtube.com/feeds/videos.xml?channel_id=UCPJVs54XcE8RrdA_uW-lfUw" # Shane McGillicuddy
"https://www.youtube.com/feeds/videos.xml?channel_id=UCFQMnBA3CS502aghlcr0_aw" # Coffeezilla
"https://www.youtube.com/feeds/videos.xml?channel_id=UCSUf5_EPEfl4zlBKZHkZdmw" # Danny Gonzalez
"https://www.youtube.com/feeds/videos.xml?channel_id=UC-ufRLYrXxrIEApGn9VG5pQ" # Reject Convenience
"https://www.youtube.com/feeds/videos.xml?channel_id=UCj1VqrHhDte54oLgPG4xpuQ" # Stuff Made Here
];
interval = "*-*-* 08:00:00";
randomDelay = "1h";
maxVideosPerFeed = 5;
};
};
# Scheduled YouTube downloads
services.scheduled-commands = {
enable = true;
serviceName = "video-downloads";
serviceDescription = "Download web videos from favorite channels";
interval = "*-*-* 08:00:00";
randomDelay = "1h";
commands = [
"dl-yt -n 3 https://www.youtube.com/@KitbogaShow"
"dl-yt -n 3 https://www.youtube.com/@JCS"
"dl-yt -n 3 https://www.youtube.com/@Shane_McGillicuddy"
"dl-yt -n 3 https://www.youtube.com/@Coffeezilla"
"dl-yt -n 3 https://www.youtube.com/@Danny-Gonzalez"
"dl-yt -n 3 https://www.youtube.com/@rejectconvenience"
"dl-yt -n 3 https://www.youtube.com/@StuffMadeHere"
"dl-yt -n 3 https://www.youtube.com/@AdamSomething"
"dl-yt -n 3 https://www.youtube.com/@_gerg"
"dl-yt -n 3 https://www.youtube.com/@Yeah_Jaron"
"dl-yt -n 3 https://www.youtube.com/@WolfgangsChannel"
];
};
programs.zsh.shellAliases = {

View file

@ -0,0 +1,97 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.services.scheduled-commands;
# Create wrapper script that sources user shell environment
commandScript = pkgs.writeScriptBin "${cfg.serviceName}-run" ''
#!${pkgs.zsh}/bin/zsh
# Source user shell to get environment and functions
source ${config.home.homeDirectory}/.zshrc
# Execute commands sequentially
${concatStringsSep "\n" cfg.commands}
'';
in
{
options.services.scheduled-commands = {
enable = mkEnableOption "scheduled command execution service";
commands = mkOption {
type = types.listOf types.str;
default = [];
example = [
"echo 'Starting backup...'"
"rsync -av /source /destination"
"echo 'Backup completed'"
];
description = "List of shell commands to execute sequentially";
};
interval = mkOption {
type = types.str;
default = "daily";
example = "*-*-* 08:00:00";
description = "Systemd timer schedule (OnCalendar format)";
};
randomDelay = mkOption {
type = types.str;
default = "0";
example = "1h";
description = "Random delay before execution (e.g., '30m', '1h')";
};
serviceName = mkOption {
type = types.str;
default = "scheduled-commands";
example = "video-downloads";
description = "Name for the systemd service and timer";
};
serviceDescription = mkOption {
type = types.str;
default = "Execute scheduled commands";
example = "Download YouTube videos from subscriptions";
description = "Description for the systemd service";
};
};
config = mkIf cfg.enable {
# Install the wrapper script
home.packages = [ commandScript ];
systemd.user.services.${cfg.serviceName} = {
Unit = {
Description = cfg.serviceDescription;
After = [ "network-online.target" ];
};
Service = {
Type = "oneshot";
ExecStart = "${commandScript}/bin/${cfg.serviceName}-run";
StandardOutput = "journal";
StandardError = "journal";
};
};
systemd.user.timers.${cfg.serviceName} = {
Unit = {
Description = "Timer for ${cfg.serviceDescription}";
};
Timer = {
OnCalendar = cfg.interval;
Persistent = true;
RandomizedDelaySec = cfg.randomDelay;
};
Install = {
WantedBy = [ "timers.target" ];
};
};
};
}

View file

@ -23,40 +23,6 @@ in
example = "/mnt/storage/videos";
description = "Base directory for downloaded videos";
};
subscriptions = {
enable = mkEnableOption "RSS subscription checking for automatic video downloads";
feeds = mkOption {
type = types.listOf types.str;
default = [];
example = [
"https://www.youtube.com/feeds/videos.xml?channel_id=UCRHXUZ0BxbkU2MYZgsuFgkQ"
];
description = "List of YouTube RSS feed URLs to monitor for new videos";
};
interval = mkOption {
type = types.str;
default = "hourly";
example = "*-*-* */4:00:00";
description = "Systemd timer schedule for checking subscriptions";
};
randomDelay = mkOption {
type = types.str;
default = "0";
example = "30m";
description = "Random delay before running subscription check (e.g., '30m', '1h')";
};
maxVideosPerFeed = mkOption {
type = types.int;
default = 5;
example = 10;
description = "Maximum number of videos to process per feed (newest first)";
};
};
};
config = mkIf cfg.enable {
@ -66,8 +32,6 @@ in
cfg.package
deno # Required for YouTube downloads due to JS challenges
ffmpeg
] ++ lib.optionals cfg.subscriptions.enable [
libxml2 # For xmllint to parse RSS feeds
];
# Cookie files - managed by Nix (read-only)
@ -116,16 +80,13 @@ in
# YouTube downloads
dl-yt = "download-youtube";
dl-yt-p = "download-youtube-playlist";
# Bilibili downloads
# Bilibili downloads
dl-bili = "download-bilibili";
dl-bili-p = "download-bilibili-playlist";
# Help
dl-help = "download-help";
} // lib.optionalAttrs cfg.subscriptions.enable {
# YouTube subscription management
dl-subs-yt = "check-youtube-subscriptions";
};
programs.zsh.initContent = ''
@ -393,91 +354,6 @@ in
# Alias for backward compatibility
alias dlv-clear-archive='dl-clear-archive'
# YouTube RSS subscription checker
check-youtube-subscriptions() {
${lib.optionalString cfg.subscriptions.enable ''
local max_videos="${toString cfg.subscriptions.maxVideosPerFeed}"
local feeds=(${lib.concatMapStringsSep " " (feed: ''"${feed}"'') cfg.subscriptions.feeds})
if [[ ''${#feeds[@]} -eq 0 ]]; then
echo "No RSS feeds configured"
return 0
fi
echo "Checking ''${#feeds[@]} YouTube subscription feeds..."
echo "Processing up to $max_videos videos per feed"
echo ""
for feed in "''${feeds[@]}"; do
echo "Processing feed: $feed"
# Fetch and parse the RSS feed, extract video links
local links=$(${pkgs.curl}/bin/curl -s "$feed" | \
${pkgs.libxml2}/bin/xmllint --xpath "//*[local-name()='entry']/*[local-name()='link'][@rel='alternate']/@href" - 2>/dev/null | \
sed 's/href="//g; s/"//g' | \
head -n "$max_videos")
if [[ -z "$links" ]]; then
echo " No videos found or feed unavailable"
continue
fi
local count=0
while IFS= read -r link; do
if [[ -n "$link" ]]; then
((count++))
echo " [$count] Downloading: $link"
download-youtube "$link"
fi
done <<< "$links"
echo " Processed $count videos from this feed"
echo ""
done
echo " Subscription check completed"
''}
}
'';
# Systemd user service and timer for subscription checking
systemd.user.services.yt-dlp-subscriptions = mkIf cfg.subscriptions.enable {
Unit = {
Description = "Check YouTube RSS subscriptions and download new videos";
After = [ "network-online.target" ];
};
Service = {
Type = "oneshot";
ExecStart = "${pkgs.writeShellScript "yt-dlp-check-subs" ''
export PATH="${pkgs.coreutils}/bin:${pkgs.curl}/bin:${pkgs.libxml2}/bin:${pkgs.gnused}/bin:${cfg.package}/bin:$PATH"
# Source the shell init to get our functions
source ${config.home.homeDirectory}/.zshrc
# Run the subscription check
check-youtube-subscriptions
''}";
StandardOutput = "journal";
StandardError = "journal";
};
};
systemd.user.timers.yt-dlp-subscriptions = mkIf cfg.subscriptions.enable {
Unit = {
Description = "Timer for YouTube subscription checks";
};
Timer = {
OnCalendar = cfg.subscriptions.interval;
Persistent = true;
RandomizedDelaySec = cfg.subscriptions.randomDelay;
};
Install = {
WantedBy = [ "timers.target" ];
};
};
};
}