feat: Use age for env secret managment
Some checks failed
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
Some checks failed
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
This commit is contained in:
@@ -159,6 +159,8 @@ let
|
|||||||
./common.nix
|
./common.nix
|
||||||
overrideModule
|
overrideModule
|
||||||
{ networking.hostName = hostName; }
|
{ networking.hostName = hostName; }
|
||||||
|
# Set athenix.host.name for secrets and other modules to use
|
||||||
|
{ athenix.host.name = hostName; }
|
||||||
{
|
{
|
||||||
# Inject user definitions from flake-parts level
|
# Inject user definitions from flake-parts level
|
||||||
config.athenix.users = lib.mapAttrs (_: user: lib.mapAttrs (_: lib.mkDefault) user) users;
|
config.athenix.users = lib.mapAttrs (_: user: lib.mapAttrs (_: lib.mkDefault) user) users;
|
||||||
|
|||||||
15
fleet/fs.nix
15
fleet/fs.nix
@@ -17,8 +17,16 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.athenix = {
|
options.athenix = {
|
||||||
host.filesystem = {
|
host = {
|
||||||
device = lib.mkOption {
|
name = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = ''
|
||||||
|
Fleet-assigned hostname for this system.
|
||||||
|
Used for secrets discovery and other host-specific configurations.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
filesystem = {
|
||||||
|
device = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.str;
|
type = lib.types.nullOr lib.types.str;
|
||||||
default = null;
|
default = null;
|
||||||
description = ''
|
description = ''
|
||||||
@@ -54,8 +62,7 @@ in
|
|||||||
'';
|
'';
|
||||||
example = "32G";
|
example = "32G";
|
||||||
};
|
};
|
||||||
};
|
}; }; };
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkMerge [
|
config = lib.mkMerge [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -129,7 +129,7 @@
|
|||||||
};
|
};
|
||||||
"usda-dash".external = {
|
"usda-dash".external = {
|
||||||
url = "https://git.factory.uga.edu/MODEL/usda-dash-config.git";
|
url = "https://git.factory.uga.edu/MODEL/usda-dash-config.git";
|
||||||
rev = "9d2783981de95bdaaf46a8f0743245566b028d64";
|
rev = "b3c17434202aa4b4aa39ad5648c98c8c539e9a25";
|
||||||
submodules = false;
|
submodules = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
203
secrets.nix
Normal file
203
secrets.nix
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Agenix Secret Recipients Configuration (Auto-Generated)
|
||||||
|
# ============================================================================
|
||||||
|
# This file automatically discovers hosts and their public keys from the
|
||||||
|
# secrets/ directory structure and generates recipient configurations.
|
||||||
|
#
|
||||||
|
# Directory structure:
|
||||||
|
# secrets/{hostname}/*.pub -> SSH/age public keys for that host
|
||||||
|
# secrets/global/*.pub -> Keys accessible to all hosts
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ragenix -e secrets/global/example.age # Edit/create secret
|
||||||
|
# ragenix -r # Re-key all secrets
|
||||||
|
#
|
||||||
|
# To add admin keys for editing secrets, create secrets/admins/*.pub files
|
||||||
|
# with your personal age public keys (generated with: age-keygen)
|
||||||
|
|
||||||
|
let
|
||||||
|
lib = builtins;
|
||||||
|
|
||||||
|
# Helper functions not in builtins
|
||||||
|
filterAttrs =
|
||||||
|
pred: set:
|
||||||
|
lib.listToAttrs (
|
||||||
|
lib.filter (item: pred item.name item.value) (
|
||||||
|
lib.map (name: {
|
||||||
|
inherit name;
|
||||||
|
value = set.${name};
|
||||||
|
}) (lib.attrNames set)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
concatLists = lists: lib.foldl' (acc: list: acc ++ list) [ ] lists;
|
||||||
|
|
||||||
|
unique =
|
||||||
|
list:
|
||||||
|
let
|
||||||
|
go =
|
||||||
|
acc: remaining:
|
||||||
|
if remaining == [ ] then
|
||||||
|
acc
|
||||||
|
else if lib.elem (lib.head remaining) acc then
|
||||||
|
go acc (lib.tail remaining)
|
||||||
|
else
|
||||||
|
go (acc ++ [ (lib.head remaining) ]) (lib.tail remaining);
|
||||||
|
in
|
||||||
|
go [ ] list;
|
||||||
|
|
||||||
|
hasSuffix =
|
||||||
|
suffix: str:
|
||||||
|
let
|
||||||
|
lenStr = lib.stringLength str;
|
||||||
|
lenSuffix = lib.stringLength suffix;
|
||||||
|
in
|
||||||
|
lenStr >= lenSuffix && lib.substring (lenStr - lenSuffix) lenSuffix str == suffix;
|
||||||
|
|
||||||
|
hasPrefix =
|
||||||
|
prefix: str:
|
||||||
|
let
|
||||||
|
lenPrefix = lib.stringLength prefix;
|
||||||
|
in
|
||||||
|
lib.stringLength str >= lenPrefix && lib.substring 0 lenPrefix str == prefix;
|
||||||
|
|
||||||
|
nameValuePair = name: value: { inherit name value; };
|
||||||
|
|
||||||
|
secretsPath = ./secrets;
|
||||||
|
|
||||||
|
# Helper to convert SSH public key content to age public key
|
||||||
|
sshToAge =
|
||||||
|
sshPubKey:
|
||||||
|
let
|
||||||
|
# This is a simple check - in practice, use ssh-to-age tool
|
||||||
|
# For now, we'll just use the keys as-is if they look like age keys
|
||||||
|
trimmed = lib.replaceStrings [ "\n" ] [ "" ] sshPubKey;
|
||||||
|
in
|
||||||
|
if lib.substring 0 4 trimmed == "age1" then trimmed else null; # Will need manual conversion with ssh-to-age
|
||||||
|
|
||||||
|
# Read all directories in secrets/
|
||||||
|
secretDirs = if lib.pathExists secretsPath then lib.readDir secretsPath else { };
|
||||||
|
|
||||||
|
# Filter to only directories (excludes files)
|
||||||
|
isDirectory = name: type: type == "directory";
|
||||||
|
directories = lib.filter (name: isDirectory name secretDirs.${name}) (lib.attrNames secretDirs);
|
||||||
|
|
||||||
|
# Read public keys from a directory and convert to age format
|
||||||
|
readHostKeys =
|
||||||
|
dirName:
|
||||||
|
let
|
||||||
|
dirPath = secretsPath + "/${dirName}";
|
||||||
|
files = if lib.pathExists dirPath then lib.readDir dirPath else { };
|
||||||
|
|
||||||
|
# Prefer .age.pub files (pre-converted), fall back to .pub files
|
||||||
|
agePubFiles = filterAttrs (name: type: type == "regular" && hasSuffix ".age.pub" name) files;
|
||||||
|
|
||||||
|
sshPubFiles = filterAttrs (
|
||||||
|
name: type: type == "regular" && hasSuffix ".pub" name && !(hasSuffix ".age.pub" name)
|
||||||
|
) files;
|
||||||
|
|
||||||
|
# Read age public keys (already in correct format)
|
||||||
|
ageKeys = lib.map (
|
||||||
|
name:
|
||||||
|
let
|
||||||
|
content = lib.readFile (dirPath + "/${name}");
|
||||||
|
# Trim whitespace/newlines
|
||||||
|
trimmed = lib.replaceStrings [ "\n" " " "\r" "\t" ] [ "" "" "" "" ] content;
|
||||||
|
in
|
||||||
|
trimmed
|
||||||
|
) (lib.attrNames agePubFiles);
|
||||||
|
|
||||||
|
# For SSH keys, just include them as-is (user needs to convert with ssh-to-age)
|
||||||
|
# Or they can run the update-age-keys.sh script
|
||||||
|
sshKeys =
|
||||||
|
if (lib.length (lib.attrNames sshPubFiles)) > 0 then
|
||||||
|
lib.trace "Warning: ${dirName} has unconverted SSH keys. Run secrets/update-age-keys.sh" [ ]
|
||||||
|
else
|
||||||
|
[ ];
|
||||||
|
in
|
||||||
|
lib.filter (k: k != null && k != "") (ageKeys ++ sshKeys);
|
||||||
|
|
||||||
|
# Build host key mappings: { hostname = [ "age1..." "age2..." ]; }
|
||||||
|
hostKeys = lib.listToAttrs (
|
||||||
|
lib.map (dir: nameValuePair dir (readHostKeys dir)) (
|
||||||
|
lib.filter (d: d != "global" && d != "admins") directories
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
# Global keys that all hosts can use
|
||||||
|
globalKeys = if lib.elem "global" directories then readHostKeys "global" else [ ];
|
||||||
|
|
||||||
|
# Admin keys for editing secrets
|
||||||
|
adminKeys = if lib.elem "admins" directories then readHostKeys "admins" else [ ];
|
||||||
|
|
||||||
|
# All host keys combined
|
||||||
|
allHostKeys = concatLists (lib.attrValues hostKeys);
|
||||||
|
|
||||||
|
# Find all .age files in the secrets directory
|
||||||
|
findSecrets =
|
||||||
|
dir:
|
||||||
|
let
|
||||||
|
dirPath = secretsPath + "/${dir}";
|
||||||
|
files = if lib.pathExists dirPath then lib.readDir dirPath else { };
|
||||||
|
ageFiles = filterAttrs (name: type: type == "regular" && hasSuffix ".age" name) files;
|
||||||
|
in
|
||||||
|
lib.map (name: "secrets/${dir}/${name}") (lib.attrNames ageFiles);
|
||||||
|
|
||||||
|
# Generate recipient list for a secret based on its location
|
||||||
|
getRecipients =
|
||||||
|
secretPath:
|
||||||
|
let
|
||||||
|
# Extract directory name from path: "secrets/nix-builder/foo.age" -> "nix-builder"
|
||||||
|
pathParts = lib.split "/" secretPath;
|
||||||
|
dirName = lib.elemAt pathParts 2;
|
||||||
|
in
|
||||||
|
if dirName == "global" then
|
||||||
|
# Global secrets: all hosts + admins
|
||||||
|
allHostKeys ++ globalKeys ++ adminKeys
|
||||||
|
else if hostKeys ? ${dirName} then
|
||||||
|
# Host-specific secrets: that host + global keys + admins
|
||||||
|
hostKeys.${dirName} ++ globalKeys ++ adminKeys
|
||||||
|
else
|
||||||
|
# Fallback: just admins
|
||||||
|
adminKeys;
|
||||||
|
|
||||||
|
# Find all secrets across all directories
|
||||||
|
allSecrets = concatLists (lib.map findSecrets directories);
|
||||||
|
|
||||||
|
# Generate the configuration
|
||||||
|
secretsConfig = lib.listToAttrs (
|
||||||
|
lib.map (
|
||||||
|
secretPath:
|
||||||
|
let
|
||||||
|
recipients = getRecipients secretPath;
|
||||||
|
# Remove duplicates and empty keys
|
||||||
|
uniqueRecipients = unique (lib.filter (k: k != null && k != "") recipients);
|
||||||
|
in
|
||||||
|
nameValuePair secretPath {
|
||||||
|
publicKeys = uniqueRecipients;
|
||||||
|
}
|
||||||
|
) allSecrets
|
||||||
|
);
|
||||||
|
|
||||||
|
# Generate wildcard rules for each directory to allow creating new secrets
|
||||||
|
wildcardRules = lib.listToAttrs (
|
||||||
|
lib.concatMap (
|
||||||
|
dir:
|
||||||
|
[
|
||||||
|
# Match with and without .age extension for ragenix compatibility
|
||||||
|
(nameValuePair "secrets/${dir}/*" {
|
||||||
|
publicKeys = let
|
||||||
|
recipients = getRecipients "secrets/${dir}/dummy.age";
|
||||||
|
in unique (lib.filter (k: k != null && k != "") recipients);
|
||||||
|
})
|
||||||
|
(nameValuePair "secrets/${dir}/*.age" {
|
||||||
|
publicKeys = let
|
||||||
|
recipients = getRecipients "secrets/${dir}/dummy.age";
|
||||||
|
in unique (lib.filter (k: k != null && k != "") recipients);
|
||||||
|
})
|
||||||
|
]
|
||||||
|
) (lib.filter (d: d != "admins") directories)
|
||||||
|
);
|
||||||
|
|
||||||
|
in
|
||||||
|
secretsConfig // wildcardRules
|
||||||
@@ -78,29 +78,48 @@ age -R secrets/nix-builder/ssh_host_ed25519_key.pub \
|
|||||||
-o secrets/nix-builder/ssh_host_key.age < /etc/ssh/ssh_host_ed25519_key
|
-o secrets/nix-builder/ssh_host_key.age < /etc/ssh/ssh_host_ed25519_key
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Using ragenix CLI (Recommended)
|
### 4. Creating and Editing Secrets
|
||||||
|
|
||||||
The `ragenix` CLI tool simplifies secret management. The `secrets/secrets.nix` file **automatically discovers** hosts and their keys from the directory structure:
|
**For new secrets**, use the helper script (automatically determines recipients):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd secrets/
|
||||||
|
|
||||||
|
# Create a host-specific secret
|
||||||
|
./create-secret.sh usda-dash/database-url.age <<< "postgresql://..."
|
||||||
|
|
||||||
|
# Create a global secret
|
||||||
|
echo "shared-api-key" | ./create-secret.sh global/api-key.age
|
||||||
|
|
||||||
|
# From a file
|
||||||
|
./create-secret.sh nix-builder/ssh-key.age < ~/.ssh/id_ed25519
|
||||||
|
```
|
||||||
|
|
||||||
|
The script automatically includes the correct recipients:
|
||||||
|
- **Host-specific**: that host's keys + global keys + admin keys
|
||||||
|
- **Global**: all host keys + admin keys
|
||||||
|
|
||||||
|
**To edit existing secrets**, use `ragenix`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install ragenix
|
# Install ragenix
|
||||||
nix shell github:yaxitech/ragenix
|
nix shell github:yaxitech/ragenix
|
||||||
|
|
||||||
# Edit a secret (creates if doesn't exist)
|
# Edit an existing secret (you must have a decryption key)
|
||||||
# Recipients are automatically determined based on the path:
|
ragenix -e secrets/global/existing-secret.age
|
||||||
# - secrets/global/*.age -> encrypted for ALL hosts + admins
|
|
||||||
# - secrets/{hostname}/*.age -> encrypted for that host + global keys + admins
|
|
||||||
ragenix -e secrets/global/example.age
|
|
||||||
|
|
||||||
# Re-key all secrets after adding/removing hosts
|
# Re-key all secrets after adding new hosts
|
||||||
ragenix -r
|
ragenix -r
|
||||||
```
|
```
|
||||||
|
|
||||||
The `secrets.nix` file automatically:
|
**Why create with `age` first?** Ragenix requires the `.age` file to exist before editing. The `secrets/secrets.nix` configuration auto-discovers recipients from the directory structure, but ragenix doesn't support wildcard patterns for creating new files.
|
||||||
- **Discovers hosts** from directory names in `secrets/`
|
|
||||||
- **Reads age public keys** from `.age.pub` files in each directory
|
**Recipient management** is automatic:
|
||||||
- **Generates recipient lists** based on secret location (global vs host-specific)
|
- **Global secrets** (`secrets/global/*.age`): encrypted for ALL hosts + admins
|
||||||
- **Includes admin keys** from `secrets/admins/*.age.pub` for editing
|
- **Host secrets** (`secrets/{hostname}/*.age`): encrypted for that host + global keys + admins
|
||||||
|
- **Admin keys** from `secrets/admins/*.age.pub` allow editing from your workstation
|
||||||
|
|
||||||
|
After creating new .age files with `age`, use `ragenix -r` to re-key all secrets with the updated recipient configuration.
|
||||||
|
|
||||||
To add admin keys for editing secrets:
|
To add admin keys for editing secrets:
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
1
secrets/admins/temp-admin.age.pub
Normal file
1
secrets/admins/temp-admin.age.pub
Normal file
@@ -0,0 +1 @@
|
|||||||
|
age14emzyraytqzmre58c452t07rtcj87cwqwmd9z3gj7upugtxk8s3sda5tju
|
||||||
BIN
secrets/core
Normal file
BIN
secrets/core
Normal file
Binary file not shown.
121
secrets/create-secret.sh
Executable file
121
secrets/create-secret.sh
Executable file
@@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Create a new age-encrypted secret with auto-determined recipients
|
||||||
|
# Usage: ./create-secret.sh <path> [content]
|
||||||
|
# path: relative to secrets/ (e.g., "usda-dash/my-secret.age" or "global/shared.age")
|
||||||
|
# content: stdin if not provided
|
||||||
|
|
||||||
|
SECRETS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
if [ $# -lt 1 ]; then
|
||||||
|
echo "Usage: $0 <path> [content]" >&2
|
||||||
|
echo "Examples:" >&2
|
||||||
|
echo " $0 usda-dash/database-url.age <<< 'postgresql://...'" >&2
|
||||||
|
echo " $0 global/api-key.age < secret-file.txt" >&2
|
||||||
|
echo " echo 'secret' | $0 nix-builder/token.age" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SECRET_PATH="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
# Extract directory from path (e.g., "usda-dash/file.age" -> "usda-dash")
|
||||||
|
SECRET_DIR="$(dirname "$SECRET_PATH")"
|
||||||
|
SECRET_FILE="$(basename "$SECRET_PATH")"
|
||||||
|
|
||||||
|
# Ensure .age extension
|
||||||
|
if [[ ! "$SECRET_FILE" =~ \.age$ ]]; then
|
||||||
|
echo "Error: Secret file must have .age extension" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_FILE="$SECRETS_DIR/$SECRET_PATH"
|
||||||
|
|
||||||
|
# Ensure target directory exists
|
||||||
|
mkdir -p "$(dirname "$TARGET_FILE")"
|
||||||
|
|
||||||
|
# Collect recipient keys
|
||||||
|
RECIPIENTS=()
|
||||||
|
|
||||||
|
if [ "$SECRET_DIR" = "global" ]; then
|
||||||
|
echo "Creating global secret (encrypted for all hosts + admins)..." >&2
|
||||||
|
|
||||||
|
# Add all host keys
|
||||||
|
for host_dir in "$SECRETS_DIR"/*/; do
|
||||||
|
host_name="$(basename "$host_dir")"
|
||||||
|
# Skip non-host directories
|
||||||
|
if [ "$host_name" = "admins" ] || [ "$host_name" = "global" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add all .age.pub files from this host
|
||||||
|
while IFS= read -r -d '' key_file; do
|
||||||
|
RECIPIENTS+=("$key_file")
|
||||||
|
done < <(find "$host_dir" -maxdepth 1 -name "*.age.pub" -print0)
|
||||||
|
done
|
||||||
|
|
||||||
|
# Add global keys
|
||||||
|
while IFS= read -r -d '' key_file; do
|
||||||
|
RECIPIENTS+=("$key_file")
|
||||||
|
done < <(find "$SECRETS_DIR/global" -maxdepth 1 -name "*.age.pub" -print0 2>/dev/null || true)
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "Creating host-specific secret for $SECRET_DIR..." >&2
|
||||||
|
|
||||||
|
# Check if host directory exists
|
||||||
|
if [ ! -d "$SECRETS_DIR/$SECRET_DIR" ]; then
|
||||||
|
echo "Error: Host directory $SECRET_DIR does not exist" >&2
|
||||||
|
echo "Create it first: mkdir -p secrets/$SECRET_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add this host's keys
|
||||||
|
while IFS= read -r -d '' key_file; do
|
||||||
|
RECIPIENTS+=("$key_file")
|
||||||
|
done < <(find "$SECRETS_DIR/$SECRET_DIR" -maxdepth 1 -name "*.age.pub" -print0)
|
||||||
|
|
||||||
|
# Add global keys (so global hosts can also decrypt)
|
||||||
|
while IFS= read -r -d '' key_file; do
|
||||||
|
RECIPIENTS+=("$key_file")
|
||||||
|
done < <(find "$SECRETS_DIR/global" -maxdepth 1 -name "*.age.pub" -print0 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add admin keys (for editing from workstations)
|
||||||
|
if [ -d "$SECRETS_DIR/admins" ]; then
|
||||||
|
while IFS= read -r -d '' key_file; do
|
||||||
|
RECIPIENTS+=("$key_file")
|
||||||
|
done < <(find "$SECRETS_DIR/admins" -maxdepth 1 -name "*.age.pub" -print0 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if we have any recipients
|
||||||
|
if [ ${#RECIPIENTS[@]} -eq 0 ]; then
|
||||||
|
echo "Error: No recipient keys found!" >&2
|
||||||
|
echo "Run ./update-age-keys.sh first to generate .age.pub files" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found ${#RECIPIENTS[@]} recipient key(s):" >&2
|
||||||
|
for key in "${RECIPIENTS[@]}"; do
|
||||||
|
echo " - $(basename "$key")" >&2
|
||||||
|
done
|
||||||
|
|
||||||
|
# Create recipient list file (temporary)
|
||||||
|
RECIPIENT_LIST=$(mktemp)
|
||||||
|
trap "rm -f $RECIPIENT_LIST" EXIT
|
||||||
|
|
||||||
|
for key in "${RECIPIENTS[@]}"; do
|
||||||
|
cat "$key" >> "$RECIPIENT_LIST"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Encrypt the secret
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
# Content provided as argument
|
||||||
|
echo "$@" | age -R "$RECIPIENT_LIST" -o "$TARGET_FILE"
|
||||||
|
else
|
||||||
|
# Content from stdin
|
||||||
|
age -R "$RECIPIENT_LIST" -o "$TARGET_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✓ Created $TARGET_FILE" >&2
|
||||||
|
echo " Edit with: ragenix -e secrets/$SECRET_PATH" >&2
|
||||||
@@ -63,7 +63,7 @@ let
|
|||||||
|
|
||||||
nameValuePair = name: value: { inherit name value; };
|
nameValuePair = name: value: { inherit name value; };
|
||||||
|
|
||||||
secretsPath = ./.;
|
secretsPath = ./secrets;
|
||||||
|
|
||||||
# Helper to convert SSH public key content to age public key
|
# Helper to convert SSH public key content to age public key
|
||||||
sshToAge =
|
sshToAge =
|
||||||
|
|||||||
8
secrets/usda-dash/default.nix
Normal file
8
secrets/usda-dash/default.nix
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Host-specific secret configuration for usda-dash
|
||||||
|
{
|
||||||
|
usda-vision-azure-env = {
|
||||||
|
mode = "0600";
|
||||||
|
owner = "root";
|
||||||
|
group = "root";
|
||||||
|
};
|
||||||
|
}
|
||||||
BIN
secrets/usda-dash/usda-vision-env.age
Normal file
BIN
secrets/usda-dash/usda-vision-env.age
Normal file
Binary file not shown.
@@ -20,8 +20,8 @@ let
|
|||||||
cfg = config.athenix.sw;
|
cfg = config.athenix.sw;
|
||||||
secretsPath = ../secrets;
|
secretsPath = ../secrets;
|
||||||
|
|
||||||
# Get the current hostname
|
# Get the fleet-assigned hostname (avoids issues with LXC empty hostnames)
|
||||||
hostname = config.networking.hostName;
|
hostname = config.athenix.host.name;
|
||||||
|
|
||||||
# Read all directories in ./secrets
|
# Read all directories in ./secrets
|
||||||
secretDirs = if builtins.pathExists secretsPath then builtins.readDir secretsPath else { };
|
secretDirs = if builtins.pathExists secretsPath then builtins.readDir secretsPath else { };
|
||||||
|
|||||||
Reference in New Issue
Block a user