diff options
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/apps/postgresql/default.nix | 31 | ||||
| -rw-r--r-- | modules/apps/postgresql/server.nix | 169 | ||||
| -rw-r--r-- | modules/deployment/default.nix | 2 | ||||
| -rw-r--r-- | modules/deployment/keyrings.nix | 155 | ||||
| -rw-r--r-- | modules/system/nixos.nix | 4 | ||||
| -rw-r--r-- | modules/system/users.nix | 2 |
6 files changed, 323 insertions, 40 deletions
diff --git a/modules/apps/postgresql/default.nix b/modules/apps/postgresql/default.nix index e7eecaa..9ac2a27 100644 --- a/modules/apps/postgresql/default.nix +++ b/modules/apps/postgresql/default.nix @@ -68,21 +68,46 @@ let concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${show n v}") (explicit opts.server)) ); - preStart = '' + + # FIXME: There is a bash builtin "csv", but it fails on a complicated, + # FIXME: but still valid string like '/tmp, "/xx, yy", "/foo"'. + # FIXME: Ref. https://www.postgresql.org/docs/current/runtime-config-connection.html + # XXX: order of options is important for postgres. + main-pre = pkgs.writeBashScriptBin "pgsql-${name}-pre" '' + set -euo pipefail + mkdir -v -p '${data_directory}' chown -R '${user}:${user}' '${data_directory}' chmod -R u=rwX,g=,o= '${data_directory}' + + mkdirs() { + for d in "$@"; do + case $d in + @*) + ;; + *) + mkdir -p -v "$d" + chmod -c a=rwxt "$d" + ;; + esac + done + } + export -f mkdirs + + ${pkgs.csvtool}/bin/csvtool call mkdirs <(${opts.package}/bin/postgres -C unix_socket_directories --config-file='${conf}') ''; main = pkgs.writeBashScriptBin "pgsql-${name}" '' set -euo pipefail + if [ ! -f '${data_directory}/PG_VERSION' ]; then ${initdb} '${data_directory}' rm -f '${data_directory}/'*hba.conf rm -f '${data_directory}/'*ident.conf rm -f '${data_directory}/postgresql.conf' fi - exec '${opts.package}/bin/postgres' -c 'config_file=${conf}' + + exec ${opts.package}/bin/postgres --config-file='${conf}' ''; psql = "${opts.package}/bin/psql -v ON_ERROR_STOP=1 -p${toString port} -U postgres"; @@ -114,8 +139,8 @@ let wantedBy = [ "multi-user.target" ]; wants = [ "keys.target" ]; after = [ "keys.target" "network.target" "local-fs.target" ]; - inherit preStart; serviceConfig = { + ExecStartPre = "${main-pre}/bin/pgsql-${name}-pre"; ExecStart = "${main}/bin/pgsql-${name}"; KillMode = "mixed"; KillSignal = "SIGINT"; diff --git a/modules/apps/postgresql/server.nix b/modules/apps/postgresql/server.nix index c24494c..baafb35 100644 --- a/modules/apps/postgresql/server.nix +++ b/modules/apps/postgresql/server.nix @@ -19,12 +19,12 @@ let in { options = { - DateStyle = optional str; - IntervalStyle = optional (enum [ "sql_standard" "postgres_verbose" "iso_8601" ]); - TimeZone = optional str; + allow_alter_system = optional bool; application_name = optional str; + archive_cleanup_command = optional path; archive_command = optional path; - archive_mode = optional bool; + archive_library = optional path; + archive_mode = optional (enum [ "always" "on" "off" ]); archive_timeout = optional int; array_nulls = optional bool; authentication_timeout = optional int; @@ -37,58 +37,93 @@ in { autovacuum_naptime = optional int; autovacuum_vacuum_cost_delay = optional int; autovacuum_vacuum_cost_limit = optional int; + autovacuum_vacuum_insert_scale_factor = optional float; + autovacuum_vacuum_insert_threshold = optional int; + autovacuum_vacuum_max_threshold = optional int; autovacuum_vacuum_scale_factor = optional float; autovacuum_vacuum_threshold = optional int; autovacuum_work_mem = optional int; + autovacuum_worker_slots = optional int; + backend_flush_after = optional int; backslash_quote = optional (enum [ "on" "off" "safe_encoding" ]); bgwriter_delay = optional int; + bgwriter_flush_after = optional int; bgwriter_lru_maxpages = optional int; bgwriter_lru_multiplier = optional int; + bonjour = optional bool; + bonjour_name = optional str; bytea_output = optional (enum [ "hex" "escape" ]); check_function_bodies = optional bool; checkpoint_completion_target = optional float; + checkpoint_flush_after = optional int; checkpoint_segments = optional int; checkpoint_timeout = optional int; checkpoint_warning = optional int; + client_connection_check_interval = optional int; client_encoding = optional str; - client_min_messages = optional (enum [ "DEBUG5" "DEBUG4" "DEBUG3" "DEBUG2" "DEBUG1" "LOG" "NOTICE" "WARNING" "ERROR" "FATAL" "PANIC" ]); + client_min_messages = optional (enum [ "DEBUG5" "DEBUG4" "DEBUG3" "DEBUG2" "DEBUG1" "LOG" "NOTICE" "WARNING" "ERROR" ]); + cluster_name = optional str; commit_delay = optional int; commit_siblings = optional int; + commit_timestamp_buffers = optional int; + compute_query_id = optional (enum [ "off" "on" "auto" "regress" ]); constraint_exclusion = optional (enum [ "on" "partition" "off" ]); cpu_index_tuple_cost = optional float; cpu_operator_cost = optional float; cpu_tuple_cost = optional float; cursor_tuple_fraction = optional float; data_directory = mandatory path; + data_sync_retry = optional bool; + DateStyle = optional str; deadlock_timeout = optional int; debug_pretty_print = optional bool; debug_print_parse = optional bool; debug_print_plan = optional bool; debug_print_rewritten = optional bool; default_statistics_target = optional int; + default_table_access_method = optional str; default_tablespace = optional str; default_text_search_config = optional str; + default_toast_compression = optional (enum [ "pglz" "lz4" ]); default_transaction_deferrable = optional bool; default_transaction_isolation = optional (enum [ "read uncommitted" "read committed" "repeatable read" "serializable" ]); default_transaction_read_only = optional bool; default_with_oids = optional bool; - dynamic_shared_memory_type = optional (enum [ "posix" "sysv" "mmap" "none" ]); + dynamic_library_path = optional str; + dynamic_shared_memory_type = optional (enum [ "posix" "sysv" "mmap" ]); effective_cache_size = optional int; effective_io_concurrency = optional int; + enable_async_append = optional bool; enable_bitmapscan = optional bool; + enable_distinct_reordering = optional bool; + enable_gathermerge = optional bool; + enable_group_by_reordering = optional bool; enable_hashagg = optional bool; enable_hashjoin = optional bool; + enable_incremental_sort = optional bool; enable_indexonlyscan = optional bool; enable_indexscan = optional bool; enable_material = optional bool; + enable_memoize = optional bool; enable_mergejoin = optional bool; enable_nestloop = optional bool; + enable_parallel_append = optional bool; + enable_parallel_hash = optional bool; + enable_partition_pruning = optional bool; + enable_partitionwise_aggregate = optional bool; + enable_partitionwise_join = optional bool; + enable_presorted_aggregate = optional bool; + enable_self_join_elimination = optional bool; enable_seqscan = optional bool; enable_sort = optional bool; enable_tidscan = optional bool; escape_string_warning = optional bool; + event_source = optional str; exit_on_error = optional bool; + extension_control_path = optional str; extra_float_digits = optional int; + file_copy_method = optional (enum [ "COPY" "CLONE" ]); + file_extend_method = optional (enum [ "posix_fallocate" "write_zeros" ]); from_collapse_limit = optional int; fsync = optional bool; full_page_writes = optional bool; @@ -99,117 +134,233 @@ in { geqo_seed = optional float; geqo_selection_bias = optional float; geqo_threshold = optional int; + gin_fuzzy_search_limit = optional int; + gss_accept_delegation = optional bool; + hash_mem_multiplier = optional float; hba_file = default "" lines; hot_standby = optional bool; hot_standby_feedback = optional bool; + huge_page_size = optional int; huge_pages = optional (enum [ "on" "off" "try" ]); + icu_validation_level = optional (enum [ "DISABLED" "DEBUG5" "DEBUG4" "DEBUG3" "DEBUG2" "DEBUG1" "LOG" "INFO" "NOTICE" "WARNING" "ERROR" ]); ident_file = default "" lines; + idle_replication_slot_timeout = optional int; + IntervalStyle = optional (enum [ "sql_standard" "postgres" "postgres_verbose" "iso_8601" ]); + io_combine_limit = optional int; + io_max_combine_limit = optional int; + io_max_concurrency = optional int; + io_method = optional (enum [ "worker" "io_uring" "sync" ]); + io_workers = optional int; + jit = optional bool; + jit_above_cost = optional float; + jit_inline_above_cost = optional float; + jit_optimize_above_cost = optional float; + jit_provider = optional str; join_collapse_limit = optional int; + krb_caseins_users = optional bool; + krb_server_keyfile = optional path; lc_messages = optional str; lc_monetary = optional str; lc_numeric = optional str; lc_time = optional str; listen_addresses = optional (either (listOf str) str); lo_compat_privileges = optional bool; + local_preload_libraries = optional (either (listOf str) str); lock_timeout = optional int; log_autovacuum_min_duration = optional int; log_checkpoints = optional bool; - log_connections = optional bool; - log_destination = optional (enum [ "stderr" "csvlog" "syslog" ]); + log_connections = let x = enum [ "receipt" "authentication" "authorization" "setup_durations" "all"]; in optional (either (listOf x) x); + log_destination = let x = enum [ "stderr" "csvlog" "syslog" "jsonlog" ]; in optional (either (listOf x) x); log_directory = optional path; log_disconnections = optional bool; log_duration = optional bool; log_error_verbosity = optional (enum [ "TERSE" "DEFAULT" "VERBOSE" ]); log_executor_stats = optional bool; + log_file_mode = optional int; log_filename = optional str; log_hostname = optional bool; log_line_prefix = optional str; + log_lock_failures = optional bool; log_lock_waits = optional bool; + log_min_duration_sample = optional int; log_min_duration_statement = optional int; log_min_error_statement = optional (enum [ "DEBUG5" "DEBUG4" "DEBUG3" "DEBUG2" "DEBUG1" "LOG" "NOTICE" "WARNING" "ERROR" "FATAL" "PANIC" ]); log_min_messages = optional (enum [ "DEBUG5" "DEBUG4" "DEBUG3" "DEBUG2" "DEBUG1" "LOG" "NOTICE" "WARNING" "ERROR" "FATAL" "PANIC" ]); + log_parameter_max_length = optional int; + log_parameter_max_length_on_error = optional int; log_parser_stats = optional bool; log_planner_stats = optional bool; + log_recovery_conflict_waits = optional bool; + log_replication_commands = optional bool; log_rotation_age = optional int; log_rotation_size = optional int; + log_startup_progress_interval = optional int; log_statement = optional (enum [ "none" "ddl" "mod" "all" ]); + log_statement_sample_rate = optional float; log_statement_stats = optional bool; log_temp_files = optional int; log_timezone = optional str; + log_transaction_sample_rate = optional float; log_truncate_on_rotation = optional bool; logging_collector = optional bool; + logical_decoding_work_mem = optional int; + maintenance_io_concurrency = optional int; maintenance_work_mem = optional int; + max_active_replication_origins = optional int; max_connections = optional int; max_files_per_process = optional int; max_locks_per_transaction = optional int; + max_logical_replication_workers = optional int; + max_notify_queue_pages = optional int; + max_parallel_apply_workers_per_subscription = optional int; + max_parallel_maintenance_workers = optional int; + max_parallel_workers = optional int; + max_parallel_workers_per_gather = optional int; + max_pred_locks_per_page = optional int; + max_pred_locks_per_relation = optional int; max_pred_locks_per_transaction = optional int; max_prepared_transactions = optional int; max_replication_slots = optional int; + max_slot_wal_keep_size = optional int; max_stack_depth = optional int; max_standby_archive_delay = optional int; max_standby_streaming_delay = optional int; + max_sync_workers_per_subscription = optional int; max_wal_senders = optional int; + max_wal_size = optional int; max_worker_processes = optional int; + min_dynamic_shared_memory = optional int; + min_parallel_index_scan_size = optional int; + min_parallel_table_scan_size = optional int; + min_wal_size = optional int; + multixact_member_buffers = optional int; + multixact_offset_buffers = optional int; + notify_buffers = optional int; + oauth_validator_libraries = optional str; + parallel_leader_participation = optional bool; + parallel_setup_cost = optional float; + parallel_tuple_cost = optional float; password_encryption = optional (enum [ "md5" "scram-sha-256" ]); + plan_cache_mode = optional (enum [ "auto" "force_custom_plan" "force_generic_plan" ]); port = default 5432 int; + primary_conninfo = optional str; + primary_slot_name = optional str; quote_all_identifiers = optional bool; random_page_cost = optional float; + recovery_end_command = optional path; + recovery_init_sync_method = optional (enum ["fsync" "syncfs"]); + recovery_min_apply_delay = optional int; + recovery_prefetch = optional (enum [ "on" "try" "off" ]); + recovery_target = optional str; + recovery_target_action = optional (enum ["pause" "promote" "shutdown"]); + recovery_target_inclusive = optional bool; + recovery_target_lsn = optional str; + recovery_target_name = optional str; + recovery_target_time = optional str; + recovery_target_timeline = optional str; + recovery_target_xid = optional str; + recursive_worktable_factor = optional float; + reserved_connections = optional int; restart_after_crash = optional bool; + restore_command = optional path; + row_security = optional bool; + scram_iterations = optional int; search_path = optional (either (listOf str) str); seq_page_cost = optional float; + serializable_buffers = optional int; + session_preload_libraries = optional (either (listOf str) str); session_replication_role = optional (enum [ "origin" "replica" "local" ]); shared_buffers = optional int; + shared_memory_type = optional (enum [ "sysv" "mmap" ]); + shared_preload_libraries = optional (either (listOf str) str); sql_inheritance = optional bool; ssl = optional bool; ssl_ca_file = optional path; ssl_cert_file = optional path; ssl_ciphers = optional str; + ssl_crl_dir = optional path; ssl_crl_file = optional path; + ssl_dh_params_file = optional str; ssl_ecdh_curve = optional str; + ssl_groups = optional str; ssl_key_file = optional path; + ssl_max_protocol_version = optional (enum [ "TLSv1" "TLSv1.1" "TLSv1.2" "TLSv1.3" ]); + ssl_min_protocol_version = optional (enum [ "TLSv1" "TLSv1.1" "TLSv1.2" "TLSv1.3" ]); ssl_prefer_server_ciphers = optional bool; ssl_renegotiation_limit = optional int; + ssl_tls13_ciphers = optional str; standard_conforming_strings = optional bool; statement_timeout = optional int; + stats_fetch_consistency = optional (enum [ "none" "cache" "snapshot" ]); stats_temp_directory = optional path; + subtransaction_buffers = optional int; + summarize_wal = optional bool; superuser_reserved_connections = optional int; + sync_replication_slots = optional bool; synchronize_seqscans = optional bool; - synchronous_commit = optional (enum [ "on" "remote_write" "local" "off" ]); + synchronized_standby_slots = optional (either (listOf str) str); + synchronous_commit = optional (enum [ "on" "remote_write" "remote_apply" "local" "off" ]); synchronous_standby_names = optional (either (listOf str) str); + syslog_facility = optional (enum [ "LOCAL0" "LOCAL1" "LOCAL2" "LOCAL3" "LOCAL4" "LOCAL5" "LOCAL6" "LOCAL7" ]); syslog_ident = optional str; + syslog_sequence_numbers = optional bool; + syslog_split_messages = optional bool; tcp_keepalives_count = optional int; tcp_keepalives_idle = optional int; tcp_keepalives_interval = optional int; + tcp_user_timeout = optional int; temp_buffers = optional int; temp_file_limit = optional int; temp_tablespaces = optional str; + TimeZone = optional str; timezone_abbreviations = optional str; track_activities = optional bool; track_activity_query_size = optional int; + track_commit_timestamp = optional bool; + track_cost_delay_timing = optional bool; track_counts = optional bool; track_functions = optional (enum [ "none" "pl" "all" ]); track_io_timing = optional bool; + track_wal_io_timing = optional bool; + transaction_buffers = optional int; transform_null_equals = optional bool; + unix_socket_group = optional str; + unix_socket_permissions = optional int; update_process_title = optional bool; + vacuum_buffer_usage_limit = optional int; vacuum_cost_delay = optional int; vacuum_cost_limit = optional int; vacuum_cost_page_dirty = optional int; vacuum_cost_page_hit = optional int; vacuum_cost_page_miss = optional int; vacuum_defer_cleanup_age = optional int; + vacuum_failsafe_age = optional int; vacuum_freeze_min_age = optional int; vacuum_freeze_table_age = optional int; + vacuum_max_eager_freeze_failure_rate = optional float; + vacuum_multixact_failsafe_age = optional int; vacuum_multixact_freeze_min_age = optional int; vacuum_multixact_freeze_table_age = optional int; + vacuum_truncate = optional bool; wal_buffers = optional int; + wal_compression = optional (enum [ "pglz" "lz4" "zstd" "off" ]); + wal_decode_buffer_size = optional int; + wal_init_zero = optional bool; wal_keep_segments = optional int; + wal_keep_size = optional int; wal_level = optional (enum [ "minimal" "replica" "logical" ]); wal_log_hints = optional bool; + wal_receiver_create_temp_slot = optional bool; wal_receiver_status_interval = optional int; wal_receiver_timeout = optional int; + wal_recycle = optional bool; + wal_retrieve_retry_interval = optional int; wal_sender_timeout = optional int; + wal_skip_threshold = optional int; + wal_summary_keep_time = optional int; wal_sync_method = optional (enum [ "open_datasync" "fdatasync" "fsync" "fsync_writethrough" "open_sync" ]); wal_writer_delay = optional int; + wal_writer_flush_after = optional int; work_mem = optional int; xmlbinary = optional (enum [ "base64" "hex" ]); xmloption = optional (enum [ "DOCUMENT" "CONTENT" ]); diff --git a/modules/deployment/default.nix b/modules/deployment/default.nix index 240d970..9ef03e8 100644 --- a/modules/deployment/default.nix +++ b/modules/deployment/default.nix @@ -1,4 +1,4 @@ -{lib, ... }: +{ lib, ... }: let all = lib.filterAttrs diff --git a/modules/deployment/keyrings.nix b/modules/deployment/keyrings.nix index 23a96d0..14c228a 100644 --- a/modules/deployment/keyrings.nix +++ b/modules/deployment/keyrings.nix @@ -1,47 +1,48 @@ -{ config, lib, ... }: - +{ config, lib, pkgs, ... }: + let inherit (builtins) - attrNames baseNameOf head match pathExists readFile ; + attrNames baseNameOf head map match + ; inherit (lib) - filter foldl genAttrs hasPrefix mapAttrsToList mkOption - optionalAttrs unique ; + filter flip foldl genAttrs hasPrefix mapAttrs' mapAttrsToList mkOption + nameValuePair optionalAttrs unique + ; inherit (lib.types) - attrsOf listOf nullOr path ; + attrsOf either externalPath int listOf nullOr package path str submodule + ; - allusers = config.users.users; cfg = config.nixsap.deployment; - # XXX If the file is encrypted: - # error: the contents of the file ‘...’ cannot be represented as a Nix string - read = key: + emptyKey = pkgs.writeText "empty" ""; + getKeySource = key: let m = match "^(.+)@[^@]+$" key; s = if m != null then head m else key; in if cfg.secrets != null - then readFile (cfg.secrets + "/${s}") - else ""; + then cfg.secrets + "/${s}" + else emptyKey; in { options.nixsap.deployment = { secrets = mkOption { description = '' - Directory with the secrets on the deploy machine. If not specified, - each key will be an empty file. + Directory with the secrets on the build machine. If not specified, + each key in the keyrings will be an empty file. ''; - type = nullOr path; + type = nullOr externalPath; default = null; example = "<secrets>"; }; keyStore = mkOption { description = '' - Directory with the keys on the target machine. NixOps uses /run/keys, - and this is default. If you use another deployment tool, you would - like to set it to something else. + Directory with the keys on the target machine. + This directory will be created, its permissions changed to + ``0750`` and ownership to ``root:keys``. ''; - type = path; + type = externalPath; default = "/run/keys"; example = "/root/keys"; }; @@ -62,21 +63,121 @@ in { pgbackup = [ "/run/keys/s3cmd.cfg@pgbackup" ]; }; }; + + keys = mkOption { + default = {}; + description = '' + The set of keys to be deployed to the machine. Each attribute maps + a key name to a file that can be accessed as ``keyStore``/``name``. + A key is a file which cannot be in Nix store for security reasons, + e. g. it contains a password, a SSH private key, etc. + Each key also gets a systemd service ``<name>-key.service`` + which is active while the key is present and inactive while the key + is absent. + ''; + + type = attrsOf (submodule ({name, config, ...}: { + options = { + source = mkOption { + description = '' + Local file contaning the key. The contents of this file will put on the machine + in a file maching the key's name. + ''; + type = path; + }; + uid = mkOption { + default = 0; + type = int; + description = '' + The user id that will be set for the key file. + ''; + }; + gid = mkOption { + default = 0; + type = int; + description = '' + The group id that will be set for the key file. + ''; + }; + mode = mkOption { + default = 400; + example = "0640"; + type = int; + description = '' + The access mode that will be set for the key file. + Should be in a numeric form accepted by ``chmod(1)``. + ''; + }; + }; + })); + }; + + send-keys-sftp = mkOption { + type = package; + description = '' + Derive this file with `nix-build '<nixpkgs/nixos>' -A config.nixsap.deployment.send-keys-sftp ... --no-out-link` + and feed it to the ``sftp(1)`` to upload the secrets (the keys) to the machine. + ''; + readOnly = true; + default = + let + key-store-dir = pkgs.runCommand "key-store-dir" {} "mkdir -p $out/'${cfg.keyStore}'"; + key-cmds = lib.concatStringsSep "\n" (flip mapAttrsToList cfg.keys (name: key: '' + put '${key.source}' '${name}.tmp' + chmod ${toString key.mode} '${name}.tmp' + chown ${toString key.uid} '${name}.tmp' + chgrp ${toString key.gid} '${name}.tmp' + rename '${name}.tmp' '${name}' + '')); + in pkgs.writeText "send-keys-sftp" ( + if cfg.keys != {} then '' + cd / + lcd '${key-store-dir}' + put -pR * + + chown ${toString config.users.users.root.uid} '${cfg.keyStore}' + chgrp ${toString config.users.groups.keys.gid} '${cfg.keyStore}' + chmod 0750 '${cfg.keyStore}' + + cd '${cfg.keyStore}' + + ${key-cmds} + bye + '' else '' + bye + '' + ); + }; }; config = { - users.users = genAttrs (attrNames cfg.keyrings) ( - name: optionalAttrs (name != "root") { extraGroups = [ "keys" ]; } - ); - - deployment.keys = foldl (a: b: a//b) {} ( - mapAttrsToList (name: keys: + nixsap.deployment.keys = foldl (a: b: a//b) {} ( + mapAttrsToList (user: keys: let realkeys = unique (filter (n: n != null && hasPrefix cfg.keyStore n) keys); in genAttrs (map baseNameOf realkeys) - (key: { text = read key; - user = toString allusers.${name}.uid; + (key: { source = getKeySource key; + uid = config.users.users.${user}.uid; }) ) cfg.keyrings ); + + users.users = genAttrs (attrNames cfg.keyrings) ( + user: optionalAttrs (user != "root") { extraGroups = [ "keys" ]; } + ); + + systemd.services = + flip mapAttrs' cfg.keys (name: key: + nameValuePair "${name}-key" { + enable = true; + serviceConfig = { + TimeoutStartSec = "infinity"; + Restart = "always"; + RestartSec = "100ms"; + ExecStartPre = "${pkgs.wait4file} '${cfg.keyStore}/${name}'"; + ExecStart = "${pkgs.hangonfile} '${cfg.keyStore}/${name}'"; + }; + } + ); + }; } diff --git a/modules/system/nixos.nix b/modules/system/nixos.nix new file mode 100644 index 0000000..a02acd6 --- /dev/null +++ b/modules/system/nixos.nix @@ -0,0 +1,4 @@ +{ config, lib, ...}: +{ + system.stateVersion = lib.mkDefault config.system.nixos.release; +} diff --git a/modules/system/users.nix b/modules/system/users.nix index 022a7e7..b348942 100644 --- a/modules/system/users.nix +++ b/modules/system/users.nix @@ -36,6 +36,7 @@ let mkDaemonUser = name: { isNormalUser = false; + isSystemUser = true; uid = uid name; group = name; }; @@ -43,6 +44,7 @@ let mkNormalUser = name: { isNormalUser = true; + isSystemUser = false; uid = uid name; }; |
