Compare commits

...

7 commits

Author SHA1 Message Date
Tristan Daniël Maat 7c65e7ad02
WIP: podman: Configure auto-updates 2021-05-17 00:35:54 +01:00
Tristan Daniël Maat 4c94932490
webserver: Use SIGKILL instead of SIGTERM 2021-05-17 00:18:51 +01:00
Tristan Daniël Maat 343c7fcc36
nginx: Don't override extra options in the host helper 2021-05-17 00:13:58 +01:00
Tristan Daniël Maat 5f8899d542
nginx: Make VM testing easier by binding virtualHosts to localhost 2021-05-17 00:13:38 +01:00
Tristan Daniël Maat b8bf3bd3a2
minecraft: Clean up use of pkgs.lib 2021-05-17 00:13:28 +01:00
Tristan Daniël Maat 458f6c7f7b
nginx: Avoid connection issues caused by IPv6 resolution
If localhost is specified in the proxyPass url, nginx will happily
resolve IPv6 addresses, even if the upstream doesn't support them.

This can result in connection issues, especially with containers that
don't support IPv6.
2021-05-16 01:34:03 +01:00
Tristan Daniël Maat 517f4f0080
postgres: Get rid of password authentication
Podman pods make this obsolete; though we need to explicitly set
slirp4netns, otherwise podman will not create private network
namespaces for the pods.
2021-05-16 00:40:09 +01:00
8 changed files with 388 additions and 18 deletions

View file

