├── template ├── configuration.nix ├── home-manager.nix └── flake.nix ├── flake.lock ├── modules ├── home-manager.nix ├── cloner-script.nix ├── nixos.nix └── options.nix ├── flake.nix └── README.md /template/configuration.nix: -------------------------------------------------------------------------------- 1 | {...}: { 2 | # Your config goes here! 3 | } 4 | -------------------------------------------------------------------------------- /template/home-manager.nix: -------------------------------------------------------------------------------- 1 | {...}: { 2 | # Your config goes here! 3 | }; 4 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1704842529, 6 | "narHash": "sha256-OTeQA+F8d/Evad33JMfuXC89VMetQbsU4qcaePchGr4=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "eabe8d3eface69f5bb16c18f8662a702f50c20d5", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "id": "nixpkgs", 14 | "ref": "nixpkgs-unstable", 15 | "type": "indirect" 16 | } 17 | }, 18 | "root": { 19 | "inputs": { 20 | "nixpkgs": "nixpkgs" 21 | } 22 | } 23 | }, 24 | "root": "root", 25 | "version": 7 26 | } 27 | -------------------------------------------------------------------------------- /template/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Example clonix configuration"; 3 | 4 | inputs = { 5 | nixpkgs.url = "nixpkgs/nixos-unstable"; 6 | home-manager = { 7 | url = "github:nix-community/home-manager/master"; 8 | inputs.nixpkgs.follows = "nixpkgs"; 9 | }; 10 | clonix = { 11 | url = "github:tulilirockz/clonix"; # github:tulilirockz/clonix/?ref= to target specific releases. 12 | inputs.nixpkgs.follows = "nixpkgs"; 13 | }; 14 | }; 15 | 16 | outputs = { 17 | nixpkgs, 18 | home-manager, 19 | clonix, 20 | ... 21 | }: let 22 | system = "x86_64-linux"; # Change this depending on your architecture! 23 | pkgs = import nixpkgs { 24 | inherit system; 25 | config = {allowUnfree = true;}; 26 | }; 27 | main_username = "example"; 28 | in { 29 | nixosConfigurations = { 30 | example = nixpkgs.lib.nixosSystem { 31 | inherit system; 32 | modules = [ 33 | clonix.nixosModules.clonix 34 | 35 | ./configuration.nix 36 | ]; 37 | }; 38 | }; 39 | 40 | homeConfigurations.${main_username} = home-manager.lib.homeManagerConfiguration { 41 | inherit pkgs; 42 | modules = [ 43 | clonix.homeManagerModules.clonix 44 | 45 | ./home-manager.nix 46 | ]; 47 | }; 48 | 49 | devShells.${system}.default = pkgs.mkShell { 50 | nativeBuildInputs = with pkgs; [nil gnumake]; 51 | }; 52 | 53 | formatter.${system} = pkgs.alejandra; 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /modules/home-manager.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: let 7 | cfg = config.services.clonix; 8 | ifNotNull = value: lib.mkIf (value != null) value; 9 | generateDeploymentHash = deployment: builtins.hashString "sha256" (builtins.toJSON deployment); 10 | generateService = deployment: { 11 | "clonix@${generateDeploymentHash deployment}-${deployment.deploymentName}" = { 12 | Unit = { 13 | Description = "Clonix for ${deployment.deploymentName}: local: ${deployment.local.dir}, target: ${deployment.targetDir}"; 14 | Documentation = "man:clonix(1)"; 15 | }; 16 | 17 | Service = { 18 | Type = "exec"; 19 | ExecStart = "${import ./cloner-script.nix {inherit cfg pkgs lib generateDeploymentHash;}}/bin/clonix-main ${generateDeploymentHash deployment}"; 20 | }; 21 | }; 22 | }; 23 | generateTimer = deployment: { 24 | "clonix@${generateDeploymentHash deployment}-${deployment.deploymentName}" = { 25 | Timer = { 26 | OnActiveSec = ifNotNull deployment.timer.OnActiveSec; 27 | OnBootSec = ifNotNull deployment.timer.OnBootSec; 28 | OnStartupSec = ifNotNull deployment.timer.OnStartupSec; 29 | OnUnitActiveSec = ifNotNull deployment.timer.OnUnitActiveSec; 30 | OnUnitInactiveSec = ifNotNull deployment.timer.OnUnitInactiveSec; 31 | OnCalendar = ifNotNull deployment.timer.OnCalendar; 32 | Unit = "clonix@${generateDeploymentHash deployment}.service"; 33 | }; 34 | Install.WantedBy = ["timers.target"]; 35 | }; 36 | }; 37 | in { 38 | options.services.clonix = import ./options.nix {inherit lib pkgs;}; 39 | 40 | config = lib.mkIf cfg.enable { 41 | systemd.user.services = lib.mkMerge (lib.lists.flatten (builtins.map generateService cfg.deployments)); 42 | systemd.user.timers = lib.mkMerge (lib.lists.flatten (builtins.map generateTimer cfg.deployments)); 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /modules/cloner-script.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | pkgs, 4 | cfg, 5 | generateDeploymentHash, 6 | ... 7 | }: let 8 | generateProperRsyncCmd = ( 9 | deployment: 10 | "${cfg.packages.rsync}/bin/rsync " 11 | + "${ 12 | if (deployment.extraOptions != null) 13 | then deployment.extraOptions + " " 14 | else "" 15 | }" 16 | + "-avh " 17 | + "${ 18 | if deployment.remote.enable == true 19 | then 20 | ( 21 | ( 22 | if deployment.remote.user.password != null 23 | then "--rsh=\"${cfg.packages.sshpass}/bin/sshpass -p ${deployment.remote.user.password} ${cfg.packages.openssh}/bin/ssh -o StrictHostKeyChecking=no -l ${deployment.remote.user.name}\" " 24 | else "" 25 | ) 26 | + ( 27 | if deployment.remote.user.keyfile != null 28 | then "-e \"${cfg.packages.openssh}/bin/ssh -i ${deployment.remote.user.keyfile}\" " 29 | else "" 30 | ) 31 | ) 32 | else "" 33 | }" 34 | + "${ 35 | if (builtins.length deployment.local.exclude > 0) 36 | then "--exclude={${lib.concatStringsSep "," deployment.local.exclude} " 37 | else "" 38 | }" 39 | + "${deployment.local.dir}/* " 40 | + "${ 41 | if deployment.remote.enable == true 42 | then "${deployment.remote.user.name}@${deployment.remote.ipOrHostname}:${deployment.targetDir} " 43 | else "${deployment.targetDir} " 44 | }" 45 | ); 46 | 47 | uniqueRsyncCmd = ( 48 | deployment: '' 49 | if [ "$1" == "${generateDeploymentHash deployment}" ] ; then 50 | 51 | ${generateProperRsyncCmd deployment} 52 | 53 | fi'' 54 | ); 55 | in 56 | pkgs.writeShellScriptBin "clonix-main" '' 57 | set -euo pipefail 58 | ${lib.concatMapStringsSep "\n" (deployment: 59 | if (deployment != null) 60 | then (uniqueRsyncCmd deployment) 61 | else "") 62 | cfg.deployments} 63 | '' 64 | -------------------------------------------------------------------------------- /modules/nixos.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: let 7 | cfg = config.services.clonix; 8 | ifNotNull = value: lib.mkIf (value != null) value; 9 | generateDeploymentHash = deployment: builtins.hashString "sha256" (builtins.toJSON deployment); 10 | generateService = deployment: { 11 | "clonix@${generateDeploymentHash deployment}-${deployment.deploymentName}" = { 12 | enable = true; 13 | unitConfig = { 14 | Description = "Clonix for ${deployment.deploymentName}: local: ${deployment.local.dir}, target: ${deployment.targetDir}"; 15 | Wants = "network-online.target"; 16 | After = "network-online.target"; 17 | }; 18 | 19 | serviceConfig = { 20 | User = "root"; 21 | WorkingDirectory = deployment.local.dir; 22 | ExecStart = "${import ./cloner-script.nix {inherit cfg pkgs lib generateDeploymentHash;}}/bin/clonix-main ${generateDeploymentHash deployment}"; 23 | }; 24 | }; 25 | }; 26 | generateTimer = deployment: { 27 | "clonix@${generateDeploymentHash deployment}-${deployment.deploymentName}" = { 28 | enable = deployment.timer.enable; 29 | 30 | wantedBy = ["timers.target"]; 31 | 32 | timerConfig = { 33 | OnActiveSec = ifNotNull deployment.timer.OnActiveSec; 34 | OnBootSec = ifNotNull deployment.timer.OnBootSec; 35 | OnStartupSec = ifNotNull deployment.timer.OnStartupSec; 36 | OnUnitActiveSec = ifNotNull deployment.timer.OnUnitActiveSec; 37 | OnUnitInactiveSec = ifNotNull deployment.timer.OnUnitInactiveSec; 38 | OnCalendar = ifNotNull deployment.timer.OnCalendar; 39 | Unit = "clonix@${generateDeploymentHash deployment}.service"; 40 | }; 41 | }; 42 | }; 43 | in { 44 | options.services.clonix = import ./options.nix {inherit lib pkgs;}; 45 | 46 | config = lib.mkIf cfg.enable { 47 | systemd.services = lib.mkMerge (lib.lists.flatten (builtins.map generateService cfg.deployments)); 48 | systemd.timers = lib.mkMerge (lib.lists.flatten (builtins.map generateTimer cfg.deployments)); 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Manage rsync deployments declaratively."; 3 | 4 | inputs.nixpkgs.url = "nixpkgs/nixpkgs-unstable"; 5 | 6 | outputs = { 7 | self, 8 | nixpkgs, 9 | ... 10 | }: let 11 | system = "x86_64-linux"; 12 | pkgs = import nixpkgs {inherit system;}; 13 | in { 14 | nixosModules = {clonix = import ./modules/nixos.nix;}; 15 | homeManagerModules = {clonix = import ./modules/home-manager.nix;}; 16 | 17 | formatter.${system} = pkgs.alejandra; 18 | 19 | template.default = { 20 | path = ./template; 21 | description = "A simple NixOS/Home-Manager configuration to get you started with Clonix"; 22 | }; 23 | 24 | checks.${system}.default = pkgs.nixosTest { 25 | name = "Rsync integration test"; 26 | 27 | nodes = { 28 | reciever = {pkgs, ...}: { 29 | services.openssh = { 30 | enable = true; 31 | allowSFTP = true; 32 | openFirewall = true; 33 | settings = { 34 | PermitRootLogin = "yes"; 35 | PasswordAuthentication = true; 36 | }; 37 | }; 38 | 39 | networking.useDHCP = false; 40 | 41 | environment.systemPackages = with pkgs; [rsync]; 42 | 43 | users.users.tester = { 44 | isNormalUser = true; 45 | password = "tester"; 46 | }; 47 | 48 | networking.interfaces.eth0.ipv4.addresses = [ 49 | { 50 | address = "192.168.1.5"; 51 | prefixLength = 24; 52 | } 53 | ]; 54 | }; 55 | sender = { 56 | nodes, 57 | pkgs, 58 | ... 59 | }: { 60 | imports = [self.nixosModules.clonix]; 61 | 62 | environment.systemPackages = with pkgs; [rsync]; 63 | 64 | users.users.tester = { 65 | isNormalUser = true; 66 | password = "tester"; 67 | }; 68 | 69 | services.clonix.enable = true; 70 | services.clonix.deployments = [ 71 | { 72 | deploymentName = "testing_deployment"; 73 | local.dir = "${nodes.sender.users.users.tester.home}/testingfiles"; 74 | targetDir = "${nodes.reciever.users.users.tester.home}/testingfiles"; 75 | timer.enable = false; 76 | timer.OnBootSec = "1"; 77 | remote.enable = nodes.reciever.services.openssh.enable; 78 | remote.ipOrHostname = (builtins.elemAt nodes.reciever.networking.interfaces.eth0.ipv4.addresses 0).address; 79 | remote.user.name = nodes.reciever.users.users.tester.name; 80 | remote.user.password = nodes.reciever.users.users.tester.password; 81 | } 82 | ]; 83 | }; 84 | }; 85 | 86 | testScript = {nodes, ...}: '' 87 | sender.wait_for_unit("network.target") 88 | reciever.wait_for_unit("network.target") 89 | reciever.succeed("mkdir ${nodes.reciever.users.users.tester.home}/testingfiles") 90 | sender.succeed("mkdir ${nodes.reciever.users.users.tester.home}/testingfiles && echo hi!!! >> ${nodes.reciever.users.users.tester.home}/testingfiles/cheetos && systemctl start clonix@*") 91 | reciever.succeed("sleep 5 ; ls ${nodes.reciever.users.users.tester.home}/testingfiles") 92 | ''; 93 | }; 94 | }; 95 | } 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clonix 2 | 3 | Declarative [rsync](https://github.com/WayneD/rsync) deployments for NixOS and Home-Manager supported system! 4 | 5 | Heavily inspired by [nix-flatpak](https://github.com/gmodena/nix-flatpak). 6 | 7 | If you dont want to read all this and just skip to how to use it, go to the `Usage & Examples` section at the end. 8 | 9 | ## Deployments 10 | 11 | They are a way to run rsync at a specific time by using a systemd timer and a systemd service. Its pretty much like a declarative [syncthing](https://syncthing.net/) of sorts! 12 | 13 | You can declare your deployments by adding them to a list with an attrset with them. They can be either remote deployments or local sync deployments. E.g.: 14 | 15 | ```nix 16 | deployments = [{ 17 | #... 18 | local.dir = "/path/to/abspath"; # This needs to be a string, because if it is of the path type itll be on the /nix/store instead of actually being where you want. 19 | local.exclude = [ "/path/to/abspath" ]; 20 | targetDir = "/path/to/other/abspath"; 21 | }]; 22 | ``` 23 | 24 | This generates pretty much this rsync command: 25 | 26 | ```shell 27 | rsync -avh /path/to/abspath/* /path/to/other/abspath 28 | ``` 29 | 30 | ## Remote 31 | 32 | You can also remotely deploy your rsync folders through SSH by specifying the user, machine, and password or keyfile that you want to use 33 | 34 | ```nix 35 | deployments = [{ 36 | #... 37 | remote.enable = true; 38 | remote.user.name = "user"; 39 | remote.user.password = "somethingsomething"; 40 | remote.user.keyfile = null; 41 | }]; 42 | ``` 43 | 44 | This would generate: 45 | 46 | ```shell 47 | rsync --rsh "sshpass -p somethingsomething ssh -l user" /path/to/abspath user@iphostname:/path/to/other/abspath 48 | ``` 49 | 50 | ## Timer 51 | 52 | When your deployments are going to be ran is also completely customizable by systemd standards, by editing the "timer" option in each of your deployments (timers are enabled by default at 12:00 PM). 53 | 54 | ```nix 55 | deployments = [{ 56 | #... 57 | timer.enable = true; 58 | timer.OnCalendar = ""; 59 | timer.OnUnitActiveSec = ""; 60 | timer.OnUnitInactiveSec = ""; 61 | timer.OnBootSec = ""; 62 | timer.OnActiveSec = ""; 63 | timer.OnStartupSec = ""; 64 | }]; 65 | ``` 66 | 67 | Which would generate a systemd unit with all these configurations in its toml-like structure. 68 | 69 | ## Overrides 70 | 71 | You can also feel free to change up whichever rsync, or whatever else's versions and packages that you want this script to run by overriding them in the options 72 | 73 | ```nix 74 | services.clonix.enable = true; 75 | services.clonix.packages = { 76 | openssh = otherplace.openssh; 77 | rsync = otherplace.rsync; 78 | sshpass = otherplace.sshpass; 79 | # and so on... 80 | }; 81 | ``` 82 | 83 | ## Usage & Examples 84 | 85 | You can either quickly import this project's template for your configurations by running: 86 | 87 | ```shell 88 | nix flake init . -t github:tulilirockz/clonix 89 | ``` 90 | 91 | Or you can manually import this repo's flake over to your NixOS or Home-Manager configuration 92 | 93 | ```nix 94 | { 95 | inputs = { 96 | nixpkgs.url = "nixpkgs/nixpkgs-unstable"; 97 | clonix = { 98 | url = "github:tulilirockz/clonix"; # github:tulilirockz/clonix/?ref= to target specific releases. 99 | inputs.nixpkgs.follows = "nixpkgs"; 100 | }; 101 | }; 102 | 103 | outputs = { clonix, ... }: { 104 | nixosConfigurations. = nixpkgs.lib.nixosSystem { 105 | modules = [ 106 | clonix.nixosModules.clonix 107 | 108 | ./configuration.nix 109 | ]; 110 | }; 111 | homeConfigurations. = home-manager.lib.homeManagerConfiguration { 112 | inherit pkgs; 113 | modules = [ 114 | clonix.homeManagerModules.clonix 115 | 116 | ./home-manager.nix 117 | ]; 118 | }; 119 | }; 120 | } 121 | ``` 122 | 123 | Then you can add your clonix configuration to your NixOS or Home-manager configurations 124 | 125 | ```nix 126 | services.clonix.enable = true; 127 | 128 | service.clonix.deployments = [ 129 | { 130 | deploymentName = "amogus"; 131 | local.dir = "/path/to/abspath"; 132 | targetDir = "/path/to/abspath"; 133 | remote.enable = true; 134 | remote.user.name = "root"; 135 | remote.user.keyfile = ./keyfile; 136 | } 137 | { 138 | deploymentName = "sussy"; 139 | local.dir = "/path/to/abspath"; 140 | targetDir = "/path/to/abspath"; 141 | remote.enable = true; 142 | remote.user.name = "momoga"; 143 | remote.user.password = "mimiga"; 144 | remote.ipOrHostname = "machine"; 145 | extraOptions = "-zi"; 146 | } 147 | { 148 | deploymentName = "baus"; 149 | local.dir = /path/to/abspath; 150 | local.exclude = ["/path/to/abspath" "/path/to/abspath"]; 151 | targetDir = "/path/to/abspath"; 152 | remote.enable = true; 153 | remote.user.name = "momoga"; 154 | remote.user.keyfile = "/path/to/abspath"; 155 | timer.enable = true; 156 | timer.onCalendar = "Mon,Tue *-*-01..04 12:00:00"; 157 | } 158 | ]; 159 | ``` 160 | 161 | ## Testing and Contributing 162 | 163 | If you want to contribute and test out your stuff, you can make a new test with `nix flake check` by adding a new option insite of `flake.nix`s `nixosTest` tests! 164 | -------------------------------------------------------------------------------- /modules/options.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | ... 5 | }: let 6 | deploymentOptions = _: { 7 | options = { 8 | deploymentName = lib.mkOption { 9 | type = lib.types.str; 10 | default = null; 11 | description = "The deployments tag name"; 12 | }; 13 | local = lib.mkOption { 14 | type = lib.types.submodule (_: { 15 | options = { 16 | dir = lib.mkOption { 17 | type = lib.types.str; 18 | default = null; 19 | description = "Path that will be used as the local directory"; 20 | }; 21 | exclude = lib.mkOption { 22 | type = lib.types.listOf lib.types.str; 23 | default = []; 24 | example = ["/example/path" "/path/to/abspath"]; 25 | description = "Paths to exclude"; 26 | }; 27 | }; 28 | }); 29 | }; 30 | targetDir = lib.mkOption { 31 | type = lib.types.str; 32 | example = "/path/to/abspath"; 33 | description = "Path that will be used as the target directory"; 34 | }; 35 | extraOptions = lib.mkOption { 36 | type = lib.types.nullOr lib.types.str; 37 | default = null; 38 | description = "Extra CLI options for rsync"; 39 | }; 40 | timer = lib.mkOption { 41 | default = {}; 42 | type = lib.types.submodule (_: { 43 | options = { 44 | enable = lib.mkEnableOption { 45 | type = lib.types.bool; 46 | default = true; 47 | description = "Whether or not the timer is enabled."; 48 | }; 49 | OnCalendar = lib.mkOption { 50 | type = lib.types.nullOr lib.types.str; 51 | default = "12:00"; 52 | description = "OnCalendar following systemd definitions"; 53 | }; 54 | OnActiveSec = lib.mkOption { 55 | type = lib.types.nullOr lib.types.str; 56 | default = null; 57 | description = "OnActiveSec following systemd definitions"; 58 | }; 59 | OnBootSec = lib.mkOption { 60 | type = lib.types.nullOr lib.types.str; 61 | default = null; 62 | description = "OnBootSec following systemd definitions"; 63 | }; 64 | OnStartupSec = lib.mkOption { 65 | type = lib.types.nullOr lib.types.str; 66 | default = null; 67 | description = "OnStartupSec following systemd definitions"; 68 | }; 69 | OnUnitActiveSec = lib.mkOption { 70 | type = lib.types.nullOr lib.types.str; 71 | default = null; 72 | description = "OnUnitActiveSec following systemd definitions"; 73 | }; 74 | OnUnitInactiveSec = lib.mkOption { 75 | type = lib.types.nullOr lib.types.str; 76 | default = null; 77 | description = "OnUnitInactiveSec following systemd definitions"; 78 | }; 79 | }; 80 | }); 81 | }; 82 | remote = lib.mkOption { 83 | default = {enable = false;}; 84 | type = with lib.types; 85 | submodule (_: { 86 | options = { 87 | enable = lib.mkEnableOption { 88 | type = lib.types.bool; 89 | default = false; 90 | description = "Whether or not rsync will be manage remotes"; 91 | }; 92 | ipOrHostname = lib.mkOption { 93 | type = lib.types.nullOr lib.types.str; 94 | default = null; 95 | description = "Target remote IP or Hostname"; 96 | }; 97 | user = lib.mkOption { 98 | default = {}; 99 | type = lib.types.submodule (_: { 100 | options = { 101 | name = lib.mkOption { 102 | type = lib.types.nullOr lib.types.str; 103 | default = null; 104 | description = "Target username for the remote"; 105 | }; 106 | password = lib.mkOption { 107 | type = lib.types.nullOr lib.types.str; 108 | default = null; 109 | description = "Target password for the remote"; 110 | }; 111 | keyfile = lib.mkOption { 112 | type = lib.types.nullOr lib.types.path; 113 | default = null; 114 | description = "SSH keyfile that will be used for authentication"; 115 | }; 116 | }; 117 | }); 118 | }; 119 | }; 120 | }); 121 | }; 122 | }; 123 | }; 124 | in { 125 | enable = lib.mkEnableOption { 126 | type = lib.types.bool; 127 | default = false; 128 | description = "Whether to enable clonix declarative management."; 129 | }; 130 | deployments = lib.mkOption { 131 | type = with lib.types; listOf (submodule deploymentOptions); 132 | default = null; 133 | description = '' 134 | Declare a list of deployments. 135 | ''; 136 | example = lib.literalExpression '' 137 | [{ deploymentName = "amogus"; local.dir = /path/to/abspath; targetDir = /path/to/abspath; remote.enable = true; remote.user = "root"; remote.ipOrHostname = "192.168.1.1"}] 138 | ''; 139 | }; 140 | packages = lib.mkOption { 141 | default = { 142 | sshpass = pkgs.sshpass; 143 | openssh = pkgs.openssh; 144 | rsync = pkgs.rsync; 145 | }; 146 | type = lib.types.submodule (_: { 147 | options = { 148 | rsync = lib.mkOption { 149 | type = lib.types.package; 150 | default = pkgs.rsync; 151 | description = "Rsync package to be used"; 152 | }; 153 | sshpass = lib.mkOption { 154 | type = lib.types.package; 155 | default = pkgs.sshpass; 156 | description = "Sshpass package to be used"; 157 | }; 158 | openssh = lib.mkOption { 159 | type = lib.types.package; 160 | default = pkgs.openssh; 161 | description = "Openssh package to be used"; 162 | }; 163 | }; 164 | }); 165 | }; 166 | } 167 | --------------------------------------------------------------------------------