Commit Diff


commit - 07461bbc2e0322a12f588a41d54805de6a87aae4
commit + 6feee7d6ad31b0b2185ed57342a88d3e570bb981
blob - 013f5b458bb09b1c1a60cf170fb3db0e8b02e326
blob + f681cda2dec4d8571d7993a81d215c31c35f77eb
Binary files hosts/impish/overlays/config/magdalena/config.json.nix and hosts/impish/overlays/config/magdalena/config.json.nix differ
blob - beb932be04cc8905c5dad7002ad382e10f4ab557
blob + 3b32bbbdb771a9eadec88ce3d169ddf74a59b07e
--- justfile
+++ justfile
@@ -8,15 +8,15 @@ check:
     nix flake check --no-update-lock-file
 
 deploy host:
-    @backup git:nix
+    @sursum git:nix
     NH_ELEVATION_STRATEGY=none nh os switch . --hostname {{ host }} --target-host root@{{ host }} --build-host root@{{ host }}
 
 home:
-    -@backup git:nix
+    -@sursum git:nix
     nh home switch .
 
 diff host:
-    -@backup git:nix
+    -@sursum git:nix
     NH_ELEVATION_STRATEGY=none nh os switch -n -d always . \
         --hostname {{ host }} \
         --target-host root@{{ host }} \
