Compare commits
75 Commits
remove-aut
...
agit-4042
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c3b96ac80 | ||
|
|
54793f79c4 | ||
|
|
9f93ad59e9 | ||
|
|
1e0860df22 | ||
|
|
8bb28b7ba7 | ||
|
|
b50fc9d6aa | ||
|
|
2138db856e | ||
|
|
f308ef7e8d | ||
|
|
430f2562c0 | ||
|
|
519b24ba0f | ||
|
|
4c0ad55e35 | ||
|
|
fae4d39a10 | ||
|
|
72dc352a3e | ||
|
|
cce4d561e4 | ||
|
|
acaf04175d | ||
|
|
98bf2f325b | ||
|
|
f7a5c99bb3 | ||
|
|
0d984488ed | ||
|
|
7dadcd99ff | ||
|
|
c4fe02ad0b | ||
|
|
3d20b41138 | ||
|
|
75a6aa810e | ||
|
|
4b20d6f56a | ||
|
|
eb47cf08b8 | ||
|
|
5f31825514 | ||
|
|
0a865eee6c | ||
|
|
6ee27bd8eb | ||
|
|
b6da0b5683 | ||
|
|
4c525c436a | ||
|
|
ac9ed0ddba | ||
|
|
0ac445facc | ||
|
|
f47db5f7c1 | ||
|
|
c8f1434b5f | ||
|
|
fbc7c1188f | ||
|
|
4f60dbfe28 | ||
|
|
c53874c525 | ||
|
|
cb1eb3ce20 | ||
|
|
1a771cca82 | ||
|
|
03b68afaa5 | ||
|
|
f80332a3f0 | ||
|
|
e785a6be4e | ||
|
|
f4a7016ece | ||
|
|
659d2a4c04 | ||
|
|
7e18dc6bb4 | ||
|
|
8ea465d331 | ||
|
|
f0322a8411 | ||
|
|
1f8fef726b | ||
|
|
c83d3a595e | ||
|
|
0adbf043de | ||
|
|
445b7a2027 | ||
|
|
7f840fde61 | ||
|
|
6de9a9a910 | ||
|
|
52c3006d9b | ||
|
|
100e01b32c | ||
|
|
1b23d5dcf3 | ||
|
|
0c432d5c25 | ||
|
|
143dfc99dc | ||
|
|
3835624040 | ||
|
|
c1a4de843a | ||
|
|
d9f2c1681f | ||
|
|
b4b6bf8e16 | ||
|
|
84e280e32d | ||
|
|
688aad716d | ||
|
|
d531ee2296 | ||
|
|
7d96e8455e | ||
|
|
12151b91dd | ||
|
|
2cb588d5a8 | ||
|
|
f6680a1f8b | ||
|
|
3f8203077e | ||
|
|
69b7b61cc7 | ||
|
|
111172e514 | ||
|
|
f796a7531c | ||
|
|
aabe253bf9 | ||
|
|
e14fbb66b1 | ||
|
|
ca0a56292e |
11
.gitignore
vendored
11
.gitignore
vendored
@@ -3,7 +3,6 @@
|
||||
.hypothesis
|
||||
out.log
|
||||
.coverage.*
|
||||
qubeclan
|
||||
pkgs/repro-hook
|
||||
testdir
|
||||
democlan
|
||||
@@ -20,9 +19,6 @@ nixos.qcow2
|
||||
# macOS stuff
|
||||
.DS_Store
|
||||
|
||||
# dream2nix
|
||||
.dream2nix
|
||||
|
||||
# python
|
||||
__pycache__
|
||||
.coverage
|
||||
@@ -32,13 +28,6 @@ __pycache__
|
||||
.ruff_cache
|
||||
htmlcov
|
||||
|
||||
# flatpak
|
||||
.flatpak-builder
|
||||
build
|
||||
build-dir
|
||||
repo
|
||||
.env
|
||||
|
||||
# node
|
||||
node_modules
|
||||
dist
|
||||
|
||||
@@ -36,29 +36,23 @@ in
|
||||
inherit (self) clanLib;
|
||||
clan-core = self;
|
||||
};
|
||||
nixosTests =
|
||||
lib.optionalAttrs (pkgs.stdenv.isLinux) {
|
||||
nixosTests = lib.optionalAttrs (pkgs.stdenv.isLinux) {
|
||||
|
||||
# Base Tests
|
||||
secrets = self.clanLib.test.baseTest ./secrets nixosTestArgs;
|
||||
borgbackup-legacy = self.clanLib.test.baseTest ./borgbackup-legacy nixosTestArgs;
|
||||
wayland-proxy-virtwl = self.clanLib.test.baseTest ./wayland-proxy-virtwl nixosTestArgs;
|
||||
# Base Tests
|
||||
secrets = self.clanLib.test.baseTest ./secrets nixosTestArgs;
|
||||
borgbackup-legacy = self.clanLib.test.baseTest ./borgbackup-legacy nixosTestArgs;
|
||||
wayland-proxy-virtwl = self.clanLib.test.baseTest ./wayland-proxy-virtwl nixosTestArgs;
|
||||
|
||||
# Container Tests
|
||||
container = self.clanLib.test.containerTest ./container nixosTestArgs;
|
||||
zt-tcp-relay = self.clanLib.test.containerTest ./zt-tcp-relay nixosTestArgs;
|
||||
matrix-synapse = self.clanLib.test.containerTest ./matrix-synapse nixosTestArgs;
|
||||
postgresql = self.clanLib.test.containerTest ./postgresql nixosTestArgs;
|
||||
# Container Tests
|
||||
container = self.clanLib.test.containerTest ./container nixosTestArgs;
|
||||
zt-tcp-relay = self.clanLib.test.containerTest ./zt-tcp-relay nixosTestArgs;
|
||||
matrix-synapse = self.clanLib.test.containerTest ./matrix-synapse nixosTestArgs;
|
||||
postgresql = self.clanLib.test.containerTest ./postgresql nixosTestArgs;
|
||||
|
||||
dummy-inventory-test = import ./dummy-inventory-test nixosTestArgs;
|
||||
dummy-inventory-test-from-flake = import ./dummy-inventory-test-from-flake nixosTestArgs;
|
||||
data-mesher = import ./data-mesher nixosTestArgs;
|
||||
}
|
||||
// lib.optionalAttrs (pkgs.stdenv.hostPlatform.system == "aarch64-linux") {
|
||||
# for some reason this hangs in an odd place in CI, but it works on my machine ...
|
||||
# on aarch64-linux it works though
|
||||
mumble = import ./mumble nixosTestArgs;
|
||||
};
|
||||
dummy-inventory-test = import ./dummy-inventory-test nixosTestArgs;
|
||||
dummy-inventory-test-from-flake = import ./dummy-inventory-test-from-flake nixosTestArgs;
|
||||
data-mesher = import ./data-mesher nixosTestArgs;
|
||||
};
|
||||
|
||||
packagesToBuild = lib.removeAttrs self'.packages [
|
||||
# exclude the check that checks that nothing depends on the repo root
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
ROOT=$(git rev-parse --show-toplevel)
|
||||
cd "$ROOT/pkgs/clan-cli"
|
||||
|
||||
# Set up custom git configuration for tests
|
||||
export GIT_CONFIG_GLOBAL=$(mktemp)
|
||||
git config --file "$GIT_CONFIG_GLOBAL" user.name "Test User"
|
||||
git config --file "$GIT_CONFIG_GLOBAL" user.email "test@example.com"
|
||||
export GIT_CONFIG_SYSTEM=/dev/null
|
||||
|
||||
# this disables dynamic dependency loading in clan-cli
|
||||
export CLAN_NO_DYNAMIC_DEPS=1
|
||||
|
||||
@@ -37,6 +43,9 @@
|
||||
jobs="$((jobs > 13 ? 13 : jobs))"
|
||||
|
||||
nix develop "$ROOT#clan-cli" -c bash -c "TMPDIR=/tmp python -m pytest -n $jobs -m impure ./clan_cli $@"
|
||||
|
||||
# Clean up temporary git config
|
||||
rm -f "$GIT_CONFIG_GLOBAL"
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
{
|
||||
pkgs,
|
||||
nixosLib,
|
||||
clan-core,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
nixosLib.runTest (
|
||||
{ ... }:
|
||||
let
|
||||
machines = [
|
||||
"peer1"
|
||||
"peer2"
|
||||
];
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
clan-core.modules.nixosVmTest.clanTest
|
||||
];
|
||||
|
||||
hostPkgs = pkgs;
|
||||
|
||||
name = "mumble";
|
||||
|
||||
defaults =
|
||||
{ pkgs, modulesPath, ... }:
|
||||
{
|
||||
imports = [
|
||||
(modulesPath + "/../tests/common/x11.nix")
|
||||
];
|
||||
|
||||
clan.services.mumble.user = "alice";
|
||||
environment.systemPackages = [ pkgs.killall ];
|
||||
};
|
||||
|
||||
clan = {
|
||||
directory = ./.;
|
||||
# TODO: container driver does not support: sleep, wait_for_window, send_chars, wait_for_text
|
||||
test.useContainers = false;
|
||||
inventory = {
|
||||
machines = lib.genAttrs machines (_: { });
|
||||
services = {
|
||||
mumble.default = {
|
||||
roles.server.machines = machines;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
enableOCR = true;
|
||||
|
||||
testScript = ''
|
||||
import time
|
||||
import re
|
||||
|
||||
|
||||
def machine_has_text(machine: Machine, regex: str) -> bool:
|
||||
variants = machine.get_screen_text_variants()
|
||||
# for debugging
|
||||
# machine.screenshot(f"/tmp/{machine.name}.png")
|
||||
for text in variants:
|
||||
print(f"Expecting '{regex}' in '{text}'")
|
||||
if re.search(regex, text) is not None:
|
||||
return True
|
||||
return False
|
||||
|
||||
start_all()
|
||||
|
||||
with subtest("Waiting for x"):
|
||||
peer1.wait_for_x()
|
||||
peer2.wait_for_x()
|
||||
|
||||
with subtest("Waiting for murmur"):
|
||||
peer1.wait_for_unit("murmur.service")
|
||||
peer2.wait_for_unit("murmur.service")
|
||||
|
||||
with subtest("Starting Mumble"):
|
||||
# starting mumble is blocking
|
||||
peer1.execute("mumble >&2 &")
|
||||
peer2.execute("mumble >&2 &")
|
||||
|
||||
with subtest("Wait for Mumble"):
|
||||
peer1.wait_for_window(r"Mumble")
|
||||
peer2.wait_for_window(r"Mumble")
|
||||
|
||||
with subtest("Wait for certificate creation"):
|
||||
peer1.wait_for_window(r"Mumble")
|
||||
peer2.wait_for_window(r"Mumble")
|
||||
|
||||
for i in range(20):
|
||||
time.sleep(1)
|
||||
peer1.send_chars("\n")
|
||||
peer1.send_chars("\n")
|
||||
peer2.send_chars("\n")
|
||||
peer2.send_chars("\n")
|
||||
if machine_has_text(peer1, r"Mumble Server Connect") and \
|
||||
machine_has_text(peer2, r"Mumble Server Connect"):
|
||||
break
|
||||
else:
|
||||
raise Exception("Timeout waiting for certificate creation")
|
||||
|
||||
with subtest("Check validity of server certificates"):
|
||||
peer1.execute("killall .mumble-wrapped")
|
||||
peer1.sleep(1)
|
||||
peer1.execute("mumble mumble://peer2 >&2 &")
|
||||
peer1.wait_for_window(r"Mumble")
|
||||
|
||||
for i in range(20):
|
||||
time.sleep(1)
|
||||
peer1.send_chars("\n")
|
||||
peer1.send_chars("\n")
|
||||
if machine_has_text(peer1, "Connected."):
|
||||
break
|
||||
else:
|
||||
raise Exception("Timeout waiting for certificate creation")
|
||||
|
||||
peer2.execute("killall .mumble-wrapped")
|
||||
peer2.sleep(1)
|
||||
peer2.execute("mumble mumble://peer1 >&2 &")
|
||||
peer2.wait_for_window(r"Mumble")
|
||||
|
||||
for i in range(20):
|
||||
time.sleep(1)
|
||||
peer2.send_chars("\n")
|
||||
peer2.send_chars("\n")
|
||||
if machine_has_text(peer2, "Connected."):
|
||||
break
|
||||
else:
|
||||
raise Exception("Timeout waiting for certificate creation")
|
||||
'';
|
||||
}
|
||||
)
|
||||
@@ -1,22 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDazCCAlOgAwIBAgIUCUjfNkF0CDhTKbO3nNczcsCW4qEwDQYJKoZIhvcNAQEL
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTM2NDZaFw0yNDA3
|
||||
MjcwOTM2NDZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQDCcdZEJvXJIeOKO5pF5XUFvUeJtCCiwfWvWS662bxc
|
||||
R/5MZucRLqfTNYo9aBv4NITw5kxZsTaaubmS4zSGQoTEAVzqzVdi3a/gNvsdVLb+
|
||||
7CivpmweLllX/OGsTL0kHPEI+74AYiTBjXfdWV1Y5T1tuwc3G8ATrguQ33Uo5vvF
|
||||
vcqsbTKcRZC0pB9O/nn4q03GsRdvlpaKakIhjMpRG/uZ3u7wtbyZ+WqjsjxZNfnY
|
||||
aMyPoaipFqX1v+L7GKlOj2NpyEZFVVwa2ZqhVSYXyDfpAWQFznwKGzD5mjtcyKym
|
||||
gnv/5LwrpH4Xj+JMt48hN+rPnu5vfXT8Y4KnID30OQW7AgMBAAGjUzBRMB0GA1Ud
|
||||
DgQWBBQBBO8Wp975pAGioMjkaxANAVInfzAfBgNVHSMEGDAWgBQBBO8Wp975pAGi
|
||||
oMjkaxANAVInfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAg
|
||||
F40MszTZXpR/A1z9B1CcXH47tNK67f8bCMR2dhvXODbpatwSihyxhQjtLb5R6kYH
|
||||
5Yq/B4yrh303j0CXaobCQ4nQH7zI7fhViww+TzW7vDhgM7ueEyyXrqCXt6JY8avg
|
||||
TuvIRtJSeWSQJ5aLNaYqmiwMf/tj9W3BMDpctGyLqu1WTSrbpYa9mA5Vudud70Yz
|
||||
DgZ/aqHilB07cVNqzVYZzRZ56WJlTjGzVevRgnHZqPiZNVrU13H6gtWa3r8aV4Gj
|
||||
i4F663eRAttj166cRgfl1QqpSG2IprNyV9UfuS2LlUaVNT3y0idawiJ4HhaA8pGB
|
||||
ZqMUUkA4DSucb6xxEcTK
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
AGE-SECRET-KEY-1UCXEUJH6JXF8LFKWFHDM4N9AQE2CCGQZGXLUNV4TKR5KY0KC8FDQ2TY4NX
|
||||
@@ -1,14 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICHTCCAaKgAwIBAgIIT2gZuvqVFP0wCgYIKoZIzj0EAwIwSjESMBAGA1UEChMJ
|
||||
U3luY3RoaW5nMSAwHgYDVQQLExdBdXRvbWF0aWNhbGx5IEdlbmVyYXRlZDESMBAG
|
||||
A1UEAxMJc3luY3RoaW5nMB4XDTIzMTIwNjAwMDAwMFoXDTQzMTIwMTAwMDAwMFow
|
||||
SjESMBAGA1UEChMJU3luY3RoaW5nMSAwHgYDVQQLExdBdXRvbWF0aWNhbGx5IEdl
|
||||
bmVyYXRlZDESMBAGA1UEAxMJc3luY3RoaW5nMHYwEAYHKoZIzj0CAQYFK4EEACID
|
||||
YgAEBAr1CsciwCa0vi7eC6xxuSGijY3txbjtsyFanec/fge4oJBD3rVpaLKFETb3
|
||||
TvHHsuvblzElcP483MEVq6FMUoxwuL9CzTtpJrRhtwSmAs8AHLFu8irVn8sZjgkL
|
||||
sXMho1UwUzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG
|
||||
AQUFBwMCMAwGA1UdEwEB/wQCMAAwFAYDVR0RBA0wC4IJc3luY3RoaW5nMAoGCCqG
|
||||
SM49BAMCA2kAMGYCMQDbrtLgfcyMMIkNQn+PJe9DHYAqj8C47LQcWuIY/nekhOu0
|
||||
aUfKctEAwyBtI60Y5zcCMQCEdgD/6CNBh7Qqq3z3CKPhlrpxHtCO5tNw17k0jfdH
|
||||
haCwJInHZvZgclHk4EtFpTw=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,6 +0,0 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIGkAgEBBDA14Nqo17Xs/xRLGH2KLuyzjKp4eW9iWFobVNM93RZZbECT++W3XcQc
|
||||
cEc5WVtiPmWgBwYFK4EEACKhZANiAAQECvUKxyLAJrS+Lt4LrHG5IaKNje3FuO2z
|
||||
IVqd5z9+B7igkEPetWlosoURNvdO8cey69uXMSVw/jzcwRWroUxSjHC4v0LNO2km
|
||||
tGG3BKYCzwAcsW7yKtWfyxmOCQuxcyE=
|
||||
-----END EC PRIVATE KEY-----
|
||||
@@ -1,22 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDazCCAlOgAwIBAgIUfENbTtH5nr7giuawwQpDYqUpWJswDQYJKoZIhvcNAQEL
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTQxNDNaFw0yNDA3
|
||||
MjcwOTQxNDNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQCfP6cZhCs9jOnWqyQP12vrOOxlBrWofYZFf9amUA24
|
||||
AfE7oGcSfkylanmkxzvGqQkhgLAvkHZj/GEvHujKyy8PgcEGP+pwmsfWNQMvU0Dz
|
||||
j3syjWOTi3eIC/3DoUnHlWCT2qCil/bjqxgU1l7fO/OXUlq5kyvIjln7Za4sUHun
|
||||
ixe/m96Er6l8a4Mh2pxh2C5pkLCvulkQhjjGG+R6MccH8wwQwmLg5oVBkFEZrnRE
|
||||
pnRKBI0DvA+wk1aJFAPOI4d8Q5T7o/MyxH3f8TYGHqbeMQFCKwusnlWPRtrNdaIc
|
||||
gaLvSpR0LVlroXGu8tYmRpvHPByoKGDbgVvO0Bwx8fmRAgMBAAGjUzBRMB0GA1Ud
|
||||
DgQWBBR7r+mQWNUZ0TpQNwrwjgxgngvOjTAfBgNVHSMEGDAWgBR7r+mQWNUZ0TpQ
|
||||
NwrwjgxgngvOjTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCO
|
||||
7B4s6uQEGE8jg3CQgy76oU/D8sazGcP8+/E4JLHSc0Nj49w4ztSpkOVk2HyEtzbm
|
||||
uR3TreIw+SfqpbiOI/ivVNDbEBsb/vEeq7qPzDH1Bi72plHZNRVhNGGV5rd7ibga
|
||||
TkfXHKPM9yt8ffffHHiu1ROvb8gg2B6JbQwboU4hvvmmorW7onyTFSYEzZVdNSpv
|
||||
pUtKPldxYjTnLlbsJdXC4xyCC4PrJt2CC0n0jsWfICJ77LMxIxTODh8oZNjbPg6r
|
||||
RdI7U/DsD+R072DjbIcrivvigotJM+jihzz5inZwbO8o0WQOHAbJLIG3C3BnRW3A
|
||||
Ek4u3+HXZMl5a0LGJ76u
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICHjCCAaOgAwIBAgIJAKbMWefkf1rVMAoGCCqGSM49BAMCMEoxEjAQBgNVBAoT
|
||||
CVN5bmN0aGluZzEgMB4GA1UECxMXQXV0b21hdGljYWxseSBHZW5lcmF0ZWQxEjAQ
|
||||
BgNVBAMTCXN5bmN0aGluZzAeFw0yMzEyMDYwMDAwMDBaFw00MzEyMDEwMDAwMDBa
|
||||
MEoxEjAQBgNVBAoTCVN5bmN0aGluZzEgMB4GA1UECxMXQXV0b21hdGljYWxseSBH
|
||||
ZW5lcmF0ZWQxEjAQBgNVBAMTCXN5bmN0aGluZzB2MBAGByqGSM49AgEGBSuBBAAi
|
||||
A2IABFZTMt4RfsfBue0va7QuNdjfXMI4HfZzJCEcG+b9MtV7FlDmwMKX5fgGykD9
|
||||
FBbC7yiza3+xCobdMb5bakz1qYJ7nUFCv1mwSDo2eNM+/XE+rJmlre8NwkwGmvzl
|
||||
h1uhyqNVMFMwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
|
||||
BgEFBQcDAjAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuCCXN5bmN0aGluZzAKBggq
|
||||
hkjOPQQDAgNpADBmAjEAwzhsroN6R4/quWeXj6dO5gt5CfSTLkLee6vrcuIP5i1U
|
||||
rZvJ3OKQVmmGG6IWYe7iAjEAyuq3X2wznaqiw2YK3IDI4qVeYWpCUap0fwRNq7/x
|
||||
4dC4k+BOzHcuJOwNBIY/bEuK
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,6 +0,0 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIGkAgEBBDCXHGpvumKjjDRxB6SsjZOb7duw3w+rdlGQCJTIvRThLjD6zwjnyImi
|
||||
7c3PD5nWtLqgBwYFK4EEACKhZANiAARWUzLeEX7HwbntL2u0LjXY31zCOB32cyQh
|
||||
HBvm/TLVexZQ5sDCl+X4BspA/RQWwu8os2t/sQqG3TG+W2pM9amCe51BQr9ZsEg6
|
||||
NnjTPv1xPqyZpa3vDcJMBpr85Ydboco=
|
||||
-----END EC PRIVATE KEY-----
|
||||
@@ -1 +0,0 @@
|
||||
AGE-SECRET-KEY-1UCXEUJH6JXF8LFKWFHDM4N9AQE2CCGQZGXLUNV4TKR5KY0KC8FDQ2TY4NX
|
||||
@@ -1,22 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDazCCAlOgAwIBAgIUCUjfNkF0CDhTKbO3nNczcsCW4qEwDQYJKoZIhvcNAQEL
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTM2NDZaFw0yNDA3
|
||||
MjcwOTM2NDZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQDCcdZEJvXJIeOKO5pF5XUFvUeJtCCiwfWvWS662bxc
|
||||
R/5MZucRLqfTNYo9aBv4NITw5kxZsTaaubmS4zSGQoTEAVzqzVdi3a/gNvsdVLb+
|
||||
7CivpmweLllX/OGsTL0kHPEI+74AYiTBjXfdWV1Y5T1tuwc3G8ATrguQ33Uo5vvF
|
||||
vcqsbTKcRZC0pB9O/nn4q03GsRdvlpaKakIhjMpRG/uZ3u7wtbyZ+WqjsjxZNfnY
|
||||
aMyPoaipFqX1v+L7GKlOj2NpyEZFVVwa2ZqhVSYXyDfpAWQFznwKGzD5mjtcyKym
|
||||
gnv/5LwrpH4Xj+JMt48hN+rPnu5vfXT8Y4KnID30OQW7AgMBAAGjUzBRMB0GA1Ud
|
||||
DgQWBBQBBO8Wp975pAGioMjkaxANAVInfzAfBgNVHSMEGDAWgBQBBO8Wp975pAGi
|
||||
oMjkaxANAVInfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAg
|
||||
F40MszTZXpR/A1z9B1CcXH47tNK67f8bCMR2dhvXODbpatwSihyxhQjtLb5R6kYH
|
||||
5Yq/B4yrh303j0CXaobCQ4nQH7zI7fhViww+TzW7vDhgM7ueEyyXrqCXt6JY8avg
|
||||
TuvIRtJSeWSQJ5aLNaYqmiwMf/tj9W3BMDpctGyLqu1WTSrbpYa9mA5Vudud70Yz
|
||||
DgZ/aqHilB07cVNqzVYZzRZ56WJlTjGzVevRgnHZqPiZNVrU13H6gtWa3r8aV4Gj
|
||||
i4F663eRAttj166cRgfl1QqpSG2IprNyV9UfuS2LlUaVNT3y0idawiJ4HhaA8pGB
|
||||
ZqMUUkA4DSucb6xxEcTK
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCcdZEJvXJIeOK
|
||||
O5pF5XUFvUeJtCCiwfWvWS662bxcR/5MZucRLqfTNYo9aBv4NITw5kxZsTaaubmS
|
||||
4zSGQoTEAVzqzVdi3a/gNvsdVLb+7CivpmweLllX/OGsTL0kHPEI+74AYiTBjXfd
|
||||
WV1Y5T1tuwc3G8ATrguQ33Uo5vvFvcqsbTKcRZC0pB9O/nn4q03GsRdvlpaKakIh
|
||||
jMpRG/uZ3u7wtbyZ+WqjsjxZNfnYaMyPoaipFqX1v+L7GKlOj2NpyEZFVVwa2Zqh
|
||||
VSYXyDfpAWQFznwKGzD5mjtcyKymgnv/5LwrpH4Xj+JMt48hN+rPnu5vfXT8Y4Kn
|
||||
ID30OQW7AgMBAAECggEAGVKn+/Iy+kG+l2cRvV6XseqnoWhjA69M5swviMgIfuAl
|
||||
Xx/boeI4mwoS+dJQKi/0zEbB1MB+gwIDB/0s/vs0vS4MQswBQG/skr+2TmiU+Hgb
|
||||
CF0dIYUZv5rAbScFTumx/mCCqxwc+1QIMzyLKqOYL203EFc92ZJGEVT4th321haZ
|
||||
8Wd+dllcYAb7BbEeBhCrTqRe9T3zt5reZgtZTquTF5hGm8EAyBp6rLjZK7dyZ9dd
|
||||
gyIsDbWgPC9vkRc6x/eANn70hgDbYOuoXwAP/qIFnWLL1Zzy8LKUyOsSgQ91S3S3
|
||||
Il4Lt6lEyU3+61MsCYss7jDoP/7REEjz5h6gfxlFSQKBgQD9u8nhHuwte4/d9VNU
|
||||
rhSBW9h8IJzwPif/eS8vh9VaS2SjR2dDCcHg6rGYKnexeEzUcx56aQMA+p3nRJwy
|
||||
Uwnx5BfEWs9FO6yPR8VEI0a2sBp+hoWKJX/Lvat+QCs6IFuGmlQpczD7/RYAkhG4
|
||||
mwyt/ymqzjukb9mFaeYIltOfPwKBgQDELnkH1ChTUH5u3HgDoelFbzR18okz6dxH
|
||||
urMbfZMAl8W5h2zAvHsAX5qxyHHankOUsiH2y3BrAgqQtTuIA2a5W7j+yHBkYiEZ
|
||||
EUNeI9YNA0KU+wwZpVVvRGUsRB5SUBo5LlcSYmX/V32f0oU5Np44i0vjl3Ju8esx
|
||||
2MLfj1A2hQKBgQDCxtZZZ0h8Pb8Z7wpSFfQNvXi5CLwQvFYuClQLk6VXVErkAJsn
|
||||
XiUjyGYeXnNVm/i2mcyKwXQZ20k90HBrPU2ED8mi5Ob5ya5Uqw6mmMHe2d7sw81d
|
||||
WB37RBWSrCXC0DYSZQQ4cYHn3sd2Fqtd4EBijV7qDLjCKU582OdKLqYzNwKBgH31
|
||||
UKQkJZgIkIThbPT4GewI0GgCRvFb76DmUGUQJTg2Oi86siq1WUwOFiabie5RuxZX
|
||||
oNLyH8W008/BbO2RMX1FVOvRCciJ8LJFkTl6TM6iDzfUUBqPOuFryoG3Yrh60btw
|
||||
81rMbqyZIgFhi0QGu2OWnC0Oadyt2tJwV/5t55R5AoGBAPspZttDmOzVkAJDSn9Z
|
||||
iByYt1KmwBQ6l7LpFg33a7ds9zWqW4+i6r0PzXvSewf/z69L0cAywSk5CaJJjDso
|
||||
dTlNMqwux01wd6V+nQGR871xnsOg+qzgJ565TJZelWgRmNRUooi4DMp5POJA33xp
|
||||
rqAISUfW0w2S+q7/5Lm0QiJE
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,22 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDazCCAlOgAwIBAgIUfENbTtH5nr7giuawwQpDYqUpWJswDQYJKoZIhvcNAQEL
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTQxNDNaFw0yNDA3
|
||||
MjcwOTQxNDNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQCfP6cZhCs9jOnWqyQP12vrOOxlBrWofYZFf9amUA24
|
||||
AfE7oGcSfkylanmkxzvGqQkhgLAvkHZj/GEvHujKyy8PgcEGP+pwmsfWNQMvU0Dz
|
||||
j3syjWOTi3eIC/3DoUnHlWCT2qCil/bjqxgU1l7fO/OXUlq5kyvIjln7Za4sUHun
|
||||
ixe/m96Er6l8a4Mh2pxh2C5pkLCvulkQhjjGG+R6MccH8wwQwmLg5oVBkFEZrnRE
|
||||
pnRKBI0DvA+wk1aJFAPOI4d8Q5T7o/MyxH3f8TYGHqbeMQFCKwusnlWPRtrNdaIc
|
||||
gaLvSpR0LVlroXGu8tYmRpvHPByoKGDbgVvO0Bwx8fmRAgMBAAGjUzBRMB0GA1Ud
|
||||
DgQWBBR7r+mQWNUZ0TpQNwrwjgxgngvOjTAfBgNVHSMEGDAWgBR7r+mQWNUZ0TpQ
|
||||
NwrwjgxgngvOjTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCO
|
||||
7B4s6uQEGE8jg3CQgy76oU/D8sazGcP8+/E4JLHSc0Nj49w4ztSpkOVk2HyEtzbm
|
||||
uR3TreIw+SfqpbiOI/ivVNDbEBsb/vEeq7qPzDH1Bi72plHZNRVhNGGV5rd7ibga
|
||||
TkfXHKPM9yt8ffffHHiu1ROvb8gg2B6JbQwboU4hvvmmorW7onyTFSYEzZVdNSpv
|
||||
pUtKPldxYjTnLlbsJdXC4xyCC4PrJt2CC0n0jsWfICJ77LMxIxTODh8oZNjbPg6r
|
||||
RdI7U/DsD+R072DjbIcrivvigotJM+jihzz5inZwbO8o0WQOHAbJLIG3C3BnRW3A
|
||||
Ek4u3+HXZMl5a0LGJ76u
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCfP6cZhCs9jOnW
|
||||
qyQP12vrOOxlBrWofYZFf9amUA24AfE7oGcSfkylanmkxzvGqQkhgLAvkHZj/GEv
|
||||
HujKyy8PgcEGP+pwmsfWNQMvU0Dzj3syjWOTi3eIC/3DoUnHlWCT2qCil/bjqxgU
|
||||
1l7fO/OXUlq5kyvIjln7Za4sUHunixe/m96Er6l8a4Mh2pxh2C5pkLCvulkQhjjG
|
||||
G+R6MccH8wwQwmLg5oVBkFEZrnREpnRKBI0DvA+wk1aJFAPOI4d8Q5T7o/MyxH3f
|
||||
8TYGHqbeMQFCKwusnlWPRtrNdaIcgaLvSpR0LVlroXGu8tYmRpvHPByoKGDbgVvO
|
||||
0Bwx8fmRAgMBAAECggEACAkjOnNj5zA0IIP0RuRc6rqtmw9ynTTwUJN51lyVxKI8
|
||||
dQDMEq/S2En+J2VyS7z92/XtbgkBIFx83u7VWl5UWpj2j4UsJFB7IwD7zyiJT4D+
|
||||
+3cM/kX8Wx4XyQZbfbm47N0MXAgFCkn45hxHH0acLReXwmN9wxoDyl7AIjZRdwvG
|
||||
Qq0rnOnIc8kkkew7L6AiFwQS8b77eyzua3d6moKXN9hU/kfiJ6YUFG/WLe0pmQA1
|
||||
HbF27YghfeLnYUt50oDuX6jF6CzQhflchWVq/wn8/cxEpg/RMicWE8ulrTk7o27l
|
||||
JwCrHrhYEBsPuZO4mxX/DHrAMmhTeFjLaV5bQlz0PQKBgQDgRPSOEixYnKz9iPs/
|
||||
EDTlji5LA3Rm6TytRCNsjYY6Trw60KcvYqwyDUCiEjruvOQ9mqgBiQm1VHSalrG3
|
||||
RcbVfpEMouyZbEwmTjS8KdOi5x4Z6AX+4yWDN31jX3b8sktgbxV/HRdg3sA3q7MJ
|
||||
vExTUuoXg57W+FepIZ+XlhSoQwKBgQC1x6UMAlAeW45/yUUm/LFRcCgb/bdCQx+e
|
||||
hSb8w3jdvVoNWgx1j7RsjjFKaZUnseK3qQvVfCm4Qjvlz6MpKDxslaUYuR162Ku0
|
||||
e153z/xc7XRoXyPyPLdGZFlWii30jirB7ZqPdyz6mwlWwqdImNerbUqdFt9R8bId
|
||||
pYsyHB5zmwKBgBjYCq9iW/9E+/TqI8sMpI95fK9app5v4AThs3rnAqOa7Ucmrh6V
|
||||
s7Wnui06D8U6r54Tb+EbqTOpM3Gcl/tRg4FLEA5yTfuA/76Ok1D04Tj+mVsNVPyz
|
||||
dQhgMUe835WGusroA12df2V/x5NjNeYyMdJZMQ2ByyrNQAjAbMmCGq+5AoGBAIj8
|
||||
ERFysMOfxUvg9b7CkDFJrsAhOzew86P2vYGfIHchGTqUkG0LRTDFGrnzxNXsBGjY
|
||||
+DUB40Kajx7IkTETxC0jvA1ceq23l/VjPrZVQt0YiC+a+rCyNn7SYkyHxsfTVr9b
|
||||
ea0BZyDXMntyJrPbkjL6Ik8tDE9pLwuOU84ISJ5fAoGAZ2+Ams/VhdZj/wpRpMky
|
||||
K4jtS4nzbCmJzzTa6vdVV7Kjer5kFxSFFqMrS/FtJ/RxHeHvxdze9dfGu9jIdTKK
|
||||
vSzbyQdHFfZgRkmAKfcoN9u567z7Oc74AQ9UgFEGdEVFQUbfWOevmr8KIPt8nDQK
|
||||
J9HuVfILi1kH0jzDd/64TvA=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,6 +0,0 @@
|
||||
[
|
||||
{
|
||||
"publickey": "age1987metkajgdefk0sfhjqjjtczy9eu2lsg700rwcac6hhy2alhdsshjmpw8",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
@@ -1,6 +0,0 @@
|
||||
[
|
||||
{
|
||||
"publickey": "age1fndalxxeduekn5s8q3znl73vjfx2n8kydylyrc2j3aurc93pypvs6pcql4",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:TfEsytctWPCLuo/icbicgRfy7O/txYCllTiLiUlusagGShZyXyIR46TNL9E4XWI2Lce9hIn8zczOdUWaEFPuXcvRMMMWILY3DzI=,iv:zDdq0rdYz/KIwKvIiu9MvKyX9v1pWYxZG3F/7KllBa0=,tag:mTPJGmJ+tKrgYaCZXJ37Nw==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2MmFpbUJuNzRnNGRlQXcy\naEhRanpHbjZpbFZxVkZ2TXFJWk8xYm9lYmlVCmVhRFdDZyt4SjJick1CdnZseWx1\nMGdvaTBYekdBeFUyaHEvTzNJVVM4TncKLS0tIG8rZ1kyTFJTRndQNFVXOC9OTTc5\nZHZGVW1FTzlLQ0RRcjNWeEpVWmVKMDgK7UDm509nexdHqG2xU8CBDZkRStjQIAAN\nDmOz5A8uWpIiyvU2LdOBcc/FQKHaXjB7OAmfT03nJccOeqSF2N3N3g==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-04-16T16:40:26Z",
|
||||
"mac": "ENC[AES256_GCM,data:5Qe20lbqERvSM5fDY9Orhrtv2U6zholh6uHMq0CqV1OOg+vVWSlqTqJrtz2rD/qQTUECRKzWUHB1D/kgLrJ33lRoEMqrhjmvBfxtDnNjLzoYITlLcYOm9qiv3gOqcrpdBKW10YyNlGP/+Q377Lfbo8tcZ8nmuaT8qA9PYr+AKcs=,iv:IIJEFAvoX9SY3jvkD0xVe1/L6iRPMyzmxeRmpGvZI0I=,tag:1D3BBUjj1suNeL+mVYDiKw==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.1"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../../users/admin
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:NI9y5OdFkBgHf+wfn+ISDL11nh/ud+1RV5SPC64TV4Hvg0w8GKkmjJI5uiGDGI1+FfWwnHWOFexavtM2ZJr/cWfhA6dGKvzrKJc=,iv:itiZFGsGEZD/SH42akh1CLCDbuZxMSj05quMNKwvKg4=,tag:v36FGDDHIuFaABHG9we6ag==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByUVVJek9Ha2ljMkt4U2pi\nSmRRd2g2R0VXZGlySG5TT1E1czFpaWFyNlFjCmRJOThCQWlCNDZnRVRFVHpSTzBW\nOWZCUU5jK2dGQTloOEZMUFFVdk04cXMKLS0tIDVzSTdXRk1UZ3psd29kdnVUcitM\nbFlqb0srUGFCVUhlNzU1dUdTTUkwN0UKAIslz1WCMZWrE+aLPJjeM+wZSXMmwnqx\nyRZT5vVzCPWv2r8sbIjhi1rFbkfF+NXHkzNZD9NS4zddwsDsz5HO1g==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-04-16T16:40:48Z",
|
||||
"mac": "ENC[AES256_GCM,data:2iDDnVdLPWxYcjdZrDlTb8PzPVOPEZ06QXCFvnZ2gf8ioXPiSY69ZAHRHTGpqCEp5Ve7qTIELbNja2TGU0ONLIcIRWyzqgc4q+G3n2V5fYQURW114pzaK0Ct6r6yR9oZQy8H66uEYQafkyuN2R9++3w5G0LGj8UovPcYQqNEQVo=,iv:TkCAdIgjRpZpsnhhvTfMqGVD/IveFyobYa9SExFWcC4=,tag:4RLhumGqeLT15waqHT0mRg==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.1"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../../users/admin
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"type": "age"
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDfzCCAmegAwIBAgIUH9AKYdV75FHHBcR4mgfTZB/7eEcwDQYJKoZIhvcNAQEL
|
||||
BQAwaDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM
|
||||
DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBENsYW4xDTALBgNVBAsMBENsYW4xDjAM
|
||||
BgNVBAMMBXBlZXIxMB4XDTI1MDQxNjE2NDAzN1oXDTI1MDUxNjE2NDAzN1owaDEL
|
||||
MAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBG
|
||||
cmFuY2lzY28xDTALBgNVBAoMBENsYW4xDTALBgNVBAsMBENsYW4xDjAMBgNVBAMM
|
||||
BXBlZXIxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA80mo3OFSaW8F
|
||||
Ni/W7WZ70bJoGGFPFK17kiRgPu6+ghDiinmzlAQOt8A/u+egl4FsvT9Oz99TjCN1
|
||||
zkK3I74ItKmumpGKGPp92bpm62vQZa4g861xKqLlcbOwJwcfofwa8r4PhhjDhdXS
|
||||
k9vsgiwy0N5FEga79QbDEO/qwSvY+O8yKNG+lNXeOetymKvVbudL8A0je150vmpg
|
||||
oYfYjH57Oa7DpGaIrOpbZsmaBlYHD5dhfJbuX0Gxuq42gkfcBtxv3NbY0NoPVZFV
|
||||
jOvhVPyV9Xme/3JAQUSti+Fd2ZfJ+Ayl90ElA5wk25T1JBEEnMYQlQVBqPawX87C
|
||||
i1EtOysfxQIDAQABoyEwHzAdBgNVHQ4EFgQUFtjyWNCF1Yxd8ymIZ4kE9fXMY5Yw
|
||||
DQYJKoZIhvcNAQELBQADggEBAAHiQcWDvZjN2VTaWY2cQMYy3m8wkdoJTR20uV2z
|
||||
MpjY4KwCiMzTtsFe2LhiYMYFETwqHpG+B6ElOghh/+F8l96vQRbcVI9I3XTKs0G4
|
||||
+zdUtMOyB2XZumB4HBQa3PiXXrA4kAGJV88y5QC4UkZMw6SfwjW8OrtQ5Jim4vUB
|
||||
PZxY75ZIjw4JhknTqKNua7xehY4TBghRrGZAlD4eon7Yc5bIew6Gw5LHIoszOZgk
|
||||
9CFEo1XLN5z8aL9L+V8dh2DNNqF4KiXCRNgwqLmLoepL2Xptd90AOZsBI9mGxMP9
|
||||
YUPsnzcGqcat1x6Fi2Guw++ESDxUp6qKjMGAxPzSXje/TiM=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/machines/peer1
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data: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,iv:IZYhje9AgGRe0gQcodG/PQAaRBipBC/7F8qAkG35cxc=,tag:jpXpm1eghy/668gT0bmqMA==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1987metkajgdefk0sfhjqjjtczy9eu2lsg700rwcac6hhy2alhdsshjmpw8",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3MDdhSTZMbXlSdDVNVVZU\ndkFyVVI0eDhOUHZRU2FFalVNR3g5dUY5T25FCnl0aXpZRVpaR1hvdm5kSHplOE0x\nckloNFF3OVhNTnAxY2ZpZjNFV3plVXMKLS0tIG4yU0w2c1VGbDVCTUhYbjVrMXhr\nb0dpUnp2YUFWSERSRTVVK3g0WTNKWE0KpUfYS71F/1J1G38/ymd/+bWhABmze1GC\nehgSMymmVdsq+ZjHdJ1XcCyecsn/9aFcaZkEbASiLU8ecLNQOEGgRQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWeXBUOU13M2VvZVNBNUZW\nMy9VV1dMV1FlQU9qekhZWitwb3JISTFwdENBCnB5ZHpNK29DRHBoZ2M4dEJ6UVpq\nWHFOM1lYS0ROQ2NpSTNUdkZqUkorWGsKLS0tIDhaalVJNE1oU0N3WUtodnlsQWla\nUTVmTnhPTHVCWXUyK1ZESGR1Ym5CMXcK3YqyKO/FTdxcxVy5zBGg+JCOWMBOxqd2\n9+FgUJaYaizGy+HLpP5jgtjgz7k504yqEQCo9aQ1CzbvNHom5tAu7A==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-04-16T16:40:41Z",
|
||||
"mac": "ENC[AES256_GCM,data:R8fWg7Vwq2mnjbTTtyYuLWwrmB6TZYZVx9xPcO5NOvGAABNIxtAVSe9yTpV25OlJiXruTNhPHDxfjwDW8Nad47Sd9fV9QzH36uygT9DOaVrrOD/TH5ojvpCuognofuJ8YHgUsq+yhiQs0QKi5efUrtRVDcXXr8s/UazyuG3vYzk=,iv:eBpSr8GKvG51govZWtqTVMWsWZDctDQ2vVgMm/jq62U=,tag:Yth78awXPAPa/7J+WxTDug==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.1"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -1,21 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDfzCCAmegAwIBAgIUYuUk46fwZ4CBcJ40NWnT9VDIEPUwDQYJKoZIhvcNAQEL
|
||||
BQAwaDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM
|
||||
DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBENsYW4xDTALBgNVBAsMBENsYW4xDjAM
|
||||
BgNVBAMMBXBlZXIyMB4XDTI1MDQxNjE2NDA1OVoXDTI1MDUxNjE2NDA1OVowaDEL
|
||||
MAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBG
|
||||
cmFuY2lzY28xDTALBgNVBAoMBENsYW4xDTALBgNVBAsMBENsYW4xDjAMBgNVBAMM
|
||||
BXBlZXIyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA45nKnn0r3HwU
|
||||
qqSRuOXbou8zpdf+5i+e1h7pmunXR7WPxPBP09t6i+99BO27GcID59zGMquabpNS
|
||||
dFhj+p+KZkqN+4sokZmyBU1civQqiwX2n5KtoaG0fU3gFFK6pfx3OQawQ6mJ50GU
|
||||
HhA2R3CuA0rXcssr6oPynj9z6pbaL7mKckOWE804xIWZuMEoWNdQEKmUmE5d1ioa
|
||||
edlblzwhqZSS+zAAeUvmb+YUEL6T54lCYYqPPnmwmiwfYFSBGu/SGyFtIijbCuIZ
|
||||
TJMDzzutx1/3Dsv2pOKC0uPb5qRcmdRePAzgBFSna4MNgfbpGHFkGPJgjiue0VIC
|
||||
qyedlpF5UQIDAQABoyEwHzAdBgNVHQ4EFgQUuIeLdxGVyhFbgFRtFbPIIJWw1R0w
|
||||
DQYJKoZIhvcNAQELBQADggEBAFj26XejazrXOfa67o8vGoZrR2TGXOLFWFeplO8B
|
||||
29AruG9poH+sInyxYo1RWAQLQMfDud/yGg73EeYylULbG1bBznKYLLHdvy4l6eXt
|
||||
SEVkEMruH0Kw93zt+NqvSO3bHCX+la1rjizyDcD4iu93xUg2uPSBmVpVpW/aeBCN
|
||||
3eF4FbBocUexmIWaygmMPY5yFY2tAf+OinBf4uSWcKEpFikIqAxQWRSDMWm8xFwY
|
||||
CG7rhfpwDauagpZtkjKkrrRedhdfGiXbxOVtYlBULuUMOggEI+ElpbD0UhyEYCsD
|
||||
XoJn7AOC0sYCGpj2F1ESwFX/5EhyciLjMuVwohFVcyWWg+Q=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/machines/peer2
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data: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,iv:zNaVGK5hNxziOoPTbwaUhUwBuFbCiGNrfVMpeMxL3JI=,tag:6v8Hf4Symd1T16MOEChtcA==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1fndalxxeduekn5s8q3znl73vjfx2n8kydylyrc2j3aurc93pypvs6pcql4",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1VGVjRkdJOGx3c05YM28y\nM3dCbkU4TXBHK1VVOFFkY3FQVk0rQVp0d1g0CnZPR3FtUGlCb2lKSVc1Z3VtM0JM\nV1ZtZ3NVVndvak43cStIRWZxWldKSncKLS0tIEdJVHFFTzdaNklLVHdURndGa3Qy\nc2lEZ1hER3dGL0FKNUZrSkxMOXMvOGsKHGJ44Ey6mR3rV6NPPmn/QTsyjL08wCzu\nkUdD0jgSMLwInX5R9Gh9+Zbc9NIfEgSzLr6up6UlgW/4iWvM4oFPRg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjcHVweTFZenhZZzVDZ2ts\nTnNxNkZLWnVQRmpoa0ZldHpxdWt0Sy9jRVFFClExS2FMM3hiSlRQR2lmb25RTEo0\nRTRGdmxCaXJoeXdNaVU3cGRIRFlibWsKLS0tIFFzVFhCR2hSOStYNk5yNmc5UkZl\nTHdWSUZTZUIyUEp2OFR0SFpzMzFFd0EKlsRWNJjapPefXxyuUtFWlPs/UIC9V1N7\nF7Ek+TAKl11SwGGA2qla1yvnDOxkZvFg7gWsurZeEBH4PuPZ1OE/Yg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-04-16T16:41:03Z",
|
||||
"mac": "ENC[AES256_GCM,data:1DcuXden9WAF3frVjOMgpt0nniqiGEAA4SubPLk86GODEaOXxZSVStX1rr0GCF0t0tR4O4jl4cnRvZHF9Zjj7smA5Wf8jPpbSCrZX4oBo/HP3UU+A78yxSrj4gmoeH4m/aaJv0co77Vwcm/HglE6Q89Oc9BUqE2e4FGVmDUZTws=,iv:OAa2hvuw6aUcp3qKkRpDeLMDcq9Kkn/Bc+86DzV5h5g=,tag:wVrs9oyfaCAv3gZxsxbMPg==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.1"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -13,7 +13,12 @@ in
|
||||
description = "Flake reference";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
warnings = [
|
||||
"The clan.auto-upgrade module is deprecated and will be removed on 2025-07-15. Please migrate to using the system.autoUpgrade NixOS option directly."
|
||||
];
|
||||
|
||||
system.autoUpgrade = {
|
||||
inherit (cfg) flake;
|
||||
enable = true;
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
warnings = [
|
||||
"The clan.deltachat module is deprecated and will be removed on 2025-07-15. Please migrate to user-maintained configuration."
|
||||
];
|
||||
|
||||
networking.firewall.interfaces."zt+".allowedTCPPorts = [ 25 ]; # smtp with other hosts
|
||||
environment.systemPackages = [ pkgs.deltachat-desktop ];
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
_: {
|
||||
|
||||
warnings = [
|
||||
"The clan.ergochat module is deprecated and will be removed on 2025-07-15. Please migrate to user-maintained configuration."
|
||||
];
|
||||
|
||||
services.ergochat = {
|
||||
enable = true;
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
] "Importing the module will already enable the service.")
|
||||
];
|
||||
config = {
|
||||
warnings = [
|
||||
"The clan.heisenbridge module is deprecated and will be removed on 2025-07-15. Please migrate to user-maintained configuration."
|
||||
];
|
||||
services.heisenbridge = {
|
||||
enable = true;
|
||||
homeserver = "http://localhost:8008"; # TODO: Sync with matrix-synapse
|
||||
|
||||
@@ -81,15 +81,7 @@ in
|
||||
})
|
||||
{
|
||||
warnings = [
|
||||
''
|
||||
The clan module `iwd` is deprecated and replaced by the clan service `wifi`
|
||||
Please migrate your config to the new service (see: https://docs.clan.lol/reference/clanServices/wifi/)
|
||||
|
||||
To keep passwords after migrating the config, use:
|
||||
clan vars get <your-machine> iwd.<network-name>/ssid | clan vars set <your-machine> wifi.<network-name>/network-name
|
||||
and:
|
||||
clan vars get <your-machine> iwd.<network-name>/password | clan vars set <your-machine> wifi.<network-name>/password
|
||||
''
|
||||
"The clan.iwd module is deprecated and will be removed on 2025-07-15. Please migrate to a user-maintained configuration or use the wifi service."
|
||||
];
|
||||
|
||||
# disable wpa supplicant
|
||||
|
||||
@@ -38,6 +38,10 @@ in
|
||||
] "Importing the module will already enable the service.")
|
||||
];
|
||||
config = {
|
||||
warnings = [
|
||||
"The clan.localsend module is deprecated and will be removed on 2025-07-15. Please migrate to user-maintained configuration."
|
||||
];
|
||||
|
||||
clan.core.state.localsend.folders = [
|
||||
"/var/localsend"
|
||||
];
|
||||
|
||||
@@ -4,6 +4,10 @@ let
|
||||
defaultPort = 48011;
|
||||
in
|
||||
{
|
||||
warnings = [
|
||||
"The clan.moonlight module is deprecated and will be removed on 2025-07-15. Please migrate to user-maintained configuration."
|
||||
];
|
||||
|
||||
hardware.opengl.enable = true;
|
||||
environment.systemPackages = [
|
||||
pkgs.moonlight-qt
|
||||
|
||||
@@ -37,6 +37,10 @@ in
|
||||
};
|
||||
|
||||
config = {
|
||||
warnings = [
|
||||
"The clan.mumble module is deprecated and will be removed on 2025-07-15. Please migrate to user-maintained configuration."
|
||||
];
|
||||
|
||||
services.murmur = {
|
||||
enable = true;
|
||||
logDays = -1;
|
||||
|
||||
@@ -17,6 +17,10 @@ let
|
||||
listenPort = 48011;
|
||||
in
|
||||
{
|
||||
warnings = [
|
||||
"The clan.sunshine module is deprecated and will be removed on 2025-07-15. Please migrate to user-maintained configuration."
|
||||
];
|
||||
|
||||
networking.firewall = {
|
||||
allowedTCPPorts = [
|
||||
47984
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
_: {
|
||||
warnings = [
|
||||
"The clan.thelounge module is deprecated and will be removed on 2025-07-15. Please migrate to user-maintained configuration."
|
||||
];
|
||||
|
||||
services.thelounge = {
|
||||
enable = true;
|
||||
public = true;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
warnings = [
|
||||
"The clan.xfce module is deprecated and will be removed on 2025-07-15. Please migrate to user-maintained configuration."
|
||||
];
|
||||
|
||||
services.xserver = {
|
||||
enable = true;
|
||||
desktopManager.xfce.enable = true;
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
};
|
||||
};
|
||||
config = {
|
||||
warnings = [
|
||||
"The clan.zt-tcp-relay module is deprecated and will be removed on 2025-07-15. Please migrate to user-maintained configuration."
|
||||
];
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ config.clan.zt-tcp-relay.port ];
|
||||
|
||||
systemd.services.zt-tcp-relay = {
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
_class = "clan.service";
|
||||
manifest.name = "clan-core/auto-upgrade";
|
||||
manifest.description = "Automatic system upgrade for the Clan App";
|
||||
manifest.categories = [ "System" ];
|
||||
|
||||
roles.default = {
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
options.flake = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Flake reference";
|
||||
};
|
||||
};
|
||||
|
||||
perInstance =
|
||||
{ settings, ... }:
|
||||
{
|
||||
nixosModule =
|
||||
{ ... }:
|
||||
{
|
||||
system.autoUpgrade = {
|
||||
inherit (settings) flake;
|
||||
enable = true;
|
||||
dates = "02:00";
|
||||
randomizedDelaySec = "45min";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
clan.modules = {
|
||||
auto-upgrade = lib.modules.importApply ./default.nix { };
|
||||
};
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
_class = "clan.service";
|
||||
manifest.name = "clan-core/deltachat";
|
||||
manifest.description = "Email-based instant messaging for Desktop";
|
||||
manifest.categories = [ "Social" ];
|
||||
|
||||
roles.default = {
|
||||
interface =
|
||||
{ ... }:
|
||||
{
|
||||
options = { };
|
||||
};
|
||||
|
||||
perInstance =
|
||||
{ settings, ... }:
|
||||
{
|
||||
nixosModule =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
networking.firewall.interfaces."zt+".allowedTCPPorts = [ 25 ]; # smtp with other hosts
|
||||
environment.systemPackages = [ pkgs.deltachat-desktop ];
|
||||
|
||||
services.maddy =
|
||||
let
|
||||
domain = "${config.clan.core.settings.machine.name}.local";
|
||||
in
|
||||
{
|
||||
enable = true;
|
||||
primaryDomain = domain;
|
||||
config = ''
|
||||
# Minimal configuration with TLS disabled, adapted from upstream example
|
||||
# configuration here https://github.com/foxcpp/maddy/blob/master/maddy.conf
|
||||
# Do not use this in unencrypted networks!
|
||||
|
||||
auth.pass_table local_authdb {
|
||||
table sql_table {
|
||||
driver sqlite3
|
||||
dsn credentials.db
|
||||
table_name passwords
|
||||
}
|
||||
}
|
||||
|
||||
storage.imapsql local_mailboxes {
|
||||
driver sqlite3
|
||||
dsn imapsql.db
|
||||
}
|
||||
|
||||
table.chain local_rewrites {
|
||||
optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3"
|
||||
optional_step static {
|
||||
entry postmaster postmaster@$(primary_domain)
|
||||
}
|
||||
optional_step file /etc/maddy/aliases
|
||||
}
|
||||
|
||||
msgpipeline local_routing {
|
||||
destination postmaster $(local_domains) {
|
||||
modify {
|
||||
replace_rcpt &local_rewrites
|
||||
}
|
||||
deliver_to &local_mailboxes
|
||||
}
|
||||
default_destination {
|
||||
reject 550 5.1.1 "User doesn't exist"
|
||||
}
|
||||
}
|
||||
|
||||
smtp tcp://[::]:25 {
|
||||
limits {
|
||||
all rate 20 1s
|
||||
all concurrency 10
|
||||
}
|
||||
dmarc yes
|
||||
check {
|
||||
require_mx_record
|
||||
dkim
|
||||
spf
|
||||
}
|
||||
source $(local_domains) {
|
||||
reject 501 5.1.8 "Use Submission for outgoing SMTP"
|
||||
}
|
||||
default_source {
|
||||
destination postmaster $(local_domains) {
|
||||
deliver_to &local_routing
|
||||
}
|
||||
default_destination {
|
||||
reject 550 5.1.1 "User doesn't exist"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
submission tcp://[::1]:587 {
|
||||
limits {
|
||||
all rate 50 1s
|
||||
}
|
||||
auth &local_authdb
|
||||
source $(local_domains) {
|
||||
check {
|
||||
authorize_sender {
|
||||
prepare_email &local_rewrites
|
||||
user_to_email identity
|
||||
}
|
||||
}
|
||||
destination postmaster $(local_domains) {
|
||||
deliver_to &local_routing
|
||||
}
|
||||
default_destination {
|
||||
modify {
|
||||
dkim $(primary_domain) $(local_domains) default
|
||||
}
|
||||
deliver_to &remote_queue
|
||||
}
|
||||
}
|
||||
default_source {
|
||||
reject 501 5.1.8 "Non-local sender domain"
|
||||
}
|
||||
}
|
||||
|
||||
target.remote outbound_delivery {
|
||||
limits {
|
||||
destination rate 20 1s
|
||||
destination concurrency 10
|
||||
}
|
||||
mx_auth {
|
||||
dane
|
||||
mtasts {
|
||||
cache fs
|
||||
fs_dir mtasts_cache/
|
||||
}
|
||||
local_policy {
|
||||
min_tls_level encrypted
|
||||
min_mx_level none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target.queue remote_queue {
|
||||
target &outbound_delivery
|
||||
autogenerated_msg_domain $(primary_domain)
|
||||
bounce {
|
||||
destination postmaster $(local_domains) {
|
||||
deliver_to &local_routing
|
||||
}
|
||||
default_destination {
|
||||
reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
imap tcp://[::1]:143 {
|
||||
auth &local_authdb
|
||||
storage &local_mailboxes
|
||||
}
|
||||
'';
|
||||
ensureAccounts = [ "user@${domain}" ];
|
||||
ensureCredentials = {
|
||||
"user@${domain}".passwordFile = pkgs.writeText "dummy" "foobar";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{ lib, ... }:
|
||||
let
|
||||
module = lib.modules.importApply ./default.nix { };
|
||||
in
|
||||
{
|
||||
clan.modules = {
|
||||
deltachat = module;
|
||||
};
|
||||
perSystem =
|
||||
{ ... }:
|
||||
{
|
||||
clan.nixosTests.deltachat = {
|
||||
imports = [ ./tests/vm/default.nix ];
|
||||
|
||||
clan.modules."@clan/deltachat" = module;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
module,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
name = "deltachat";
|
||||
|
||||
clan = {
|
||||
directory = ./.;
|
||||
inventory = {
|
||||
machines.server = { };
|
||||
|
||||
instances = {
|
||||
deltachat-test = {
|
||||
module.name = "@clan/deltachat";
|
||||
roles.default.machines."server".settings = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
nodes = {
|
||||
server = { };
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
server.wait_for_unit("maddy")
|
||||
|
||||
# imap
|
||||
server.wait_until_succeeds("${pkgs.netcat}/bin/nc -z -v ::1 143")
|
||||
# smtp submission
|
||||
server.succeed("${pkgs.netcat}/bin/nc -z -v ::1 587")
|
||||
# smtp
|
||||
server.succeed("${pkgs.netcat}/bin/nc -z -v ::1 25")
|
||||
'';
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"type": "age"
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
_class = "clan.service";
|
||||
manifest.name = "clan-core/ergochat";
|
||||
manifest.description = "A modern IRC server";
|
||||
manifest.categories = [ "Social" ];
|
||||
|
||||
roles.default = {
|
||||
interface =
|
||||
{ ... }:
|
||||
{
|
||||
options = { };
|
||||
};
|
||||
|
||||
perInstance =
|
||||
{ settings, ... }:
|
||||
{
|
||||
nixosModule =
|
||||
{ ... }:
|
||||
{
|
||||
services.ergochat = {
|
||||
enable = true;
|
||||
|
||||
settings = {
|
||||
datastore = {
|
||||
autoupgrade = true;
|
||||
path = "/var/lib/ergo/ircd.db";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
clan.core.state.ergochat.folders = [ "/var/lib/ergo" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{ lib, ... }:
|
||||
let
|
||||
module = lib.modules.importApply ./default.nix { };
|
||||
in
|
||||
{
|
||||
clan.modules = {
|
||||
ergochat = module;
|
||||
};
|
||||
perSystem =
|
||||
{ ... }:
|
||||
{
|
||||
clan.nixosTests.ergochat = {
|
||||
imports = [ ./tests/vm/default.nix ];
|
||||
|
||||
clan.modules."@clan/ergochat" = module;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
module,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
name = "ergochat";
|
||||
|
||||
clan = {
|
||||
directory = ./.;
|
||||
inventory = {
|
||||
machines.server = { };
|
||||
|
||||
instances = {
|
||||
ergochat-test = {
|
||||
module.name = "@clan/ergochat";
|
||||
roles.default.machines."server".settings = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
nodes = {
|
||||
server = { };
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
server.wait_for_unit("ergochat")
|
||||
|
||||
# Check that ergochat is running
|
||||
server.succeed("systemctl status ergochat")
|
||||
|
||||
# Check that the data directory exists
|
||||
server.succeed("test -d /var/lib/ergo")
|
||||
|
||||
# Check that the server is listening on the correct ports
|
||||
server.succeed("${pkgs.netcat}/bin/nc -z -v ::1 6667")
|
||||
'';
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"type": "age"
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
_class = "clan.service";
|
||||
manifest.name = "clan-core/heisenbridge";
|
||||
manifest.description = "A matrix bridge to communicate with IRC";
|
||||
manifest.categories = [ "Social" ];
|
||||
|
||||
roles.default = {
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
options.homeserver = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "http://localhost:8008";
|
||||
description = "URL of the Matrix homeserver";
|
||||
};
|
||||
};
|
||||
|
||||
perInstance =
|
||||
{ settings, ... }:
|
||||
{
|
||||
nixosModule = {
|
||||
|
||||
services.heisenbridge = {
|
||||
enable = true;
|
||||
homeserver = settings.homeserver;
|
||||
};
|
||||
|
||||
services.matrix-synapse.settings.app_service_config_files = [
|
||||
"/var/lib/heisenbridge/registration.yml"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{ lib, ... }:
|
||||
let
|
||||
module = lib.modules.importApply ./default.nix { };
|
||||
in
|
||||
{
|
||||
clan.modules = {
|
||||
heisenbridge = module;
|
||||
};
|
||||
perSystem =
|
||||
{ ... }:
|
||||
{
|
||||
clan.nixosTests.heisenbridge = {
|
||||
imports = [ ./tests/vm/default.nix ];
|
||||
|
||||
clan.modules."@clan/heisenbridge" = module;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
{
|
||||
module,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
name = "heisenbridge";
|
||||
|
||||
clan = {
|
||||
directory = ./.;
|
||||
inventory = {
|
||||
machines.server = { };
|
||||
|
||||
instances = {
|
||||
heisenbridge-test = {
|
||||
module.name = "@clan/heisenbridge";
|
||||
roles.default.machines."server".settings = {
|
||||
homeserver = "http://127.0.0.1:8008";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
nodes = {
|
||||
server = {
|
||||
# Setup a minimal matrix-synapse to test with
|
||||
services.matrix-synapse = {
|
||||
enable = true;
|
||||
settings.server_name = "example.com";
|
||||
settings.database = {
|
||||
name = "sqlite3";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
server.wait_for_unit("matrix-synapse")
|
||||
server.wait_for_unit("heisenbridge")
|
||||
|
||||
# Check that heisenbridge is running
|
||||
server.succeed("systemctl status heisenbridge")
|
||||
|
||||
# Wait for the bridge to initialize
|
||||
server.wait_until_succeeds("journalctl -u heisenbridge | grep -q 'bridge is now running'")
|
||||
|
||||
# Check that heisenbridge is listening on the default port
|
||||
server.succeed("${pkgs.netcat}/bin/nc -z -v 127.0.0.1 9898")
|
||||
'';
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"type": "age"
|
||||
}
|
||||
@@ -44,10 +44,7 @@ in
|
||||
test_simple = {
|
||||
inherit testFlake;
|
||||
|
||||
expr = {
|
||||
};
|
||||
expected = {
|
||||
|
||||
};
|
||||
expr = { };
|
||||
expected = { };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
LocalSend is a free, open-source alternative to AirDrop that allows you to
|
||||
securely share files and messages with nearby devices over your local network
|
||||
without needing an internet connection.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```nix
|
||||
inventory.instances = {
|
||||
localsend = {
|
||||
module = {
|
||||
name = "localsend";
|
||||
input = "clan";
|
||||
};
|
||||
roles.default.machines.draper = { };
|
||||
};
|
||||
}
|
||||
```
|
||||
@@ -1,82 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
_class = "clan.service";
|
||||
manifest.name = "localsend";
|
||||
manifest.description = "Local network file sharing application";
|
||||
manifest.categories = [ "Utility" ];
|
||||
|
||||
roles.default = {
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
options = {
|
||||
displayName = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "The name that localsend will use to display your instance.";
|
||||
};
|
||||
|
||||
package = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.package;
|
||||
default = null;
|
||||
defaultText = "pkgs.localsend of the machine";
|
||||
description = "The localsend package to use.";
|
||||
};
|
||||
|
||||
ipv4Addr = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "192.168.56.2/24";
|
||||
description = "Optional IPv4 address for ZeroTier network. Only needed until IPv6 multicasting is supported.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
perInstance =
|
||||
{
|
||||
settings,
|
||||
...
|
||||
}:
|
||||
{
|
||||
nixosModule =
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
||||
config = {
|
||||
|
||||
clan.core.state.localsend.folders = [ "/var/localsend" ];
|
||||
|
||||
environment.systemPackages =
|
||||
let
|
||||
localsend-ensure-config = pkgs.writers.writePython3Bin "localsend-ensure-config" {
|
||||
} ./localsend-ensure-config.py;
|
||||
|
||||
localsend = pkgs.writeShellScriptBin "localsend" ''
|
||||
set -xeu
|
||||
${lib.getExe localsend-ensure-config} ${
|
||||
lib.optionalString (settings.displayName != null) settings.displayName
|
||||
}
|
||||
${if settings.package != null then lib.getExe settings.package else lib.getExe pkgs.localsend}
|
||||
'';
|
||||
in
|
||||
[ localsend ];
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 53317 ];
|
||||
|
||||
# This is currently needed because there is no ipv6 multicasting support yet
|
||||
systemd.network.networks = lib.mkIf (settings.ipv4Addr != null) {
|
||||
"09-zerotier" = {
|
||||
networkConfig = {
|
||||
Address = settings.ipv4Addr;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{ lib, ... }:
|
||||
let
|
||||
module = lib.modules.importApply ./default.nix { };
|
||||
in
|
||||
{
|
||||
clan.modules = {
|
||||
localsend = module;
|
||||
};
|
||||
|
||||
perSystem =
|
||||
{ ... }:
|
||||
{
|
||||
clan.nixosTests.localsend = {
|
||||
imports = [ ./tests/vm/default.nix ];
|
||||
|
||||
clan.modules."@clan/localsend" = module;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def load_json(file_path: Path) -> dict[str, any]:
|
||||
try:
|
||||
with file_path.open("r") as file:
|
||||
return json.load(file)
|
||||
except FileNotFoundError:
|
||||
return {}
|
||||
|
||||
|
||||
def save_json(file_path: Path, data: dict[str, any]) -> None:
|
||||
with file_path.open("w") as file:
|
||||
json.dump(data, file, indent=4)
|
||||
|
||||
|
||||
def update_json(file_path: Path, updates: dict[str, any]) -> None:
|
||||
data = load_json(file_path)
|
||||
data.update(updates)
|
||||
save_json(file_path, data)
|
||||
|
||||
|
||||
def config_location() -> str:
|
||||
config_file = "shared_preferences.json"
|
||||
config_directory = ".local/share/org.localsend.localsend_app"
|
||||
config_path = Path.home() / Path(config_directory) / Path(config_file)
|
||||
return config_path
|
||||
|
||||
|
||||
def ensure_config_directory() -> None:
|
||||
config_directory = Path(config_location()).parent
|
||||
config_directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def load_config() -> dict[str, any]:
|
||||
return load_json(config_location())
|
||||
|
||||
|
||||
def save_config(data: dict[str, any]) -> None:
|
||||
save_json(config_location(), data)
|
||||
|
||||
|
||||
def update_username(username: str, data: dict[str, any]) -> dict[str, any]:
|
||||
data["flutter.ls_alias"] = username
|
||||
return data
|
||||
|
||||
|
||||
def main(argv: list[str]) -> None:
|
||||
try:
|
||||
display_name = argv[1]
|
||||
except IndexError:
|
||||
# This is not an error, just don't update the name
|
||||
print("No display name provided.")
|
||||
sys.exit(0)
|
||||
|
||||
ensure_config_directory()
|
||||
updated_data = update_username(display_name, load_config())
|
||||
save_config(updated_data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[:2])
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
module,
|
||||
...
|
||||
}:
|
||||
{
|
||||
name = "localsend";
|
||||
|
||||
clan = {
|
||||
directory = ./.;
|
||||
inventory = {
|
||||
machines.server = { };
|
||||
|
||||
instances = {
|
||||
localsend-test = {
|
||||
module.name = "@clan/localsend";
|
||||
roles.default.machines."server".settings = {
|
||||
displayName = "Test Instance";
|
||||
ipv4Addr = "192.168.56.2/24";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
nodes = {
|
||||
server = { };
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
# Check that the localsend wrapper script is available
|
||||
server.succeed("command -v localsend")
|
||||
|
||||
# Verify the 09-zerotier network is configured with the specified IP address
|
||||
server.succeed("grep -q 'Address=192.168.56.2/24' /etc/systemd/network/09-zerotier.network")
|
||||
'';
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"type": "age"
|
||||
}
|
||||
@@ -83,16 +83,11 @@ nav:
|
||||
- Services:
|
||||
- Overview: reference/clanServices/index.md
|
||||
- reference/clanServices/admin.md
|
||||
- reference/clanServices/auto-upgrade.md
|
||||
- reference/clanServices/borgbackup.md
|
||||
- reference/clanServices/deltachat.md
|
||||
- reference/clanServices/emergency-access.md
|
||||
- reference/clanServices/ergochat.md
|
||||
- reference/clanServices/garage.md
|
||||
- reference/clanServices/heisenbridge.md
|
||||
- reference/clanServices/hello-world.md
|
||||
- reference/clanServices/importer.md
|
||||
- reference/clanServices/localsend.md
|
||||
- reference/clanServices/mycelium.md
|
||||
- reference/clanServices/packages.md
|
||||
- reference/clanServices/sshd.md
|
||||
|
||||
@@ -104,7 +104,7 @@ Limitations:
|
||||
|
||||
### Where to find examples for NixOS container tests
|
||||
|
||||
Existing nixos container tests in clan-core can be found by using ripgrep:
|
||||
Existing NixOS container tests in clan-core can be found by using `ripgrep`:
|
||||
|
||||
```shellSession
|
||||
rg self.clanLib.test.containerTest
|
||||
@@ -131,7 +131,7 @@ Due to superior efficiency,
|
||||
|
||||
### Finding examples of python tests
|
||||
|
||||
Existing python tests in clan-core can be found by using ripgrep:
|
||||
Existing python tests in clan-core can be found by using `ripgrep`:
|
||||
```shellSession
|
||||
rg "import pytest"
|
||||
```
|
||||
@@ -196,17 +196,17 @@ Nix eval tests are good for testing any nix logic, including
|
||||
|
||||
- nix functions
|
||||
- nix libraries
|
||||
- modules for the nixos module system
|
||||
- modules for the NixOS module system
|
||||
|
||||
When not to use
|
||||
|
||||
- tests that require building nix derivations (except some very cheap ones)
|
||||
- tests that require running programs written in other languages
|
||||
- tests that require building or running nixos machines
|
||||
- tests that require building or running NixOS machines
|
||||
|
||||
### Finding examples of nix eval tests
|
||||
|
||||
Existing nix eval tests can be found via this ripgrep command:
|
||||
Existing nix eval tests can be found via this `ripgrep` command:
|
||||
|
||||
```shellSession
|
||||
rg "nix-unit --eval-store"
|
||||
@@ -284,11 +284,11 @@ Add `lib.trace` or `lib.traceVal` statements in order to print some variables du
|
||||
|
||||
#### Nix repl
|
||||
|
||||
Use `nix repl` to evaluate to inspec the test.
|
||||
Use `nix repl` to evaluate and inspect the test.
|
||||
|
||||
Each test consists opf an `expr` (expression) and an `expected` field. `nix-unit` simply checks if `expr == expected` and prints the diff if that's not the case.
|
||||
Each test consists of an `expr` (expression) and an `expected` field. `nix-unit` simply checks if `expr == expected` and prints the diff if that's not the case.
|
||||
|
||||
`nix repl` can be used to inspect `expr` manually, or any other variables that you choose to expose.
|
||||
`nix repl` can be used to inspect an `expr` manually, or any other variables that you choose to expose.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
12
flake.lock
generated
12
flake.lock
generated
@@ -74,11 +74,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1749873626,
|
||||
"narHash": "sha256-1Mc/D/1RwwmDKY59f4IpDBgcQttxffm+4o0m67lQ8hc=",
|
||||
"lastModified": 1750618568,
|
||||
"narHash": "sha256-w9EG5FOXrjXGfbqCcQg9x1lMnTwzNDW5BMXp8ddy15E=",
|
||||
"owner": "nix-darwin",
|
||||
"repo": "nix-darwin",
|
||||
"rev": "2f140d6ac8840c6089163fb43ba95220c230f22b",
|
||||
"rev": "1dd19f19e4b53a1fd2e8e738a08dd5fe635ec7e5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -102,11 +102,11 @@
|
||||
},
|
||||
"nixos-facter-modules": {
|
||||
"locked": {
|
||||
"lastModified": 1743671943,
|
||||
"narHash": "sha256-7sYig0+RcrR3sOL5M+2spbpFUHyEP7cnUvCaqFOBjyU=",
|
||||
"lastModified": 1750412875,
|
||||
"narHash": "sha256-uP9Xxw5XcFwjX9lNoYRpybOnIIe1BHfZu5vJnnPg3Jc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixos-facter-modules",
|
||||
"rev": "58ad9691670d293a15221d4a78818e0088d2e086",
|
||||
"rev": "14df13c84552a7d1f33c1cd18336128fbc43f920",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -19,6 +19,7 @@ let
|
||||
{ modules, prefix }:
|
||||
(lib.evalModules {
|
||||
class = "clan.service";
|
||||
specialArgs._ctx = prefix;
|
||||
modules = [
|
||||
./service-module.nix
|
||||
# feature modules
|
||||
|
||||
@@ -1,31 +1,18 @@
|
||||
{ lib, config, ... }:
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
_ctx,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib) mkOption types;
|
||||
inherit (types) attrsWith submoduleWith;
|
||||
|
||||
errorContext = "Error context: ${lib.concatStringsSep "." _ctx}";
|
||||
# TODO:
|
||||
# Remove once this gets merged upstream; performs in O(n*log(n) instead of O(n^2))
|
||||
# https://github.com/NixOS/nixpkgs/pull/355616/files
|
||||
uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list);
|
||||
|
||||
checkInstanceRoles =
|
||||
instanceName: instanceRoles:
|
||||
let
|
||||
unmatchedRoles = lib.filter (roleName: !lib.elem roleName (lib.attrNames config.roles)) (
|
||||
lib.attrNames instanceRoles
|
||||
);
|
||||
in
|
||||
if unmatchedRoles == [ ] then
|
||||
true
|
||||
else
|
||||
throw ''
|
||||
inventory instance: 'instances.${instanceName}' defines the following roles:
|
||||
${builtins.toJSON unmatchedRoles}
|
||||
|
||||
But the clan-service module '${config.manifest.name}' defines roles:
|
||||
${builtins.toJSON (lib.attrNames config.roles)}
|
||||
'';
|
||||
|
||||
/**
|
||||
Merges the role- and machine-settings using the role interface
|
||||
|
||||
@@ -39,8 +26,6 @@ let
|
||||
|
||||
The caller is responsible to use .config or .extendModules
|
||||
*/
|
||||
# TODO: evaluate against the role.settings statically and use extendModules to get the machineSettings
|
||||
# Doing this might improve performance
|
||||
evalMachineSettings =
|
||||
{
|
||||
roleName,
|
||||
@@ -53,7 +38,8 @@ let
|
||||
# This prints the path where the option should be defined rather than the plain path within settings
|
||||
# "The option `instances.foo.roles.server.machines.test.settings.<>' was accessed but has no value defined. Try setting the option."
|
||||
prefix =
|
||||
[
|
||||
_ctx
|
||||
++ [
|
||||
"instances"
|
||||
instanceName
|
||||
"roles"
|
||||
@@ -78,7 +64,7 @@ let
|
||||
(lib.setDefaultModuleLocation "Via clan.service module: roles.${roleName}.interface"
|
||||
config.roles.${roleName}.interface
|
||||
)
|
||||
(lib.setDefaultModuleLocation "inventory.instances.${instanceName}.roles.${roleName}.settings"
|
||||
(lib.setDefaultModuleLocation "instances.${instanceName}.roles.${roleName}.settings"
|
||||
config.instances.${instanceName}.roles.${roleName}.settings
|
||||
)
|
||||
settings
|
||||
@@ -88,32 +74,6 @@ let
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
Makes a module extensible
|
||||
returning its config
|
||||
and making it extensible via '__functor' polymorphism
|
||||
|
||||
Example:
|
||||
|
||||
```nix-repl
|
||||
res = makeExtensibleConfig (evalModules { options.foo = mkOption { default = 42; };)
|
||||
res
|
||||
=>
|
||||
{
|
||||
foo = 42;
|
||||
_functor = <function>;
|
||||
}
|
||||
|
||||
# This allows to override using mkDefault, mkForce, etc.
|
||||
res { foo = 100; }
|
||||
=>
|
||||
{
|
||||
foo = 100;
|
||||
_functor = <function>;
|
||||
}
|
||||
```
|
||||
*/
|
||||
|
||||
# Extend evalModules result by a module, returns .config.
|
||||
extendEval = eval: m: (eval.extendModules { modules = lib.toList m; }).config;
|
||||
|
||||
@@ -129,15 +89,12 @@ let
|
||||
instanceName: instance:
|
||||
lib.mapAttrs (roleName: role: {
|
||||
machines = lib.mapAttrs (machineName: v: {
|
||||
# TODO: evaluate the settings against the interface
|
||||
# settings = (evalMachineSettings { inherit roleName instanceName; inherit (v) settings; }).config;
|
||||
settings =
|
||||
(evalMachineSettings {
|
||||
inherit roleName instanceName machineName;
|
||||
inherit (v) settings;
|
||||
}).config;
|
||||
}) role.machines;
|
||||
# TODO: evaluate the settings against the interface
|
||||
settings =
|
||||
(evalMachineSettings {
|
||||
inherit roleName instanceName;
|
||||
@@ -147,16 +104,15 @@ let
|
||||
in
|
||||
{
|
||||
options = {
|
||||
# TODO: deduplicate this with inventory.instances
|
||||
# Although inventory has stricter constraints
|
||||
instances = mkOption {
|
||||
# Instances are created in the inventory
|
||||
visible = false;
|
||||
defaultText = "Throws: 'The service must define its instances' when not defined";
|
||||
default = throw ''
|
||||
The clan service module ${config.manifest.name} doesn't define any instances.
|
||||
|
||||
Did you forget to create instances via 'inventory.instances'?
|
||||
Did you forget to create instances via 'instances'?
|
||||
|
||||
${errorContext}
|
||||
'';
|
||||
description = ''
|
||||
Instances of the service.
|
||||
@@ -179,11 +135,6 @@ in
|
||||
(
|
||||
{ name, ... }:
|
||||
{
|
||||
# options.settings = mkOption {
|
||||
# description = "settings of 'instance': ${name}";
|
||||
# default = {};
|
||||
# apply = v: lib.seq (checkInstanceSettings name v) v;
|
||||
# };
|
||||
options.roles = mkOption {
|
||||
description = ''
|
||||
Roles of the instance.
|
||||
@@ -204,7 +155,9 @@ in
|
||||
Instance '${name}' of service '${config.manifest.name}' mut define members via 'roles'.
|
||||
|
||||
To include a machine:
|
||||
'instances.${name}.roles.<role-name>.machines.<your-machine-name>' must be set.
|
||||
'instances.${name}.roles.<role-name>.machines.<machine-name>' must be set.
|
||||
|
||||
${errorContext}
|
||||
'';
|
||||
type = attrsWith {
|
||||
placeholder = "roleName";
|
||||
@@ -258,7 +211,34 @@ in
|
||||
];
|
||||
};
|
||||
};
|
||||
apply = v: lib.seq (checkInstanceRoles name v) v;
|
||||
apply =
|
||||
v:
|
||||
lib.seq (
|
||||
(
|
||||
|
||||
instanceName: instanceRoles:
|
||||
let
|
||||
unmatchedRoles = lib.filter (roleName: !lib.elem roleName (lib.attrNames config.roles)) (
|
||||
lib.attrNames instanceRoles
|
||||
);
|
||||
in
|
||||
if unmatchedRoles == [ ] then
|
||||
true
|
||||
else
|
||||
throw ''
|
||||
Instance: 'instances.${instanceName}' uses the following roles:
|
||||
${builtins.toJSON unmatchedRoles}
|
||||
|
||||
But the clan-service module '${config.manifest.name}' only defines roles:
|
||||
${builtins.toJSON (lib.attrNames config.roles)}
|
||||
|
||||
${errorContext}
|
||||
''
|
||||
|
||||
)
|
||||
name
|
||||
v
|
||||
) v;
|
||||
};
|
||||
}
|
||||
)
|
||||
@@ -301,6 +281,8 @@ in
|
||||
|
||||
To define multiple instance behavior:
|
||||
`roles.client.perInstance = { ... }: {}`
|
||||
|
||||
${errorContext}
|
||||
'';
|
||||
type = attrsWith {
|
||||
placeholder = "roleName";
|
||||
@@ -336,8 +318,6 @@ in
|
||||
- *defaults* that depend on the *machine* or *instance* should be added to *settings* later in 'perInstance' or 'perMachine'
|
||||
'';
|
||||
type = types.deferredModule;
|
||||
# TODO: Default to an empty module
|
||||
# need to test that an the empty module can be evaluated to empty settings
|
||||
default = { };
|
||||
};
|
||||
options.perInstance = mkOption {
|
||||
@@ -379,7 +359,7 @@ in
|
||||
};
|
||||
```
|
||||
|
||||
- `settings`: The settings of the role, as defined in `inventory`
|
||||
- `settings`: The settings of the role, as defined in `instances`
|
||||
```nix
|
||||
{
|
||||
timeout = 30;
|
||||
@@ -432,16 +412,25 @@ in
|
||||
```
|
||||
'';
|
||||
};
|
||||
# TODO: Recursive services
|
||||
options.services = mkOption {
|
||||
visible = false;
|
||||
type = attrsWith {
|
||||
placeholder = "serviceName";
|
||||
elemType = submoduleWith {
|
||||
modules = [ ./service-module.nix ];
|
||||
modules = [
|
||||
{
|
||||
_module.args._ctx = _ctx ++ [
|
||||
config.manifest.name
|
||||
"roles"
|
||||
roleName
|
||||
"perInstance"
|
||||
"services"
|
||||
];
|
||||
}
|
||||
./service-module.nix
|
||||
];
|
||||
};
|
||||
};
|
||||
apply = _: throw "Not implemented yet";
|
||||
default = { };
|
||||
};
|
||||
})
|
||||
@@ -548,16 +537,23 @@ in
|
||||
```
|
||||
'';
|
||||
};
|
||||
# TODO: Recursive services
|
||||
options.services = mkOption {
|
||||
visible = false;
|
||||
type = attrsWith {
|
||||
placeholder = "serviceName";
|
||||
elemType = submoduleWith {
|
||||
modules = [ ./service-module.nix ];
|
||||
modules = [
|
||||
{
|
||||
_module.args._ctx = _ctx ++ [
|
||||
config.manifest.name
|
||||
"perMachine"
|
||||
"services"
|
||||
];
|
||||
}
|
||||
./service-module.nix
|
||||
];
|
||||
};
|
||||
};
|
||||
apply = _: throw "Not implemented yet";
|
||||
default = { };
|
||||
};
|
||||
})
|
||||
@@ -591,7 +587,6 @@ in
|
||||
in
|
||||
uniqueStrings (collectRoles machineScope.instances);
|
||||
};
|
||||
# TODO: instances.<instanceName>.roles should contain all roles, even if nobody has the role
|
||||
inherit (machineScope) instances;
|
||||
|
||||
# There are no machine settings.
|
||||
@@ -605,6 +600,8 @@ in
|
||||
- 'instances.<instanceName>.roles.<roleName>.machines.<machineName>.settings' should be used instead.
|
||||
|
||||
If that is insufficient, you might also consider using 'roles.<roleName>.perInstance' instead of 'perMachine'.
|
||||
|
||||
${errorContext}
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -627,7 +624,7 @@ in
|
||||
allMachines :: {
|
||||
<machineName> :: {
|
||||
nixosModule :: NixOSModule;
|
||||
services :: { }; # TODO: nested services
|
||||
services :: { };
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -666,6 +663,7 @@ in
|
||||
type = types.attrsOf types.raw;
|
||||
};
|
||||
|
||||
# The result collected from 'perMachine'
|
||||
result.allMachines = mkOption {
|
||||
visible = false;
|
||||
readOnly = true;
|
||||
@@ -720,43 +718,93 @@ in
|
||||
default = lib.mapAttrs (
|
||||
machineName: machineResult:
|
||||
let
|
||||
instanceResults = lib.foldlAttrs (
|
||||
acc: roleName: role:
|
||||
acc
|
||||
++ lib.foldlAttrs (
|
||||
acc: instanceName: instance:
|
||||
if instance.allMachines.${machineName}.nixosModule or { } != { } then
|
||||
acc
|
||||
++ [
|
||||
(lib.setDefaultModuleLocation
|
||||
"Via instances.${instanceName}.roles.${roleName}.machines.${machineName}"
|
||||
instance.allMachines.${machineName}.nixosModule
|
||||
)
|
||||
]
|
||||
else
|
||||
acc
|
||||
) [ ] role.allInstances
|
||||
) [ ] config.result.allRoles;
|
||||
instanceResults =
|
||||
lib.foldlAttrs
|
||||
(
|
||||
roleAcc: roleName: role:
|
||||
roleAcc
|
||||
// lib.foldlAttrs (
|
||||
instanceAcc: instanceName: instance:
|
||||
instanceAcc
|
||||
// {
|
||||
nixosModules =
|
||||
(
|
||||
(lib.mapAttrsToList (
|
||||
nestedServiceName: serviceModule:
|
||||
let
|
||||
unmatchedMachines = lib.attrNames (
|
||||
lib.removeAttrs serviceModule.result.final (lib.attrNames config.result.allMachines)
|
||||
);
|
||||
in
|
||||
if unmatchedMachines != [ ] then
|
||||
throw ''
|
||||
The following machines are not part of the parent service: ${builtins.toJSON unmatchedMachines}
|
||||
Either remove the machines, or include them into the parent via a role.
|
||||
(Added via roles.${roleName}.perInstance.services.${nestedServiceName})
|
||||
|
||||
${errorContext}
|
||||
''
|
||||
else
|
||||
serviceModule.result.final.${machineName}.nixosModule
|
||||
) instance.allMachines.${machineName}.services or { })
|
||||
|
||||
)
|
||||
++ (
|
||||
if instance.allMachines.${machineName}.nixosModule or { } != { } then
|
||||
instanceAcc.nixosModules
|
||||
++ [
|
||||
(lib.setDefaultModuleLocation
|
||||
"Via instances.${instanceName}.roles.${roleName}.machines.${machineName}"
|
||||
instance.allMachines.${machineName}.nixosModule
|
||||
)
|
||||
]
|
||||
else
|
||||
instanceAcc.nixosModules
|
||||
);
|
||||
}
|
||||
) roleAcc role.allInstances
|
||||
)
|
||||
{
|
||||
nixosModules = [ ];
|
||||
# ...
|
||||
}
|
||||
config.result.allRoles;
|
||||
in
|
||||
{
|
||||
inherit instanceResults;
|
||||
inherit instanceResults machineResult;
|
||||
nixosModule = {
|
||||
imports = [
|
||||
# include service assertions:
|
||||
(
|
||||
imports =
|
||||
[
|
||||
# include service assertions:
|
||||
(
|
||||
let
|
||||
failedAssertions = (lib.filterAttrs (_: v: !v.assertion) config.result.assertions);
|
||||
in
|
||||
{
|
||||
assertions = lib.attrValues failedAssertions;
|
||||
}
|
||||
)
|
||||
(lib.setDefaultModuleLocation "Via ${config.manifest.name}.perMachine - machine='${machineName}';" machineResult.nixosModule)
|
||||
]
|
||||
++ (lib.mapAttrsToList (
|
||||
nestedServiceName: serviceModule:
|
||||
let
|
||||
failedAssertions = (lib.filterAttrs (_: v: !v.assertion) config.result.assertions);
|
||||
unmatchedMachines = lib.attrNames (
|
||||
lib.removeAttrs serviceModule.result.final (lib.attrNames config.result.allMachines)
|
||||
);
|
||||
in
|
||||
{
|
||||
assertions = lib.attrValues failedAssertions;
|
||||
}
|
||||
)
|
||||
if unmatchedMachines != [ ] then
|
||||
throw ''
|
||||
The following machines are not part of the parent service: ${builtins.toJSON unmatchedMachines}
|
||||
Either remove the machines, or include them into the parent via a role.
|
||||
(Added via perMachine.services.${nestedServiceName})
|
||||
|
||||
# For error backtracing. This module was produced by the 'perMachine' function
|
||||
# TODO: check if we need this or if it leads to better errors if we pass the underlying module locations
|
||||
# (lib.setDefaultModuleLocation "clan.service: ${config.manifest.name} - via perMachine" machineResult.nixosModule)
|
||||
(machineResult.nixosModule)
|
||||
] ++ instanceResults;
|
||||
${errorContext}
|
||||
''
|
||||
else
|
||||
serviceModule.result.final.${machineName}.nixosModule
|
||||
) machineResult.services)
|
||||
++ instanceResults.nixosModules;
|
||||
};
|
||||
}
|
||||
) config.result.allMachines;
|
||||
|
||||
@@ -278,4 +278,5 @@ in
|
||||
|
||||
per_machine_args = import ./per_machine_args.nix { inherit lib callInventoryAdapter; };
|
||||
per_instance_args = import ./per_instance_args.nix { inherit lib callInventoryAdapter; };
|
||||
nested = import ./nested_services { inherit lib clanLib; };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{ clanLib, lib, ... }:
|
||||
{
|
||||
test_simple = import ./simple.nix { inherit clanLib lib; };
|
||||
|
||||
test_multi_machine = import ./multi_machine.nix { inherit clanLib lib; };
|
||||
|
||||
test_multi_import_duplication = import ./multi_import_duplication.nix { inherit clanLib lib; };
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
{ clanLib, lib, ... }:
|
||||
let
|
||||
# Potentially imported many times
|
||||
# To add the ssh key
|
||||
example-admin = (
|
||||
{ lib, ... }:
|
||||
{
|
||||
manifest.name = "example-admin";
|
||||
|
||||
roles.client.interface = {
|
||||
options.keys = lib.mkOption { };
|
||||
};
|
||||
|
||||
roles.client.perInstance =
|
||||
{ settings, ... }:
|
||||
{
|
||||
nixosModule = {
|
||||
inherit (settings) keys;
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
consumer-A =
|
||||
{ ... }:
|
||||
{
|
||||
manifest.name = "consumer-A";
|
||||
|
||||
instances.foo = {
|
||||
roles.server.machines."jon" = { };
|
||||
};
|
||||
instances.bar = {
|
||||
roles.server.machines."jon" = { };
|
||||
};
|
||||
|
||||
roles.server = {
|
||||
perInstance =
|
||||
{ machine, instanceName, ... }:
|
||||
{
|
||||
services."example-admin" = {
|
||||
imports = [
|
||||
example-admin
|
||||
];
|
||||
instances."${instanceName}" = {
|
||||
roles.client.machines.${machine.name} = {
|
||||
settings.keys = [ "pubkey-1" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
consumer-B =
|
||||
{ ... }:
|
||||
{
|
||||
manifest.name = "consumer-A";
|
||||
|
||||
instances.foo = {
|
||||
roles.server.machines."jon" = { };
|
||||
};
|
||||
instances.bar = {
|
||||
roles.server.machines."jon" = { };
|
||||
};
|
||||
|
||||
roles.server = {
|
||||
perInstance =
|
||||
{ machine, instanceName, ... }:
|
||||
{
|
||||
services."example-admin" = {
|
||||
imports = [
|
||||
example-admin
|
||||
];
|
||||
instances."${instanceName}" = {
|
||||
roles.client.machines.${machine.name} = {
|
||||
settings.keys = [
|
||||
"pubkey-1"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
eval = clanLib.inventory.evalClanService {
|
||||
modules = [
|
||||
(consumer-A)
|
||||
];
|
||||
prefix = [ ];
|
||||
};
|
||||
eval2 = clanLib.inventory.evalClanService {
|
||||
modules = [
|
||||
(consumer-B)
|
||||
];
|
||||
prefix = [ ];
|
||||
};
|
||||
|
||||
evalNixos = lib.evalModules {
|
||||
modules = [
|
||||
{
|
||||
options.assertions = lib.mkOption { };
|
||||
# This is suboptimal
|
||||
options.keys = lib.mkOption { };
|
||||
}
|
||||
eval.config.result.final.jon.nixosModule
|
||||
eval2.config.result.final.jon.nixosModule
|
||||
];
|
||||
};
|
||||
in
|
||||
{
|
||||
# Check that the nixos system has the settings from the nested module, as well as those from the "perMachine" and "perInstance"
|
||||
inherit eval;
|
||||
expr = evalNixos.config;
|
||||
expected = {
|
||||
assertions = [ ];
|
||||
# TODO: Some deduplication mechanism is nice
|
||||
# Could add types.set or do 'apply = unique', or something else ?
|
||||
keys = [
|
||||
"pubkey-1"
|
||||
"pubkey-1"
|
||||
"pubkey-1"
|
||||
"pubkey-1"
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
{ clanLib, lib, ... }:
|
||||
let
|
||||
service-B = (
|
||||
{ lib, ... }:
|
||||
{
|
||||
manifest.name = "service-B";
|
||||
|
||||
roles.client.interface = {
|
||||
options.user = lib.mkOption { };
|
||||
options.host = lib.mkOption { };
|
||||
};
|
||||
roles.client.perInstance =
|
||||
{ settings, instanceName, ... }:
|
||||
{
|
||||
nixosModule = {
|
||||
units.${instanceName} = {
|
||||
script = settings.user + "@" + settings.host;
|
||||
};
|
||||
};
|
||||
};
|
||||
perMachine =
|
||||
{ ... }:
|
||||
{
|
||||
nixosModule = {
|
||||
ssh.enable = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
service-A =
|
||||
{ ... }:
|
||||
{
|
||||
manifest.name = "service-A";
|
||||
|
||||
instances.foo = {
|
||||
roles.server.machines."jon" = { };
|
||||
roles.server.machines."sara" = { };
|
||||
};
|
||||
|
||||
roles.server = {
|
||||
perInstance =
|
||||
{ machine, instanceName, ... }:
|
||||
{
|
||||
services."B" = {
|
||||
imports = [
|
||||
service-B
|
||||
];
|
||||
instances."A-${instanceName}-B" = {
|
||||
roles.client.machines.${machine.name} = {
|
||||
settings.user = "johnny";
|
||||
settings.host = machine.name;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
eval = clanLib.inventory.evalClanService {
|
||||
modules = [
|
||||
(service-A)
|
||||
];
|
||||
prefix = [ ];
|
||||
};
|
||||
|
||||
evalNixos = lib.mapAttrs (
|
||||
_n: v:
|
||||
(lib.evalModules {
|
||||
modules = [
|
||||
{
|
||||
options.assertions = lib.mkOption { };
|
||||
options.units = lib.mkOption { };
|
||||
options.ssh = lib.mkOption { };
|
||||
}
|
||||
v.nixosModule
|
||||
];
|
||||
}).config
|
||||
) eval.config.result.final;
|
||||
in
|
||||
{
|
||||
# Check that the nixos system has the settings from the nested module, as well as those from the "perMachine" and "perInstance"
|
||||
inherit eval;
|
||||
expr = evalNixos;
|
||||
expected = {
|
||||
jon = {
|
||||
assertions = [ ];
|
||||
ssh = {
|
||||
enable = true;
|
||||
};
|
||||
units = {
|
||||
A-foo-B = {
|
||||
script = "johnny@jon";
|
||||
};
|
||||
};
|
||||
};
|
||||
sara = {
|
||||
assertions = [ ];
|
||||
ssh = {
|
||||
enable = true;
|
||||
};
|
||||
units = {
|
||||
A-foo-B = {
|
||||
script = "johnny@sara";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
service-B :: Service
|
||||
exports a nixosModule which set "address" and "hostname"
|
||||
Note: How we use null together with mkIf to create optional values.
|
||||
This is a method, to create mergable modules
|
||||
|
||||
service-A :: Service
|
||||
|
||||
service-A.roles.server.perInstance.services."B"
|
||||
imports service-B
|
||||
configures a client with hostname = "johnny"
|
||||
|
||||
service-A.perMachine.services."B"
|
||||
imports service-B
|
||||
configures a client with address = "root"
|
||||
*/
|
||||
{ clanLib, lib, ... }:
|
||||
let
|
||||
service-B = (
|
||||
{ lib, ... }:
|
||||
{
|
||||
manifest.name = "service-B";
|
||||
|
||||
roles.client.interface = {
|
||||
options.hostname = lib.mkOption { default = null; };
|
||||
options.address = lib.mkOption { default = null; };
|
||||
};
|
||||
roles.client.perInstance =
|
||||
{ settings, ... }:
|
||||
{
|
||||
nixosModule = {
|
||||
imports = [
|
||||
# Only export the value that is actually set.
|
||||
(lib.mkIf (settings.hostname != null) {
|
||||
hostname = settings.hostname;
|
||||
})
|
||||
(lib.mkIf (settings.address != null) {
|
||||
address = settings.address;
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
service-A =
|
||||
{ ... }:
|
||||
{
|
||||
manifest.name = "service-A";
|
||||
|
||||
instances.foo = {
|
||||
roles.server.machines."jon" = { };
|
||||
};
|
||||
instances.bar = {
|
||||
roles.server.machines."jon" = { };
|
||||
};
|
||||
|
||||
roles.server = {
|
||||
perInstance =
|
||||
{ machine, instanceName, ... }:
|
||||
{
|
||||
services."B" = {
|
||||
imports = [
|
||||
service-B
|
||||
];
|
||||
instances."B-for-A" = {
|
||||
roles.client.machines.${machine.name} = {
|
||||
settings.hostname = instanceName + "+johnny";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
perMachine =
|
||||
{ machine, ... }:
|
||||
{
|
||||
services."B" = {
|
||||
imports = [
|
||||
service-B
|
||||
];
|
||||
instances."B-for-A" = {
|
||||
roles.client.machines.${machine.name} = {
|
||||
settings.address = "root";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
eval = clanLib.inventory.evalClanService {
|
||||
modules = [
|
||||
(service-A)
|
||||
];
|
||||
prefix = [ ];
|
||||
};
|
||||
|
||||
evalNixos = lib.evalModules {
|
||||
modules = [
|
||||
{
|
||||
options.assertions = lib.mkOption { };
|
||||
options.hostname = lib.mkOption { type = lib.types.separatedString " "; };
|
||||
options.address = lib.mkOption { type = lib.types.str; };
|
||||
}
|
||||
eval.config.result.final."jon".nixosModule
|
||||
];
|
||||
};
|
||||
in
|
||||
{
|
||||
# Check that the nixos system has the settings from the nested module, as well as those from the "perMachine" and "perInstance"
|
||||
inherit eval;
|
||||
expr = evalNixos.config;
|
||||
expected = {
|
||||
address = "root";
|
||||
assertions = [ ];
|
||||
# Concatenates hostnames from both instances
|
||||
hostname = "bar+johnny foo+johnny";
|
||||
};
|
||||
}
|
||||
@@ -5,16 +5,17 @@ A helper script for the AGit workflow with a gitea instance.
|
||||
<!-- `$ agit --help` -->
|
||||
|
||||
```
|
||||
usage: agit [-h] {create,c} ...
|
||||
usage: agit [-h] {create,c,list,l} ...
|
||||
|
||||
AGit utility for creating and pulling PRs
|
||||
|
||||
positional arguments:
|
||||
{create,c} Commands
|
||||
create (c) Create an AGit PR
|
||||
{create,c,list,l} Commands
|
||||
create (c) Create an AGit PR
|
||||
list (l) List open AGit pull requests
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-h, --help show this help message and exit
|
||||
|
||||
The defaults that are assumed are:
|
||||
TARGET_REMOTE_REPOSITORY = origin
|
||||
@@ -22,13 +23,19 @@ DEFAULT_TARGET_BRANCH = main
|
||||
|
||||
Examples:
|
||||
$ agit create
|
||||
Will create an AGit Pr with the latest commit message title as it's topic.
|
||||
Opens editor to compose PR title and description (first line is title, rest is body)
|
||||
|
||||
$ agit create --auto
|
||||
Creates PR using latest commit message automatically
|
||||
|
||||
$ agit create --topic "my-feature"
|
||||
Set a custom topic.
|
||||
|
||||
$ agit create --force
|
||||
Force push to a certain topic
|
||||
|
||||
$ agit list
|
||||
Lists all open pull requests for the current repository
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import argparse
|
||||
import contextlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
# push origin HEAD:refs/for/main
|
||||
@@ -14,6 +17,161 @@ TARGET_REMOTE_REPOSITORY = "origin"
|
||||
DEFAULT_TARGET_BRANCH = "main"
|
||||
|
||||
|
||||
def get_gitea_api_url(remote: str = "origin") -> str:
|
||||
"""Parse the gitea api url, this parser is fairly naive, but should work for most setups"""
|
||||
exit_code, remote_url, error = run_git_command(["git", "remote", "get-url", remote])
|
||||
|
||||
if exit_code != 0:
|
||||
print(f"Error getting remote URL for '{remote}': {error}")
|
||||
sys.exit(1)
|
||||
|
||||
# Parse different remote URL formats
|
||||
# SSH formats: git@git.clan.lol:clan/clan-core.git or gitea@git.clan.lol:clan/clan-core.git
|
||||
# HTTPS format: https://git.clan.lol/clan/clan-core.git
|
||||
|
||||
if (
|
||||
"@" in remote_url
|
||||
and ":" in remote_url
|
||||
and not remote_url.startswith("https://")
|
||||
):
|
||||
# SSH format: [user]@git.clan.lol:clan/clan-core.git
|
||||
host_and_path = remote_url.split("@")[1] # git.clan.lol:clan/clan-core.git
|
||||
host = host_and_path.split(":")[0] # git.clan.lol
|
||||
repo_path = host_and_path.split(":")[1] # clan/clan-core.git
|
||||
if repo_path.endswith(".git"):
|
||||
repo_path = repo_path[:-4] # clan/clan-core
|
||||
elif remote_url.startswith("https://"):
|
||||
# HTTPS format: https://git.clan.lol/clan/clan-core.git
|
||||
url_parts = remote_url.replace("https://", "").split("/")
|
||||
host = url_parts[0] # git.clan.lol
|
||||
repo_path = "/".join(url_parts[1:]) # clan/clan-core.git
|
||||
if repo_path.endswith(".git"):
|
||||
repo_path = repo_path.removesuffix(".git") # clan/clan-core
|
||||
else:
|
||||
print(f"Unsupported remote URL format: {remote_url}")
|
||||
sys.exit(1)
|
||||
|
||||
api_url = f"https://{host}/api/v1/repos/{repo_path}/pulls"
|
||||
return api_url
|
||||
|
||||
|
||||
def fetch_open_prs(remote: str = "origin") -> list[dict]:
|
||||
"""Fetch open pull requests from the Gitea API."""
|
||||
api_url = get_gitea_api_url(remote)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(f"{api_url}?state=open") as response:
|
||||
data = json.loads(response.read().decode())
|
||||
return data
|
||||
except urllib.error.URLError as e:
|
||||
print(f"Error fetching PRs from {api_url}: {e}")
|
||||
sys.exit(1)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error parsing JSON response: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_repo_info_from_api_url(api_url: str) -> tuple[str, str]:
|
||||
"""Extract repository owner and name from API URL."""
|
||||
# api_url format: https://git.clan.lol/api/v1/repos/clan/clan-core/pulls
|
||||
parts = api_url.split("/")
|
||||
if len(parts) >= 6 and "repos" in parts:
|
||||
repo_index = parts.index("repos")
|
||||
if repo_index + 2 < len(parts):
|
||||
owner = parts[repo_index + 1]
|
||||
repo_name = parts[repo_index + 2]
|
||||
return owner, repo_name
|
||||
msg = f"Invalid API URL format: {api_url}"
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
def fetch_pr_statuses(
|
||||
repo_owner: str, repo_name: str, commit_sha: str, host: str
|
||||
) -> list[dict]:
|
||||
"""Fetch CI statuses for a specific commit SHA."""
|
||||
status_url = (
|
||||
f"https://{host}/api/v1/repos/{repo_owner}/{repo_name}/statuses/{commit_sha}"
|
||||
)
|
||||
|
||||
try:
|
||||
request = urllib.request.Request(status_url)
|
||||
with urllib.request.urlopen(request, timeout=3) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
return data
|
||||
except (urllib.error.URLError, json.JSONDecodeError, TimeoutError):
|
||||
# Fail silently for individual status requests to keep listing fast
|
||||
return []
|
||||
|
||||
|
||||
def get_latest_status_by_context(statuses: list[dict]) -> dict[str, str]:
|
||||
"""Group statuses by context and return the latest status for each context."""
|
||||
context_statuses = {}
|
||||
|
||||
for status in statuses:
|
||||
context = status.get("context", "unknown")
|
||||
created_at = status.get("created_at", "")
|
||||
status_state = status.get("status", "unknown")
|
||||
|
||||
if (
|
||||
context not in context_statuses
|
||||
or created_at > context_statuses[context]["created_at"]
|
||||
):
|
||||
context_statuses[context] = {
|
||||
"status": status_state,
|
||||
"created_at": created_at,
|
||||
}
|
||||
|
||||
return {context: info["status"] for context, info in context_statuses.items()}
|
||||
|
||||
|
||||
def status_to_emoji(status: str) -> str:
|
||||
"""Convert status string to emoji."""
|
||||
status_map = {"success": "✅", "failure": "❌", "pending": "🟡", "error": "❓"}
|
||||
return status_map.get(status.lower(), "❓")
|
||||
|
||||
|
||||
def create_osc8_link(url: str, text: str) -> str:
|
||||
return f"\033]8;;{url}\033\\{text}\033]8;;\033\\"
|
||||
|
||||
|
||||
def format_pr_with_status(pr: dict, remote: str = "origin") -> str:
|
||||
"""Format PR title with status emojis and OSC8 link."""
|
||||
title = pr["title"]
|
||||
pr_url = pr.get("html_url", "")
|
||||
|
||||
commit_sha = pr.get("head", {}).get("sha")
|
||||
if not commit_sha:
|
||||
if pr_url:
|
||||
return create_osc8_link(pr_url, title)
|
||||
return title
|
||||
|
||||
try:
|
||||
api_url = get_gitea_api_url(remote)
|
||||
repo_owner, repo_name = get_repo_info_from_api_url(api_url)
|
||||
|
||||
host = api_url.split("/")[2]
|
||||
|
||||
statuses = fetch_pr_statuses(repo_owner, repo_name, commit_sha, host)
|
||||
if not statuses:
|
||||
if pr_url:
|
||||
return create_osc8_link(pr_url, title)
|
||||
return title
|
||||
|
||||
latest_statuses = get_latest_status_by_context(statuses)
|
||||
|
||||
emojis = [status_to_emoji(status) for status in latest_statuses.values()]
|
||||
formatted_title = f"{title} {' '.join(emojis)}" if emojis else title
|
||||
|
||||
return create_osc8_link(pr_url, formatted_title) if pr_url else formatted_title
|
||||
|
||||
except (ValueError, IndexError):
|
||||
# If there's any error in processing, just return the title with link if available
|
||||
if pr_url:
|
||||
return create_osc8_link(pr_url, title)
|
||||
|
||||
return title
|
||||
|
||||
|
||||
def run_git_command(command: list) -> tuple[int, str, str]:
|
||||
"""Run a git command and return exit code, stdout, and stderr."""
|
||||
try:
|
||||
@@ -191,6 +349,27 @@ def cmd_create(args: argparse.Namespace) -> None:
|
||||
)
|
||||
|
||||
|
||||
def cmd_list(args: argparse.Namespace) -> None:
|
||||
"""Handle the list subcommand."""
|
||||
prs = fetch_open_prs(args.remote)
|
||||
|
||||
if not prs:
|
||||
print("No open AGit pull requests found.")
|
||||
return
|
||||
|
||||
# This is the only way I found to query the actual AGit PRs
|
||||
# Gitea doesn't seem to have an actual api endpoint for them
|
||||
filtered_prs = [pr for pr in prs if pr.get("head", {}).get("label", "") == ""]
|
||||
|
||||
if not filtered_prs:
|
||||
print("No open AGit pull requests found.")
|
||||
return
|
||||
|
||||
for pr in filtered_prs:
|
||||
formatted_pr = format_pr_with_status(pr, args.remote)
|
||||
print(formatted_pr)
|
||||
|
||||
|
||||
def create_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="agit",
|
||||
@@ -203,16 +382,19 @@ DEFAULT_TARGET_BRANCH = {DEFAULT_TARGET_BRANCH}
|
||||
|
||||
Examples:
|
||||
$ agit create
|
||||
Opens editor to compose PR title and description (first line is title, rest is body).
|
||||
Opens editor to compose PR title and description (first line is title, rest is body)
|
||||
|
||||
$ agit create --auto
|
||||
Creates PR using latest commit message automatically (old behavior).
|
||||
Creates PR using latest commit message automatically
|
||||
|
||||
$ agit create --topic "my-feature"
|
||||
Set a custom topic.
|
||||
|
||||
$ agit create --force
|
||||
Force push to a certain topic
|
||||
|
||||
$ agit list
|
||||
Lists all open pull requests for the current repository
|
||||
""",
|
||||
)
|
||||
|
||||
@@ -239,6 +421,28 @@ Examples:
|
||||
""",
|
||||
)
|
||||
|
||||
list_parser = subparsers.add_parser(
|
||||
"list",
|
||||
aliases=["l"],
|
||||
help="List open AGit pull requests",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=f"""
|
||||
Examples:
|
||||
$ agit list
|
||||
Lists all open AGit PRs for the current repository.
|
||||
|
||||
$ agit list --remote upstream
|
||||
Lists PRs using the 'upstream' remote instead of '{TARGET_REMOTE_REPOSITORY}'.
|
||||
""",
|
||||
)
|
||||
|
||||
list_parser.add_argument(
|
||||
"-r",
|
||||
"--remote",
|
||||
default=TARGET_REMOTE_REPOSITORY,
|
||||
help=f"Git remote to use for fetching PRs (default: {TARGET_REMOTE_REPOSITORY})",
|
||||
)
|
||||
|
||||
create_parser.add_argument(
|
||||
"-r",
|
||||
"--remote",
|
||||
@@ -284,6 +488,7 @@ Examples:
|
||||
)
|
||||
|
||||
create_parser.set_defaults(func=cmd_create)
|
||||
list_parser.set_defaults(func=cmd_list)
|
||||
return parser
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ in
|
||||
writers.writePython3Bin "agit" {
|
||||
flakeIgnore = [
|
||||
"E501"
|
||||
"W503" # treefmt reapplies the conditions to trigger this check
|
||||
];
|
||||
makeWrapperArgs = [
|
||||
"--prefix"
|
||||
|
||||
39
pkgs/clan-app/process-compose-2d.yaml
Normal file
39
pkgs/clan-app/process-compose-2d.yaml
Normal file
@@ -0,0 +1,39 @@
|
||||
version: "0.5"
|
||||
|
||||
processes:
|
||||
# App Dev
|
||||
|
||||
clan-app-ui:
|
||||
namespace: "app"
|
||||
command: |
|
||||
cd $(git rev-parse --show-toplevel)/pkgs/clan-app/ui-2d
|
||||
npm install
|
||||
vite
|
||||
ready_log_line: "VITE"
|
||||
|
||||
clan-app:
|
||||
namespace: "app"
|
||||
command: |
|
||||
cd $(git rev-parse --show-toplevel)/pkgs/clan-app
|
||||
./bin/clan-app --debug --content-uri http://localhost:3000
|
||||
depends_on:
|
||||
clan-app-ui:
|
||||
condition: "process_log_ready"
|
||||
is_foreground: true
|
||||
ready_log_line: "Debug mode enabled"
|
||||
|
||||
# Storybook Dev
|
||||
|
||||
storybook:
|
||||
namespace: "storybook"
|
||||
command: |
|
||||
cd $(git rev-parse --show-toplevel)/pkgs/clan-app/ui-2d
|
||||
npm run storybook-dev -- --ci
|
||||
ready_log_line: "started"
|
||||
|
||||
luakit:
|
||||
namespace: "storybook"
|
||||
command: "luakit http://localhost:6006"
|
||||
depends_on:
|
||||
storybook:
|
||||
condition: "process_log_ready"
|
||||
1
pkgs/clan-app/ui-2d/.fonts
Symbolic link
1
pkgs/clan-app/ui-2d/.fonts
Symbolic link
@@ -0,0 +1 @@
|
||||
../ui/.fonts
|
||||
5
pkgs/clan-app/ui-2d/.gitignore
vendored
Normal file
5
pkgs/clan-app/ui-2d/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
app/api
|
||||
app/.fonts
|
||||
|
||||
.vite
|
||||
storybook-static
|
||||
1
pkgs/clan-app/ui-2d/.storybook
Symbolic link
1
pkgs/clan-app/ui-2d/.storybook
Symbolic link
@@ -0,0 +1 @@
|
||||
../ui/.storybook
|
||||
7
pkgs/clan-app/ui-2d/.vscode/settings.json
vendored
Normal file
7
pkgs/clan-app/ui-2d/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["cx\\(([^)]*)\\)", "[\"'`]([^\"'`]*)[\"'`]"]
|
||||
],
|
||||
"editor.wordWrap": "on"
|
||||
}
|
||||
1
pkgs/clan-app/ui-2d/api
Symbolic link
1
pkgs/clan-app/ui-2d/api
Symbolic link
@@ -0,0 +1 @@
|
||||
../ui/api
|
||||
1
pkgs/clan-app/ui-2d/eslint.config.mjs
Symbolic link
1
pkgs/clan-app/ui-2d/eslint.config.mjs
Symbolic link
@@ -0,0 +1 @@
|
||||
../ui/eslint.config.mjs
|
||||
1
pkgs/clan-app/ui-2d/gtk.webview.js
Symbolic link
1
pkgs/clan-app/ui-2d/gtk.webview.js
Symbolic link
@@ -0,0 +1 @@
|
||||
../ui/gtk.webview.js
|
||||
1
pkgs/clan-app/ui-2d/icons
Symbolic link
1
pkgs/clan-app/ui-2d/icons
Symbolic link
@@ -0,0 +1 @@
|
||||
../ui/icons
|
||||
14
pkgs/clan-app/ui-2d/index.html
Normal file
14
pkgs/clan-app/ui-2d/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Solid App</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="/src/index.tsx" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
10
pkgs/clan-app/ui-2d/knip.json
Normal file
10
pkgs/clan-app/ui-2d/knip.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"ignore": [
|
||||
"gtk.webview.js",
|
||||
"stylelint.config.js",
|
||||
"util.ts",
|
||||
"src/components/v2/**",
|
||||
"api/**",
|
||||
"tailwind/**"
|
||||
]
|
||||
}
|
||||
1
pkgs/clan-app/ui-2d/package-lock.json
generated
Symbolic link
1
pkgs/clan-app/ui-2d/package-lock.json
generated
Symbolic link
@@ -0,0 +1 @@
|
||||
../ui/package-lock.json
|
||||
84
pkgs/clan-app/ui-2d/package.json
Normal file
84
pkgs/clan-app/ui-2d/package.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"name": "@clan/ui",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"dev": "vite",
|
||||
"build": "npm run check && npm run test && vite build && npm run convert-html",
|
||||
"convert-html": "node gtk.webview.js",
|
||||
"serve": "vite preview",
|
||||
"check": "tsc --noEmit --skipLibCheck && eslint ./src --fix",
|
||||
"knip": "knip --fix",
|
||||
"test": "vitest run --project unit --typecheck",
|
||||
"storybook": "storybook",
|
||||
"storybook-build": "storybook build",
|
||||
"storybook-dev": "storybook dev -p 6006",
|
||||
"test-storybook": "vitest run --project storybook",
|
||||
"test-storybook-update-snapshots": "vitest run --project storybook --update",
|
||||
"test-storybook-static": "npm run storybook-build && concurrently -k -s first -n 'SB,TEST' -c 'magenta,blue' 'npx http-server storybook-static --port 6006 --silent' 'npx wait-on tcp:127.0.0.1:6006 && npm run test-storybook'"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.3.0",
|
||||
"@kachurun/storybook-solid": "^9.0.11",
|
||||
"@kachurun/storybook-solid-vite": "^9.0.11",
|
||||
"@storybook/addon-a11y": "^9.0.8",
|
||||
"@storybook/addon-docs": "^9.0.8",
|
||||
"@storybook/addon-links": "^9.0.8",
|
||||
"@storybook/addon-onboarding": "^9.0.8",
|
||||
"@storybook/addon-vitest": "^9.0.8",
|
||||
"@tailwindcss/typography": "^0.5.13",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"@types/node": "^22.15.19",
|
||||
"@vitest/browser": "^3.2.3",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"classnames": "^2.5.1",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-plugin-tailwindcss": "^3.17.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"knip": "^5.61.2",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.2.5",
|
||||
"solid-devtools": "^0.34.0",
|
||||
"storybook": "^9.0.8",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript-eslint": "^8.32.1",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-solid": "^2.8.2",
|
||||
"vite-plugin-solid-svg": "^0.8.1",
|
||||
"vitest": "^3.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.6.8",
|
||||
"@kobalte/core": "^0.13.10",
|
||||
"@kobalte/tailwindcss": "^0.9.0",
|
||||
"@modular-forms/solid": "^0.25.1",
|
||||
"@solid-primitives/storage": "^4.3.2",
|
||||
"@solidjs/router": "^0.15.3",
|
||||
"@tanstack/eslint-plugin-query": "^5.51.12",
|
||||
"@tanstack/solid-query": "^5.76.0",
|
||||
"corvu": "^0.7.1",
|
||||
"nanoid": "^5.0.7",
|
||||
"solid-js": "^1.9.7",
|
||||
"solid-toast": "^0.5.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/darwin-arm64": "^0.25.4",
|
||||
"@esbuild/darwin-x64": "^0.25.4",
|
||||
"@esbuild/linux-arm64": "^0.25.4",
|
||||
"@esbuild/linux-x64": "^0.25.4"
|
||||
},
|
||||
"overrides": {
|
||||
"vite": {
|
||||
"rollup": "npm:@rollup/wasm-node@^4.34.9"
|
||||
},
|
||||
"@rollup/rollup-darwin-x64": "npm:@rollup/wasm-node@^4.34.9",
|
||||
"@rollup/rollup-linux-x64": "npm:@rollup/wasm-node@^4.34.9",
|
||||
"@rollup/rollup-darwin-arm64": "npm:@rollup/wasm-node@^4.34.9",
|
||||
"@rollup/rollup-linux-arm64": "npm:@rollup/wasm-node@^4.34.9"
|
||||
}
|
||||
}
|
||||
1
pkgs/clan-app/ui-2d/postcss.config.js
Symbolic link
1
pkgs/clan-app/ui-2d/postcss.config.js
Symbolic link
@@ -0,0 +1 @@
|
||||
../ui/postcss.config.js
|
||||
1
pkgs/clan-app/ui-2d/prettier.config.js
Symbolic link
1
pkgs/clan-app/ui-2d/prettier.config.js
Symbolic link
@@ -0,0 +1 @@
|
||||
../ui/prettier.config.js
|
||||
@@ -6,10 +6,8 @@ import type {
|
||||
} from "@floating-ui/dom";
|
||||
import { computePosition } from "@floating-ui/dom";
|
||||
|
||||
export interface UseFloatingOptions<
|
||||
R extends ReferenceElement,
|
||||
F extends HTMLElement,
|
||||
> extends Partial<ComputePositionConfig> {
|
||||
interface UseFloatingOptions<R extends ReferenceElement, F extends HTMLElement>
|
||||
extends Partial<ComputePositionConfig> {
|
||||
whileElementsMounted?: (
|
||||
reference: R,
|
||||
floating: F,
|
||||
@@ -23,7 +21,7 @@ interface UseFloatingState extends Omit<ComputePositionReturn, "x" | "y"> {
|
||||
y?: number | null;
|
||||
}
|
||||
|
||||
export interface UseFloatingResult extends UseFloatingState {
|
||||
interface UseFloatingResult extends UseFloatingState {
|
||||
update(): void;
|
||||
}
|
||||
|
||||
8
pkgs/clan-app/ui-2d/src/Form/fields/FormSection.tsx
Normal file
8
pkgs/clan-app/ui-2d/src/Form/fields/FormSection.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import { JSX } from "solid-js";
|
||||
|
||||
interface FormSectionProps {
|
||||
children: JSX.Element;
|
||||
}
|
||||
const FormSection = (props: FormSectionProps) => {
|
||||
return <div class="p-2">{props.children}</div>;
|
||||
};
|
||||
@@ -21,7 +21,7 @@ import { FieldLayout } from "./layout";
|
||||
import Icon from "@/src/components/icon";
|
||||
import { useContext } from "corvu/dialog";
|
||||
|
||||
export interface Option {
|
||||
interface Option {
|
||||
value: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
@@ -241,9 +241,9 @@ export function SelectInput(props: SelectInputpProps) {
|
||||
top: `${position.y ?? 0}px`,
|
||||
left: `${position.x ?? 0}px`,
|
||||
}}
|
||||
class="z-[1000] shadow"
|
||||
class="rounded-md border border-gray-200 bg-white shadow-lg"
|
||||
>
|
||||
<ul class="flex max-h-96 flex-col gap-1 overflow-x-hidden overflow-y-scroll">
|
||||
<ul class="flex max-h-96 flex-col gap-1 overflow-x-hidden overflow-y-scroll p-1">
|
||||
<Show when={!props.loading} fallback={"Loading ...."}>
|
||||
<For each={props.options}>
|
||||
{(opt) => (
|
||||
57
pkgs/clan-app/ui-2d/src/Form/fields/TextInput.tsx
Normal file
57
pkgs/clan-app/ui-2d/src/Form/fields/TextInput.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { splitProps, type JSX } from "solid-js";
|
||||
import {
|
||||
InputBase,
|
||||
InputError,
|
||||
InputLabel,
|
||||
InputVariant,
|
||||
} from "@/src/components/inputBase";
|
||||
import { FieldLayout } from "./layout";
|
||||
|
||||
interface TextInputProps {
|
||||
// Common
|
||||
error?: string;
|
||||
required?: boolean;
|
||||
disabled?: boolean;
|
||||
// Passed to input
|
||||
value: string;
|
||||
inputProps?: JSX.InputHTMLAttributes<HTMLInputElement>;
|
||||
placeholder?: string;
|
||||
variant?: InputVariant;
|
||||
// Passed to label
|
||||
label: JSX.Element;
|
||||
help?: string;
|
||||
// Passed to layout
|
||||
class?: string;
|
||||
}
|
||||
|
||||
export function TextInput(props: TextInputProps) {
|
||||
const [layoutProps, rest] = splitProps(props, ["class"]);
|
||||
return (
|
||||
<FieldLayout
|
||||
label={
|
||||
<InputLabel
|
||||
class="col-span-2"
|
||||
required={props.required}
|
||||
error={!!props.error}
|
||||
help={props.help}
|
||||
>
|
||||
{props.label}
|
||||
</InputLabel>
|
||||
}
|
||||
field={
|
||||
<InputBase
|
||||
variant={props.variant}
|
||||
error={!!props.error}
|
||||
required={props.required}
|
||||
disabled={props.disabled}
|
||||
placeholder={props.placeholder}
|
||||
class="col-span-10"
|
||||
{...props.inputProps}
|
||||
value={props.value}
|
||||
/>
|
||||
}
|
||||
error={props.error && <InputError error={props.error} />}
|
||||
{...layoutProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
2
pkgs/clan-app/ui-2d/src/Form/fields/index.ts
Normal file
2
pkgs/clan-app/ui-2d/src/Form/fields/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./FormSection";
|
||||
export * from "./TextInput";
|
||||
26
pkgs/clan-app/ui-2d/src/Form/fields/layout.tsx
Normal file
26
pkgs/clan-app/ui-2d/src/Form/fields/layout.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { JSX, splitProps } from "solid-js";
|
||||
import cx from "classnames";
|
||||
|
||||
interface LayoutProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||
field?: JSX.Element;
|
||||
label?: JSX.Element;
|
||||
error?: JSX.Element;
|
||||
}
|
||||
export const FieldLayout = (props: LayoutProps) => {
|
||||
const [intern, divProps] = splitProps(props, [
|
||||
"field",
|
||||
"label",
|
||||
"error",
|
||||
"class",
|
||||
]);
|
||||
return (
|
||||
<div
|
||||
class={cx("grid grid-cols-10 items-center", intern.class)}
|
||||
{...divProps}
|
||||
>
|
||||
<div class="col-span-5 flex items-center">{props.label}</div>
|
||||
<div class="col-span-5">{props.field}</div>
|
||||
{props.error && <span class="col-span-full">{props.error}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user