nix-configs

Personal NixOS and home-manager configuration files
Log | Files | Refs

commit 7a7890742a7ceb2b138c6103eed7940d7afd4e7b
parent 52bf7d85b0354f412a16912603792186b2ac453a
Author: breadcat <breadcat@users.noreply.github.com>
Date:   Sun, 24 Aug 2025 12:30:41 +0100

I have totally given up on systemd timers

Broken commit due to other refactoring of includes

Diffstat:
Dcommon/blog-duolingo.nix | 45---------------------------------------------
Dcommon/blog-status.nix | 43-------------------------------------------
Dcommon/magnets.nix | 101-------------------------------------------------------------------------------
Dcommon/restic.nix | 42------------------------------------------
Dcommon/stagit-generate.nix | 141-------------------------------------------------------------------------------
Dcommon/tank-log.nix | 66------------------------------------------------------------------
Dcommon/tank-sort.nix | 95-------------------------------------------------------------------------------
Mmachines/artemis.nix | 12++++++++++++
Mmachines/ilias.nix | 10++++++++++
Ascripts/blog-duolingo.nix | 29+++++++++++++++++++++++++++++
Ascripts/blog-status.nix | 27+++++++++++++++++++++++++++
Ascripts/magnets.nix | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/restic.nix | 21+++++++++++++++++++++
Ascripts/stagit-generate.nix | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/tank-log.nix | 48++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/tank-sort.nix | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
16 files changed, 423 insertions(+), 533 deletions(-)

