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:
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)