blob - f9f473e401e40749c33df98ed8639622a8105461 (mode 644)
blob + /dev/null
--- modules/mixins/dotfiles/bin/backup
+++ /dev/null
@@ -1,401 +0,0 @@
-#!/usr/bin/env ruby
-# vim: ft=ruby
-# frozen_string_literal: true
-
-require "English"
-require "optparse"
-require "open3"
-require "timeout"
-require "yaml"
-
-module Backup
-  Result = Struct.new(:status, :out, :err) do
-    def success? = status&.success?
-    def exitcode = status&.exitstatus || 127
-  end
-
-  Backend = Struct.new(:list, :run)
-
-  @failures = []
-  @current_pid = nil
-
-  def self.ts = Time.now.strftime("%Y-%m-%dT%H:%M:%S%z")
-  def self.log(msg) = puts("[#{ts}]: #{msg}")
-  def self.err(msg) = warn("[#{ts}]: #{msg}")
-
-  def self.fatal(msg)
-    err(msg)
-    exit(1)
-  end
-
-  def self.record_failure(msg)
-    @failures << msg
-    err("ERROR: #{msg}")
-  end
-
-  def self.check(msg, result)
-    return true if result.success?
-
-    detail = result.err.to_s.strip
-    record_failure(detail.empty? ? msg : "#{msg}: #{detail}")
-    false
-  end
-
-  HOME = ENV.fetch("HOME") { fatal("HOME not set") }
-  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",
-    "notes" => "#{HOME}/notes",
-    "pass" => "#{HOME}/.password-store",
-    "releases" => "#{HOME}/src/mtmn.name/releases"
-  }.freeze
-
-  TARSNAP_DIRS = {
-    "mpd" => "#{HOME}/.config/mpd",
-    "notes" => "#{HOME}/notes",
-    "password-store" => "#{HOME}/.password-store",
-    "releases" => "#{HOME}/src/mtmn.name/releases"
-  }.freeze
-
-  RCLONE_REMOTE = "backup"
-
-  def self.run_cmd(verbose, *args)
-    if verbose
-      $stdout.flush
-      $stderr.flush
-      @current_pid = Process.spawn(*args, in: :close)
-      _, status = Process.wait2(@current_pid)
-      @current_pid = nil
-      Result.new(status, "", status.nil? ? "command not found: #{args.first}" : "")
-    else
-      out, err, status = Open3.capture3(*args)
-      Result.new(status, out, err)
-    end
-  rescue Errno::ENOENT => e
-    @current_pid = nil
-    Result.new(nil, "", e.message)
-  end
-
-  def self.git(dir, *args, verbose:)
-    run_cmd(verbose, "git", "--git-dir=#{dir}/.git", "--work-tree=#{dir}", *args)
-  end
-
-  def self.commit_changes(dir, verbose:)
-    r = git(dir, "diff", "--cached", "--name-only", verbose: false)
-    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 false unless check("Failed to commit changes in #{dir}", r2)
-
-    log("Committed changes in #{dir}")
-    true
-  end
-
-  def self.git_remote(dir, action, verb)
-    log("#{verb.capitalize} changes from #{dir}")
-    r = git(dir, action, verbose: false)
-    if r.success?
-      log("Done #{verb}ing #{dir}")
-      true
-    else
-      record_failure("#{verb.capitalize} failed for #{dir} (exit #{r.exitcode}): #{r.err.strip}")
-      false
-    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}")
-    r = git(dir, "add", "-A", verbose: verbose)
-    return unless check("Failed to stage changes in #{dir}", r)
-
-    diff = git(dir, "diff", "--cached", "--quiet", verbose: false)
-    committed = if diff.success?
-      log("Nothing to commit in #{dir}")
-      true
-    else
-      commit_changes(dir, verbose: verbose)
-    end
-
-    return unless committed
-    return if fetch && !fetch_changes(dir)
-
-    push_changes(dir) if push && committed
-  end
-
-  def self.tarsnap_archive(verbose:, name:, dir:)
-    stamp = Time.now.strftime("%Y-%m-%d_%H:%M:%S")
-    log("Archiving tarsnap/#{name} from #{dir}")
-    r = run_cmd(verbose, "tarsnap", "-c", "-f", "#{name}-#{stamp}", "-C", dir, ".")
-    return unless check("tarsnap failed for #{name}", r)
-
-    log("Done tarsnap/#{name}")
-  end
-
-  def self.rclone_backup(verbose:, name:, dir:)
-    remote_path = "#{RCLONE_REMOTE}:#{name}"
-    log("Syncing rclone/#{name} from #{dir} to #{remote_path}")
-    args = ["rclone", "copy", dir, remote_path]
-    if verbose
-      args.concat(["--progress", "--verbose"])
-    else
-      args.concat(["--stats-one-line"])
-    end
-    args.concat([
-      "--transfers", "4",
-      "--checkers", "8",
-      "--checksum"
-    ])
-    r = run_cmd(verbose, *args)
-    return unless check("rclone failed for #{name}", r)
-
-    log("Done rclone/#{name}")
-  end
-
-  def self.parse_plakar_show(text)
-    records = {}
-    name = nil
-    text.each_line do |line|
-      stripped = line.strip
-      next if stripped.empty?
-
-      if line.start_with?(" ")
-        key, val = stripped.split(":", 2)
-        records[name][key] = val&.strip if name
-      else
-        name = stripped.delete_suffix(":")
-        records[name] = {}
-      end
-    end
-
-    records
-  end
-
-  def self.plakar_query(subcommand)
-    r = run_cmd(false, "plakar", subcommand, "show")
-    return {} unless r.success?
-
-    parse_plakar_show(r.out)
-  end
-
-  def self.load_plakar_yml(name)
-    path = File.join(PLAKAR_CONFIG_DIR, "#{name}.yml")
-    return nil unless File.file?(path)
-
-    YAML.load_file(path)[name.to_s] || {}
-  rescue => e
-    fatal("Failed to parse #{path}: #{e.message}")
-  end
-
-  def self.plakar_stores
-    @plakar_stores ||= (load_plakar_yml("stores") || plakar_query("store")).freeze
-  end
-
-  def self.plakar_sources
-    @plakar_sources ||= (load_plakar_yml("sources") || plakar_query("source")).freeze
-  end
-
-  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_source_dirs = plakar_sources.transform_values { |cfg| cfg["location"] }.freeze
-
-  def self.plakar_ignore_file(store)
-    path = File.join(PLAKAR_CONFIG_DIR, "#{store}.ignore")
-    File.file?(path) ? path : nil
-  end
-
-  def self.plakar_source(verbose:, store:, source:)
-    ignore = plakar_ignore_file(store)
-    args = ["plakar", "at", "@#{store}", "backup"]
-    args.push("-ignore-file", ignore) if ignore
-    args.push("@#{source}")
-    log("Syncing plakar/#{store}/#{source}")
-    r = run_cmd(verbose, *args)
-    return unless check("plakar @#{store} failed for #{source}", r)
-
-    log("Done plakar/#{store}/#{source}")
-  end
-
-  def self.plakar_store(verbose:, store:, exclude: [])
-    fatal("Unknown plakar store: #{store}") unless plakar_stores_list.include?(store)
-    (plakar_sources_list - exclude).each { |src| plakar_source(verbose: verbose, store: store, source: src) }
-  end
-
-  def self.backup_plakar(verbose:, exclude: [])
-    plakar_stores_list.each { |store| plakar_store(verbose: verbose, store: store, exclude: exclude) }
-  end
-
-  def self.list_flat(name, dirs)
-    puts(name)
-    dirs.keys.sort.each { |k| puts("  #{k}") }
-  end
-
-  def self.list_plakar
-    puts("plakar")
-    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})")
-    end
-  end
-
-  def self.list_rclone
-    puts("rclone")
-    puts("  sources")
-    plakar_source_dirs.keys.sort.each { |s| puts("    #{s}") }
-    puts("  #{RCLONE_REMOTE}")
-  end
-
-  def self.git_run(verbose:, push:, fetch:, parts:, exclude:)
-    if parts.empty?
-      GIT_DIRS.except(*exclude).each { |n, d| sync_repo(verbose: verbose, push: push, fetch: fetch, name: n, dir: d) }
-    else
-      name = parts.first
-      return if exclude.include?(name)
-      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:, exclude:)
-    if parts.empty?
-      TARSNAP_DIRS.except(*exclude).each { |n, d| tarsnap_archive(verbose: verbose, name: n, dir: d) }
-    else
-      name = parts.first
-      return if exclude.include?(name)
-      tarsnap_archive(
-        verbose: verbose,
-        name: name,
-        dir: TARSNAP_DIRS.fetch(name) { fatal("Unknown tarsnap target: #{name}") }
-      )
-    end
-  end
-
-  def self.rclone_run(verbose:, parts:, exclude:)
-    if parts.empty?
-      plakar_source_dirs.except(*exclude).each { |n, d| rclone_backup(verbose: verbose, name: n, dir: d) }
-    else
-      name = parts.first
-      return if exclude.include?(name)
-      rclone_backup(
-        verbose: verbose,
-        name: name,
-        dir: plakar_source_dirs.fetch(name) { fatal("Unknown rclone target: #{name}") }
-      )
-    end
-  end
-
-  def self.plakar_run(verbose:, parts:, exclude:)
-    if parts.empty?
-      backup_plakar(verbose: verbose, exclude: exclude)
-    elsif parts.size == 1
-      plakar_store(verbose: verbose, store: parts.first, exclude: exclude)
-    else
-      store = parts[0]
-      selected = parts[1].split(",") - exclude
-      selected.each do |source|
-        plakar_source(verbose: verbose, store: store, source: source)
-      end
-    end
-  end
-
-  BACKENDS = {
-    "git" => Backend.new(
-      list: -> { list_flat("git", GIT_DIRS) },
-      run: ->(verbose:, push:, fetch:, exclude:, parts:) { git_run(verbose: verbose, push: push, fetch: fetch, exclude: exclude, parts: parts) }
-    ),
-    "tarsnap" => Backend.new(
-      list: -> { list_flat("tarsnap", TARSNAP_DIRS) },
-      run: ->(verbose:, push:, fetch:, exclude:, parts:) { tarsnap_run(verbose: verbose, exclude: exclude, parts: parts) }
-    ),
-    "plakar" => Backend.new(
-      list: -> { list_plakar },
-      run: ->(verbose:, push:, fetch:, exclude:, parts:) { plakar_run(verbose: verbose, exclude: exclude, parts: parts) }
-    ),
-    "rclone" => Backend.new(
-      list: -> { list_rclone },
-      run: ->(verbose:, push:, fetch:, exclude:, parts:) { rclone_run(verbose: verbose, exclude: exclude, parts: parts) }
-    )
-  }.freeze
-
-  def self.list_targets
-    BACKENDS.each_value { |b| b.list.call }
-  end
-
-  def self.backup_target(verbose:, push:, fetch:, exclude:, target:)
-    parts = target.split(":")
-    bname = parts.shift
-    backend = BACKENDS[bname] || fatal("Unknown target: #{target}")
-    backend.run.call(verbose: verbose, push: push, fetch: fetch, exclude: exclude, parts: parts)
-  end
-
-  def self.main(args)
-    Signal.trap("INT") do
-      warn("[#{Time.now.strftime("%Y-%m-%dT%H:%M:%S%z")}]: Interrupted")
-      if @current_pid
-        begin
-          Process.kill("TERM", @current_pid)
-          begin
-            Timeout.timeout(60) { Process.wait(@current_pid) }
-          rescue Timeout::Error
-            begin
-              Process.kill("KILL", @current_pid)
-            rescue
-              Errno::ESRCH
-            end
-          end
-        rescue Errno::ESRCH
-        end
-      end
-      exit(130)
-    end
-
-    options = {verbose: true, push: false, fetch: false, list: false, exclude: []}
-    parser = OptionParser.new do |o|
-      o.banner = "Usage: backup [options] [target]\n" \
-        "  target: git, plakar, tarsnap, rclone, <backend>:<name>\n" \
-        "  plakar sources may be comma-separated: plakar:<store>:<src1,src2,...>\n" \
-        "  -e may exclude sources: backup plakar:<store> -e <src1,src2,...>"
-      o.on("-p", "--push", "Fetch and push changes after commit") { options[:push] = true }
-      o.on("-f", "--fetch", "Run git pull for git targets before backup") { options[:fetch] = true }
-      o.on("-q", "--quiet", "Suppress command output") { options[:verbose] = false }
-      o.on("-l", "--list", "List all configured backup targets") { options[:list] = true }
-      o.on("-e", "--exclude LIST", "Comma-separated list of sources/targets to skip") { |v| options[:exclude].concat(v.split(",").map(&:strip)) }
-      o.on("-h", "--help") do
-        puts(o)
-        exit(0)
-      end
-    end
-
-    parser.parse!(args)
-    target = args.shift
-    unless options[:list] || target
-      list_targets
-      exit(0)
-    end
-
-    options[:fetch] = true if options[:push]
-
-    list_targets if options[:list]
-    if target
-      backup_target(verbose: options[:verbose], push: options[:push], fetch: options[:fetch], exclude: options[:exclude], target: target)
-    end
-
-    fatal("#{@failures.size} backup target(s) failed") unless @failures.empty?
-  end
-end
-
-Backup.main(ARGV)
blob - e3d5063e4cfc82737d397f7678b74fe4bfa4017c
blob + 6f0dfddcdbe41ffd8105e2c99aca3f39d1fd688b
--- modules/mixins/dotfiles/bin/tuped
+++ modules/mixins/dotfiles/bin/tuped
@@ -97,7 +97,7 @@
     (print usage)
     (os/exit 0))
   (def opts (parse-args argv))
