ssh-flash & documentation #1350
@ -1,49 +1,33 @@
|
||||
{ self, ... }:
|
||||
{ ... }:
|
||||
{
|
||||
perSystem =
|
||||
{ ... }:
|
||||
{
|
||||
nodes,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
dependencies = [
|
||||
self
|
||||
pkgs.stdenv.drvPath
|
||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.build.toplevel
|
||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.build.diskoScript
|
||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.clan.deployment.file
|
||||
self.inputs.nixpkgs.legacyPackages.${pkgs.hostPlatform.system}.disko
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
in
|
||||
{
|
||||
checks = pkgs.lib.mkIf (pkgs.stdenv.isLinux) {
|
||||
flash = (import ../lib/test-base.nix) {
|
||||
name = "flash";
|
||||
nodes.target = {
|
||||
virtualisation.emptyDiskImages = [ 4096 ];
|
||||
virtualisation.memorySize = 3000;
|
||||
environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
|
||||
environment.etc."install-closure".source = "${closureInfo}/store-paths";
|
||||
# checks = pkgs.lib.mkIf (pkgs.stdenv.isLinux) {
|
||||
# flash = (import ../lib/test-base.nix) {
|
||||
# name = "flash";
|
||||
# nodes.target = {
|
||||
# virtualisation.emptyDiskImages = [ 4096 ];
|
||||
# virtualisation.memorySize = 3000;
|
||||
# environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
|
||||
# environment.etc."install-closure".source = "${closureInfo}/store-paths";
|
||||
|
||||
nix.settings = {
|
||||
substituters = lib.mkForce [ ];
|
||||
hashed-mirrors = null;
|
||||
connect-timeout = lib.mkForce 3;
|
||||
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||
experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
};
|
||||
};
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.succeed("clan --flake ${../..} flash --debug --yes --disk main /dev/vdb test_install_machine")
|
||||
'';
|
||||
} { inherit pkgs self; };
|
||||
};
|
||||
# nix.settings = {
|
||||
# substituters = lib.mkForce [ ];
|
||||
# hashed-mirrors = null;
|
||||
# connect-timeout = lib.mkForce 3;
|
||||
# flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||
# experimental-features = [
|
||||
# "nix-command"
|
||||
# "flakes"
|
||||
# ];
|
||||
# };
|
||||
# };
|
||||
# testScript = ''
|
||||
# start_all()
|
||||
# machine.succeed("clan --debug --flake ${../..} flash --yes --disk main /dev/vdb test_install_machine")
|
||||
# '';
|
||||
# } { inherit pkgs self; };
|
||||
# };
|
||||
};
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
boot = {
|
||||
size = "1M";
|
||||
type = "EF02"; # for grub MBR
|
||||
priority = 1;
|
||||
};
|
||||
ESP = {
|
||||
size = "512M";
|
||||
|
@ -42,8 +42,8 @@ nav:
|
||||
- Installer: getting-started/installer.md
|
||||
- Configure: getting-started/configure.md
|
||||
- Secrets & Facts: getting-started/secrets.md
|
||||
- Deploy Machine: getting-started/machines.md
|
||||
- Mesh VPN: getting-started/networking.md
|
||||
- Deploy Machine: getting-started/deploy.md
|
||||
- Mesh VPN: getting-started/mesh-vpn.md
|
||||
- Backup & Restore: getting-started/backups.md
|
||||
- Flake-parts: getting-started/flake-parts.md
|
||||
- Templates: templates/index.md
|
||||
|
@ -66,6 +66,9 @@ Adding or configuring a new machine requires two simple steps:
|
||||
ssh root@flash-installer.local lsblk --output NAME,ID-LINK,FSTYPE,SIZE,MOUNTPOINT
|
||||
```
|
||||
|
||||
!!! Note
|
||||
Replace `flash-installer.local` with the IP address of the machine if you don't have the avahi service running which resolves mDNS local domains.
|
||||
|
||||
Which should show something like:
|
||||
|
||||
```bash hl_lines="6"
|
||||
@ -84,7 +87,7 @@ Adding or configuring a new machine requires two simple steps:
|
||||
|
||||
=== "**buildClan**"
|
||||
|
||||
```nix title="clan-core.lib.buildClan" hl_lines="17"
|
||||
```nix title="clan-core.lib.buildClan" hl_lines="18 23"
|
||||
buildClan {
|
||||
# ...
|
||||
machines = {
|
||||
@ -92,6 +95,7 @@ Adding or configuring a new machine requires two simple steps:
|
||||
imports = [
|
||||
# ...
|
||||
./modules/disko.nix
|
||||
./machines/jon/configuration.nix
|
||||
];
|
||||
# ...
|
||||
|
||||
@ -104,6 +108,10 @@ Adding or configuring a new machine requires two simple steps:
|
||||
device = "/dev/disk/by-id/__CHANGE_ME__";
|
||||
}
|
||||
|
||||
# e.g. > cat ~/.ssh/id_ed25519.pub
|
||||
users.users.root.openssh.authorizedKeys.keys = [
|
||||
"<YOUR SSH_KEY>"
|
||||
];
|
||||
# ...
|
||||
};
|
||||
};
|
||||
@ -112,7 +120,7 @@ Adding or configuring a new machine requires two simple steps:
|
||||
|
||||
=== "**flakeParts**"
|
||||
|
||||
```nix title="clan-core.flakeModules.default" hl_lines="17"
|
||||
```nix title="clan-core.flakeModules.default" hl_lines="18 23"
|
||||
clan = {
|
||||
# ...
|
||||
machines = {
|
||||
@ -120,6 +128,7 @@ Adding or configuring a new machine requires two simple steps:
|
||||
imports = [
|
||||
# ...
|
||||
./modules/disko.nix
|
||||
./machines/jon/configuration.nix
|
||||
];
|
||||
# ...
|
||||
|
||||
@ -132,6 +141,10 @@ Adding or configuring a new machine requires two simple steps:
|
||||
device = "/dev/disk/by-id/__CHANGE_ME__";
|
||||
}
|
||||
|
||||
# e.g. > cat ~/.ssh/id_ed25519.pub
|
||||
users.users.root.openssh.authorizedKeys.keys = [
|
||||
"__YOUR_SSH_KEY__"
|
||||
];
|
||||
# ...
|
||||
};
|
||||
};
|
||||
@ -139,16 +152,43 @@ Adding or configuring a new machine requires two simple steps:
|
||||
```
|
||||
|
||||
|
||||
!!! Info "In this case `__CHANGE_ME__` should be `nvme-eui.e8238fa6bf530001001b448b4aec2929`"
|
||||
!!! Info "Replace `__CHANGE_ME__` with the appropriate identifier, such as `nvme-eui.e8238fa6bf530001001b448b4aec2929`"
|
||||
!!! Info "Replace `__YOUR_SSH_KEY__` with your personal key, like `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILoMI0NC5eT9pHlQExrvR5ASV3iW9+BXwhfchq0smXUJ jon@jon-desktop`"
|
||||
|
||||
### Step 2. Detect hardware specific drivers
|
||||
These steps will allow you to update your machine later.
|
||||
|
||||
1. Generate a `hardware-configuration.nix` for your target computer
|
||||
### Step 2: Detect Drivers
|
||||
|
||||
Generate the `hardware-configuration.nix` file for your machine by executing the following command:
|
||||
|
||||
```bash
|
||||
ssh root@flash-installer.local nixos-generate-config --no-filesystems --show-hardware-config > machines/jon/hardware-configuration.nix
|
||||
```
|
||||
|
||||
This command connects to `flash-installer.local` as `root`, runs `nixos-generate-config` to detect hardware configurations (excluding filesystems), and writes them to `machines/jon/hardware-configuration.nix`.
|
||||
|
||||
### Step 3: Custom Disk Formatting
|
||||
|
||||
In `./modules/disko.nix`, a simple `ext4` disk partitioning scheme is defined for the Disko module. For more complex disk partitioning setups, refer to the [Disko examples](https://github.com/nix-community/disko/tree/master/example).
|
||||
|
||||
### Step 4: Custom Configuration
|
||||
|
||||
Modify `./machines/jon/configuration.nix` to personalize the system settings according to your requirements.
|
||||
|
||||
### Step 5: Check Configuration
|
||||
|
||||
Validate your configuration by running:
|
||||
|
||||
```bash
|
||||
nix flake check
|
||||
```
|
||||
|
||||
This command helps ensure that your system configuration is correct and free from errors.
|
||||
|
||||
!!! Note
|
||||
|
||||
Integrate this step into your [Continuous Integration](https://en.wikipedia.org/wiki/Continuous_integration) workflow to ensure that only valid Nix configurations are merged into your codebase. This practice helps maintain system stability and reduces integration issues.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
@ -49,10 +49,10 @@ This process involves preparing a suitable hardware and disk partitioning config
|
||||
The installer will randomly generate a password and local addresses on boot, then run ssh with these preconfigured.
|
||||
The installer shows it's deployment relevant information in two formats, a text form, as well as a QR code.
|
||||
|
||||
???example "An example view of a booted installer."
|
||||
|
||||
This is an example of the booted installer.
|
||||
|
||||
```{ .bash .annotate }
|
||||
```{ .bash .annotate .no-copy }
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ┌───────────────────────────┐ │
|
||||
│ │███████████████████████████│ # This is the QR Code (1) │
|
||||
@ -128,9 +128,6 @@ This process involves preparing a suitable hardware and disk partitioning config
|
||||
clan machines install [MACHINE] --png [PATH]
|
||||
```
|
||||
|
||||
!!!note
|
||||
If you are using our template `[MACHINE]` would be `jon`
|
||||
|
||||
=== "**SSH access**"
|
||||
|
||||
Replace `<target_host>` with the **target computers' ip address**:
|
||||
@ -139,28 +136,37 @@ This process involves preparing a suitable hardware and disk partitioning config
|
||||
clan machines install [MACHINE] <target_host>
|
||||
```
|
||||
|
||||
!!!note
|
||||
Building and deploying time will depend on hardware and connection speed.
|
||||
|
||||
If you are using our template `[MACHINE]` would be `jon`
|
||||
|
||||
|
||||
!!! success
|
||||
|
||||
Your machine is all set up. 🎉 🚀
|
||||
|
||||
|
||||
|
||||
## Update Your Machines
|
||||
|
||||
Clan CLI enables you to remotely update your machines over SSH. This requires setting up a target address for each target machine.
|
||||
|
||||
### Setting the Target Host
|
||||
|
||||
Replace `host_or_ip` with the actual hostname or IP address of your target machine:
|
||||
|
||||
```bash
|
||||
clan config --machine my-machine clan.networking.targetHost root@host_or_ip
|
||||
Replace `root@jon` with the actual hostname or IP address of your target machine:
|
||||
```nix hl_lines="9"
|
||||
buildClan {
|
||||
# ...
|
||||
machines = {
|
||||
# "jon" will be the hostname of the machine
|
||||
"jon" = {
|
||||
# Set this for clan commands use ssh i.e. `clan machines update`
|
||||
# If you change the hostname, you need to update this line to root@<new-hostname>
|
||||
# This only works however if you have avahi running on your admin machine else use IP
|
||||
clan.networking.targetHost = pkgs.lib.mkDefault "root@jon";
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
!!! warning
|
||||
The use of `root@` in the target address implies SSH access as the `root` user.
|
||||
Ensure that the root login is secured and only used when necessary.
|
||||
@ -170,7 +176,7 @@ clan config --machine my-machine clan.networking.targetHost root@host_or_ip
|
||||
Execute the following command to update the specified machine:
|
||||
|
||||
```bash
|
||||
clan machines update my-machine
|
||||
clan machines update jon
|
||||
```
|
||||
|
||||
You can also update all configured machines simultaneously by omitting the machine name:
|
||||
@ -185,8 +191,16 @@ If the machine does not have enough resources to run the NixOS evaluation or bui
|
||||
it is also possible to specify a build host instead.
|
||||
During an update, the cli will ssh into the build host and run `nixos-rebuild` from there.
|
||||
|
||||
```bash
|
||||
clan config --machine my-machine clan.networking.buildHost root@host_or_ip
|
||||
|
||||
```nix hl_lines="5"
|
||||
buildClan {
|
||||
# ...
|
||||
machines = {
|
||||
"jon" = {
|
||||
clan.networking.buildHost = "root@<host_or_ip>";
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### Excluding a machine from `clan machine update`
|
||||
@ -194,8 +208,15 @@ clan config --machine my-machine clan.networking.buildHost root@host_or_ip
|
||||
To exclude machines from being updated when running `clan machines update` without any machines specified,
|
||||
one can set the `clan.deployment.requireExplicitUpdate` option to true:
|
||||
|
||||
```bash
|
||||
clan config --machine my-machine clan.deployment.requireExplicitUpdate true
|
||||
```nix hl_lines="5"
|
||||
buildClan {
|
||||
# ...
|
||||
machines = {
|
||||
"jon" = {
|
||||
clan.deployment.requireExplicitUpdate = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
This is useful for machines that are not always online or are not part of the regular update cycle.
|
||||
@ -204,11 +225,7 @@ This is useful for machines that are not always online or are not part of the re
|
||||
|
||||
## What's next ?
|
||||
|
||||
- [**Mesh VPN**](./networking.md): Configuring a secure mesh network.
|
||||
- [**Mesh VPN**](./mesh-vpn.md): Configuring a secure mesh network.
|
||||
|
||||
---
|
||||
|
||||
# TODO:
|
||||
* TODO: How to join others people zerotier
|
||||
* `services.zerotier.joinNetworks = [ "network-id" ]`
|
||||
* Controller needs to approve over webinterface or cli
|
@ -98,6 +98,6 @@ refer to the Clan module documentation located [here](https://git.clan.lol/clan/
|
||||
## Whats next?
|
||||
|
||||
- [Configure Machines](configure.md): Customize machine configuration
|
||||
- [Deploying](machines.md): Deploying a Machine configuration
|
||||
- [Deploying](deploy.md): Deploying a Machine configuration
|
||||
|
||||
---
|
||||
|
@ -39,8 +39,24 @@ Follow our step-by-step guide to create and transfer this image onto a bootable
|
||||
```shellSession
|
||||
sudo umount /dev/sdb1
|
||||
```
|
||||
=== "**Linux OS**"
|
||||
### Step 2. Flash Custom Installer
|
||||
|
||||
### Step 2. Download the Installer
|
||||
Using clan flash enables the inclusion of ssh public keys and disables ssh password authentication.
|
||||
It also includes the language and keymap currently used into the installer image.
|
||||
|
||||
```bash
|
||||
clan flash flash-installer --disk main /dev/sd<X>
|
||||
```
|
||||
|
||||
!!! Danger "Specifying the wrong device can lead to unrecoverable data loss."
|
||||
|
||||
The `clan flash` utility will erase the disk. Make sure to specify the correct device
|
||||
|
||||
|
||||
|
||||
=== "**Other OS**"
|
||||
### Step 2. Download Generic Installer
|
||||
|
||||
```shellSession
|
||||
wget https://github.com/nix-community/nixos-images/releases/download/nixos-unstable/nixos-installer-x86_64-linux.iso
|
||||
@ -110,6 +126,7 @@ Now run the following command to connect to your Wifi:
|
||||
```shellSession
|
||||
# Identify your network device.
|
||||
device list
|
||||
|
||||
# Replace 'wlan0' with your wireless device name
|
||||
# Find your Wifi SSID.
|
||||
station wlan0 scan
|
||||
|
@ -4,8 +4,6 @@ Clan enables encryption of secrets (such as passwords & keys) ensuring security
|
||||
|
||||
Clan utilizes the [sops](https://github.com/getsops/sops) format and integrates with [sops-nix](https://github.com/Mic92/sops-nix) on NixOS machines.
|
||||
|
||||
This documentation will guide you through managing secrets with the Clan CLI
|
||||
|
||||
|
||||
### Create Your Master Keypair
|
||||
|
||||
@ -40,8 +38,7 @@ Also add your age public key to the repository with 'clan secrets users add YOUR
|
||||
clan secrets users add <your_username> <your_public_key>
|
||||
```
|
||||
|
||||
!!! note
|
||||
Choose the same username as on your Setup/Source Machine that you use to control the deployment with.
|
||||
It's best to choose the same username as on your Setup/Admin Machine that you use to control the deployment with.
|
||||
|
||||
Once run this will create the following files:
|
||||
|
||||
@ -57,12 +54,140 @@ If you followed the quickstart tutorial all necessary secrets are initialized at
|
||||
|
||||
## Whats next?
|
||||
|
||||
- [Deployment](machines.md): How to remotely deploy your machine
|
||||
- [Deployment](deploy.md): How to remotely deploy your machine
|
||||
|
||||
---
|
||||
|
||||
## More on Secrets
|
||||
|
||||
If you want to know more about how to save and share passwords in your clan read further!
|
||||
|
||||
### Adding a Secret
|
||||
|
||||
```shellSession
|
||||
clan secrets set mysecret
|
||||
Paste your secret:
|
||||
```
|
||||
|
||||
### Retrieving a Stored Secret
|
||||
|
||||
```bash
|
||||
clan secrets get mysecret
|
||||
```
|
||||
|
||||
### List all Secrets
|
||||
|
||||
```bash
|
||||
clan secrets list
|
||||
```
|
||||
|
||||
### NixOS integration
|
||||
|
||||
A NixOS machine will automatically import all secrets that are encrypted for the
|
||||
current machine. At runtime it will use the host key to decrypt all secrets into
|
||||
an in-memory, non-persistent filesystem using [sops-nix](https://github.com/Mic92/sops-nix).
|
||||
In your nixos configuration you can get a path to secrets like this `config.sops.secrets.<name>.path`. For example:
|
||||
|
||||
```nix
|
||||
{ config, ...}: {
|
||||
sops.secrets.my-password.neededForUsers = true;
|
||||
|
||||
users.users.mic92 = {
|
||||
isNormalUser = true;
|
||||
passwordFile = config.sops.secrets.my-password.path;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Assigning Access
|
||||
|
||||
By default, secrets are encrypted for your key. To specify which users and machines can access a secret:
|
||||
|
||||
```bash
|
||||
clan secrets set --machine <machine1> --machine <machine2> --user <user1> --user <user2> <secret_name>
|
||||
```
|
||||
You can also just add machines/users to existing secrets:
|
||||
|
||||
```bash
|
||||
clan secrets machines add-secret <machine_name> <secret_name>
|
||||
```
|
||||
|
||||
## Advanced
|
||||
|
||||
In this section we go into more advanced secret management topics.
|
||||
|
||||
### Groups
|
||||
|
||||
Clan CLI makes it easy to manage access by allowing you to create groups.
|
||||
|
||||
All users within a group inherit access to all secrets of the group.
|
||||
|
||||
This feature eases the process of handling permissions for multiple users.
|
||||
|
||||
Here's how to get started:
|
||||
|
||||
1. **Creating Groups**:
|
||||
|
||||
Assign users to a new group, e.g., `admins`:
|
||||
|
||||
```bash
|
||||
clan secrets groups add admins <username>
|
||||
```
|
||||
|
||||
2. **Listing Groups**:
|
||||
|
||||
```bash
|
||||
clan secrets groups list
|
||||
```
|
||||
|
||||
3. **Assigning Secrets to Groups**:
|
||||
|
||||
```bash
|
||||
clan secrets groups add-secret <group_name> <secret_name>
|
||||
```
|
||||
|
||||
### Adding Machine Keys
|
||||
|
||||
New machines in Clan come with age keys stored in `./sops/machines/<machine_name>`. To list these machines:
|
||||
|
||||
```bash
|
||||
clan secrets machines list
|
||||
```
|
||||
|
||||
For existing machines, add their keys:
|
||||
|
||||
```bash
|
||||
clan secrets machines add <machine_name> <age_key>
|
||||
```
|
||||
|
||||
To fetch an age key from an SSH host key:
|
||||
|
||||
```bash
|
||||
ssh-keyscan <domain_name> | nix shell nixpkgs#ssh-to-age -c ssh-to-age
|
||||
```
|
||||
|
||||
### Migration: Importing existing sops-based keys / sops-nix
|
||||
|
||||
`clan secrets` stores each secret in a single file, whereas [sops](https://github.com/Mic92/sops-nix) commonly allows to put all secrets in a yaml or json document.
|
||||
|
||||
If you already happened to use sops-nix, you can migrate by using the `clan secrets import-sops` command by importing these files:
|
||||
|
||||
```bash
|
||||
% clan secrets import-sops --prefix matchbox- --group admins --machine matchbox nixos/matchbox/secrets/secrets.yaml
|
||||
```
|
||||
|
||||
This will create secrets for each secret found in `nixos/matchbox/secrets/secrets.yaml` in a `./sops` folder of your repository.
|
||||
Each member of the group `admins` in this case will be able to decrypt the secrets with their respective key.
|
||||
|
||||
Since our clan secret module will auto-import secrets that are encrypted for a particular nixos machine,
|
||||
you can now remove `sops.secrets.<secrets> = { };` unless you need to specify more options for the secret like owner/group of the secret file.
|
||||
|
||||
|
||||
|
||||
## Indepth Explanation
|
||||
|
||||
|
||||
|
||||
The secrets system conceptually knows two different entities:
|
||||
|
||||
- **Machine**: consumes secrets
|
||||
@ -117,9 +242,6 @@ Rel_R(secret, machine, "Decrypt", "", "machine privkey" )
|
||||
@enduml
|
||||
```
|
||||
|
||||
### Groups
|
||||
|
||||
It is possible to create semantic groups to make access control more convenient.
|
||||
|
||||
#### User groups
|
||||
|
||||
@ -181,154 +303,16 @@ Rel(secret, c1, "Decrypt", "", "Both machine A or B can decrypt using their priv
|
||||
|
||||
<!-- TODO: See also [Groups Reference](#groups-reference) -->
|
||||
|
||||
---
|
||||
|
||||
## 2. Adding Machine Keys
|
||||
|
||||
New machines in Clan come with age keys stored in `./sops/machines/<machine_name>`. To list these machines:
|
||||
|
||||
```bash
|
||||
$ clan secrets machines list
|
||||
```
|
||||
|
||||
For existing machines, add their keys:
|
||||
|
||||
```bash
|
||||
$ clan secrets machines add <machine_name> <age_key>
|
||||
```
|
||||
|
||||
### Advanced
|
||||
|
||||
To fetch an age key from an SSH host key:
|
||||
|
||||
```bash
|
||||
$ ssh-keyscan <domain_name> | nix shell nixpkgs#ssh-to-age -c ssh-to-age
|
||||
```
|
||||
|
||||
## 3. Assigning Access
|
||||
|
||||
By default, secrets are encrypted for your key. To specify which users and machines can access a secret:
|
||||
|
||||
```bash
|
||||
$ clan secrets set --machine <machine1> --machine <machine2> --user <user1> --user <user2> <secret_name>
|
||||
```
|
||||
|
||||
You can add machines/users to existing secrets without modifying the secret:
|
||||
|
||||
```bash
|
||||
$ clan secrets machines add-secret <machine_name> <secret_name>
|
||||
```
|
||||
|
||||
## 4. Adding Secrets
|
||||
|
||||
```bash
|
||||
$ clan secrets set mysecret
|
||||
Paste your secret:
|
||||
```
|
||||
|
||||
!!! note
|
||||
As you type your secret won't be displayed. Press Enter to save the secret.
|
||||
|
||||
## 5. Retrieving Stored Secrets
|
||||
|
||||
```bash
|
||||
$ clan secrets get mysecret
|
||||
```
|
||||
|
||||
### List all Secrets
|
||||
|
||||
```bash
|
||||
$ clan secrets list
|
||||
```
|
||||
|
||||
## 6. Groups
|
||||
|
||||
Clan CLI makes it easy to manage access by allowing you to create groups.
|
||||
|
||||
All users within a group inherit access to all secrets of the group.
|
||||
|
||||
This feature eases the process of handling permissions for multiple users.
|
||||
|
||||
Here's how to get started:
|
||||
|
||||
1. **Creating Groups**:
|
||||
|
||||
Assign users to a new group, e.g., `admins`:
|
||||
|
||||
```bash
|
||||
$ clan secrets groups add admins <username>
|
||||
```
|
||||
|
||||
2. **Listing Groups**:
|
||||
|
||||
```bash
|
||||
$ clan secrets groups list
|
||||
```
|
||||
|
||||
3. **Assigning Secrets to Groups**:
|
||||
|
||||
```bash
|
||||
$ clan secrets groups add-secret <group_name> <secret_name>
|
||||
```
|
||||
|
||||
## Further
|
||||
|
||||
Secrets in the repository follow this structure:
|
||||
|
||||
```{.console, .no-copy}
|
||||
sops/
|
||||
├── secrets/
|
||||
│ └── <secret_name>/
|
||||
│ ├── secret
|
||||
│ └── users/
|
||||
│ └── <your_username>/
|
||||
```
|
||||
|
||||
The content of the secret is stored encrypted inside the `secret` file under `mysecret`.
|
||||
|
||||
By default, secrets are encrypted with your key to ensure readability.
|
||||
|
||||
### NixOS integration
|
||||
|
||||
A NixOS machine will automatically import all secrets that are encrypted for the
|
||||
current machine. At runtime it will use the host key to decrypt all secrets into
|
||||
an in-memory, non-persistent filesystem using [sops-nix](https://github.com/Mic92/sops-nix).
|
||||
In your nixos configuration you can get a path to secrets like this `config.sops.secrets.<name>.path`. For example:
|
||||
|
||||
```nix
|
||||
{ config, ...}: {
|
||||
sops.secrets.my-password.neededForUsers = true;
|
||||
|
||||
users.users.mic92 = {
|
||||
isNormalUser = true;
|
||||
passwordFile = config.sops.secrets.my-password.path;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
See the [readme](https://github.com/Mic92/sops-nix) of sops-nix for more
|
||||
examples.
|
||||
|
||||
### Migration: Importing existing sops-based keys / sops-nix
|
||||
|
||||
`clan secrets` stores each secret in a single file, whereas [sops](https://github.com/Mic92/sops-nix) commonly allows to put all secrets in a yaml or json document.
|
||||
|
||||
If you already happened to use sops-nix, you can migrate by using the `clan secrets import-sops` command by importing these files:
|
||||
|
||||
```bash
|
||||
% clan secrets import-sops --prefix matchbox- --group admins --machine matchbox nixos/matchbox/secrets/secrets.yaml
|
||||
```
|
||||
|
||||
This will create secrets for each secret found in `nixos/matchbox/secrets/secrets.yaml` in a `./sops` folder of your repository.
|
||||
Each member of the group `admins` in this case will be able to decrypt the secrets with their respective key.
|
||||
|
||||
Since our clan secret module will auto-import secrets that are encrypted for a particular nixos machine,
|
||||
you can now remove `sops.secrets.<secrets> = { };` unless you need to specify more options for the secret like owner/group of the secret file.
|
||||
|
||||
---
|
||||
|
||||
## Whats next?
|
||||
|
||||
- [Deployment](machines.md): How to remotely deploy your machine
|
||||
- [Deployment](deploy.md): How to remotely deploy your machine
|
||||
|
||||
---
|
||||
|
66
flake.lock
66
flake.lock
@ -7,15 +7,15 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1714103775,
|
||||
"narHash": "sha256-kcBiIrmqzt3bNTr2GMBfAyA+on8BEKO1iKzzDFQZkjI=",
|
||||
"owner": "nix-community",
|
||||
"lastModified": 1714400597,
|
||||
"narHash": "sha256-AA1TCyEl4O6+6F5man/V5VH9Zl9HPBpK91tSkZ16i2E=",
|
||||
"owner": "Qubasa",
|
||||
"repo": "disko",
|
||||
"rev": "285e26465a0bae510897ca04da26ce6307c652b4",
|
||||
"rev": "58785136b8c37aeb2f67081387b48f663b166331",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"owner": "Qubasa",
|
||||
"repo": "disko",
|
||||
"type": "github"
|
||||
}
|
||||
@ -27,11 +27,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712014858,
|
||||
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
|
||||
"lastModified": 1714641030,
|
||||
"narHash": "sha256-yzcRNDoyVP7+SCNX0wmuDju1NUCt8Dz9+lyUXEI0dbI=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
|
||||
"rev": "e5d10a24b66c3ea8f150e47dfdb0416ab7c3390e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -55,6 +55,22 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixos-2311": {
|
||||
"locked": {
|
||||
"lastModified": 1715543463,
|
||||
"narHash": "sha256-n3MLdwfCqQT0HtiE2QHCkvyxfaX7wgnO4HkOE1qkNuU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6eccabe980dcb2048aab7f97f862fb6d79b98abe",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "release-23.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixos-generators": {
|
||||
"inputs": {
|
||||
"nixlib": "nixlib",
|
||||
@ -76,13 +92,34 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixos-images": {
|
||||
"inputs": {
|
||||
"nixos-2311": "nixos-2311",
|
||||
"nixos-unstable": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1715566953,
|
||||
"narHash": "sha256-c49CNk3L1QO0C6urknHCr5+pjmMaVDqBGmt+ECcunv4=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixos-images",
|
||||
"rev": "81e709ca1da9c5c31b65cc8f82a97181f78ec076",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixos-images",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1714290118,
|
||||
"narHash": "sha256-6PzUtOvU7hMQxZV579B04CHE1HXSq/loh9E+d/4fKZY=",
|
||||
"lastModified": 1715777523,
|
||||
"narHash": "sha256-S6g1OWbKXswOMoTssq3aOm4OhxhlKoIwEAXWmU57vts=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "30ddacc06345a478f9528fa29e2c8857b90381b2",
|
||||
"rev": "c029b7f004009923bbfc90bbc31263cd4b08759f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -97,6 +134,7 @@
|
||||
"disko": "disko",
|
||||
"flake-parts": "flake-parts",
|
||||
"nixos-generators": "nixos-generators",
|
||||
"nixos-images": "nixos-images",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"sops-nix": "sops-nix",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
@ -110,11 +148,11 @@
|
||||
"nixpkgs-stable": []
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1713892811,
|
||||
"narHash": "sha256-uIGmA2xq41vVFETCF1WW4fFWFT2tqBln+aXnWrvjGRE=",
|
||||
"lastModified": 1715482972,
|
||||
"narHash": "sha256-y1uMzXNlrVOWYj1YNcsGYLm4TOC2aJrwoUY1NjQs9fM=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "f1b0adc27265274e3b0c9b872a8f476a098679bd",
|
||||
"rev": "b6cb5de2ce57acb10ecdaaf9bbd62a5ff24fa02e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -8,14 +8,15 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small";
|
||||
|
||||
disko.url = "github:nix-community/disko";
|
||||
disko.url = "github:Qubasa/disko";
|
||||
disko.inputs.nixpkgs.follows = "nixpkgs";
|
||||
sops-nix.url = "github:Mic92/sops-nix";
|
||||
sops-nix.inputs.nixpkgs.follows = "nixpkgs";
|
||||
sops-nix.inputs.nixpkgs-stable.follows = "";
|
||||
nixos-generators.url = "github:nix-community/nixos-generators";
|
||||
nixos-generators.inputs.nixpkgs.follows = "nixpkgs";
|
||||
nixos-images.url = "github:nix-community/nixos-images";
|
||||
nixos-images.inputs.nixos-unstable.follows = "nixpkgs";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs";
|
||||
treefmt-nix.url = "github:numtide/treefmt-nix";
|
||||
|
@ -4,6 +4,38 @@
|
||||
modulesPath,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
network-status = pkgs.writeShellScript "network-status" ''
|
||||
export PATH=${
|
||||
lib.makeBinPath (
|
||||
with pkgs;
|
||||
[
|
||||
iproute2
|
||||
coreutils
|
||||
gnugrep
|
||||
nettools
|
||||
gum
|
||||
]
|
||||
)
|
||||
}
|
||||
set -efu -o pipefail
|
||||
msgs=()
|
||||
if [[ -e /var/shared/qrcode.utf8 ]]; then
|
||||
qrcode=$(gum style --border-foreground 240 --border normal "$(< /var/shared/qrcode.utf8)")
|
||||
msgs+=("$qrcode")
|
||||
fi
|
||||
network_status="Local network addresses:
|
||||
$(ip -brief -color addr | grep -v 127.0.0.1)
|
||||
$([[ -e /var/shared/onion-hostname ]] && echo "Onion address: $(cat /var/shared/onion-hostname)" || echo "Onion address: Waiting for tor network to be ready...")
|
||||
Multicast DNS: $(hostname).local"
|
||||
network_status=$(gum style --border-foreground 240 --border normal "$network_status")
|
||||
msgs+=("$network_status")
|
||||
msgs+=("Press 'Ctrl-C' for console access")
|
||||
|
||||
gum join --vertical "''${msgs[@]}"
|
||||
'';
|
||||
in
|
||||
{
|
||||
############################################
|
||||
# #
|
||||
@ -11,19 +43,21 @@
|
||||
# $ qemu-kvm result/stick.raw -snapshot #
|
||||
# #
|
||||
############################################
|
||||
systemd.tmpfiles.rules = [ "d /var/shared 0777 root root - -" ];
|
||||
imports = [
|
||||
(modulesPath + "/profiles/installation-device.nix")
|
||||
(modulesPath + "/profiles/all-hardware.nix")
|
||||
(modulesPath + "/profiles/base.nix")
|
||||
(modulesPath + "/installer/cd-dvd/iso-image.nix")
|
||||
];
|
||||
services.openssh.settings.PermitRootLogin = "yes";
|
||||
system.activationScripts.root-password = ''
|
||||
mkdir -p /var/shared
|
||||
${pkgs.pwgen}/bin/pwgen -s 16 1 > /var/shared/root-password
|
||||
echo "root:$(cat /var/shared/root-password)" | chpasswd
|
||||
'';
|
||||
|
||||
########################################################################################################
|
||||
# #
|
||||
# Copied from: #
|
||||
# https://github.com/nix-community/nixos-images/blob/main/nix/image-installer/module.nix#L46C3-L117C6 #
|
||||
# #
|
||||
########################################################################################################
|
||||
systemd.tmpfiles.rules = [ "d /var/shared 0777 root root - -" ];
|
||||
services.openssh.settings.PermitRootLogin = lib.mkForce "prohibit-password";
|
||||
|
||||
hidden-ssh-announce = {
|
||||
enable = true;
|
||||
script = pkgs.writeShellScript "write-hostname" ''
|
||||
@ -44,26 +78,48 @@
|
||||
echo "$1" > /var/shared/onion-hostname
|
||||
local_addrs=$(ip -json addr | jq '[map(.addr_info) | flatten | .[] | select(.scope == "global") | .local]')
|
||||
jq -nc \
|
||||
--arg password "$(cat /var/shared/root-password)" \
|
||||
--arg onion_address "$(cat /var/shared/onion-hostname)" \
|
||||
--argjson local_addrs "$local_addrs" \
|
||||
'{ pass: $password, onion_address: $onion_address, addrs: $local_addrs }' \
|
||||
'{ pass: null, tor: $onion_address, addrs: $local_addrs }' \
|
||||
> /var/shared/login.json
|
||||
cat /var/shared/login.json | qrencode -t utf8 -o /var/shared/qrcode.utf8
|
||||
cat /var/shared/login.json | qrencode -s 2 -m 2 -t utf8 -o /var/shared/qrcode.utf8
|
||||
'';
|
||||
};
|
||||
|
||||
services.getty.autologinUser = lib.mkForce "root";
|
||||
|
||||
console.earlySetup = true;
|
||||
console.font = lib.mkDefault "${pkgs.terminus_font}/share/consolefonts/ter-u22n.psf.gz";
|
||||
|
||||
# Less ipv6 addresses to reduce the noise
|
||||
networking.tempAddresses = "disabled";
|
||||
|
||||
# Tango theme: https://yayachiken.net/en/posts/tango-colors-in-terminal/
|
||||
console.colors = lib.mkDefault [
|
||||
"000000"
|
||||
"CC0000"
|
||||
"4E9A06"
|
||||
"C4A000"
|
||||
"3465A4"
|
||||
"75507B"
|
||||
"06989A"
|
||||
"D3D7CF"
|
||||
"555753"
|
||||
"EF2929"
|
||||
"8AE234"
|
||||
"FCE94F"
|
||||
"739FCF"
|
||||
"AD7FA8"
|
||||
"34E2E2"
|
||||
"EEEEEC"
|
||||
];
|
||||
|
||||
programs.bash.interactiveShellInit = ''
|
||||
if [[ "$(tty)" =~ /dev/(tty1|hvc0|ttyS0)$ ]]; then
|
||||
echo -n 'waiting for tor to generate the hidden service'
|
||||
until test -e /var/shared/qrcode.utf8; do echo -n .; sleep 1; done
|
||||
echo
|
||||
echo "Root password: $(cat /var/shared/root-password)"
|
||||
echo "Onion address: $(cat /var/shared/onion-hostname)"
|
||||
echo "Local network addresses:"
|
||||
${pkgs.iproute}/bin/ip -brief -color addr | grep -v 127.0.0.1
|
||||
cat /var/shared/qrcode.utf8
|
||||
# workaround for https://github.com/NixOS/nixpkgs/issues/219239
|
||||
systemctl restart systemd-vconsole-setup.service
|
||||
|
||||
watch --no-title --color ${network-status}
|
||||
fi
|
||||
'';
|
||||
isoImage.squashfsCompression = "zstd";
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ let
|
||||
boot = {
|
||||
size = "1M";
|
||||
type = "EF02"; # for grub MBR
|
||||
priority = 1; # Needs to be first partition
|
||||
};
|
||||
ESP = {
|
||||
size = "100M";
|
||||
|
@ -58,6 +58,7 @@ def create_parser(prog: str | None = None) -> argparse.ArgumentParser:
|
||||
"--debug",
|
||||
help="Enable debug logging",
|
||||
action="store_true",
|
||||
default=False,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
|
@ -43,6 +43,10 @@ def create_flake(directory: Path, url: str) -> dict[str, CmdOut]:
|
||||
out = run(command, cwd=directory)
|
||||
response["git config"] = out
|
||||
|
||||
command = ["nix", "flake", "update"]
|
||||
out = run(command, cwd=directory)
|
||||
response["flake update"] = out
|
||||
|
||||
return response
|
||||
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
import argparse
|
||||
import importlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import shlex
|
||||
import re
|
||||
import shutil
|
||||
import textwrap
|
||||
from collections.abc import Sequence
|
||||
@ -20,8 +21,71 @@ from .nix import nix_shell
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def list_available_ssh_keys(ssh_dir: Path = Path("~/.ssh").expanduser()) -> list[Path]:
|
||||
"""
|
||||
Function to list all available SSH public keys in the default .ssh directory.
|
||||
Returns a list of paths to available public key files.
|
||||
"""
|
||||
public_key_patterns = ["*.pub"]
|
||||
available_keys: list[Path] = []
|
||||
|
||||
# Check for public key files
|
||||
for pattern in public_key_patterns:
|
||||
for key_path in ssh_dir.glob(pattern):
|
||||
if key_path.is_file():
|
||||
available_keys.append(key_path)
|
||||
|
||||
return available_keys
|
||||
|
||||
|
||||
def read_public_key_contents(public_keys: list[Path]) -> list[str]:
|
||||
"""
|
||||
Function to read and return the contents of available SSH public keys.
|
||||
Returns a list containing the contents of each public key.
|
||||
"""
|
||||
public_key_contents = []
|
||||
|
||||
for key_path in public_keys:
|
||||
try:
|
||||
with open(key_path.expanduser()) as key_file:
|
||||
public_key_contents.append(key_file.read().strip())
|
||||
except FileNotFoundError:
|
||||
log.error(f"Public key file not found: {key_path}")
|
||||
|
||||
return public_key_contents
|
||||
|
||||
|
||||
def get_keymap_and_locale() -> dict[str, str]:
|
||||
locale = "en_US.UTF-8"
|
||||
keymap = "en"
|
||||
|
||||
# Execute the `localectl status` command
|
||||
result = run(["localectl", "status"])
|
||||
|
||||
if result.returncode == 0:
|
||||
output = result.stdout
|
||||
|
||||
# Extract the Keymap (X11 Layout)
|
||||
keymap_match = re.search(r"X11 Layout:\s+(.*)", output)
|
||||
if keymap_match:
|
||||
keymap = keymap_match.group(1)
|
||||
|
||||
# Extract the System Locale (LANG only)
|
||||
locale_match = re.search(r"System Locale:\s+LANG=(.*)", output)
|
||||
if locale_match:
|
||||
locale = locale_match.group(1)
|
||||
|
||||
return {"keymap": keymap, "locale": locale}
|
||||
|
||||
|
||||
def flash_machine(
|
||||
machine: Machine, mode: str, disks: dict[str, str], dry_run: bool, debug: bool
|
||||
machine: Machine,
|
||||
*,
|
||||
mode: str,
|
||||
disks: dict[str, str],
|
||||
system_config: dict[str, Any],
|
||||
dry_run: bool,
|
||||
debug: bool,
|
||||
) -> None:
|
||||
secret_facts_module = importlib.import_module(machine.secret_facts_module)
|
||||
secret_facts_store: SecretStoreBase = secret_facts_module.SecretStore(
|
||||
@ -58,12 +122,17 @@ def flash_machine(
|
||||
disko_install.extend(["--extra-files", str(local_dir), upload_dir])
|
||||
disko_install.extend(["--flake", str(machine.flake) + "#" + machine.name])
|
||||
disko_install.extend(["--mode", str(mode)])
|
||||
disko_install.extend(
|
||||
[
|
||||
"--system-config",
|
||||
json.dumps(system_config),
|
||||
]
|
||||
)
|
||||
|
||||
cmd = nix_shell(
|
||||
["nixpkgs#disko"],
|
||||
["/home/lhebendanz/Projects/disko"],
|
||||
disko_install,
|
||||
)
|
||||
print("$", " ".join(map(shlex.quote, cmd)))
|
||||
run(cmd, log=Log.BOTH, error_msg=f"Failed to flash {machine}")
|
||||
|
||||
|
||||
@ -72,10 +141,13 @@ class FlashOptions:
|
||||
flake: Path
|
||||
machine: str
|
||||
disks: dict[str, str]
|
||||
ssh_keys_path: list[Path]
|
||||
dry_run: bool
|
||||
confirm: bool
|
||||
debug: bool
|
||||
mode: str
|
||||
language: str
|
||||
keymap: str
|
||||
|
||||
|
||||
class AppendDiskAction(argparse.Action):
|
||||
@ -99,11 +171,15 @@ def flash_command(args: argparse.Namespace) -> None:
|
||||
flake=args.flake,
|
||||
machine=args.machine,
|
||||
disks=args.disk,
|
||||
ssh_keys_path=args.ssh_pubkey,
|
||||
dry_run=args.dry_run,
|
||||
confirm=not args.yes,
|
||||
debug=args.debug,
|
||||
mode=args.mode,
|
||||
language=args.lang,
|
||||
keymap=args.keymap,
|
||||
)
|
||||
|
||||
machine = Machine(opts.machine, flake=opts.flake)
|
||||
if opts.confirm and not opts.dry_run:
|
||||
disk_str = ", ".join(f"{name}={device}" for name, device in opts.disks.items())
|
||||
@ -114,8 +190,49 @@ def flash_command(args: argparse.Namespace) -> None:
|
||||
ask = input(msg)
|
||||
if ask != "y":
|
||||
return
|
||||
|
||||
root_keys = read_public_key_contents(opts.ssh_keys_path)
|
||||
if opts.confirm and not root_keys:
|
||||
msg = "Should we add your SSH public keys to the root user? [y/N] "
|
||||
ask = input(msg)
|
||||
if ask == "y":
|
||||
pubkeys = list_available_ssh_keys()
|
||||
root_keys.extend(read_public_key_contents(pubkeys))
|
||||
else:
|
||||
raise ClanError(
|
||||
"No SSH public keys provided. Use --ssh-pubkey to add keys."
|
||||
)
|
||||
elif not opts.confirm and not root_keys:
|
||||
pubkeys = list_available_ssh_keys()
|
||||
root_keys.extend(read_public_key_contents(pubkeys))
|
||||
# If ssh-pubkeys set, we don't need to ask for confirmation
|
||||
elif opts.confirm and root_keys:
|
||||
pass
|
||||
elif not opts.confirm and root_keys:
|
||||
pass
|
||||
else:
|
||||
raise ClanError("Invalid state")
|
||||
|
||||
localectl = get_keymap_and_locale()
|
||||
extra_config = {
|
||||
"users": {
|
||||
"users": {"root": {"openssh": {"authorizedKeys": {"keys": root_keys}}}}
|
||||
},
|
||||
"console": {
|
||||
"keyMap": opts.keymap if opts.keymap else localectl["keymap"],
|
||||
},
|
||||
"i18n": {
|
||||
"defaultLocale": opts.language if opts.language else localectl["locale"],
|
||||
},
|
||||
}
|
||||
|
||||
flash_machine(
|
||||
machine, opts.mode, disks=opts.disks, dry_run=opts.dry_run, debug=opts.debug
|
||||
machine,
|
||||
mode=opts.mode,
|
||||
disks=opts.disks,
|
||||
system_config=extra_config,
|
||||
dry_run=opts.dry_run,
|
||||
debug=opts.debug,
|
||||
)
|
||||
|
||||
|
||||
@ -147,7 +264,23 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
|
||||
choices=["format", "mount"],
|
||||
default="format",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--ssh-pubkey",
|
||||
type=Path,
|
||||
action="append",
|
||||
default=[],
|
||||
help="ssh pubkey file to add to the root user. Can be used multiple times",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--lang",
|
||||
type=str,
|
||||
help="system language",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--keymap",
|
||||
type=str,
|
||||
help="system keymap",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--yes",
|
||||
action="store_true",
|
||||
@ -160,10 +293,4 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
|
||||
default=False,
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug",
|
||||
help="Print debug information",
|
||||
default=False,
|
||||
action="store_true",
|
||||
)
|
||||
parser.set_defaults(func=flash_command)
|
||||
|
@ -25,6 +25,7 @@ def install_nixos(
|
||||
kexec: str | None = None,
|
||||
debug: bool = False,
|
||||
password: str | None = None,
|
||||
no_reboot: bool = False,
|
||||
) -> None:
|
||||
secret_facts_module = importlib.import_module(machine.secret_facts_module)
|
||||
log.info(f"installing {machine.name}")
|
||||
@ -53,11 +54,13 @@ def install_nixos(
|
||||
"nixos-anywhere",
|
||||
"--flake",
|
||||
f"{machine.flake}#{machine.name}",
|
||||
"--no-reboot",
|
||||
"--extra-files",
|
||||
str(tmpdir),
|
||||
]
|
||||
|
||||
if no_reboot:
|
||||
cmd.append("--no-reboot")
|
||||
|
||||
if password:
|
||||
cmd += [
|
||||
"--env-password",
|
||||
@ -90,6 +93,7 @@ class InstallOptions:
|
||||
kexec: str | None
|
||||
confirm: bool
|
||||
debug: bool
|
||||
no_reboot: bool
|
||||
json_ssh_deploy: dict[str, str] | None
|
||||
|
||||
|
||||
@ -121,6 +125,7 @@ def install_command(args: argparse.Namespace) -> None:
|
||||
kexec=args.kexec,
|
||||
confirm=not args.yes,
|
||||
debug=args.debug,
|
||||
no_reboot=args.no_reboot,
|
||||
json_ssh_deploy=json_ssh_deploy,
|
||||
)
|
||||
machine = Machine(opts.machine, flake=opts.flake)
|
||||
@ -131,7 +136,13 @@ def install_command(args: argparse.Namespace) -> None:
|
||||
if ask != "y":
|
||||
return
|
||||
|
||||
install_nixos(machine, kexec=opts.kexec, debug=opts.debug, password=password)
|
||||
install_nixos(
|
||||
machine,
|
||||
kexec=opts.kexec,
|
||||
debug=opts.debug,
|
||||
password=password,
|
||||
no_reboot=opts.no_reboot,
|
||||
)
|
||||
|
||||
|
||||
def find_reachable_host_from_deploy_json(deploy_json: dict[str, str]) -> str:
|
||||
@ -161,15 +172,15 @@ def register_install_parser(parser: argparse.ArgumentParser) -> None:
|
||||
help="use another kexec tarball to bootstrap NixOS",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--yes",
|
||||
"--no-reboot",
|
||||
action="store_true",
|
||||
help="do not ask for confirmation",
|
||||
help="do not reboot after installation",
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug",
|
||||
"--yes",
|
||||
action="store_true",
|
||||
help="print debug information",
|
||||
help="do not ask for confirmation",
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
|
@ -106,13 +106,7 @@ def nix_shell(packages: list[str], cmd: list[str]) -> list[str]:
|
||||
if os.environ.get("IN_NIX_SANDBOX"):
|
||||
return cmd
|
||||
return [
|
||||
*nix_command(
|
||||
[
|
||||
"shell",
|
||||
"--inputs-from",
|
||||
f"{nixpkgs_flake()!s}",
|
||||
]
|
||||
),
|
||||
*nix_command(["shell", "--offline", "--inputs-from", f"{nixpkgs_flake()!s}"]),
|
||||
*packages,
|
||||
"-c",
|
||||
*cmd,
|
||||
|
@ -23,11 +23,11 @@
|
||||
zbar,
|
||||
tor,
|
||||
git,
|
||||
nixpkgs,
|
||||
qemu,
|
||||
gnupg,
|
||||
e2fsprogs,
|
||||
mypy,
|
||||
nixpkgs,
|
||||
clan-core-path,
|
||||
}:
|
||||
let
|
||||
@ -95,17 +95,16 @@ let
|
||||
description = "dependencies for the clan-cli";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs";
|
||||
nixpkgs.url = "path://${nixpkgs}";
|
||||
};
|
||||
|
||||
outputs = _inputs: { };
|
||||
}
|
||||
EOF
|
||||
ln -s ${nixpkgs} $out/path
|
||||
nix flake lock $out \
|
||||
nix flake update $out \
|
||||
--store ./. \
|
||||
--extra-experimental-features 'nix-command flakes' \
|
||||
--override-input nixpkgs ${nixpkgs}
|
||||
--extra-experimental-features 'nix-command flakes'
|
||||
'';
|
||||
in
|
||||
python3.pkgs.buildPythonApplication {
|
||||
|
@ -38,6 +38,7 @@
|
||||
'';
|
||||
in
|
||||
{
|
||||
|
||||
devShells.clan-cli = pkgs.callPackage ./shell.nix { inherit (self'.packages) clan-cli; };
|
||||
packages = {
|
||||
clan-cli = pkgs.python3.pkgs.callPackage ./default.nix {
|
||||
|
@ -12,6 +12,7 @@ let
|
||||
rope
|
||||
setuptools
|
||||
wheel
|
||||
ipdb
|
||||
pip
|
||||
]);
|
||||
in
|
||||
@ -21,6 +22,8 @@ mkShell {
|
||||
ruff
|
||||
] ++ devshellTestDeps;
|
||||
|
||||
PYTHONBREAKPOINT = "ipdb.set_trace";
|
||||
|
||||
shellHook = ''
|
||||
export GIT_ROOT="$(git rev-parse --show-toplevel)"
|
||||
export PKG_ROOT="$GIT_ROOT/pkgs/clan-cli"
|
||||
|
@ -40,6 +40,8 @@ mkShell {
|
||||
desktop-file-utils # verify desktop files
|
||||
]);
|
||||
|
||||
PYTHONBREAKPOINT = "ipdb.set_trace";
|
||||
|
||||
shellHook = ''
|
||||
export GIT_ROOT=$(git rev-parse --show-toplevel)
|
||||
export PKG_ROOT=$GIT_ROOT/pkgs/clan-vm-manager
|
||||
|
@ -1,4 +1,5 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./clan-cli/flake-module.nix
|
||||
|
@ -1,51 +1,123 @@
|
||||
{ self, lib, ... }:
|
||||
|
||||
let
|
||||
installerModule =
|
||||
{ config, ... }:
|
||||
wifiModule =
|
||||
{ ... }:
|
||||
{
|
||||
imports = [
|
||||
self.nixosModules.installer
|
||||
self.inputs.nixos-generators.nixosModules.all-formats
|
||||
];
|
||||
# Provide convenience for connecting to wifi
|
||||
# use iwd instead of wpa_supplicant
|
||||
networking.wireless.enable = false;
|
||||
|
||||
# Use iwd instead of wpa_supplicant. It has a user friendly CLI
|
||||
networking.wireless.iwd = {
|
||||
enable = true;
|
||||
settings = {
|
||||
Network = {
|
||||
EnableIPv6 = true;
|
||||
RoutePriorityOffset = 300;
|
||||
};
|
||||
Settings = {
|
||||
AutoConnect = true;
|
||||
Settings.AutoConnect = true;
|
||||
};
|
||||
};
|
||||
enable = true;
|
||||
};
|
||||
installerModule =
|
||||
{ config, modulesPath, ... }:
|
||||
{
|
||||
imports = [
|
||||
wifiModule
|
||||
self.nixosModules.installer
|
||||
self.inputs.nixos-generators.nixosModules.all-formats
|
||||
(modulesPath + "/installer/cd-dvd/iso-image.nix")
|
||||
];
|
||||
|
||||
isoImage.squashfsCompression = "zstd";
|
||||
|
||||
system.stateVersion = config.system.nixos.version;
|
||||
nixpkgs.pkgs = self.inputs.nixpkgs.legacyPackages.x86_64-linux;
|
||||
};
|
||||
|
||||
installer = lib.nixosSystem {
|
||||
installerSystem = lib.nixosSystem {
|
||||
modules = [
|
||||
self.inputs.disko.nixosModules.default
|
||||
installerModule
|
||||
{ disko.memSize = 4096; } # FIXME: otherwise the image builder goes OOM
|
||||
];
|
||||
};
|
||||
|
||||
flashInstallerModule =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
wifiModule
|
||||
self.nixosModules.installer
|
||||
];
|
||||
system.stateVersion = config.system.nixos.version;
|
||||
nixpkgs.pkgs = self.inputs.nixpkgs.legacyPackages.x86_64-linux;
|
||||
}
|
||||
// flashDiskoConfig;
|
||||
|
||||
# Important: The partition names need to be different to the clan install
|
||||
flashDiskoConfig = {
|
||||
boot.loader.grub.efiSupport = lib.mkDefault true;
|
||||
boot.loader.grub.efiInstallAsRemovable = lib.mkDefault true;
|
||||
disko.devices = {
|
||||
disk = {
|
||||
main = {
|
||||
type = "disk";
|
||||
device = lib.mkDefault "/dev/null";
|
||||
content = {
|
||||
type = "gpt";
|
||||
partitions = {
|
||||
installer-boot = {
|
||||
size = "1M";
|
||||
type = "EF02"; # for grub MBR
|
||||
priority = 1;
|
||||
};
|
||||
installer-ESP = {
|
||||
size = "512M";
|
||||
type = "EF00";
|
||||
content = {
|
||||
type = "filesystem";
|
||||
format = "vfat";
|
||||
mountpoint = "/boot";
|
||||
};
|
||||
};
|
||||
installer-root = {
|
||||
size = "100%";
|
||||
content = {
|
||||
type = "filesystem";
|
||||
format = "ext4";
|
||||
mountpoint = "/";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
|
||||
clan = {
|
||||
clanName = "clan-core";
|
||||
directory = self;
|
||||
machines.installer = {
|
||||
|
||||
# To build a generic installer image (without ssh pubkeys),
|
||||
# use the following command:
|
||||
# $ nix build .#iso-installer
|
||||
machines.iso-installer = {
|
||||
imports = [ installerModule ];
|
||||
fileSystems."/".device = lib.mkDefault "/dev/null";
|
||||
};
|
||||
|
||||
# To directly flash the installer to a disk, use the following command:
|
||||
# $ clan flash flash-installer --disk main /dev/sdX --yes
|
||||
# This will include your ssh public keys in the installer.
|
||||
machines.flash-installer = {
|
||||
imports = [ flashInstallerModule ];
|
||||
boot.loader.grub.enable = lib.mkDefault true;
|
||||
};
|
||||
flake.packages.x86_64-linux.install-iso = installer.config.formats.iso;
|
||||
flake.apps.x86_64-linux.install-vm.program = installer.config.formats.vm.outPath;
|
||||
flake.apps.x86_64-linux.install-vm-nogui.program = installer.config.formats.vm-nogui.outPath;
|
||||
};
|
||||
flake.packages.x86_64-linux.iso-installer = installerSystem.config.formats.iso;
|
||||
flake.apps.x86_64-linux.install-vm.program = installerSystem.config.formats.vm.outPath;
|
||||
flake.apps.x86_64-linux.install-vm-nogui.program = installerSystem.config.formats.vm-nogui.outPath;
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
# local> mkdir -p ./machines/machine1
|
||||
# local> Edit ./machines/machine1/configuration.nix to your liking
|
||||
machines = {
|
||||
# "jon" will be the hostname of the machine
|
||||
jon = {
|
||||
imports = [
|
||||
./modules/shared.nix
|
||||
@ -31,18 +32,27 @@
|
||||
clanCore.machineIcon = null; # Optional, a path to an image file
|
||||
|
||||
# Set this for clan commands use ssh i.e. `clan machines update`
|
||||
# If you change the hostname, you need to update this line to root@<new-hostname>
|
||||
# This only works however if you have avahi running on your admin machine else use IP
|
||||
clan.networking.targetHost = pkgs.lib.mkDefault "root@jon";
|
||||
|
||||
# TODO: Example how to use disko for more complicated setups
|
||||
|
||||
# ssh root@flash-installer.local lsblk --output NAME,ID-LINK,FSTYPE,SIZE,MOUNTPOINT
|
||||
disko.devices.disk.main = {
|
||||
device = "/dev/disk/by-id/__CHANGE_ME__";
|
||||
};
|
||||
|
||||
# TODO: Document that there needs to be one controller
|
||||
# IMPORTANT! Add your SSH key here
|
||||
# e.g. > cat ~/.ssh/id_ed25519.pub
|
||||
users.users.root.openssh.authorizedKeys.keys = throw ''
|
||||
Don't forget to add your SSH key here!
|
||||
users.users.root.openssh.authorizedKeys.keys = [ "<YOUR SSH_KEY>" ]
|
||||
'';
|
||||
|
||||
# Zerotier needs one controller to accept new nodes. Once accepted
|
||||
# the controller can be offline and routing still works.
|
||||
clan.networking.zerotier.controller.enable = true;
|
||||
};
|
||||
# "sara" will be the hostname of the machine
|
||||
sara = {
|
||||
imports = [
|
||||
./modules/shared.nix
|
||||
@ -55,14 +65,22 @@
|
||||
clanCore.machineIcon = null; # Optional, a path to an image file
|
||||
|
||||
# Set this for clan commands use ssh i.e. `clan machines update`
|
||||
# If you change the hostname, you need to update this line to root@<new-hostname>
|
||||
# This only works however if you have avahi running on your admin machine else use IP
|
||||
clan.networking.targetHost = pkgs.lib.mkDefault "root@sara";
|
||||
|
||||
# local> clan facts generate
|
||||
|
||||
# ssh root@flash-installer.local lsblk --output NAME,ID-LINK,FSTYPE,SIZE,MOUNTPOINT
|
||||
disko.devices.disk.main = {
|
||||
device = "/dev/disk/by-id/__CHANGE_ME__";
|
||||
};
|
||||
|
||||
# IMPORTANT! Add your SSH key here
|
||||
# e.g. > cat ~/.ssh/id_ed25519.pub
|
||||
users.users.root.openssh.authorizedKeys.keys = throw ''
|
||||
Don't forget to add your SSH key here!
|
||||
users.users.root.openssh.authorizedKeys.keys = [ "<YOUR SSH_KEY>" ]
|
||||
'';
|
||||
|
||||
/*
|
||||
After jon is deployed, uncomment the following line
|
||||
This will allow sara to share the VPN overlay network with jon
|
||||
|
@ -1,15 +1,35 @@
|
||||
{ ... }:
|
||||
{ config, ... }:
|
||||
let
|
||||
username = config.networking.hostName;
|
||||
in
|
||||
{
|
||||
imports = [ ./hardware-configuration.nix ];
|
||||
users.users.root.openssh.authorizedKeys.keys = [
|
||||
# IMPORTANT! Add your SSH key here
|
||||
# e.g. > cat ~/.ssh/id_ed25519.pub
|
||||
"<YOUR SSH_KEY>"
|
||||
];
|
||||
|
||||
services.xserver.enable = true;
|
||||
services.xserver.desktopManager.gnome.enable = true;
|
||||
services.xserver.displayManager.gdm.enable = true;
|
||||
# Disable the default gnome apps to speed up deployment
|
||||
services.gnome.core-utilities.enable = false;
|
||||
|
||||
# Enable automatic login for the user.
|
||||
services.displayManager.autoLogin = {
|
||||
enable = true;
|
||||
user = username;
|
||||
};
|
||||
|
||||
users.users.${username} = {
|
||||
initialPassword = username;
|
||||
isNormalUser = true;
|
||||
extraGroups = [
|
||||
"wheel"
|
||||
"networkmanager"
|
||||
"video"
|
||||
"audio"
|
||||
"input"
|
||||
"dialout"
|
||||
"disk"
|
||||
];
|
||||
uid = 1000;
|
||||
openssh.authorizedKeys.keys = config.users.users.root.openssh.authorizedKeys.keys;
|
||||
};
|
||||
}
|
||||
|
@ -1,15 +1,36 @@
|
||||
{ ... }:
|
||||
{ config, ... }:
|
||||
|
||||
let
|
||||
username = config.networking.hostName;
|
||||
in
|
||||
{
|
||||
imports = [ ./hardware-configuration.nix ];
|
||||
users.users.root.openssh.authorizedKeys.keys = [
|
||||
# IMPORTANT! Add your SSH key here
|
||||
# e.g. > cat ~/.ssh/id_ed25519.pub
|
||||
"<YOUR SSH_KEY>"
|
||||
];
|
||||
|
||||
services.xserver.enable = true;
|
||||
services.xserver.desktopManager.gnome.enable = true;
|
||||
services.xserver.displayManager.gdm.enable = true;
|
||||
# Disable the default gnome apps to speed up deployment
|
||||
services.gnome.core-utilities.enable = false;
|
||||
|
||||
# Enable automatic login for the user.
|
||||
services.displayManager.autoLogin = {
|
||||
enable = true;
|
||||
user = username;
|
||||
};
|
||||
|
||||
users.users.${username} = {
|
||||
initialPassword = username;
|
||||
isNormalUser = true;
|
||||
extraGroups = [
|
||||
"wheel"
|
||||
"networkmanager"
|
||||
"video"
|
||||
"audio"
|
||||
"input"
|
||||
"dialout"
|
||||
"disk"
|
||||
];
|
||||
uid = 1000;
|
||||
openssh.authorizedKeys.keys = config.users.users.root.openssh.authorizedKeys.keys;
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
boot.loader.grub.efiSupport = lib.mkDefault true;
|
||||
boot.loader.grub.efiInstallAsRemovable = lib.mkDefault true;
|
||||
disko.devices = {
|
||||
disk = {
|
||||
main = {
|
||||
@ -11,6 +14,7 @@
|
||||
boot = {
|
||||
size = "1M";
|
||||
type = "EF02"; # for grub MBR
|
||||
priority = 1;
|
||||
};
|
||||
ESP = {
|
||||
size = "512M";
|
||||
|
@ -4,4 +4,7 @@
|
||||
clan-core.clanModules.sshd
|
||||
clan-core.clanModules.root-password
|
||||
];
|
||||
|
||||
# Locale service discovery and mDNS
|
||||
services.avahi.enable = true;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user