diff --git a/common/blog-duolingo.nix b/common/blog-duolingo.nix @@ -1,45 +0,0 @@ -{ pkgs, username, domain, ... }: - -let - blog-duolingo = pkgs.writeShellScriptBin "blog-duolingo" '' - # variables - username="$(awk -F'[/()]' '/Duolingo/ {print $5}' "$HOME/vault/src/blog.${domain}/content/about.md")" - post_file="$HOME/vault/src/blog.${domain}/content/posts/logging-duolingo-ranks-over-time.md" - # functions - function lastmod { - echo -n "Amending lastmod value... " - mod_timestamp="$(date +%FT%H:%M:00)" - sed -i "s/lastmod: .*/lastmod: $mod_timestamp/g" "$1" - echo -e "$i \e[32mdone\e[39m" - } - # process - page_source="$(curl -s https://duome.eu/"$username")" - rank_lingot="$(printf %s "$page_source" | awk -F"[#><]" '/icon lingot/ {print $15}')" - rank_streak="$(printf %s "$page_source" | awk -F"[#><]" '/icon streak/{getline;print $15}')" - # write - echo -e "$i \e[32mdone\e[39m" - echo -n "Appending ranks to page... " - sed -i '/<\/tbody><\/table>/d' "$post_file" - printf " <tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n</tbody></table>" "$(date +%F)" "$(date +%H:%M)" "$rank_streak" "$rank_lingot" >>"$post_file" - echo -e "$i \e[32mdone\e[39m" - lastmod "$post_file" - ''; -in { - environment.systemPackages = [blog-duolingo]; - - systemd.timers.blog-duolingo = { - wantedBy = [ "timers.target" ]; - timerConfig = { - OnCalendar = "Sun 23:59"; - Persistent = true; - }; - }; - systemd.services.blog-duolingo = { - script = "blog-duolingo"; - path = [ "/run/current-system/sw" ]; - serviceConfig = { - Type = "oneshot"; - User = "${username}"; - }; - }; -} diff --git a/common/blog-status.nix b/common/blog-status.nix @@ -1,43 +0,0 @@ -{ pkgs, username, domain, ... }: - -let - blog-status = pkgs.writeShellScriptBin "blog-status" '' - # variables - status_uptime=$(($(cut -f1 -d. </proc/uptime) / 86400)) - # process - { - printf -- "---\\ntitle: Status\\nlayout: single\\n---\\n\\n" - printf "*Generated on %(%Y-%m-%d at %H:%M)T*\\n\\n" -1 - printf "* Uptime: %s Day%s\\n" "$status_uptime" "$(if (("$status_uptime" > 1)); then echo s; fi)" - printf "* CPU Load: %s\\n" "$(cut -d" " -f1-3 </proc/loadavg)" - printf "* Users: %s\\n" "$(who | wc -l)" - printf "* RAM Usage: %s%%\\n" "$(printf "%.2f" "$(free | awk '/Mem/ {print $3/$2 * 100.0}')")" - printf "* Root Storage: %s\\n" "$(df / | awk 'END{print $5}')" - printf "* Tank Storage: %s\\n" "$(df | awk -v tank="/tank" '$0 ~ tank {print $5}')" - printf "* Torrent Ratio: %s\\n" "$(echo "scale=3; $(awk '/upload/ {print $2}' "$HOME/docker/transmission/stats.json") / $(awk '/download/ {print $2}' "$HOME/docker/transmission/stats.json" | sed 's/,//g')" | ${pkgs.bc}/bin/bc)" - printf "* NAS Storage: %s\\n" "$(git --git-dir="$HOME/vault/src/logger/.git" show | awk 'END{print $3" "$4}')" - printf "* [Containers](https://github.com/breadcat/nix-configs/blob/main/common/docker.nix): %s\\n" "$(docker ps -q | wc -l)/$(docker ps -aq | wc -l)" - printf "* Packages: %s\\n" "$(${pkgs.fastfetch}/bin/fastfetch | awk -F": " '/Packages/ {print $2}')" - printf "* Monthly Data: %s\\n" "$(vnstat -m --oneline | cut -f11 -d\;)" - printf "\\nHardware specifications themselves are covered on the [hardware page](/hardware/#server).\\n" - } >"$HOME/vault/src/blog.${domain}/content/status.md" - ''; -in { - environment.systemPackages = [blog-status]; - - systemd.timers.blog-status = { - wantedBy = [ "timers.target" ]; - timerConfig = { - OnCalendar = "*:0/10"; - Persistent = true; - }; - }; - systemd.services.blog-status = { - script = "blog-status"; - path = [ "/run/current-system/sw" ]; - serviceConfig = { - Type = "oneshot"; - User = "${username}"; - }; - }; -} diff --git a/common/magnets.nix b/common/magnets.nix @@ -1,101 +0,0 @@ -{ pkgs, username, ... }: - -let - magnets = pkgs.writeShellScriptBin "magnets" '' - # variables - working_directory="$HOME/vault/watch" - # trackers - trackers_list=( - "http://0123456789nonexistent.com:80/announce" - "http://bt.okmp3.ru:2710/announce" - "http://ipv4.rer.lol:2710/announce" - "http://open.trackerlist.xyz:80/announce" - "http://shubt.net:2710/announce" - "http://taciturn-shadow.spb.ru:6969/announce" - "http://torrent.hificode.in:6969/announce" - "http://tracker.bt4g.com:2095/announce" - "http://tracker.mywaifu.best:6969/announce" - "http://tracker.netmap.top:6969/announce" - "http://tracker.privateseedbox.xyz:2710/announce" - "http://tracker.renfei.net:8080/announce" - "https://pybittrack.retiolus.net:443/announce" - "https://t.213891.xyz:443/announce" - "https://tr.nyacat.pw:443/announce" - "https://tracker.aburaya.live:443/announce" - "https://tracker.expli.top:443/announce" - "https://tracker.ghostchu-services.top:443/announce" - "https://tracker.jdx3.org:443/announce" - "https://tracker.leechshield.link:443/announce" - "https://tracker.moeblog.cn:443/announce" - "https://tracker.yemekyedim.com:443/announce" - "https://tracker.zhuqiy.top:443/announce" - "udp://1c.premierzal.ru:6969/announce" - "udp://bandito.byterunner.io:6969/announce" - "udp://d40969.acod.regrucolo.ru:6969/announce" - "udp://evan.im:6969/announce" - "udp://extracker.dahrkael.net:6969/announce" - "udp://martin-gebhardt.eu:25/announce" - "udp://open.demonii.com:1337/announce" - "udp://open.dstud.io:6969/announce" - "udp://open.stealth.si:80/announce" - "udp://p4p.arenabg.com:1337/announce" - "udp://retracker.lanta.me:2710/announce" - "udp://retracker01-msk-virt.corbina.net:80/announce" - "udp://tracker.bitcoinindia.space:6969/announce" - "udp://tracker.dler.com:6969/announce" - "udp://tracker.fnix.net:6969/announce" - "udp://tracker.gigantino.net:6969/announce" - "udp://tracker.gmi.gd:6969/announce" - "udp://tracker.hifimarket.in:2710/announce" - "udp://tracker.hifitechindia.com:6969/announce" - "udp://tracker.kmzs123.cn:17272/announce" - "udp://tracker.opentrackr.org:1337/announce" - "udp://tracker.plx.im:6969/announce" - "udp://tracker.qu.ax:6969/announce" - "udp://tracker.rescuecrew7.com:1337/announce" - "udp://tracker.skillindia.site:6969/announce" - "udp://tracker.srv00.com:6969/announce" - "udp://tracker.therarbg.to:6969/announce" - "udp://tracker.torrent.eu.org:451/announce" - "udp://tracker.torrust-demo.com:6969/announce" - "udp://tracker.tryhackx.org:6969/announce" - "udp://tracker.tvunderground.org.ru:3218/announce" - "udp://tracker.valete.tf:9999/announce" - "udp://tracker.yume-hatsuyuki.moe:6969/announce" - "udp://tracker-udp.gbitt.info:80/announce" - "udp://ttk2.nbaonlineservice.com:6969/announce" - "udp://udp.tracker.projectk.org:23333/announce" - "udp://www.torrent.eu.org:451/announce" - ) - for i in "''${trackers_list[@]}"; do trackers="$i,$trackers"; done - # process - cd "$working_directory" || exit 1 - # magnet loop - for j in *.magnet; do - timeout 3m ${pkgs.aria2}/bin/aria2c --bt-tracker="$trackers" --bt-metadata-only=true --bt-save-metadata=true "$(cat "$j")" && rm "$j" - # wait for files to be picked up - sleep 30s - done - # removed added files - rm -- *.added - ''; -in { - environment.systemPackages = [ magnets ]; - - systemd.timers.magnet-watcher = { - wantedBy = [ "timers.target" ]; - timerConfig = { - OnCalendar = "*:0/10"; - Persistent = true; - }; - }; - - systemd.services.magnet-watcher = { - script = "magnets"; - path = [ "/run/current-system/sw" ]; - serviceConfig = { - Type = "oneshot"; - User = "${username}"; - }; - }; -} diff --git a/common/restic.nix b/common/restic.nix @@ -1,42 +0,0 @@ -{ pkgs, username, ... }: - -let - backup-cloud = pkgs.writeShellScriptBin "backup-cloud" '' - # variables - directories=( "$HOME/docker/" "$HOME/vault/" ) - # process - source "$HOME/vault/docs/secure/restic.env" - # Directory loop - for dir in "''${directories[@]}"; do - if [[ -d "$dir" ]]; then - echo "Directory exists: $dir" - ${pkgs.restic}/bin/restic backup "$dir" - else - echo "Directory does not exist: $dir" - fi - done - ''; -in { - environment.systemPackages = [ backup-cloud ]; - - systemd.timers.restic-backup = { - description = "Timer to run Restic backup"; - wantedBy = [ "timers.target" ]; - timerConfig = { - OnCalendar = "0/12:00:00"; - RandomizedDelaySec = "30min"; - Persistent = true; - }; - }; - - systemd.services.restic-backup = { - description = "Backup specific directories to BorgBase"; - script = "backup-cloud"; - path = [ "/run/current-system/sw" ]; - serviceConfig = { - Type = "oneshot"; - User = "${username}"; - }; - }; - -} diff --git a/common/stagit-generate.nix b/common/stagit-generate.nix @@ -1,140 +0,0 @@ -{ pkgs, username, ...}: let - css = '' - body,pre{font-family:monospace} - #blob,article img{max-width:100%} - html{font-size:12px;height:100%} - body{margin:5rem auto;color:#aaa;background-color:#272727;width:66rem} - pre{-moz-tab-size:4;tab-size:4} - h1,h2,h3,h4,h5,h6{font-size:1em;margin:0} - h1,h2,img{vertical-align:middle} - img{border:0} - a,a.d,a.h,a.i,a.line{color:#3498db;text-decoration:none} - #blob{display:block;overflow-x:scroll} - article.markup{font-size:15px;border:2px solid #00000017;border-radius:10px;font-family:sans-serif;padding:2.5em;margin:2em 0} - article.markup code{font-size:.9em;border:1px solid #dbdbdb;background-color:#f7f7f7;padding:0 .3em;border-radius:.3em} - article.markup pre code{border:none;background:0 0;padding:0;border-radius:0} - article.markup pre{background-color:#f7f7f7;padding:1em;border:1px solid #dbdbdb;border-radius:.3em} - article.markup h1{font-size:2.4em;padding-bottom:6px;border-bottom:5px solid #0000000a} - article.markup h2{font-size:1.9em;padding-bottom:5px;border-bottom:2px solid #00000014} - article.markup h3{font-size:1.5em} - article.markup h4{font-size:1.3em} - article.markup h5{font-size:1.1em} - article.markup h6{font-size:1em} - .linenos{margin-right:0;border-right:1px solid;user-select:none} - .linenos a{margin-right:.9em;user-select:none;text-decoration:none} - #blob a,.desc{color:#777} - table thead td{font-weight:700} - table td{padding:0 .4em} - #content table td{vertical-align:top;white-space:nowrap} - #branches tr:hover td,#files tr:hover td,#index tr:hover td,#log tr:hover td,#tags tr:hover td{background-color:#414141} - #branches tr td:nth-child(3),#index tr td:nth-child(2),#log tr td:nth-child(2),#tags tr td:nth-child(3){white-space:normal} - td.num{text-align:right} - hr{border:0;border-top:1px solid #777;height:1px} - .A,pre a.i,span.i{color:#29b74e} - .D,pre a.d,span.d{color:#e42533} - .url td:nth-child(2){padding-top:.2em;padding-bottom:.9em} - .url td:nth-child(2) span{padding:1px 5px;background-color:#eee;border:1px solid #ddd;border-radius:5px} - .url td:nth-child(2) span a{color:#444} - ''; - stagit-generate = pkgs.writeShellScriptBin "stagit-generate" '' - # variables - source_directory="$HOME/vault/src" - destination_directory="$HOME/docker/stagit" - state_file="$destination_directory/.stagit-state" - - mkdir -p "$destination_directory" - - # state file - if [ ! -f "$state_file" ]; then - echo "# stagit-generate state file" > "$state_file" - echo "# format: repo_path:last_commit_hash" >> "$state_file" - fi - get_latest_commit() { - local repo_path="$1" - cd "$repo_path" || return 1 - git rev-parse HEAD 2>/dev/null || echo "no-commits" - } - get_stored_commit() { - local repo_name="$1" - grep "^$repo_name:" "$state_file" 2>/dev/null | cut -d':' -f2- || echo "" - } - update_stored_commit() { - local repo_name="$1" - local commit_hash="$2" - grep -v "^$repo_name:" "$state_file" > "$state_file.tmp" 2>/dev/null || touch "$state_file.tmp" - echo "$repo_name:$commit_hash" >> "$state_file.tmp" - mv "$state_file.tmp" "$state_file" - } - - updated_repos=0 - skipped_repos=0 - index_needs_update=false - - # stagit loop - for repo in $(find "$source_directory" -type d -name '.git' | sed 's|/\.git$||'); do - repo_name=$(basename "$repo") - output_directory="$destination_directory/$repo_name" - current_commit=$(get_latest_commit "$repo") - stored_commit=$(get_stored_commit "$repo_name") - - # repo update check - if [ "$current_commit" != "$stored_commit" ] || [ ! -d "$output_directory" ]; then - echo "Updating $repo_name... (was: $stored_commit, now: $current_commit)" - - mkdir -p "$output_directory" - cd "$output_directory" || exit - - ${pkgs.stagit}/bin/stagit "$repo" - - cat > "style.css" <<EOF - ${css} -EOF - - # update state - update_stored_commit "$repo_name" "$current_commit" - updated_repos=$((updated_repos + 1)) - index_needs_update=true - else - echo "Skipping $repo_name (no changes since $stored_commit)" - skipped_repos=$((skipped_repos + 1)) - fi - done - - # re-generate index repositories were updated - if [ "$index_needs_update" = true ]; then - echo "Regenerating index..." - cd "$destination_directory" || exit - ${pkgs.stagit}/bin/stagit-index "''${source_directory}/"*/ >index.html - cat > "style.css" <<EOF - ${css} -EOF - else - echo "Index unchanged, skipping re-generation" - fi - - echo "Summary: Updated $updated_repos repositories, skipped $skipped_repos repositories" - ''; -in { - environment.systemPackages = [stagit-generate]; - - # Systemd service and timer configuration - systemd.services.stagit-generate = { - serviceConfig = { - Type = "oneshot"; - User = "${username}"; - ExecStart = "${stagit-generate}/bin/stagit-generate"; - StandardOutput = "journal"; - StandardError = "journal"; - }; - }; - - systemd.timers.stagit-generate = { - wantedBy = [ "timers.target" ]; - timerConfig = { - OnCalendar = "*-*-* 00,04,08,12,16,20:00:00"; - RandomizedDelaySec = "15m"; - Persistent = true; - AccuracySec = "1m"; - }; - }; -} -\ No newline at end of file diff --git a/common/tank-log.nix b/common/tank-log.nix @@ -1,66 +0,0 @@ -{ pkgs, username, ... }: - -let - tank-log = pkgs.writeShellScriptBin "tank-log" '' - # variables - git_directory="$HOME/vault/src/logger/" - file_git_log="$git_directory/media.log" - log_remote="nas:" - git_logger="git --git-dir=$git_directory/.git --work-tree=$git_directory" - # git configuruation - if [ ! -e "$git_directory" ]; then - printf "Logger directory not found, quitting...\n" - exit 1 - fi - if [ ! -e "$git_directory/.git" ]; then - printf "Initialising blank git repo...\n" - $git_logger init - fi - if [ -e "$file_git_log.xz" ]; then - printf "Decompressing existing xz archive...\n" - xz -d "$file_git_log.xz" - fi - if [ -e "$file_git_log" ]; then - printf "Removing existing log file...\n" - rm "$file_git_log" - fi - printf "Creating log...\n" - ${pkgs.rclone}/bin/rclone ls "$log_remote" | sort -k2 >"$file_git_log" - printf "Appending size information...\n" - ${pkgs.rclone}/bin/rclone size "$log_remote" >>"$file_git_log" - printf "Commiting log file to repository...\n" - $git_logger add "$file_git_log" - $git_logger commit -m "Update: $(date +%F)" - if [ -e "$file_git_log.xz" ]; then - printf "Removing xz archive...\n" - rm "$file_git_log.xz" - fi - printf "Compressing log file...\n" - xz "$file_git_log" - printf "Compressing repository...\n" - $git_logger config pack.windowMemory 10m - $git_logger config pack.packSizeLimit 20m - $git_logger gc --aggressive --prune - printf "Log complete!\n" - ''; -in { - environment.systemPackages = [tank-log]; - - systemd.timers.tank-log = { - wantedBy = [ "timers.target" ]; - timerConfig = { - OnCalendar = [ "0/12:20:00" ]; - RandomizedDelaySec = "10min"; - Persistent = true; - }; - }; - - systemd.services.tank-log = { - script = "tank-log"; - path = [ "/run/current-system/sw" ]; - serviceConfig = { - Type = "oneshot"; - User = "${username}"; - }; - }; -} diff --git a/common/tank-sort.nix b/common/tank-sort.nix @@ -1,95 +0,0 @@ -{ pkgs, username, ... }: - -let - media-sort = pkgs.callPackage ./media-sort.nix {}; - - tank-sort = pkgs.writeShellScriptBin "tank-sort" '' - set -euo pipefail # Exit on any error - - # variables - temp_mount="$(mktemp -d)" - rclone_remote="seedbox:" - destination_tvshows="/tank/media/videos/television" - template_tvshows="{{ .Name }}/{{ .Name }} S{{ printf \"%02d\" .Season }}E{{ printf \"%02d\" .Episode }}{{ if ne .ExtraEpisode -1 }}-{{ printf \"%02d\" .ExtraEpisode }}{{end}}.{{ .Ext }}" - destination_movies="/tank/media/videos/movies" - template_movies="{{ .Name }} ({{ .Year }})/{{ .Name }}.{{ .Ext }}" - - # Cleanup function - cleanup() { - echo "Cleaning up..." - if mountpoint -q "$temp_mount" 2>/dev/null; then - fusermount -uz "$temp_mount" 2>/dev/null || true - fi - if [ -d "$temp_mount" ]; then - rmdir "$temp_mount" 2>/dev/null || true - fi - } - trap cleanup EXIT - - # mount remote - echo "Mounting rclone remote..." - if ! ${pkgs.rclone}/bin/rclone mount "$rclone_remote" "$temp_mount" \ - --vfs-cache-mode writes \ - --daemon-timeout 10s \ - --daemon; then - echo "ERROR: Failed to mount rclone remote" - exit 1 - fi - - # Wait for mount to be ready - echo "Waiting for mount to be ready..." - for i in {1..30}; do - if mountpoint -q "$temp_mount" 2>/dev/null; then - echo "Mount is ready" - break - fi - if [ $i -eq 30 ]; then - echo "ERROR: Mount failed to become ready within 30 seconds" - exit 1 - fi - sleep 1 - done - - # sorting process - echo "Starting media sort..." - ${media-sort}/bin/media-sort \ - --action copy \ - --concurrency 1 \ - --accuracy-threshold 90 \ - --tv-dir "$destination_tvshows" \ - --movie-dir "$destination_movies" \ - --tv-template "$template_tvshows" \ - --movie-template "$template_movies" \ - --recursive \ - --overwrite-if-larger \ - --extensions "mp4,m4v,mkv" \ - "$temp_mount" - - echo "Media sort completed successfully" - ''; -in { - environment.systemPackages = [tank-sort media-sort]; - - systemd.timers.tank-sort = { - wantedBy = [ "timers.target" ]; - timerConfig = { - OnCalendar = "0/12:00:00"; - RandomizedDelaySec = "5min"; - Persistent = true; - }; - }; - - systemd.services.tank-sort = { - script = "tank-sort"; - path = with pkgs; [ "/run/current-system/sw" rclone fuse util-linux media-sort ]; - serviceConfig = { - Type = "oneshot"; - User = "${username}"; - Restart = "no"; - RestartSec = "30s"; - SupplementaryGroups = [ "fuse" ]; - WorkingDirectory = "/tmp"; - TimeoutStartSec = "300s"; - }; - }; -} diff --git a/machines/artemis.nix b/machines/artemis.nix @@ -60,5 +60,17 @@ in { networking.hostName = "artemis"; # Define your hostname. system.stateVersion = "25.05"; # Did you read the comment? + # Cron jobs + services.cron = { + enable = true; + systemCronJobs = [ + "*/10 * * * * ${username} blog-status" + "*/10 * * * * ${username} magnets" + "*/10 * * * * ${username} stagit-generate" + "55 23 * * SUN ${username} blog-duolingo" + "0 */12 * * * ${username} backup-cloud" + ]; + }; + } diff --git a/machines/ilias.nix b/machines/ilias.nix @@ -71,6 +71,16 @@ in { # Hostname networking.hostName = "ilias"; # Define your hostname. + # Cron jobs + services = { + cron = { + enable = true; + systemCronJobs = [ + "0 */4 * * * ${username} tank-sort" + "5 */4 * * * ${username} tank-log" + "0 */12 * * * ${username} backup-cloud" + ]; + }; }; # Firewall and NFS server ports networking.firewall.enable = true; diff --git a/scripts/blog-duolingo.nix b/scripts/blog-duolingo.nix @@ -0,0 +1,29 @@ +{ pkgs, domain, ... }: + +let + blog-duolingo = pkgs.writeShellScriptBin "blog-duolingo" '' + # variables + username="$(awk -F'[/()]' '/Duolingo/ {print $5}' "$HOME/vault/src/blog.${domain}/content/about.md")" + post_file="$HOME/vault/src/blog.${domain}/content/posts/logging-duolingo-ranks-over-time.md" + # functions + function lastmod { + echo -n "Amending lastmod value... " + mod_timestamp="$(date +%FT%H:%M:00)" + sed -i "s/lastmod: .*/lastmod: $mod_timestamp/g" "$1" + echo -e "$i \e[32mdone\e[39m" + } + # process + page_source="$(curl -s https://duome.eu/"$username")" + rank_lingot="$(printf %s "$page_source" | awk -F"[#><]" '/icon lingot/ {print $15}')" + rank_streak="$(printf %s "$page_source" | awk -F"[#><]" '/icon streak/{getline;print $15}')" + # write + echo -e "$i \e[32mdone\e[39m" + echo -n "Appending ranks to page... " + sed -i '/<\/tbody><\/table>/d' "$post_file" + printf " <tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n</tbody></table>" "$(date +%F)" "$(date +%H:%M)" "$rank_streak" "$rank_lingot" >>"$post_file" + echo -e "$i \e[32mdone\e[39m" + lastmod "$post_file" + ''; +in { + environment.systemPackages = [blog-duolingo]; +} diff --git a/scripts/blog-status.nix b/scripts/blog-status.nix @@ -0,0 +1,27 @@ +{ pkgs, domain, ... }: + +let + blog-status = pkgs.writeShellScriptBin "blog-status" '' + # variables + status_uptime=$(($(cut -f1 -d. </proc/uptime) / 86400)) + # process + { + printf -- "---\\ntitle: Status\\nlayout: single\\n---\\n\\n" + printf "*Generated on %(%Y-%m-%d at %H:%M)T*\\n\\n" -1 + printf "* Uptime: %s Day%s\\n" "$status_uptime" "$(if (("$status_uptime" > 1)); then echo s; fi)" + printf "* CPU Load: %s\\n" "$(cut -d" " -f1-3 </proc/loadavg)" + printf "* Users: %s\\n" "$(who | wc -l)" + printf "* RAM Usage: %s%%\\n" "$(printf "%.2f" "$(free | awk '/Mem/ {print $3/$2 * 100.0}')")" + printf "* Root Storage: %s\\n" "$(df / | awk 'END{print $5}')" + printf "* Tank Storage: %s\\n" "$(df | awk -v tank="/tank" '$0 ~ tank {print $5}')" + printf "* Torrent Ratio: %s\\n" "$(echo "scale=3; $(awk '/upload/ {print $2}' "$HOME/docker/transmission/stats.json") / $(awk '/download/ {print $2}' "$HOME/docker/transmission/stats.json" | sed 's/,//g')" | ${pkgs.bc}/bin/bc)" + printf "* NAS Storage: %s\\n" "$(git --git-dir="$HOME/vault/src/logger/.git" show | awk 'END{print $3" "$4}')" + printf "* [Containers](https://github.com/breadcat/nix-configs/blob/main/common/docker.nix): %s\\n" "$(docker ps -q | wc -l)/$(docker ps -aq | wc -l)" + printf "* Packages: %s\\n" "$(${pkgs.fastfetch}/bin/fastfetch | awk -F": " '/Packages/ {print $2}')" + printf "* Monthly Data: %s\\n" "$(vnstat -m --oneline | cut -f11 -d\;)" + printf "\\nHardware specifications themselves are covered on the [hardware page](/hardware/#server).\\n" + } >"$HOME/vault/src/blog.${domain}/content/status.md" + ''; +in { + environment.systemPackages = [blog-status]; +} diff --git a/scripts/magnets.nix b/scripts/magnets.nix @@ -0,0 +1,84 @@ +{ pkgs, ... }: + +let + magnets = pkgs.writeShellScriptBin "magnets" '' + # variables + working_directory="$HOME/vault/watch" + # trackers + trackers_list=( + "http://0123456789nonexistent.com:80/announce" + "http://bt.okmp3.ru:2710/announce" + "http://ipv4.rer.lol:2710/announce" + "http://open.trackerlist.xyz:80/announce" + "http://shubt.net:2710/announce" + "http://taciturn-shadow.spb.ru:6969/announce" + "http://torrent.hificode.in:6969/announce" + "http://tracker.bt4g.com:2095/announce" + "http://tracker.mywaifu.best:6969/announce" + "http://tracker.netmap.top:6969/announce" + "http://tracker.privateseedbox.xyz:2710/announce" + "http://tracker.renfei.net:8080/announce" + "https://pybittrack.retiolus.net:443/announce" + "https://t.213891.xyz:443/announce" + "https://tr.nyacat.pw:443/announce" + "https://tracker.aburaya.live:443/announce" + "https://tracker.expli.top:443/announce" + "https://tracker.ghostchu-services.top:443/announce" + "https://tracker.jdx3.org:443/announce" + "https://tracker.leechshield.link:443/announce" + "https://tracker.moeblog.cn:443/announce" + "https://tracker.yemekyedim.com:443/announce" + "https://tracker.zhuqiy.top:443/announce" + "udp://1c.premierzal.ru:6969/announce" + "udp://bandito.byterunner.io:6969/announce" + "udp://d40969.acod.regrucolo.ru:6969/announce" + "udp://evan.im:6969/announce" + "udp://extracker.dahrkael.net:6969/announce" + "udp://martin-gebhardt.eu:25/announce" + "udp://open.demonii.com:1337/announce" + "udp://open.dstud.io:6969/announce" + "udp://open.stealth.si:80/announce" + "udp://p4p.arenabg.com:1337/announce" + "udp://retracker.lanta.me:2710/announce" + "udp://retracker01-msk-virt.corbina.net:80/announce" + "udp://tracker.bitcoinindia.space:6969/announce" + "udp://tracker.dler.com:6969/announce" + "udp://tracker.fnix.net:6969/announce" + "udp://tracker.gigantino.net:6969/announce" + "udp://tracker.gmi.gd:6969/announce" + "udp://tracker.hifimarket.in:2710/announce" + "udp://tracker.hifitechindia.com:6969/announce" + "udp://tracker.kmzs123.cn:17272/announce" + "udp://tracker.opentrackr.org:1337/announce" + "udp://tracker.plx.im:6969/announce" + "udp://tracker.qu.ax:6969/announce" + "udp://tracker.rescuecrew7.com:1337/announce" + "udp://tracker.skillindia.site:6969/announce" + "udp://tracker.srv00.com:6969/announce" + "udp://tracker.therarbg.to:6969/announce" + "udp://tracker.torrent.eu.org:451/announce" + "udp://tracker.torrust-demo.com:6969/announce" + "udp://tracker.tryhackx.org:6969/announce" + "udp://tracker.tvunderground.org.ru:3218/announce" + "udp://tracker.valete.tf:9999/announce" + "udp://tracker.yume-hatsuyuki.moe:6969/announce" + "udp://tracker-udp.gbitt.info:80/announce" + "udp://ttk2.nbaonlineservice.com:6969/announce" + "udp://udp.tracker.projectk.org:23333/announce" + "udp://www.torrent.eu.org:451/announce" + ) + for i in "''${trackers_list[@]}"; do trackers="$i,$trackers"; done + # process + cd "$working_directory" || exit 1 + # magnet loop + for j in *.magnet; do + timeout 3m ${pkgs.aria2}/bin/aria2c --bt-tracker="$trackers" --bt-metadata-only=true --bt-save-metadata=true "$(cat "$j")" && rm "$j" + # wait for files to be picked up + sleep 30s + done + # removed added files + rm -- *.added + ''; +in { + environment.systemPackages = [ magnets ]; +} diff --git a/scripts/restic.nix b/scripts/restic.nix @@ -0,0 +1,21 @@ +{ pkgs, ... }: + +let + backup-cloud = pkgs.writeShellScriptBin "backup-cloud" '' + # variables + directories=( "$HOME/docker/" "$HOME/vault/" ) + # process + source "$HOME/vault/docs/secure/restic.env" + # Directory loop + for dir in "''${directories[@]}"; do + if [[ -d "$dir" ]]; then + echo "Directory exists: $dir" + ${pkgs.restic}/bin/restic backup "$dir" + else + echo "Directory does not exist: $dir" + fi + done + ''; +in { + environment.systemPackages = [ backup-cloud ]; +} diff --git a/scripts/stagit-generate.nix b/scripts/stagit-generate.nix @@ -0,0 +1,119 @@ +{ pkgs, ...}: let + css = '' + body,pre{font-family:monospace} + #blob,article img{max-width:100%} + html{font-size:12px;height:100%} + body{margin:5rem auto;color:#aaa;background-color:#272727;width:66rem} + pre{-moz-tab-size:4;tab-size:4} + h1,h2,h3,h4,h5,h6{font-size:1em;margin:0} + h1,h2,img{vertical-align:middle} + img{border:0} + a,a.d,a.h,a.i,a.line{color:#3498db;text-decoration:none} + #blob{display:block;overflow-x:scroll} + article.markup{font-size:15px;border:2px solid #00000017;border-radius:10px;font-family:sans-serif;padding:2.5em;margin:2em 0} + article.markup code{font-size:.9em;border:1px solid #dbdbdb;background-color:#f7f7f7;padding:0 .3em;border-radius:.3em} + article.markup pre code{border:none;background:0 0;padding:0;border-radius:0} + article.markup pre{background-color:#f7f7f7;padding:1em;border:1px solid #dbdbdb;border-radius:.3em} + article.markup h1{font-size:2.4em;padding-bottom:6px;border-bottom:5px solid #0000000a} + article.markup h2{font-size:1.9em;padding-bottom:5px;border-bottom:2px solid #00000014} + article.markup h3{font-size:1.5em} + article.markup h4{font-size:1.3em} + article.markup h5{font-size:1.1em} + article.markup h6{font-size:1em} + .linenos{margin-right:0;border-right:1px solid;user-select:none} + .linenos a{margin-right:.9em;user-select:none;text-decoration:none} + #blob a,.desc{color:#777} + table thead td{font-weight:700} + table td{padding:0 .4em} + #content table td{vertical-align:top;white-space:nowrap} + #branches tr:hover td,#files tr:hover td,#index tr:hover td,#log tr:hover td,#tags tr:hover td{background-color:#414141} + #branches tr td:nth-child(3),#index tr td:nth-child(2),#log tr td:nth-child(2),#tags tr td:nth-child(3){white-space:normal} + td.num{text-align:right} + hr{border:0;border-top:1px solid #777;height:1px} + .A,pre a.i,span.i{color:#29b74e} + .D,pre a.d,span.d{color:#e42533} + .url td:nth-child(2){padding-top:.2em;padding-bottom:.9em} + .url td:nth-child(2) span{padding:1px 5px;background-color:#eee;border:1px solid #ddd;border-radius:5px} + .url td:nth-child(2) span a{color:#444} + ''; + stagit-generate = pkgs.writeShellScriptBin "stagit-generate" '' + # variables + source_directory="$HOME/vault/src" + destination_directory="$HOME/docker/stagit" + state_file="$destination_directory/.stagit-state" + + mkdir -p "$destination_directory" + + # state file + if [ ! -f "$state_file" ]; then + echo "# stagit-generate state file" > "$state_file" + echo "# format: repo_path:last_commit_hash" >> "$state_file" + fi + get_latest_commit() { + local repo_path="$1" + cd "$repo_path" || return 1 + git rev-parse HEAD 2>/dev/null || echo "no-commits" + } + get_stored_commit() { + local repo_name="$1" + grep "^$repo_name:" "$state_file" 2>/dev/null | cut -d':' -f2- || echo "" + } + update_stored_commit() { + local repo_name="$1" + local commit_hash="$2" + grep -v "^$repo_name:" "$state_file" > "$state_file.tmp" 2>/dev/null || touch "$state_file.tmp" + echo "$repo_name:$commit_hash" >> "$state_file.tmp" + mv "$state_file.tmp" "$state_file" + } + + updated_repos=0 + skipped_repos=0 + index_needs_update=false + + # stagit loop + for repo in $(find "$source_directory" -type d -name '.git' | sed 's|/\.git$||'); do + repo_name=$(basename "$repo") + output_directory="$destination_directory/$repo_name" + current_commit=$(get_latest_commit "$repo") + stored_commit=$(get_stored_commit "$repo_name") + + # repo update check + if [ "$current_commit" != "$stored_commit" ] || [ ! -d "$output_directory" ]; then + echo "Updating $repo_name... (was: $stored_commit, now: $current_commit)" + + mkdir -p "$output_directory" + cd "$output_directory" || exit + + ${pkgs.stagit}/bin/stagit "$repo" + + cat > "style.css" <<EOF + ${css} +EOF + + # update state + update_stored_commit "$repo_name" "$current_commit" + updated_repos=$((updated_repos + 1)) + index_needs_update=true + else + echo "Skipping $repo_name (no changes since $stored_commit)" + skipped_repos=$((skipped_repos + 1)) + fi + done + + # re-generate index repositories were updated + if [ "$index_needs_update" = true ]; then + echo "Regenerating index..." + cd "$destination_directory" || exit + ${pkgs.stagit}/bin/stagit-index "''${source_directory}/"*/ >index.html + cat > "style.css" <<EOF + ${css} +EOF + else + echo "Index unchanged, skipping re-generation" + fi + + echo "Summary: Updated $updated_repos repositories, skipped $skipped_repos repositories" + ''; +in { + environment.systemPackages = [stagit-generate]; +} +\ No newline at end of file diff --git a/scripts/tank-log.nix b/scripts/tank-log.nix @@ -0,0 +1,48 @@ +{ pkgs, ... }: + +let + tank-log = pkgs.writeShellScriptBin "tank-log" '' + # variables + git_directory="$HOME/vault/src/logger/" + file_git_log="$git_directory/media.log" + log_remote="nas:" + git_logger="git --git-dir=$git_directory/.git --work-tree=$git_directory" + # git configuruation + if [ ! -e "$git_directory" ]; then + printf "Logger directory not found, quitting...\n" + exit 1 + fi + if [ ! -e "$git_directory/.git" ]; then + printf "Initialising blank git repo...\n" + $git_logger init + fi + if [ -e "$file_git_log.xz" ]; then + printf "Decompressing existing xz archive...\n" + xz -d "$file_git_log.xz" + fi + if [ -e "$file_git_log" ]; then + printf "Removing existing log file...\n" + rm "$file_git_log" + fi + printf "Creating log...\n" + ${pkgs.rclone}/bin/rclone ls "$log_remote" | sort -k2 >"$file_git_log" + printf "Appending size information...\n" + ${pkgs.rclone}/bin/rclone size "$log_remote" >>"$file_git_log" + printf "Commiting log file to repository...\n" + $git_logger add "$file_git_log" + $git_logger commit -m "Update: $(date +%F)" + if [ -e "$file_git_log.xz" ]; then + printf "Removing xz archive...\n" + rm "$file_git_log.xz" + fi + printf "Compressing log file...\n" + xz "$file_git_log" + printf "Compressing repository...\n" + $git_logger config pack.windowMemory 10m + $git_logger config pack.packSizeLimit 20m + $git_logger gc --aggressive --prune + printf "Log complete!\n" + ''; +in { + environment.systemPackages = [tank-log]; +} diff --git a/scripts/tank-sort.nix b/scripts/tank-sort.nix @@ -0,0 +1,72 @@ +{ pkgs, ... }: + +let + media-sort = pkgs.callPackage ../common/media-sort.nix {}; + + tank-sort = pkgs.writeShellScriptBin "tank-sort" '' + set -euo pipefail # Exit on any error + + # variables + temp_mount="$(mktemp -d)" + rclone_remote="seedbox:" + destination_tvshows="/tank/media/videos/television" + template_tvshows="{{ .Name }}/{{ .Name }} S{{ printf \"%02d\" .Season }}E{{ printf \"%02d\" .Episode }}{{ if ne .ExtraEpisode -1 }}-{{ printf \"%02d\" .ExtraEpisode }}{{end}}.{{ .Ext }}" + destination_movies="/tank/media/videos/movies" + template_movies="{{ .Name }} ({{ .Year }})/{{ .Name }}.{{ .Ext }}" + + # Cleanup function + cleanup() { + echo "Cleaning up..." + if mountpoint -q "$temp_mount" 2>/dev/null; then + fusermount -uz "$temp_mount" 2>/dev/null || true + fi + if [ -d "$temp_mount" ]; then + rmdir "$temp_mount" 2>/dev/null || true + fi + } + trap cleanup EXIT + + # mount remote + echo "Mounting rclone remote..." + if ! ${pkgs.rclone}/bin/rclone mount "$rclone_remote" "$temp_mount" \ + --vfs-cache-mode writes \ + --daemon-timeout 10s \ + --daemon; then + echo "ERROR: Failed to mount rclone remote" + exit 1 + fi + + # Wait for mount to be ready + echo "Waiting for mount to be ready..." + for i in {1..30}; do + if mountpoint -q "$temp_mount" 2>/dev/null; then + echo "Mount is ready" + break + fi + if [ $i -eq 30 ]; then + echo "ERROR: Mount failed to become ready within 30 seconds" + exit 1 + fi + sleep 1 + done + + # sorting process + echo "Starting media sort..." + ${media-sort}/bin/media-sort \ + --action copy \ + --concurrency 1 \ + --accuracy-threshold 90 \ + --tv-dir "$destination_tvshows" \ + --movie-dir "$destination_movies" \ + --tv-template "$template_tvshows" \ + --movie-template "$template_movies" \ + --recursive \ + --overwrite-if-larger \ + --extensions "mp4,m4v,mkv" \ + "$temp_mount" + + echo "Media sort completed successfully" + ''; +in { + environment.systemPackages = [tank-sort media-sort]; +}