nix-community / home-manager

Manage a user environment using Nix [maintainer=@rycee]
https://nix-community.github.io/home-manager/
MIT License
6.68k stars 1.75k forks source link

bug: programs.borgmatic should support Darwin #5757

Open kamushadenes opened 3 weeks ago

kamushadenes commented 3 weeks ago

Are you following the right branch?

Is there an existing issue for this?

Issue description

While services.borgmatic doesn't work in Darwin, borgmatic itself does, and thus programs.borgmatic should not have https://github.com/nix-community/home-manager/blob/e1391fb22e18a36f57e6999c7a9f966dc80ac073/modules/programs/borgmatic.nix#L277

I tested locally and it works flawlessly, and I can use https://github.com/LnL7/nix-darwin to do the service part with launchd.

Maintainer CC

No response

System information

 - system: `"aarch64-darwin"`
 - host os: `Darwin 23.6.0, macOS 14.6.1`
 - multi-user?: `yes`
 - sandbox: `no`
 - version: `nix-env (Nix) 2.18.4`
 - channels(root): `"nixpkgs"`
 - nixpkgs: `/nix/var/nix/profiles/per-user/root/channels/nixpkgs`
kamushadenes commented 3 weeks ago

If anyone is wondering how I did the nix-darwin thing:

{ config, pkgs, ... }:
let
  borgmaticRunScript = pkgs.writeScriptBin "borgmatic-run" ''
    #!/bin/bash

    LOCKFILE="/tmp/borgmatic.lock"

    # Check if lock file exists and if the process is still running
    if [ -f "$LOCKFILE" ]; then
      PID=$(cat "$LOCKFILE")
      if ps -p $PID > /dev/null 2>&1; then
        echo "A previous instance of borgmatic is still running."
        exit 1
      else
        echo "Stale lock file found. Removing it."
        rm -f "$LOCKFILE"
      fi
    fi

    # Create a lock file with the current PID
    echo $$ > "$LOCKFILE"

    # Run borgmatic
    ${pkgs.borgmatic}/bin/borgmatic -v 1 $@

    # Remove the lock file after the process completes
    rm -f "$LOCKFILE"
  '';
in
{
  environment.systemPackages = with pkgs; [ borgmaticRunScript ];

  environment.launchDaemons."org.torsion.borgmatic.plist" = {
    enable = true;
    text = ''
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
      <plist version="1.0">
        <dict>
          <key>Label</key>
          <string>org.torsion.borgmatic</string>

          <key>ProgramArguments</key>
          <array>
                  <string>${borgmaticRunScript}/bin/borgmatic-run</string>
          </array>

          <key>StandardOutPath</key>
          <string>/tmp/borgmatic.log</string>

          <key>StandardErrorPath</key>
          <string>/tmp/borgmatic.err</string>

          <key>StartInterval</key>
          <integer>3600</integer>

          <key>UserName</key>
          <string>${config.users.users.kamushadenes.name}</string>

          <key>RunAtLoad</key>
          <true/>
        </dict>
      </plist>
    '';
  };
}
kamushadenes commented 3 weeks ago

