Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Support sudo & SSH connection options #14

Merged
merged 1 commit into from
Jan 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,16 @@ lollypops.deployment = {
# Where on the remote the configuration (system flake) is placed
config-dir = "/var/src/lollypops";

# Ssh connection parameters
host = "${config.networking.hostName}";
user = "root";
# SSH connection parameters
ssh.host = "${config.networking.hostName}";
ssh.user = "root";
ssh.command = "ssh";
ssh.opts = [];

# sudo options
sudo.enable = false;
sudo.command = "sudo";
sudo.opts = [];
};
```

Expand All @@ -214,6 +221,9 @@ be missing still on the first deployment. To fix this, either add it to your
$PATH on the remote side or do your first deployment with
`lollypops.deployment.local-evaluation` set to `true`.

**Note:** If your flake includes remote Git repositories in its inputs, `git` is
required to be installed on the remote host.

### Secrets

Secrets are specified as attribute set under `lollypops.secrets.files`. All
Expand Down
190 changes: 106 additions & 84 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -55,23 +55,7 @@
])
homeUsers));

mkSeclist = config: pkgs.lib.lists.flatten (map
(x: [
"echo 'Deploying ${x.name} to ${pkgs.lib.escapeShellArg x.path}'"
# Create parent directory if it does not exist
''
set -o pipefail -e; ssh {{.REMOTE_USER}}@{{.REMOTE_HOST}} 'umask 077; mkdir -p "$(dirname ${pkgs.lib.escapeShellArg x.path})"'
''
# Copy file
''
set -o pipefail -e; ${x.cmd} | ssh {{.REMOTE_USER}}@{{.REMOTE_HOST}} "umask 077; cat > ${pkgs.lib.escapeShellArg x.path}"
''
# # Set group and owner
''
set -o pipefail -e; ssh {{.REMOTE_USER}}@{{.REMOTE_HOST}} "chown ${x.owner}:${x.group-name} ${pkgs.lib.escapeShellArg x.path}"
''
])
(builtins.attrValues config.lollypops.secrets.files));


in
{
Expand All @@ -85,83 +69,121 @@
output = "prefixed";

vars = with hostConfig.config.lollypops; {
REMOTE_USER = deployment.user;
REMOTE_HOST = deployment.host;
REMOTE_USER = deployment.ssh.user;
REMOTE_HOST = deployment.ssh.host;
REMOTE_COMMAND = deployment.ssh.command;
REMOTE_SSH_OPTS = pkgs.lib.concatStrings deployment.ssh.opts;
REMOTE_SUDO_COMMAND = deployment.sudo.command;
REMOTE_SUDO_OPTS = pkgs.lib.concatStrings deployment.sudo.opts;
REBUILD_ACTION = ''{{default "switch" .REBUILD_ACTION}}'';
REMOTE_CONFIG_DIR = deployment.config-dir;
LOCAL_FLAKE_SOURCE = configFlake;
HOSTNAME = hostName;
};

tasks = {

check-vars.preconditions = [{
sh = ''[ ! -z "{{.HOSTNAME}}" ]'';
msg = "HOSTNAME not set: {{.HOSTNAME}}";
}];

deploy-secrets = {
deps = [ "check-vars" ];

desc = "Deploy secrets to: ${hostName}";

cmds = [
''echo "Deploying secrets to: {{.HOSTNAME}}"''
]
++ mkSeclist hostConfig.config
++ (if builtins.hasAttr "home-manager" hostConfig.config then
mkSeclistUser hostConfig.config.home-manager.users else [ ]);
};

rebuild = {
dir = self;

desc = "Rebuild configuration of: ${hostName}";
deps = [ "check-vars" ];
cmds = [
''echo "Rebuilding: {{.HOSTNAME}}"''
# For dry-running use `nixos-rebuild dry-activate`
(
if hostConfig.config.lollypops.deployment.local-evaluation then
tasks =
let
useSudo = hostConfig.config.lollypops.deployment.sudo.enable;
in
with pkgs.lib; {

check-vars.preconditions = [{
sh = ''[ ! -z "{{.HOSTNAME}}" ]'';
msg = "HOSTNAME not set: {{.HOSTNAME}}";
}];

deploy-secrets =
let mkSeclist = config: lists.flatten (map
(x:
let
path = escapeShellArg x.path;
in
[
"echo 'Deploying ${x.name} to ${path}'"

# Create parent directory if it does not exist
''
set -o pipefail -e; {{.REMOTE_COMMAND}} {{.REMOTE_OPTS}} {{.REMOTE_USER}}@{{.REMOTE_HOST}} \
'${optionalString useSudo "{{.REMOTE_SUDO_COMMAND}} {{.REMOTE_SUDO_OPTS}} "} install -d -m 077 "$(dirname ${path})"'
''

# Copy file
''
set -o pipefail -e; ${x.cmd} | {{.REMOTE_COMMAND}} {{.REMOTE_OPTS}} {{.REMOTE_USER}}@{{.REMOTE_HOST}} \
"${optionalString useSudo "{{.REMOTE_SUDO_COMMAND}} {{.REMOTE_SUDO_OPTS}}"} \
install -m 077 /dev/null ${path}; \
${optionalString useSudo "{{.REMOTE_SUDO_COMMAND}} {{.REMOTE_SUDO_OPTS}}"} \
cat > ${path}"
''

# Set group and owner
''
set -o pipefail -e; {{.REMOTE_COMMAND}} {{.REMOTE_OPTS}} {{.REMOTE_USER}}@{{.REMOTE_HOST}} \
"${optionalString useSudo "{{.REMOTE_SUDO_COMMAND}} {{.REMOTE_SUDO_OPTS}}"} \
chown ${x.owner}:${x.group-name} ${path}"
''
])
(builtins.attrValues config.lollypops.secrets.files)); in
{
deps = [ "check-vars" ];

desc = "Deploy secrets to: ${hostName}";

cmds = [
''echo "Deploying secrets to: {{.HOSTNAME}}"''
]
++ mkSeclist hostConfig.config
++ (if builtins.hasAttr "home-manager" hostConfig.config then
mkSeclistUser hostConfig.config.home-manager.users else [ ]);
};

rebuild = {
dir = self;

desc = "Rebuild configuration of: ${hostName}";
deps = [ "check-vars" ];
cmds = [
(if hostConfig.config.lollypops.deployment.local-evaluation then
''
nixos-rebuild {{.REBUILD_ACTION}} --flake '{{.REMOTE_CONFIG_DIR}}#{{.HOSTNAME}}' \
${optionalString useSudo ''NIX_SSHOPTS="{{.REMOTE_SSH_OPTS}}"''} nixos-rebuild {{.REBUILD_ACTION}} \
--flake '{{.LOCAL_CONFIG_DIR}}#{{.HOSTNAME}}' \
--target-host {{.REMOTE_USER}}@{{.REMOTE_HOST}} \
--build-host root@{{.REMOTE_HOST}}
''
else
''
ssh {{.REMOTE_USER}}@{{.REMOTE_HOST}} "nixos-rebuild {{.REBUILD_ACTION}} --flake '{{.REMOTE_CONFIG_DIR}}#{{.HOSTNAME}}'"
''
)
];
};
${optionalString useSudo "--use-remote-sudo"}
'' else ''
{{.REMOTE_COMMAND}} {{.REMOTE_OPTS}} {{.REMOTE_USER}}@{{.REMOTE_HOST}} \
"${optionalString useSudo "{{.REMOTE_SUDO_COMMAND}} {{.REMOTE_SUDO_OPTS}}"} nixos-rebuild {{.REBUILD_ACTION}} \
--flake '{{.REMOTE_CONFIG_DIR}}#{{.HOSTNAME}}'"
'')
];
};

deploy-flake = {

deps = [ "check-vars" ];
desc = "Deploy flake repository to: ${hostName}";
cmds = [
''echo "Deploying flake to: {{.HOSTNAME}}"''
''
source_path={{.LOCAL_FLAKE_SOURCE}}
if test -d "$source_path"; then
source_path=$source_path/
fi
${pkgs.rsync}/bin/rsync \
--checksum \
--verbose \
-e ssh\ -l\ {{.REMOTE_USER}}\ -T \
-FD \
--times \
--perms \
--recursive \
--links \
--delete-excluded \
$source_path {{.REMOTE_USER}}\@{{.REMOTE_HOST}}:{{.REMOTE_CONFIG_DIR}}
''
];
deploy-flake = {

deps = [ "check-vars" ];
desc = "Deploy flake repository to: ${hostName}";
cmds = [
''echo "Deploying flake to: {{.HOSTNAME}}"''
''
source_path={{.LOCAL_FLAKE_SOURCE}}
if test -d "$source_path"; then
source_path=$source_path/
fi
${pkgs.rsync}/bin/rsync \
--verbose \
-e {{.REMOTE_COMMAND}}\ -l\ {{.REMOTE_USER}}\ -T \
-FD \
--times \
--perms \
--recursive \
--links \
--delete-excluded \
--mkpath \
${optionalString useSudo ''--rsync-path="{{.REMOTE_SUDO_COMMAND}} {{.REMOTE_SUDO_OPTS}} rsync" \''}
$source_path {{.REMOTE_USER}}\@{{.REMOTE_HOST}}:{{.REMOTE_CONFIG_DIR}}
''
];
};
};
};
});

# Taskfile passed to go-task
Expand Down
57 changes: 47 additions & 10 deletions module.nix
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,59 @@ in
description = "Path to place the configuration on the remote host";
};

host = mkOption {
type = types.str;
default = "${config.networking.hostName}";
description = "Host to deploy to";
defaultText = "<config.networking.hostName>";
sudo = {

enable = mkOption {
type = types.bool;
default = false;
description = "Enables the use of sudo for deployment on remote servers";
};

command = mkOption {
type = types.str;
default = "sudo";
description = "Command to run for permission elevation";
};

opts = mkOption {
type = types.listOf types.str;
default = [ "" ];
example = [ "--user=user" ];
description = "Options to pass to the configured sudo command";
};
};

user = mkOption {
type = types.str;
default = "root";
description = "User to deploy as";
ssh = {

command = mkOption {
type = types.str;
default = "ssh";
description = "Command to run for connection to another server";
};

opts = mkOption {
type = types.listOf types.str;
default = [ "" ];
example = [ "-A" ];
description = "Options to pass to the configured SSH command";
};

host = mkOption {
type = types.str;
default = "${config.networking.hostName}";
description = "Host to deploy to";
};

user = mkOption {
type = types.str;
default = "root";
description = "User to deploy as";
};
};
};
};

config = {
environment.systemPackages = with pkgs; [ rsync ];
environment.systemPackages = with pkgs; [ rsync ];
};
}