Compare commits
10 Commits
ke-facts-c
...
update-tem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba3d57aa63 | ||
|
|
6ccee60e39 | ||
|
|
463db1537a | ||
|
|
fc4f4987ff | ||
|
|
e39333abed | ||
|
|
e407009183 | ||
|
|
9ff0215781 | ||
|
|
84d6400c25 | ||
|
|
8c583180ac | ||
|
|
1bc6d8c046 |
@@ -9,7 +9,9 @@
|
|||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
options.allowedKeys = lib.mkOption {
|
|
||||||
|
options = {
|
||||||
|
allowedKeys = lib.mkOption {
|
||||||
default = { };
|
default = { };
|
||||||
type = lib.types.attrsOf lib.types.str;
|
type = lib.types.attrsOf lib.types.str;
|
||||||
description = "The allowed public keys for ssh access to the admin user";
|
description = "The allowed public keys for ssh access to the admin user";
|
||||||
@@ -18,6 +20,26 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
rsaHostKey.enable = lib.mkEnableOption "Generate RSA host key";
|
||||||
|
|
||||||
|
# TODO: allow per-server domains that we than collect in the inventory
|
||||||
|
#certicficateDomains = lib.mkOption {
|
||||||
|
# type = lib.types.listOf lib.types.str;
|
||||||
|
# default = [ ];
|
||||||
|
# example = [ "git.mydomain.com" ];
|
||||||
|
# description = "List of domains to include in the certificate. This option will not prepend the machine name in front of each domain.";
|
||||||
|
#};
|
||||||
|
|
||||||
|
certificateSearchDomains = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "mydomain.com" ];
|
||||||
|
description = ''
|
||||||
|
List of domains to include in the certificate.
|
||||||
|
This option will prepend the machine name in front of each domain before adding it to the certificate.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
perInstance =
|
perInstance =
|
||||||
@@ -27,10 +49,15 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../../clanModules/sshd
|
# We don't have a good way to specify dependencies between
|
||||||
../../clanModules/root-password
|
# clanServices for now. When it get's implemtende, we should just
|
||||||
|
# use the ssh and users modules here.
|
||||||
|
./ssh.nix
|
||||||
|
./root-password.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
_module.args = { inherit settings; };
|
||||||
|
|
||||||
users.users.root.openssh.authorizedKeys.keys = builtins.attrValues settings.allowedKeys;
|
users.users.root.openssh.authorizedKeys.keys = builtins.attrValues settings.allowedKeys;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
39
clanServices/admin/root-password.nix
Normal file
39
clanServices/admin/root-password.nix
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# We don't have a way of specifying dependencies between clanServices for now.
|
||||||
|
# When it get's added this file should be removed and the users module used instead.
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
|
||||||
|
users.mutableUsers = false;
|
||||||
|
users.users.root.hashedPasswordFile =
|
||||||
|
config.clan.core.vars.generators.root-password.files.password-hash.path;
|
||||||
|
|
||||||
|
clan.core.vars.generators.root-password = {
|
||||||
|
files.password-hash.neededFor = "users";
|
||||||
|
|
||||||
|
files.password.deploy = false;
|
||||||
|
|
||||||
|
runtimeInputs = [
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.mkpasswd
|
||||||
|
pkgs.xkcdpass
|
||||||
|
];
|
||||||
|
|
||||||
|
prompts.password.type = "hidden";
|
||||||
|
prompts.password.persist = true;
|
||||||
|
prompts.password.description = "You can autogenerate a password, if you leave this prompt blank.";
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
prompt_value="$(cat "$prompts"/password)"
|
||||||
|
if [[ -n "''${prompt_value-}" ]]; then
|
||||||
|
echo "$prompt_value" | tr -d "\n" > "$out"/password
|
||||||
|
else
|
||||||
|
xkcdpass --numwords 5 --delimiter - --count 1 | tr -d "\n" > "$out"/password
|
||||||
|
fi
|
||||||
|
mkpasswd -s -m sha-512 < "$out"/password | tr -d "\n" > "$out"/password-hash
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
115
clanServices/admin/ssh.nix
Normal file
115
clanServices/admin/ssh.nix
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
settings,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
stringSet = list: builtins.attrNames (builtins.groupBy lib.id list);
|
||||||
|
|
||||||
|
domains = stringSet settings.certificateSearchDomains;
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
|
||||||
|
services.openssh = {
|
||||||
|
enable = true;
|
||||||
|
settings.PasswordAuthentication = false;
|
||||||
|
|
||||||
|
settings.HostCertificate = lib.mkIf (
|
||||||
|
settings.certificateSearchDomains != [ ]
|
||||||
|
) config.clan.core.vars.generators.openssh-cert.files."ssh.id_ed25519-cert.pub".path;
|
||||||
|
|
||||||
|
hostKeys =
|
||||||
|
[
|
||||||
|
{
|
||||||
|
path = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519".path;
|
||||||
|
type = "ed25519";
|
||||||
|
}
|
||||||
|
]
|
||||||
|
++ lib.optional settings.rsaHostKey.enable {
|
||||||
|
path = config.clan.core.vars.generators.openssh-rsa.files."ssh.id_rsa".path;
|
||||||
|
type = "rsa";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
clan.core.vars.generators.openssh = {
|
||||||
|
files."ssh.id_ed25519" = { };
|
||||||
|
files."ssh.id_ed25519.pub".secret = false;
|
||||||
|
migrateFact = "openssh";
|
||||||
|
runtimeInputs = [
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.openssh
|
||||||
|
];
|
||||||
|
script = ''
|
||||||
|
ssh-keygen -t ed25519 -N "" -C "" -f "$out"/ssh.id_ed25519
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
programs.ssh.knownHosts.clan-sshd-self-ed25519 = {
|
||||||
|
hostNames = [
|
||||||
|
"localhost"
|
||||||
|
config.networking.hostName
|
||||||
|
] ++ (lib.optional (config.networking.domain != null) config.networking.fqdn);
|
||||||
|
publicKey = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519.pub".value;
|
||||||
|
};
|
||||||
|
|
||||||
|
clan.core.vars.generators.openssh-rsa = lib.mkIf settings.rsaHostKey.enable {
|
||||||
|
files."ssh.id_rsa" = { };
|
||||||
|
files."ssh.id_rsa.pub".secret = false;
|
||||||
|
runtimeInputs = [
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.openssh
|
||||||
|
];
|
||||||
|
script = ''
|
||||||
|
ssh-keygen -t rsa -b 4096 -N "" -C "" -f "$out"/ssh.id_rsa
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
clan.core.vars.generators.openssh-cert = lib.mkIf (settings.certificateSearchDomains != [ ]) {
|
||||||
|
files."ssh.id_ed25519-cert.pub".secret = false;
|
||||||
|
dependencies = [
|
||||||
|
"openssh"
|
||||||
|
"openssh-ca"
|
||||||
|
];
|
||||||
|
validation = {
|
||||||
|
name = config.clan.core.settings.machine.name;
|
||||||
|
domains = lib.genAttrs settings.certificateSearchDomains lib.id;
|
||||||
|
};
|
||||||
|
runtimeInputs = [
|
||||||
|
pkgs.openssh
|
||||||
|
pkgs.jq
|
||||||
|
];
|
||||||
|
script = ''
|
||||||
|
ssh-keygen \
|
||||||
|
-s $in/openssh-ca/id_ed25519 \
|
||||||
|
-I ${config.clan.core.settings.machine.name} \
|
||||||
|
-h \
|
||||||
|
-n ${lib.concatMapStringsSep "," (d: "${config.clan.core.settings.machine.name}.${d}") domains} \
|
||||||
|
$in/openssh/ssh.id_ed25519.pub
|
||||||
|
mv $in/openssh/ssh.id_ed25519-cert.pub "$out"/ssh.id_ed25519-cert.pub
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
clan.core.vars.generators.openssh-ca = lib.mkIf (settings.certificateSearchDomains != [ ]) {
|
||||||
|
share = true;
|
||||||
|
files.id_ed25519.deploy = false;
|
||||||
|
files."id_ed25519.pub" = {
|
||||||
|
deploy = false;
|
||||||
|
secret = false;
|
||||||
|
};
|
||||||
|
runtimeInputs = [
|
||||||
|
pkgs.openssh
|
||||||
|
];
|
||||||
|
script = ''
|
||||||
|
ssh-keygen -t ed25519 -N "" -C "" -f "$out"/id_ed25519
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
programs.ssh.knownHosts.ssh-ca = lib.mkIf (settings.certificateSearchDomains != [ ]) {
|
||||||
|
certAuthority = true;
|
||||||
|
extraHostNames = builtins.map (domain: "*.${domain}") settings.certificateSearchDomains;
|
||||||
|
publicKey = config.clan.core.vars.generators.openssh-ca.files."id_ed25519.pub".value;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -55,9 +55,37 @@ If you're using VSCode, it has a handy feature that makes paths to source code f
|
|||||||
|
|
||||||
## Finding Print Messages
|
## Finding Print Messages
|
||||||
|
|
||||||
To identify where a specific print message comes from, you can enable a helpful feature. Simply set the environment variable `export TRACE_PRINT=1`. When you run commands with `--debug` mode, each print message will include information about its source location.
|
To trace the origin of print messages in `clan-cli`, you can enable special debugging features using environment variables:
|
||||||
|
|
||||||
|
- Set `TRACE_PRINT=1` to include the source location with each print message:
|
||||||
|
```bash
|
||||||
|
export TRACE_PRINT=1
|
||||||
|
```
|
||||||
|
When running commands with `--debug`, every print will show where it was triggered in the code.
|
||||||
|
|
||||||
|
- To see a deeper stack trace for each print, set `TRACE_DEPTH` to the desired number of stack frames (e.g., 3):
|
||||||
|
```bash
|
||||||
|
export TRACE_DEPTH=3
|
||||||
|
```
|
||||||
|
|
||||||
|
### Additional Debug Logging
|
||||||
|
|
||||||
|
You can enable more detailed logging for specific components by setting these environment variables:
|
||||||
|
|
||||||
|
- `CLAN_DEBUG_NIX_SELECTORS=1` — verbose logs for flake.select operations
|
||||||
|
- `CLAN_DEBUG_NIX_PREFETCH=1` — verbose logs for flake.prefetch operations
|
||||||
|
- `CLAN_DEBUG_COMMANDS=1` — print the diffed environment of executed commands
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```bash
|
||||||
|
export CLAN_DEBUG_NIX_SELECTORS=1
|
||||||
|
export CLAN_DEBUG_NIX_PREFETCH=1
|
||||||
|
export CLAN_DEBUG_COMMANDS=1
|
||||||
|
```
|
||||||
|
|
||||||
|
These options help you pinpoint the source and context of print messages and debug logs during development.
|
||||||
|
|
||||||
|
|
||||||
If you need more details, you can expand the stack trace information that appears with each print by setting the environment variable `export TRACE_DEPTH=3`.
|
|
||||||
|
|
||||||
## Analyzing Performance
|
## Analyzing Performance
|
||||||
|
|
||||||
|
|||||||
@@ -181,6 +181,13 @@ You can have a look and customize it if needed.
|
|||||||
!!! tip
|
!!! tip
|
||||||
For advanced partitioning, see [Disko templates](https://github.com/nix-community/disko-templates) or [Disko examples](https://github.com/nix-community/disko/tree/master/example).
|
For advanced partitioning, see [Disko templates](https://github.com/nix-community/disko-templates) or [Disko examples](https://github.com/nix-community/disko/tree/master/example).
|
||||||
|
|
||||||
|
!!! Danger
|
||||||
|
Don't change the `disko.nix` after the machine is installed for the first time.
|
||||||
|
|
||||||
|
Changing disko configuration requires wiping and reinstalling the machine.
|
||||||
|
|
||||||
|
Unless you really know what you are doing.
|
||||||
|
|
||||||
## Deploy the machine
|
## Deploy the machine
|
||||||
|
|
||||||
**Finally deployment time!** Use one of the following commands to build and deploy the image via SSH onto your machine.
|
**Finally deployment time!** Use one of the following commands to build and deploy the image via SSH onto your machine.
|
||||||
@@ -267,4 +274,3 @@ clan {
|
|||||||
```
|
```
|
||||||
|
|
||||||
This is useful for machines that are not always online or are not part of the regular update cycle.
|
This is useful for machines that are not always online or are not part of the regular update cycle.
|
||||||
|
|
||||||
|
|||||||
8
flake.lock
generated
8
flake.lock
generated
@@ -16,11 +16,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751846468,
|
"lastModified": 1752451292,
|
||||||
"narHash": "sha256-h0mpWZIOIAKj4fmLNyI2HDG+c0YOkbYmyJXSj/bQ9s0=",
|
"narHash": "sha256-jvLbfYFvcS5f0AEpUlFS2xZRnK770r9TRM2smpUFFaU=",
|
||||||
"rev": "a2166c13b0cb3febdaf36391cd2019aa2ccf4366",
|
"rev": "309e06fbc9a6d133ab6dd1c7d8e4876526e058bb",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/a2166c13b0cb3febdaf36391cd2019aa2ccf4366.tar.gz"
|
"url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/309e06fbc9a6d133ab6dd1c7d8e4876526e058bb.tar.gz"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
|
|||||||
@@ -297,8 +297,6 @@ For more detailed information, visit: {help_hyperlink("secrets", "https://docs.c
|
|||||||
description="Manage facts",
|
description="Manage facts",
|
||||||
epilog=(
|
epilog=(
|
||||||
f"""
|
f"""
|
||||||
Note: Facts are being deprecated, please use Vars instead.
|
|
||||||
For a migration guide visit: {help_hyperlink("vars", "https://docs.clan.lol/guides/migrations/migration-facts-vars")}
|
|
||||||
|
|
||||||
This subcommand provides an interface to facts of clan machines.
|
This subcommand provides an interface to facts of clan machines.
|
||||||
Facts are artifacts that a service can generate.
|
Facts are artifacts that a service can generate.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import textwrap
|
||||||
from dataclasses import asdict, dataclass, field
|
from dataclasses import asdict, dataclass, field
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
@@ -588,7 +589,7 @@ class FlakeCache:
|
|||||||
|
|
||||||
def load_from_file(self, path: Path) -> None:
|
def load_from_file(self, path: Path) -> None:
|
||||||
with path.open("r") as f:
|
with path.open("r") as f:
|
||||||
log.debug(f"Loading cache from {path}")
|
log.debug("Loading flake cache from file")
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
self.cache = FlakeCacheEntry.from_json(data["cache"])
|
self.cache = FlakeCacheEntry.from_json(data["cache"])
|
||||||
|
|
||||||
@@ -662,7 +663,7 @@ class Flake:
|
|||||||
"""
|
"""
|
||||||
Loads the flake into the store and populates self.store_path and self.hash such that the flake can evaluate locally and offline
|
Loads the flake into the store and populates self.store_path and self.hash such that the flake can evaluate locally and offline
|
||||||
"""
|
"""
|
||||||
from clan_lib.cmd import run
|
from clan_lib.cmd import RunOpts, run
|
||||||
from clan_lib.nix import (
|
from clan_lib.nix import (
|
||||||
nix_command,
|
nix_command,
|
||||||
)
|
)
|
||||||
@@ -681,7 +682,10 @@ class Flake:
|
|||||||
self.identifier,
|
self.identifier,
|
||||||
]
|
]
|
||||||
|
|
||||||
flake_prefetch = run(nix_command(cmd))
|
trace_prefetch = os.environ.get("CLAN_DEBUG_NIX_PREFETCH", "0") == "1"
|
||||||
|
if not trace_prefetch:
|
||||||
|
log.debug(f"Prefetching flake {self.identifier}")
|
||||||
|
flake_prefetch = run(nix_command(cmd), RunOpts(trace=trace_prefetch))
|
||||||
flake_metadata = json.loads(flake_prefetch.stdout)
|
flake_metadata = json.loads(flake_prefetch.stdout)
|
||||||
self.store_path = flake_metadata["storePath"]
|
self.store_path = flake_metadata["storePath"]
|
||||||
self.hash = flake_metadata["hash"]
|
self.hash = flake_metadata["hash"]
|
||||||
@@ -698,8 +702,6 @@ class Flake:
|
|||||||
nix_metadata,
|
nix_metadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
log.debug(f"Invalidating cache for {self.identifier}")
|
|
||||||
|
|
||||||
self.prefetch()
|
self.prefetch()
|
||||||
|
|
||||||
self._cache = FlakeCache()
|
self._cache = FlakeCache()
|
||||||
@@ -813,12 +815,15 @@ class Flake:
|
|||||||
];
|
];
|
||||||
}}
|
}}
|
||||||
"""
|
"""
|
||||||
if len(selectors) > 1:
|
if len(selectors) > 1 :
|
||||||
log.debug(f"""
|
msg = textwrap.dedent(f"""
|
||||||
selecting: {selectors}
|
clan select "{selectors}"
|
||||||
to debug run:
|
""").lstrip("\n").rstrip("\n")
|
||||||
nix repl --expr 'rec {{
|
if os.environ.get("CLAN_DEBUG_NIX_SELECTORS"):
|
||||||
flake = builtins.getFlake "self.identifier";
|
msg += textwrap.dedent(f"""
|
||||||
|
to debug run:
|
||||||
|
nix repl --expr 'rec {{
|
||||||
|
flake = builtins.getFlake "{self.identifier}";
|
||||||
selectLib = (builtins.getFlake "path:{select_source()}?narHash={select_hash}").lib;
|
selectLib = (builtins.getFlake "path:{select_source()}?narHash={select_hash}").lib;
|
||||||
query = [
|
query = [
|
||||||
{" ".join(
|
{" ".join(
|
||||||
@@ -828,21 +833,24 @@ nix repl --expr 'rec {{
|
|||||||
]
|
]
|
||||||
)}
|
)}
|
||||||
];
|
];
|
||||||
}}'
|
}}'
|
||||||
""")
|
""").lstrip("\n")
|
||||||
|
log.debug(msg)
|
||||||
# fmt: on
|
# fmt: on
|
||||||
elif len(selectors) == 1:
|
elif len(selectors) == 1:
|
||||||
log.debug(
|
msg = textwrap.dedent(f"""
|
||||||
f"""
|
$ clan select "{selectors[0]}"
|
||||||
selecting: {selectors[0]}
|
""").lstrip("\n").rstrip("\n")
|
||||||
to debug run:
|
if os.environ.get("CLAN_DEBUG_NIX_SELECTORS"):
|
||||||
nix repl --expr 'rec {{
|
msg += textwrap.dedent(f"""
|
||||||
|
to debug run:
|
||||||
|
nix repl --expr 'rec {{
|
||||||
flake = builtins.getFlake "{self.identifier}";
|
flake = builtins.getFlake "{self.identifier}";
|
||||||
selectLib = (builtins.getFlake "path:{select_source()}?narHash={select_hash}").lib;
|
selectLib = (builtins.getFlake "path:{select_source()}?narHash={select_hash}").lib;
|
||||||
query = selectLib.select '"''{selectors[0]}''"' flake;
|
query = selectLib.select '"''{selectors[0]}''"' flake;
|
||||||
}}'
|
}}'
|
||||||
"""
|
""").lstrip("\n")
|
||||||
)
|
log.debug(msg)
|
||||||
|
|
||||||
build_output = Path(
|
build_output = Path(
|
||||||
run(
|
run(
|
||||||
|
|||||||
Reference in New Issue
Block a user