And my home-manager config (using https://github.com/ryantm/agenix) that also works in Linux with no changes :

{
  config,
  pkgs,
  lib,
  machine,
  ...
}:
let
  borgmaticage = "${config.xdg.configHome}/borgmatic.d/borgmatic.age";

  mkExclusionList =
    path:
    let
      content = builtins.readFile path;
      lines = builtins.split "\n" content;
      nonEmptyLines = lib.filter (
        line: (builtins.isString (line) && line != "" && !lib.strings.hasPrefix "#" line)
      ) lines;
    in
    nonEmptyLines;

  commonExclusions = lib.concatMap (path: mkExclusionList path) [
    ./resources/borgmatic/exclusions/common.lst
  ];

  macOsExclusions = lib.optionals pkgs.stdenv.isDarwin (
    lib.concatMap (path: mkExclusionList path) [
      ./resources/borgmatic/exclusions/macos/core.lst
      ./resources/borgmatic/exclusions/macos/applications.lst
      ./resources/borgmatic/exclusions/macos/programming.lst
    ]
  );

  mkBackup =
    machine: name: healthCheckId: patterns: repos: extraConfigOverrides: consistencyOverrides: retentionOverrides: {
      location = {
        patterns = patterns ++ commonExclusions ++ macOsExclusions;

        repositories = repos;
        excludeHomeManagerSymlinks = true;

        extraConfig = lib.mkMerge [
          {
            compression = "auto,zstd";
            archive_name_format = "{hostname}-{now:%Y-%m-%d-%H%M%S}";
            ssh_command = "ssh -o StrictHostKeyChecking=accept-new -i ${config.age.secrets."borg.age".path}";
            exclude_if_present = [ ".nobackup" ];
            healthchecks = {
              ping_url = "https://hc-ping.com/${healthCheckId}";
            };
          }
          extraConfigOverrides
        ];
      };

      consistency = lib.mkMerge [
        { checks = [ ]; }
        consistencyOverrides
      ];

      retention = lib.mkMerge [
        {
          keepWeekly = 4;
          keepMonthly = 12;
        }
        retentionOverrides
      ];

      storage = {
        encryptionPasscommand = "${pkgs.age}/bin/age --decrypt -i ${config.home.homeDirectory}/.age/age.pem -o - ${borgmaticage}";
      };
    };
in
{
  home.packages = with pkgs; [ age ];

  age.secrets."borg.age" = {
    file = ./resources/ssh/keys/borg.age;
    path = "${config.home.homeDirectory}/.ssh/keys/borg";
  };

  home.file."borgmatic.age" = {
    source = ./resources/borgmatic/borgmatic.age;
    target = borgmaticage;
  };

  services = lib.mkMerge [
    (lib.mkIf pkgs.stdenv.isLinux {
      borgmatic = {
        enable = true;
        frequency = "hourly";
      };
    })
  ];

  programs.borgmatic = {
    enable = true;
    package = pkgs.borgmatic;

    backups = lib.mkMerge [
      (lib.mkIf (machine == "Kamus-Mac-Studio") {
        home = (
          mkBackup machine "home" "XXXXXXXXX-2e51-4db4-8eaf-XXXXXXXXX" [
            "R ${config.home.homeDirectory}"
            "- ${config.home.homeDirectory}/Applications"
            "- ${config.home.homeDirectory}/.config/nix"
            "- ${config.home.homeDirectory}/Dropbox"
          ] [ "ssh://XXXXXXXXX@ XXXXXXXXX.repo.borgbase.com/./repo" ] { } { } { }
        );

        dropbox = (
          mkBackup machine "dropbox" "XXXXXXXXX-8302-442e-a3b9-XXXXXXXXX" [ "R /Volumes/Dropbox" ] [
            "ssh://XXXXXXXXX@ XXXXXXXXX.repo.borgbase.com/./repo"
          ] { } { } { }
        );
      })
    ];
  };
}

Exclusion lists are based on https://github.com/SterlingHooten/borg-backup-exclusions-macos.

szethh commented 1 week ago

Hi @kamushadenes, I'm also trying to set up borg/borgmatic on darwin. I'm not too sure I understand how you managed to get it to work on macos. Like, how did you get past the "The module programs.borgmatic does not support your platform" error message?

Also, I tried your launchD script but compiling borgbackup fails due to some tests not passing. Did you have to configure anything else to get it to work on macos?

kamushadenes commented 1 week ago

Hi @szethh , I just replaced the assertion on my fork https://github.com/kamushadenes/home-manager/commit/7e4108431c228918f49aa17f9f0f51b18dd7fa5b#diff-f14ed0b5cfaa1a90e13d7639d071709108038591ff076e42d2241f9a6de03e96R277.

As for the test failing, that's weird, I didn't face any such issue. I'm running on stable though, maybe that's related?

szethh commented 1 week ago

hi @kamushadenes thanks, I thought about forking but was hoping for a solution that didn't involve keeping a fork of the project ahahah.

yeah, I'm running unstable so that might be it. will try on 24.05, hopefully it works then

edit: wait no actually i'm running 24.05 on darwin... not sure why compiling borg fails on my machine.

szethh commented 1 week ago

for anyone stumbling into this, I had to create an overlay and disable the offending tests, now it works :)