-  (unless (have-spiped) (die "spiped not found on PATH (install net/spiped)"))
+  (unless (have-spiped) (die "spiped not found"))
   (unless (opts :key) (die "--key is required"))
   (when (zero? (opts :local-port)) (die "--local-port is required"))
   (when (zero? (opts :remote-port)) (die "--remote-port is required"))
blob - 418d45818ead0246b5592bfb08545518c6c1f72d
blob + 1c0c36295391d53d53f269f7236ad97bd49cc2f4
--- modules/mixins/dotfiles/config/nvim/fnl/init.fnl
+++ modules/mixins/dotfiles/config/nvim/fnl/init.fnl
@@ -38,6 +38,7 @@
       (gh :jpalardy/vim-slime)
       (gh :gpanders/nvim-parinfer)
       (gh :L3MON4D3/LuaSnip)
+      (gh :janet-lang/janet.vim)
       {:src (gh :neovim/nvim-lspconfig) :version :master}
       {:src (gh :saghen/blink.cmp) :version (vim.version.range "^1")}]
     {:load true})
blob - 16b6538f40b07b3db232d246fd8717463ee5b686
blob + 4fcc19d5c21f4103b3bf86df9b3d954ee6c9def5
--- modules/mixins/dotfiles/config/shell/rc/command_overrides
+++ modules/mixins/dotfiles/config/shell/rc/command_overrides
@@ -69,3 +69,4 @@ alias gdb='gdb -q'
 alias dmesg='dmesg -T --color=always | less -R +G'
 
 alias scc='scc --no-cocomo'
+alias backup='sursum'