nix

configuration files that power my machines
Log | Files | Refs | README | LICENSE

commit 20dd40fe3905aefe346b99ef55c0cac7d5fa355c
parent af0a367c276bea4f3fdb0a9aa5f25130cb4e793d
Author: mtmn <miro@haravara.org>
Date:   Tue,  7 Jul 2026 16:04:55 +0200

void: install stacked-git, redo backup plakar sources

Diffstat:
Mflake.lock | 12++++++------
Mhosts/void/default.nix | 1+
Mmodules/mixins/dotfiles/bin/backup | 157+++++++++++++++++++++++++++++++++++++------------------------------------------
3 files changed, 81 insertions(+), 89 deletions(-)

diff --git a/flake.lock b/flake.lock @@ -164,11 +164,11 @@ ] }, "locked": { - "lastModified": 1783358420, - "narHash": "sha256-UEyBpf+XmQe8laXfDzIlo11Er7E0rKrLS8jeMnMj0Ds=", + "lastModified": 1783436070, + "narHash": "sha256-F80z1JoiJgZyTT5D+3siLOVzqmCEphLR7t0Ym/I2CLI=", "owner": "nix-community", "repo": "home-manager", - "rev": "0d33882bd46c1f9ef62276700f5e5f2bd6ff6604", + "rev": "f09af49406ffad37acb6538d3207189a1a1e0b7e", "type": "github" }, "original": { @@ -206,11 +206,11 @@ ] }, "locked": { - "lastModified": 1783233851, - "narHash": "sha256-jzisD+3wWZPC4QvAsKtYguiGsmlH2SN3Mjx5LWd68Ok=", + "lastModified": 1783414108, + "narHash": "sha256-RY8e+gR2/DhXsYDxGDL6Hhe9+POD9u4+llwxMBqMQDs=", "owner": "nix-community", "repo": "nix-index-database", - "rev": "fab14c7b63499d57cb6673d5690168c3ec42b99a", + "rev": "96d6de10eb281b98f5cfcd1841394c03da5df77c", "type": "github" }, "original": { diff --git a/hosts/void/default.nix b/hosts/void/default.nix @@ -59,6 +59,7 @@ prr tig dura + stgit ]; net = lib.concatLists [ diff --git a/modules/mixins/dotfiles/bin/backup b/modules/mixins/dotfiles/bin/backup @@ -1,5 +1,6 @@ #!/usr/bin/env ruby # frozen_string_literal: true + # vim: ft=ruby require "English" @@ -13,7 +14,7 @@ module Backup def exitcode = status&.exitstatus || 127 end - Backend = Struct.new(:max_depth, :list, :run_all, :run_one, :run_leaf, keyword_init: true) + Backend = Struct.new(:list, :run) @failures = [] @@ -43,19 +44,19 @@ module Backup PLAKAR_CONFIG_DIR = "#{HOME}/.config/plakar".freeze GIT_DIRS = { + "bandcamp" => "#{HOME}/misc/music/backlog/_todo", "mpd" => "#{HOME}/.config/mpd", "nix" => "#{HOME}/src/sr.ht/nix", "nota" => "#{HOME}/.nb/notes", "pass" => "#{HOME}/.password-store", - "releases" => "#{HOME}/src/mtmn.name/releases", - "bandcamp" => "#{HOME}/misc/music/backlog/_todo" + "releases" => "#{HOME}/src/mtmn.name/releases" }.freeze TARSNAP_DIRS = { - "password-store" => "#{HOME}/.password-store", - "releases" => "#{HOME}/src/mtmn.name/releases", "mpd" => "#{HOME}/.config/mpd", - "nota" => "#{HOME}/.nb/notes" + "nota" => "#{HOME}/.nb/notes", + "password-store" => "#{HOME}/.password-store", + "releases" => "#{HOME}/src/mtmn.name/releases" }.freeze def self.run_cmd(verbose, *args) @@ -68,7 +69,6 @@ module Backup out, err, status = Open3.capture3(*args) Result.new(status, out, err) end - rescue Errno::ENOENT => e Result.new(nil, "", e.message) end @@ -79,41 +79,31 @@ module Backup def self.commit_changes(dir, verbose:) r = git(dir, "diff", "--cached", "--name-only", verbose: false) - return unless check("Failed to list staged changes in #{dir}", r) + return false unless check("Failed to list staged changes in #{dir}", r) files = r.out.lines.map(&:strip).reject(&:empty?).join(" ") r2 = git(dir, "commit", "-m", files, verbose: verbose) - return unless check("Failed to commit changes in #{dir}", r2) + return false unless check("Failed to commit changes in #{dir}", r2) log("Committed changes in #{dir}") true end - def self.push_changes(dir) - log("Pushing changes from #{dir}") - r = git(dir, "push", verbose: false) + def self.git_remote(dir, action, verb) + log("#{verb.capitalize} changes from #{dir}") + r = git(dir, action, verbose: false) case r.exitcode when 0 - log("Done pushing #{dir}") + log("Done #{verb}ing #{dir}") when 128 - log("No push destination for #{dir} (skipped)") + log("No #{verb} destination for #{dir} (skipped)") else - record_failure("Push failed for #{dir} (exit #{r.exitcode}): #{r.err.strip}") + record_failure("#{verb.capitalize} failed for #{dir} (exit #{r.exitcode}): #{r.err.strip}") end end - def self.fetch_changes(dir) - log("Fetching changes for #{dir}") - r = git(dir, "pull", verbose: false) - case r.exitcode - when 0 - log("Done fetching #{dir}") - when 128 - log("No fetch destination for #{dir} (skipped)") - else - record_failure("Fetch failed for #{dir} (exit #{r.exitcode}): #{r.err.strip}") - end - end + def self.push_changes(dir) = git_remote(dir, "push", "push") + def self.fetch_changes(dir) = git_remote(dir, "pull", "fetch") def self.sync_repo(verbose:, push:, fetch:, name:, dir:) log("git/#{name}") @@ -122,14 +112,14 @@ module Backup return unless check("Failed to stage changes in #{dir}", r) diff = git(dir, "diff", "--cached", "--quiet", verbose: false) - ok = if diff.success? + committed = if diff.success? log("Nothing to commit in #{dir}") true else commit_changes(dir, verbose: verbose) end - push_changes(dir) if push && ok + push_changes(dir) if push && committed end def self.tarsnap_archive(verbose:, name:, dir:) @@ -172,21 +162,21 @@ module Backup return nil unless File.file?(path) YAML.load_file(path)[name.to_s] || {} - rescue StandardError => e + rescue => e fatal("Failed to parse #{path}: #{e.message}") end def self.plakar_stores - load_plakar_yml("stores") || plakar_query("store") + @plakar_stores ||= (load_plakar_yml("stores") || plakar_query("store")).freeze end def self.plakar_sources - load_plakar_yml("sources") || plakar_query("source") + @plakar_sources ||= (load_plakar_yml("sources") || plakar_query("source")).freeze end - PLAKAR_STORES_HASH = plakar_stores.freeze - PLAKAR_STORES = PLAKAR_STORES_HASH.keys.freeze - PLAKAR_SOURCES = plakar_sources.keys.freeze + def self.plakar_stores_hash = plakar_stores + def self.plakar_stores_list = plakar_stores.keys + def self.plakar_sources_list = plakar_sources.keys def self.plakar_ignore_file(store) path = File.join(PLAKAR_CONFIG_DIR, "#{store}.ignore") @@ -194,7 +184,6 @@ module Backup end def self.plakar_source(verbose:, store:, source:) - fatal("Unknown plakar source: #{source}") unless PLAKAR_SOURCES.include?(source) ignore = plakar_ignore_file(store) args = ["plakar", "at", "@#{store}", "backup"] args.push("-ignore-file", ignore) if ignore @@ -207,12 +196,12 @@ module Backup end def self.plakar_store(verbose:, store:) - fatal("Unknown plakar store: #{store}") unless PLAKAR_STORES.include?(store) - PLAKAR_SOURCES.each { |src| plakar_source(verbose: verbose, store: store, source: src) } + fatal("Unknown plakar store: #{store}") unless plakar_stores_list.include?(store) + plakar_sources_list.each { |src| plakar_source(verbose: verbose, store: store, source: src) } end def self.backup_plakar(verbose:) - PLAKAR_STORES.each { |store| plakar_store(verbose: verbose, store: store) } + plakar_stores_list.each { |store| plakar_store(verbose: verbose, store: store) } end def self.list_flat(name, dirs) @@ -222,54 +211,64 @@ module Backup def self.list_plakar puts("plakar") - PLAKAR_STORES.sort.each do |store| - loc = PLAKAR_STORES_HASH.dig(store, "location") || "?" + puts(" sources") + plakar_sources_list.sort.each { |s| puts(" #{s}") } + plakar_stores_list.sort.each do |store| + loc = plakar_stores_hash.dig(store, "location") || "?" puts(" #{store} (#{loc})") - PLAKAR_SOURCES.sort.each { |s| puts(" #{s}") } + end + end + + def self.git_run(verbose:, push:, fetch:, parts:) + if parts.empty? + GIT_DIRS.each { |n, d| sync_repo(verbose: verbose, push: push, fetch: fetch, name: n, dir: d) } + else + name = parts.first + sync_repo( + verbose: verbose, + push: push, + fetch: fetch, + name: name, + dir: GIT_DIRS.fetch(name) { fatal("Unknown git target: #{name}") } + ) + end + end + + def self.tarsnap_run(verbose:, parts:) + if parts.empty? + TARSNAP_DIRS.each { |n, d| tarsnap_archive(verbose: verbose, name: n, dir: d) } + else + name = parts.first + tarsnap_archive( + verbose: verbose, + name: name, + dir: TARSNAP_DIRS.fetch(name) { fatal("Unknown tarsnap target: #{name}") } + ) + end + end + + def self.plakar_run(verbose:, parts:) + if parts.empty? + backup_plakar(verbose: verbose) + elsif parts.size == 1 + plakar_store(verbose: verbose, store: parts.first) + else + plakar_source(verbose: verbose, store: parts[0], source: parts[1]) end end BACKENDS = { "git" => Backend.new( - max_depth: 2, list: -> { list_flat("git", GIT_DIRS) }, - run_all: -> (verbose:, push:, fetch:, **) { - GIT_DIRS.each { |n, d| sync_repo(verbose: verbose, push: push, fetch: fetch, name: n, dir: d) } - }, - run_one: -> (verbose:, push:, fetch:, name:, **) { - sync_repo( - verbose: verbose, - push: push, - fetch: fetch, - name: name, - dir: GIT_DIRS.fetch(name) { fatal("Unknown git target: #{name}") } - ) - }, - run_leaf: nil + run: ->(verbose:, push:, fetch:, parts:) { git_run(verbose: verbose, push: push, fetch: fetch, parts: parts) } ), "tarsnap" => Backend.new( - max_depth: 2, list: -> { list_flat("tarsnap", TARSNAP_DIRS) }, - run_all: -> (verbose:, push:, **) { - TARSNAP_DIRS.each { |n, d| tarsnap_archive(verbose: verbose, name: n, dir: d) } - }, - run_one: -> (verbose:, push:, name:, **) { - tarsnap_archive( - verbose: verbose, - name: name, - dir: TARSNAP_DIRS.fetch(name) { fatal("Unknown tarsnap target: #{name}") } - ) - }, - run_leaf: nil + run: ->(verbose:, push:, fetch:, parts:) { tarsnap_run(verbose: verbose, parts: parts) } ), "plakar" => Backend.new( - max_depth: 3, list: -> { list_plakar }, - run_all: -> (verbose:, push:, **) { backup_plakar(verbose: verbose) }, - run_one: -> (verbose:, push:, name:, **) { plakar_store(verbose: verbose, store: name) }, - run_leaf: -> (verbose:, push:, store:, source:, **) { - plakar_source(verbose: verbose, store: store, source: source) - } + run: ->(verbose:, push:, fetch:, parts:) { plakar_run(verbose: verbose, parts: parts) } ) }.freeze @@ -281,15 +280,7 @@ module Backup parts = target.split(":") bname = parts.shift backend = BACKENDS[bname] || fatal("Unknown target: #{target}") - if parts.size > backend.max_depth - 1 - fatal("Unknown target: #{target}") - elsif parts.empty? - backend.run_all.call(verbose: verbose, push: push, fetch: fetch) - elsif parts.size == 1 - backend.run_one.call(verbose: verbose, push: push, fetch: fetch, name: parts[0]) - else - backend.run_leaf.call(verbose: verbose, push: push, fetch: fetch, store: parts[0], source: parts[1]) - end + backend.run.call(verbose: verbose, push: push, fetch: fetch, parts: parts) end def self.main(args)