@ -1,4 +1,4 @@
{ config, pkgs, ... }: { config, pkgs, lib, ... }:
{ {
imports = [ imports = [
@ -54,18 +54,20 @@
recommendedGzipSettings = true; recommendedGzipSettings = true;
recommendedProxySettings = true; recommendedProxySettings = true;
clientMaxBodySize = "10G"; clientMaxBodySize = "10G";
domain = "tlater.net";
virtualHosts = let virtualHosts = let
host = port: extra: host = port: extra:
{ lib.recursiveUpdate {
forceSSL = true; forceSSL = true;
enableACME = true; enableACME = true;
locations."/" = { proxyPass = "http://localhost:${toString port}"; }; locations."/" = { proxyPass = "http://127.0.0.1:${toString port}"; };
} // extra; } extra;
domain = config.services.nginx.domain;
in { in {
"tlater.net" = host 3002 { serverAliases = [ "www.tlater.net" ]; }; "${domain}" = host 3002 { serverAliases = [ "www.${domain}" ]; };
"gitea.tlater.net" = host 3000 { }; "gitea.${domain}" = host 3000 { };
"nextcloud.tlater.net" = host 3001 { }; "nextcloud.${domain}" = host 3001 { };
}; };
}; };

View file

@ -14,10 +14,11 @@
virtualisation.pods.gitea = { virtualisation.pods.gitea = {
hostname = "gitea.tlater.net"; hostname = "gitea.tlater.net";
publish = [ "3000:3000" "2221:2221" ]; publish = [ "3000:3000" "2221:2221" ];
network = "slirp4netns";
containers = { containers = {
gitea = { gitea = {
image = "gitea/gitea:latest"; image = "docker.io/gitea/gitea:latest";
volumes = [ "gitea:/data:Z" "/etc/localtime:/etc/localtime:ro" ]; volumes = [ "gitea:/data:Z" "/etc/localtime:/etc/localtime:ro" ];
dependsOn = [ "postgres" ]; dependsOn = [ "postgres" ];
@ -26,7 +27,6 @@
DB_HOST = "gitea-postgres:5432"; DB_HOST = "gitea-postgres:5432";
DB_NAME = "gitea"; DB_NAME = "gitea";
DB_USER = "gitea"; DB_USER = "gitea";
DB_PASSWD = "/qNDDK9WCMuubfA7D8DFwfl9T+Gy2IMDvPhiNpcxZjY=";
USER_UID = toString config.users.extraUsers.gitea.uid; USER_UID = toString config.users.extraUsers.gitea.uid;
USER_GID = toString config.users.extraGroups.gitea.gid; USER_GID = toString config.users.extraGroups.gitea.gid;
@ -35,6 +35,11 @@
DOMAIN = "gitea.tlater.net"; DOMAIN = "gitea.tlater.net";
SSH_PORT = "2221"; SSH_PORT = "2221";
}; };
extraOptions = [
"--replace"
"--label" "io.containers.autoupdate=image"
];
}; };
postgres = { postgres = {
@ -42,7 +47,6 @@
environment = { environment = {
POSTGRES_DB = "gitea"; POSTGRES_DB = "gitea";
POSTGRES_USER = "gitea"; POSTGRES_USER = "gitea";
POSTGRES_PASSWORD = "/qNDDK9WCMuubfA7D8DFwfl9T+Gy2IMDvPhiNpcxZjY=";
}; };
volumes = [ "gitea-db-data:/var/lib/postgresql/data" ]; volumes = [ "gitea-db-data:/var/lib/postgresql/data" ];
}; };

View file

@ -1,4 +1,4 @@
{ config, pkgs, ... }: { config, pkgs, lib, ... }:
let let
minecraft-server-args = [ minecraft-server-args = [
@ -52,7 +52,7 @@ let
in { in {
nixpkgs.config.allowUnfreePredicate = pkg: nixpkgs.config.allowUnfreePredicate = pkg:
builtins.elem (pkgs.lib.getName pkg) [ "forge-server" ]; builtins.elem (lib.getName pkg) [ "forge-server" ];
virtualisation.oci-containers.containers.minecraft-voor-kia = let virtualisation.oci-containers.containers.minecraft-voor-kia = let
properties = ./configs/minecraft/voor-kia/server.properties; properties = ./configs/minecraft/voor-kia/server.properties;

View file

@ -4,6 +4,7 @@
virtualisation.pods.nextcloud = { virtualisation.pods.nextcloud = {
hostname = "nextcloud.tlater.net"; hostname = "nextcloud.tlater.net";
publish = [ "3001:80" ]; publish = [ "3001:80" ];
network = "slirp4netns";
containers = { containers = {
nextcloud = { nextcloud = {
@ -18,7 +19,6 @@
POSTGRES_DB = "nextcloud"; POSTGRES_DB = "nextcloud";
POSTGRES_USER = "nextcloud"; POSTGRES_USER = "nextcloud";
POSTGRES_HOST = "nextcloud-postgres"; POSTGRES_HOST = "nextcloud-postgres";
POSTGRES_PASSWORD = "rI7t7Nek1yGA9ucrRc7Uhy0jcjwPjnXa8me4o8tJON8=";
OVERWRITEPROTOCOL = "https"; OVERWRITEPROTOCOL = "https";
}; };
}; };
@ -43,7 +43,6 @@
environment = { environment = {
POSTGRES_DB = "nextcloud"; POSTGRES_DB = "nextcloud";
POSTGRES_USER = "nextcloud"; POSTGRES_USER = "nextcloud";
POSTGRES_PASSWORD = "rI7t7Nek1yGA9ucrRc7Uhy0jcjwPjnXa8me4o8tJON8=";
}; };
volumes = [ "nextcloud-db-data:/var/lib/postgresql/data" ]; volumes = [ "nextcloud-db-data:/var/lib/postgresql/data" ];
}; };

View file

@ -34,6 +34,10 @@
ports = [ "3002:3002" ]; ports = [ "3002:3002" ];
volumes = [ "tlaternet-mail:/srv/mail" ]; volumes = [ "tlaternet-mail:/srv/mail" ];
extraOptions = [ "--hostname=tlater.net" ]; extraOptions = [
"--hostname=tlater.net"
# Rocket 0.4 doesn't support SIGTERM anyway, so SIGKILL is the cleanest exit possible.
"--stop-signal=SIGKILL"
];
}; };
} }

View file

@ -68,12 +68,16 @@
(import ./modules) (import ./modules)
(import ./configuration) (import ./configuration)
({ ... }: { ({ lib, ... }: {
users.users.tlater.password = "insecure"; users.users.tlater.password = "insecure";
# Disable graphical tty so -curses works # Disable graphical tty so -curses works
boot.kernelParams = [ "nomodeset" ]; boot.kernelParams = [ "nomodeset" ];
# Sets the base domain for nginx to localhost so that we
# can easily test locally with the VM.
services.nginx.domain = lib.mkOverride 99 "localhost";
# # Set up VM settings to match real VPS # # Set up VM settings to match real VPS
# virtualisation.memorySize = 3941; # virtualisation.memorySize = 3941;
# virtualisation.cores = 2; # virtualisation.cores = 2;

View file

@ -1,5 +1,12 @@
{ ... }: { lib, ... }:
with lib;
{ {
imports = [ ./virtualisation/pods.nix ]; imports = [ ./virtualisation/pods.nix ./virtualisation/oci-containers.nix ];
options.services.nginx.domain = mkOption {
type = types.str;
description = "The base domain name to append to virtual domain names";
};
} }

View file

@ -0,0 +1,350 @@
# Pulled from my own modified fork of nixpkgs, awaiting merge
# upstream.
{ config, options, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.oci-containers;
proxy_env = config.networking.proxy.envVars;
defaultBackend = options.virtualisation.oci-containers.backend.default;
containerOptions =
{ ... }: {
options = {
image = mkOption {
type = with types; str;
description = "OCI image to run.";
example = "library/hello-world";
};
imageFile = mkOption {
type = with types; nullOr package;
default = null;
description = ''
Path to an image file to load instead of pulling from a registry.
If defined, do not pull from registry.
You still need to set the <literal>image</literal> attribute, as it
will be used as the image name for docker to start a container.
'';
example = literalExample "pkgs.dockerTools.buildDockerImage {...};";
};
cmd = mkOption {
type = with types; listOf str;
default = [];
description = "Commandline arguments to pass to the image's entrypoint.";
example = literalExample ''
["--port=9000"]
'';
};
entrypoint = mkOption {
type = with types; nullOr str;
description = "Override the default entrypoint of the image.";
default = null;
example = "/bin/my-app";
};
environment = mkOption {
type = with types; attrsOf str;
default = {};
description = "Environment variables to set for this container.";
example = literalExample ''
{
DATABASE_HOST = "db.example.com";
DATABASE_PORT = "3306";
}
'';
};
environmentFiles = mkOption {
type = with types; listOf path;
default = [];
description = "Environment files for this container.";
example = literalExample ''
[
/path/to/.env
/path/to/.env.secret
]
'';
};
log-driver = mkOption {
type = types.str;
default = "journald";
description = ''
Logging driver for the container. The default of
<literal>"journald"</literal> means that the container's logs will be
handled as part of the systemd unit.
For more details and a full list of logging drivers, refer to respective backends documentation.
For Docker:
<link xlink:href="https://docs.docker.com/engine/reference/run/#logging-drivers---log-driver">Docker engine documentation</link>
For Podman:
Refer to the docker-run(1) man page.
'';
};
ports = mkOption {
type = with types; listOf str;
default = [];
description = ''
Network ports to publish from the container to the outer host.
Valid formats:
<itemizedlist>
<listitem>
<para>
<literal>&lt;ip&gt;:&lt;hostPort&gt;:&lt;containerPort&gt;</literal>
</para>
</listitem>
<listitem>
<para>
<literal>&lt;ip&gt;::&lt;containerPort&gt;</literal>
</para>
</listitem>
<listitem>
<para>
<literal>&lt;hostPort&gt;:&lt;containerPort&gt;</literal>
</para>
</listitem>
<listitem>
<para>
<literal>&lt;containerPort&gt;</literal>
</para>
</listitem>
</itemizedlist>
Both <literal>hostPort</literal> and
<literal>containerPort</literal> can be specified as a range of
ports. When specifying ranges for both, the number of container
ports in the range must match the number of host ports in the
range. Example: <literal>1234-1236:1234-1236/tcp</literal>
When specifying a range for <literal>hostPort</literal> only, the
<literal>containerPort</literal> must <emphasis>not</emphasis> be a
range. In this case, the container port is published somewhere
within the specified <literal>hostPort</literal> range. Example:
<literal>1234-1236:1234/tcp</literal>
Refer to the
<link xlink:href="https://docs.docker.com/engine/reference/run/#expose-incoming-ports">
Docker engine documentation</link> for full details.
'';
example = literalExample ''
[
"8080:9000"
]
'';
};
user = mkOption {
type = with types; nullOr str;
default = null;
description = ''
Override the username or UID (and optionally groupname or GID) used
in the container.
'';
example = "nobody:nogroup";
};
volumes = mkOption {
type = with types; listOf str;
default = [];
description = ''
List of volumes to attach to this container.
Note that this is a list of <literal>"src:dst"</literal> strings to
allow for <literal>src</literal> to refer to
<literal>/nix/store</literal> paths, which would be difficult with an
attribute set. There are also a variety of mount options available
as a third field; please refer to the
<link xlink:href="https://docs.docker.com/engine/reference/run/#volume-shared-filesystems">
docker engine documentation</link> for details.
'';
example = literalExample ''
[
"volume_name:/path/inside/container"
"/path/on/host:/path/inside/container"
]
'';
};
workdir = mkOption {
type = with types; nullOr str;
default = null;
description = "Override the default working directory for the container.";
example = "/var/lib/hello_world";
};
dependsOn = mkOption {
type = with types; listOf str;
default = [];
description = ''
Define which other containers this one depends on. They will be added to both After and Requires for the unit.
Use the same name as the attribute under <literal>virtualisation.oci-containers.containers</literal>.
'';
example = literalExample ''
virtualisation.oci-containers.containers = {
node1 = {};
node2 = {
dependsOn = [ "node1" ];
}
}
'';
};
extraOptions = mkOption {
type = with types; listOf str;
default = [];
description = "Extra options for <command>${defaultBackend} run</command>.";
example = literalExample ''
["--network=host"]
'';
};
autoStart = mkOption {
type = types.bool;
default = true;
description = ''
When enabled, the container is automatically started on boot.
If this option is set to false, the container has to be started on-demand via its service.
'';
};
};
};
mkService = name: container: let
dependsOn = map (x: "${cfg.backend}-${x}.service") container.dependsOn;
in {
wantedBy = [] ++ optional (container.autoStart) "multi-user.target";
after = lib.optionals (cfg.backend == "docker") [ "docker.service" "docker.socket" ] ++ dependsOn;
requires = dependsOn;
environment = proxy_env;
path =
if cfg.backend == "docker" then [ config.virtualisation.docker.package ]
else if cfg.backend == "podman" then [ config.virtualisation.podman.package ]
else throw "Unhandled backend: ${cfg.backend}";
preStart = ''
${cfg.backend} rm -f ${name} || true
${optionalString (container.imageFile != null) ''
${cfg.backend} load -i ${container.imageFile}
''}
'';
# Podman likes knowing what systemd unit launched its container,
# so that it can auto-update containers and restart them.
#
# Sadly, the NixOS `script` option doesn't expose the systemd
# syntax `%n` that would expose the unit location, so we pass it
# as $1 in a scriptArg.
scriptArgs = lib.optionals (cfg.backend == "podman") "%n";
script = concatStringsSep " \\\n " ([
"exec ${cfg.backend} run"
"--rm"
"--name=${escapeShellArg name}"
"--log-driver=${container.log-driver}"
] ++ optional (container.entrypoint != null)
"--entrypoint=${escapeShellArg container.entrypoint}"
++ (mapAttrsToList (k: v: "-e ${escapeShellArg k}=${escapeShellArg v}") container.environment)
++ optional (cfg.backend == "podman") ''-e 'PODMAN_SYSTEMD_UNIT'="$1"''
++ map (f: "--env-file ${escapeShellArg f}") container.environmentFiles
++ map (p: "-p ${escapeShellArg p}") container.ports
++ optional (container.user != null) "-u ${escapeShellArg container.user}"
++ map (v: "-v ${escapeShellArg v}") container.volumes
++ optional (container.workdir != null) "-w ${escapeShellArg container.workdir}"
++ map escapeShellArg container.extraOptions
++ [container.image]
++ map escapeShellArg container.cmd
);
preStop = "[ $SERVICE_RESULT = success ] || ${cfg.backend} stop ${name}";
postStop = "${cfg.backend} rm -f ${name} || true";
serviceConfig = {
StandardOutput = "null";
StandardError = "null";
### There is no generalized way of supporting `reload` for docker
### containers. Some containers may respond well to SIGHUP sent to their
### init process, but it is not guaranteed; some apps have other reload
### mechanisms, some don't have a reload signal at all, and some docker
### images just have broken signal handling. The best compromise in this
### case is probably to leave ExecReload undefined, so `systemctl reload`
### will at least result in an error instead of potentially undefined
### behaviour.
###
### Advanced users can still override this part of the unit to implement
### a custom reload handler, since the result of all this is a normal
### systemd service from the perspective of the NixOS module system.
###
# ExecReload = ...;
###
TimeoutStartSec = 0;
TimeoutStopSec = 120;
Restart = "always";
};
};
in {
imports = [
(
lib.mkChangedOptionModule
[ "docker-containers" ]
[ "virtualisation" "oci-containers" ]
(oldcfg: {
backend = "docker";
containers = lib.mapAttrs (n: v: builtins.removeAttrs (v // {
extraOptions = v.extraDockerOptions or [];
}) [ "extraDockerOptions" ]) oldcfg.docker-containers;
})
)
];
disabledModules = [ "virtualisation/oci-containers.nix" ];
options.virtualisation.oci-containers = {
backend = mkOption {
type = types.enum [ "podman" "docker" ];
default =
# TODO: Once https://github.com/NixOS/nixpkgs/issues/77925 is resolved default to podman
# if versionAtLeast config.system.stateVersion "20.09" then "podman"
# else "docker";
"docker";
description = "The underlying Docker implementation to use.";
};
containers = mkOption {
default = {};
type = types.attrsOf (types.submodule containerOptions);
description = "OCI (Docker) containers to run as systemd services.";
};
};
config = lib.mkIf (cfg.containers != {}) (lib.mkMerge [
{
systemd.services = mapAttrs' (n: v: nameValuePair "${cfg.backend}-${n}" (mkService n v)) cfg.containers;
}
(lib.mkIf (cfg.backend == "podman") {
virtualisation.podman.enable = true;
})
(lib.mkIf (cfg.backend == "docker") {
virtualisation.docker.enable = true;
})
]);
}