initial retooling

This commit is contained in:
2026-03-27 14:23:08 -05:00
commit 5057ed2a05
46 changed files with 4969 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
{config, pkgs, lib, ...}:
{
systemd.services.Sovran_SystemsOS_File_Fixes_And_New_Services = {
unitConfig = {
After = "btcpayserver.service";
Requires = "network-online.target";
};
serviceConfig = {
ExecStartPre= "/run/current-system/sw/bin/sleep 30";
ExecStart = "/run/current-system/sw/bin/wget https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/file_fixes_and_new_services/Sovran_SystemsOS_File_Fixes_And_New_Services.sh -O /home/free/Downloads/Sovran_SystemsOS_File_Fixes_And_New_Services.sh ; /run/current-system/sw/bin/bash /home/free/Downloads/Sovran_SystemsOS_File_Fixes_And_New_Services.sh";
RemainAfterExit = "yes";
User = "root";
Type = "oneshot";
};
wantedBy = [ "multi-user.target" ];
};
}

23
modules/bip110.nix Executable file
View File

@@ -0,0 +1,23 @@
{ config, lib, pkgs, ... }:
let
cfg = config.sovran_systemsOS;
in
{
options.sovran_systemsOS.packages.bip110 = lib.mkOption {
type = lib.types.nullOr lib.types.package;
default = null;
description = "BIP110 Bitcoin package";
};
config = lib.mkIf (
cfg.features.bip110 &&
cfg.packages.bip110 != null
) {
services.bitcoind.package = lib.mkForce cfg.packages.bip110;
environment.systemPackages = [
cfg.packages.bip110
];
};
}

7
modules/bitcoin-core.nix Executable file
View File

@@ -0,0 +1,7 @@
{ config, pkgs, lib, ... }:
lib.mkIf config.sovran_systemsOS.features.bitcoin-core {
services.bitcoind.package = lib.mkForce config.nix-bitcoin.pkgs.bitcoind;
}

95
modules/bitcoinecosystem.nix Executable file
View File

@@ -0,0 +1,95 @@
{ config, pkgs, lib, ... }:
lib.mkIf config.sovran_systemsOS.features.bitcoin {
## Bitcoind
services.bitcoind = {
enable = true;
package = config.nix-bitcoin.pkgs.bitcoind-knots;
dataDir = "/run/media/Second_Drive/BTCEcoandBackup/Bitcoin_Node";
txindex = true;
tor.proxy = true;
tor.enforce = true;
disablewallet = true;
extraConfig = ''
peerbloomfilters=1
server=1
'';
};
nix-bitcoin.onionServices.bitcoind.enable = true;
nix-bitcoin.onionServices.electrs.enable = true;
nix-bitcoin.onionServices.rtl.enable = true;
## Electrs
services.electrs = {
enable = true;
tor.enforce = true;
dataDir = "/run/media/Second_Drive/BTCEcoandBackup/Electrs_Data";
};
## LND
services.lnd = {
enable = true;
tor.enforce = true;
tor.proxy = true;
extraConfig = ''
protocol.option-scid-alias=true
'';
};
nix-bitcoin.onionServices.lnd.public = true;
## LNDconnect
services.lnd.lndconnect = {
enable = true;
onion = true;
};
## RTL
services.rtl = {
enable = true;
tor.enforce = true;
port = 3050;
nightTheme = true;
nodes = {
lnd = {
enable = true;
};
};
};
## BTCpayserver
services.btcpayserver = {
enable = true;
};
services.btcpayserver.lightningBackend = "lnd";
## System
nix-bitcoin.generateSecrets = true;
nix-bitcoin.nodeinfo.enable = true;
nix-bitcoin.operator = {
enable = true;
name = "free";
};
nix-bitcoin.useVersionLockedPkgs = false;
}

108
modules/core/caddy.nix Normal file
View File

@@ -0,0 +1,108 @@
{ config, pkgs, lib, ... }:
{
services.caddy = {
enable = true;
user = "caddy";
group = "root";
configFile = "/run/caddy/Caddyfile";
};
systemd.services.caddy-generate-config = {
description = "Generate Caddyfile from /var/lib/domains at runtime";
before = [ "caddy.service" ];
requiredBy = [ "caddy.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
RuntimeDirectory = "caddy";
};
path = [ pkgs.coreutils ];
script = ''
MATRIX=$(cat /var/lib/domains/matrix)
WORDPRESS=$(cat /var/lib/domains/wordpress)
NEXTCLOUD=$(cat /var/lib/domains/nextcloud)
BTCPAY=$(cat /var/lib/domains/btcpayserver)
VAULTWARDEN=$(cat /var/lib/domains/vaultwarden)
HAVEN=$(cat /var/lib/domains/haven)
ACME_EMAIL=$(cat /var/lib/domains/sslemail)
# Start with global config
cat > /run/caddy/Caddyfile <<EOF
{
email $ACME_EMAIL
}
EOF
# If element-calling is enabled, it wrote a snippet with
# enhanced Matrix vhosts (.well-known, element-calling routes)
if [ -f /run/caddy/element-calling.snippet ]; then
cat /run/caddy/element-calling.snippet >> /run/caddy/Caddyfile
else
# Fallback: basic Matrix vhosts without element-calling
cat >> /run/caddy/Caddyfile <<EOF
$MATRIX {
reverse_proxy /_matrix/* http://localhost:8008
reverse_proxy /_synapse/client/* http://localhost:8008
}
$MATRIX:8448 {
reverse_proxy http://localhost:8008
}
EOF
fi
# Append remaining vhosts
cat >> /run/caddy/Caddyfile <<EOF
$WORDPRESS {
encode gzip zstd
root * /var/lib/www/wordpress
php_fastcgi unix//run/phpfpm/mypool.sock
file_server browse
}
$NEXTCLOUD {
encode gzip zstd
root * /var/lib/www/nextcloud
php_fastcgi unix//run/phpfpm/mypool.sock {
trusted_proxies private_ranges
}
file_server
redir /.well-known/carddav /remote.php/dav/ 301
redir /.well-known/caldav /remote.php/dav/ 301
header {
Strict-Transport-Security max-age=31536000;
}
}
$BTCPAY {
reverse_proxy http://localhost:23000
encode gzip zstd
}
$VAULTWARDEN {
reverse_proxy http://localhost:8777
encode gzip zstd
}
$HAVEN {
reverse_proxy localhost:3355 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
transport http {
versions 1.1
}
}
request_body {
max_size 100MB
}
}
EOF
'';
};
}

View File

@@ -0,0 +1,68 @@
{ config, pkgs, lib, ... }:
{
# The cron job scans /var/lib/njalla/hooks.d/ for DDNS URLs
systemd.services.njalla-ddns = {
description = "Njalla Dynamic DNS Updater";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
Type = "oneshot";
};
script = ''
set -euo pipefail
IP=$(${pkgs.dig}/bin/dig @resolver4.opendns.com myip.opendns.com +short -4)
if [ -z "$IP" ]; then
echo "Failed to resolve external IP"
exit 1
fi
# Only update if IP changed
LAST_IP_FILE="/var/lib/njalla/.last_ip"
LAST_IP=""
[ -f "$LAST_IP_FILE" ] && LAST_IP=$(cat "$LAST_IP_FILE")
if [ "$IP" = "$LAST_IP" ]; then
echo "IP unchanged ($IP), skipping"
exit 0
fi
echo -n "$IP" > "$LAST_IP_FILE"
echo "IP changed to $IP, updating DNS records..."
# Update external_ip secret
echo -n "$IP" > /var/lib/secrets/external_ip
# Process each DDNS hook
HOOKS_DIR="/var/lib/njalla/hooks.d"
mkdir -p "$HOOKS_DIR"
for hook in "$HOOKS_DIR"/*; do
[ -f "$hook" ] || continue
DDNS_URL=$(cat "$hook")
SERVICE=$(basename "$hook")
echo "Updating $SERVICE..."
${pkgs.curl}/bin/curl -s "''${DDNS_URL}''${IP}" || echo "Failed: $SERVICE"
done
echo "Done."
'';
};
# Run every 15 minutes
systemd.timers.njalla-ddns = {
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "*:0/15";
Persistent = true;
};
};
# Ensure directory exists
systemd.tmpfiles.rules = [
"d /var/lib/njalla 0700 root root -"
"d /var/lib/njalla/hooks.d 0700 root root -"
];
}

37
modules/core/role-logic.nix Executable file
View File

@@ -0,0 +1,37 @@
{ config, lib, ... }:
{
config = lib.mkMerge [
# Server-Desktop Role most services enabled
(lib.mkIf config.sovran_systemsOS.roles.server-desktop {
sovran_systemsOS.features = {
synapse = true;
bitcoin = true;
coturn = true;
vaultwarden = true;
haven = false;
mempool = false;
bip110 = false;
element-calling = false;
bitcoin-core = false;
rdp = false;
};
})
# Desktop role
(lib.mkIf config.sovran_systemsOS.roles.desktop {
services.xserver.enable = true;
services.desktopManager.gnome.enable = true;
})
# Bitcoin node role
(lib.mkIf config.sovran_systemsOS.roles.node {
sovran_systemsOS.features = {
bitcoin = true;
bip110 = false;
};
})
];
}

33
modules/core/roles.nix Executable file
View File

@@ -0,0 +1,33 @@
{ config, lib, ... }:
{
options.sovran_systemsOS = {
roles = {
server-desktop = lib.mkOption {
type = lib.types.bool;
default = !config.sovran_systemsOS.roles.desktop && !config.sovran_systemsOS.roles.node;
};
desktop = lib.mkEnableOption "Desktop Role";
node = lib.mkEnableOption "Bitcoin Node Only Role";
};
features = {
coturn = lib.mkEnableOption "TURN server";
synapse = lib.mkEnableOption "Matrix Synapse";
bitcoin = lib.mkEnableOption "Bitcoin Ecosystem";
vaultwarden = lib.mkEnableOption "Vaultwarden";
haven = lib.mkEnableOption "Haven NOSTR relay";
bip110 = lib.mkEnableOption "BIP-110 Bitcoin Better Money";
mempool = lib.mkEnableOption "Bitcoin Mempool Explorer";
element-calling = lib.mkEnableOption "Element Video and Audio Calling";
bitcoin-core = lib.mkEnableOption "Bitcoin Core";
rdp = lib.mkEnableOption "Gnome Remote Desktop";
};
nostr_npub = lib.mkOption {
type = lib.types.str;
default = "";
description = "Nostr public key (npub1...) for Haven relay";
};
};
}

View File

@@ -0,0 +1,13 @@
{ config, pkgs, lib, ... }:
let
sovran-manage = pkgs.writeShellScriptBin "sovran-manage" (builtins.readFile ../../scripts/sovran-manage.sh);
in
{
environment.systemPackages = [
sovran-manage
pkgs.pwgen
pkgs.dig
pkgs.curl
];
}

54
modules/coturn.nix Executable file
View File

@@ -0,0 +1,54 @@
{config, pkgs, lib, ...}:
let
personalization = import ./personalization.nix;
in
lib.mkIf config.sovran_systemsOS.features.coturn {
systemd.services.coturn-helper = {
script = ''
systemctl restart coturn
'';
unitConfig = {
Type = "simple";
After = "btcpayserver.service";
Requires = "network-online.target";
};
serviceConfig = {
RemainAfterExit = "yes";
Type = "oneshot";
};
wantedBy = [ "multi-user.target" ];
};
services.coturn = {
enable = true;
use-auth-secret = true;
static-auth-secret = "${personalization.coturn_static_auth_secret}";
realm = personalization.matrix_url;
cert = "/var/lib/coturn/${personalization.matrix_url}.crt.pem";
pkey = "/var/lib/coturn/${personalization.matrix_url}.key.pem";
min-port = 49152;
max-port = 65535;
listening-port = 5349;
no-cli = true;
extraConfig = ''
verbose
external-ip=${personalization.external_ip_secret}
stale-nonce
fingerprint
'';
};
}

248
modules/element-calling.nix Executable file
View File

@@ -0,0 +1,248 @@
{ config, pkgs, lib, ... }:
let
livekitKeyFile = "/var/lib/livekit/livekit_keyFile";
in
lib.mkIf config.sovran_systemsOS.features.element-calling {
####### LIVEKIT KEY GENERATION #######
systemd.tmpfiles.rules = [
"d /var/lib/livekit 0750 root root -"
];
systemd.services.livekit-key-setup = {
description = "Generate LiveKit key file if missing";
wantedBy = [ "multi-user.target" ];
before = [ "livekit.service" "lk-jwt-service.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [ pkgs.openssl ];
script = ''
if [ ! -f ${livekitKeyFile} ]; then
API_KEY="devkey_$(openssl rand -hex 16)"
API_SECRET="$(openssl rand -base64 36 | tr -d '\n')"
echo "$API_KEY: $API_SECRET" > ${livekitKeyFile}
chmod 600 ${livekitKeyFile}
echo "LiveKit key file generated at ${livekitKeyFile}"
else
echo "LiveKit key file already exists, skipping generation"
fi
'';
};
####### ENSURE SERVICES START AFTER KEY EXISTS #######
systemd.services.livekit.after = [ "livekit-key-setup.service" ];
systemd.services.livekit.wants = [ "livekit-key-setup.service" ];
systemd.services.lk-jwt-service.after = [ "livekit-key-setup.service" ];
systemd.services.lk-jwt-service.wants = [ "livekit-key-setup.service" ];
####### CADDY SNIPPET — written to /run/caddy for caddy.nix to pick up #######
systemd.services.element-calling-caddy-config = {
description = "Generate Element Calling Caddy config snippet";
before = [ "caddy-generate-config.service" ];
requiredBy = [ "caddy-generate-config.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [ pkgs.coreutils ];
script = ''
MATRIX=$(cat /var/lib/domains/matrix)
ELEMENT_CALLING=$(cat /var/lib/domains/element-calling)
mkdir -p /run/caddy
cat > /run/caddy/element-calling.snippet <<EOF
$MATRIX {
reverse_proxy /_matrix/* http://localhost:8008
reverse_proxy /_synapse/client/* http://localhost:8008
header /.well-known/matrix/* Content-Type "application/json"
header /.well-known/matrix/* Access-Control-Allow-Origin "*"
header /.well-known/matrix/* Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header /.well-known/matrix/* Access-Control-Allow-Headers "X-Requested-With, Content-Type, Authorization"
respond /.well-known/matrix/client \`{ "m.homeserver": {"base_url": "https://$MATRIX" }, "org.matrix.msc4143.rtc_foci": [{ "type":"livekit", "livekit_service_url":"https://$ELEMENT_CALLING/livekit/jwt" }] }\`
}
$MATRIX:8448 {
reverse_proxy http://localhost:8008
}
$ELEMENT_CALLING {
handle /livekit/jwt/sfu/get {
uri strip_prefix /livekit/jwt
reverse_proxy [::1]:8073 {
header_up Host {host}
header_up X-Forwarded-Server {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
}
}
handle {
reverse_proxy localhost:7880
}
}
EOF
'';
};
####### LIVEKIT RUNTIME CONFIG #######
systemd.services.livekit-runtime-config = {
description = "Generate LiveKit runtime config from domain files";
before = [ "livekit.service" ];
after = [ "livekit-key-setup.service" ];
requiredBy = [ "livekit.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [ pkgs.coreutils ];
script = ''
MATRIX=$(cat /var/lib/domains/matrix)
mkdir -p /run/livekit
cat > /run/livekit/runtime-config.yaml <<EOF
turn:
domain: $MATRIX
cert_file: /var/lib/livekit/$MATRIX.crt
key_file: /var/lib/livekit/$MATRIX.key
EOF
chmod 640 /run/livekit/runtime-config.yaml
'';
};
####### LIVEKIT SERVICE #######
services.livekit = {
enable = true;
openFirewall = true;
keyFile = livekitKeyFile;
settings = {
rtc.use_external_ip = true;
rtc.udp_port = "7882-7894";
room.auto_create = false;
turn = {
enabled = true;
tls_port = 5349;
udp_port = 3478;
};
};
};
networking.firewall.allowedTCPPorts = [ 7881 ];
networking.firewall.allowedUDPPortRanges = [
{ from = 7882; to = 7894; }
];
####### JWT SERVICE #######
systemd.services.lk-jwt-service-runtime-config = {
description = "Generate lk-jwt-service runtime config from domain files";
before = [ "lk-jwt-service.service" ];
after = [ "livekit-key-setup.service" ];
requiredBy = [ "lk-jwt-service.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [ pkgs.coreutils ];
script = ''
ELEMENT_CALLING=$(cat /var/lib/domains/element-calling)
mkdir -p /run/lk-jwt-service
cat > /run/lk-jwt-service/env <<EOF
LIVEKIT_URL=wss://$ELEMENT_CALLING
EOF
chmod 640 /run/lk-jwt-service/env
'';
};
services.lk-jwt-service = {
enable = true;
port = 8073;
keyFile = livekitKeyFile;
};
systemd.services.lk-jwt-service.serviceConfig.EnvironmentFile = [
"/run/lk-jwt-service/env"
];
####### SYNAPSE RUNTIME CONFIG (element-calling additions) #######
systemd.services.element-calling-synapse-config = {
description = "Generate Synapse runtime config for Element Calling";
before = [ "matrix-synapse.service" ];
requiredBy = [ "matrix-synapse.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [ pkgs.coreutils ];
script = ''
MATRIX=$(cat /var/lib/domains/matrix)
mkdir -p /run/matrix-synapse
cat > /run/matrix-synapse/element-calling-config.yaml <<EOF
server_name: "$MATRIX"
public_baseurl: "https://$MATRIX"
serve_server_wellknown: true
experimental_features:
msc3266_enabled: true
msc4222_enabled: true
max_event_delay_duration: "24h"
rc_message:
per_second: 0.5
burst_count: 30
rc_delayed_event_mgmt:
per_second: 1
burst_count: 20
EOF
chown matrix-synapse:matrix-synapse /run/matrix-synapse/element-calling-config.yaml
chmod 640 /run/matrix-synapse/element-calling-config.yaml
'';
};
services.matrix-synapse = {
extraConfigFiles = [ "/run/matrix-synapse/element-calling-config.yaml" ];
settings = lib.mkForce {
push.include_content = false;
url_preview_enabled = true;
group_unread_count_by_room = false;
encryption_enabled_by_default_for_room_type = "invite";
allow_profile_lookup_over_federation = false;
allow_device_name_lookup_over_federation = false;
url_preview_ip_range_blacklist = [
"10.0.0.0/8" "100.64.0.0/10" "169.254.0.0/16" "172.16.0.0/12"
"192.0.0.0/24" "192.0.2.0/24" "192.168.0.0/16" "192.88.99.0/24"
"198.18.0.0/15" "198.51.100.0/24" "2001:db8::/32" "203.0.113.0/24"
"224.0.0.0/4" "::1/128" "fc00::/7" "fe80::/10" "fec0::/10" "ff00::/8"
];
url_preview_ip_ranger_whitelist = [ "127.0.0.1" ];
presence.enabled = true;
enable_registration = false;
registration_shared_secret = config.age.secrets.matrix_reg_secret.path;
listeners = [
{
port = 8008;
bind_addresses = [ "::1" ];
type = "http";
tls = false;
x_forwarded = true;
resources = [
{ names = [ "client" ]; compress = true; }
{ names = [ "federation" ]; compress = false; }
];
}
];
};
};
}

158
modules/haven.nix Executable file
View File

@@ -0,0 +1,158 @@
{ config, pkgs, lib, ... }:
let
npub = config.sovran_systemsOS.nostr_npub;
in
lib.mkIf (config.sovran_systemsOS.features.haven && npub != "") {
# ── Caddy vhost is now handled centrally in caddy.nix ─────
# ── Generate Haven runtime config from domain files ───────
systemd.services.haven-runtime-config = {
description = "Generate Haven runtime config from domain files";
before = [ "haven.service" ];
requiredBy = [ "haven.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [ pkgs.coreutils ];
script = ''
HAVEN=$(cat /var/lib/domains/haven)
mkdir -p /run/haven
cat > /run/haven/runtime.env <<EOF
RELAY_URL=$HAVEN
PRIVATE_RELAY_NAME=$HAVEN private relay
PRIVATE_RELAY_DESCRIPTION=The Relay From
CHAT_RELAY_NAME=$HAVEN chat relay
CHAT_RELAY_DESCRIPTION=a relay for private chats
OUTBOX_RELAY_NAME=$HAVEN outbox relay
OUTBOX_RELAY_DESCRIPTION=a relay and Blossom server for public messages and media
INBOX_RELAY_NAME=$HAVEN inbox relay
INBOX_RELAY_DESCRIPTION=send your interactions with my notes here
EOF
chmod 640 /run/haven/runtime.env
chown haven:haven /run/haven/runtime.env
'';
};
services.haven = {
enable = true;
settings = {
OWNER_NPUB = npub;
# RELAY_URL injected at runtime via EnvironmentFile
RELAY_PORT = 3355;
RELAY_BIND_ADDRESS = "0.0.0.0";
DB_ENGINE = "badger";
LMDB_MAPSIZE = 3000000000;
BLOSSOM_PATH = "blossom/";
# Relay names/descriptions injected at runtime via EnvironmentFile
PRIVATE_RELAY_NPUB = npub;
CHAT_RELAY_NPUB = npub;
OUTBOX_RELAY_NPUB = npub;
INBOX_PULL_INTERVAL_SECONDS = 600;
PRIVATE_RELAY_EVENT_IP_LIMITER_TOKENS_PER_INTERVAL = 50;
PRIVATE_RELAY_EVENT_IP_LIMITER_INTERVAL = 1;
PRIVATE_RELAY_EVENT_IP_LIMITER_MAX_TOKENS = 100;
PRIVATE_RELAY_ALLOW_EMPTY_FILTERS = true;
PRIVATE_RELAY_ALLOW_COMPLEX_FILTERS = true;
PRIVATE_RELAY_CONNECTION_RATE_LIMITER_TOKENS_PER_INTERVAL = 3;
PRIVATE_RELAY_CONNECTION_RATE_LIMITER_INTERVAL = 5;
PRIVATE_RELAY_CONNECTION_RATE_LIMITER_MAX_TOKENS = 9;
CHAT_RELAY_WOT_DEPTH = 3;
CHAT_RELAY_WOT_REFRESH_INTERVAL_HOURS = 24;
CHAT_RELAY_MINIMUM_FOLLOWERS = 3;
CHAT_RELAY_EVENT_IP_LIMITER_TOKENS_PER_INTERVAL = 50;
CHAT_RELAY_EVENT_IP_LIMITER_INTERVAL = 1;
CHAT_RELAY_EVENT_IP_LIMITER_MAX_TOKENS = 100;
CHAT_RELAY_ALLOW_EMPTY_FILTERS = false;
CHAT_RELAY_ALLOW_COMPLEX_FILTERS = false;
CHAT_RELAY_CONNECTION_RATE_LIMITER_TOKENS_PER_INTERVAL = 3;
CHAT_RELAY_CONNECTION_RATE_LIMITER_INTERVAL = 3;
CHAT_RELAY_CONNECTION_RATE_LIMITER_MAX_TOKENS = 9;
OUTBOX_RELAY_EVENT_IP_LIMITER_TOKENS_PER_INTERVAL = 100;
OUTBOX_RELAY_EVENT_IP_LIMITER_INTERVAL = 600;
OUTBOX_RELAY_EVENT_IP_LIMITER_MAX_TOKENS = 1000;
OUTBOX_RELAY_ALLOW_EMPTY_FILTERS = true;
OUTBOX_RELAY_ALLOW_COMPLEX_FILTERS = true;
OUTBOX_RELAY_CONNECTION_RATE_LIMITER_TOKENS_PER_INTERVAL = 30;
OUTBOX_RELAY_CONNECTION_RATE_LIMITER_INTERVAL = 10;
OUTBOX_RELAY_CONNECTION_RATE_LIMITER_MAX_TOKENS = 90;
INBOX_RELAY_EVENT_IP_LIMITER_TOKENS_PER_INTERVAL = 10;
INBOX_RELAY_EVENT_IP_LIMITER_INTERVAL = 1;
INBOX_RELAY_EVENT_IP_LIMITER_MAX_TOKENS = 20;
INBOX_RELAY_ALLOW_EMPTY_FILTERS = false;
INBOX_RELAY_ALLOW_COMPLEX_FILTERS = false;
INBOX_RELAY_CONNECTION_RATE_LIMITER_TOKENS_PER_INTERVAL = 3;
INBOX_RELAY_CONNECTION_RATE_LIMITER_INTERVAL = 1;
INBOX_RELAY_CONNECTION_RATE_LIMITER_MAX_TOKENS = 9;
WOT_FETCH_TIMEOUT_SECONDS = 60;
WHITELISTED_NPUBS_FILE = "/var/lib/haven/whitelisted_npubs.json";
BLACKLISTED_NPUBS_FILE = "";
HAVEN_LOG_LEVEL = "INFO";
};
blastrRelays = [
"nos.lol"
"relay.nostr.band"
"relay.snort.social"
"nostr.mom"
"relay.primal.net"
"no.str.cr"
"nostr21.com"
"nostrue.com"
"wot.nostr.party"
"wot.sovbit.host"
"wot.girino.org"
"relay.lexingtonbitcoin.org"
"zap.watch"
"satsage.xyz"
"wons.calva.dev"
];
};
systemd.services.haven.serviceConfig.EnvironmentFile = [
"/run/haven/runtime.env"
];
systemd.tmpfiles.rules = [
"d /var/lib/haven 0750 haven haven -"
];
systemd.services.haven-whitelist-setup = {
description = "Ensure Haven whitelisted_npubs.json is valid";
wantedBy = [ "multi-user.target" ];
before = [ "haven.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
FILE="/var/lib/haven/whitelisted_npubs.json"
if [ ! -s "$FILE" ] || ! ${pkgs.jq}/bin/jq empty "$FILE" 2>/dev/null; then
echo '[]' > "$FILE"
chown haven:haven "$FILE"
chmod 770 "$FILE"
echo "Wrote valid empty JSON array to $FILE"
else
echo "$FILE already contains valid JSON, skipping"
fi
'';
};
systemd.services.haven.after = [ "haven-whitelist-setup.service" "haven-runtime-config.service" ];
systemd.services.haven.wants = [ "haven-whitelist-setup.service" "haven-runtime-config.service" ];
}

25
modules/mempool.nix Executable file
View File

@@ -0,0 +1,25 @@
{ config, pkgs, lib, ... }:
lib.mkIf config.sovran_systemsOS.features.mempool {
services.mempool = {
enable = true;
frontend.enable = true;
};
services.mysql.package = lib.mkForce pkgs.mariadb;
nix-bitcoin.onionServices.mempool-frontend.enable = true;
services.caddy = {
virtualHosts = {
":60847" = {
extraConfig = ''
reverse_proxy :60845
encode gzip zstd
'';
};
};
};
}

25
modules/modules.nix Normal file
View File

@@ -0,0 +1,25 @@
{ config, pkgs, lib, ... }:
{
imports = [
./core/roles.nix
./core/role-logic.nix
./core/caddy.nix
./core/sovran-manage.nix
./php.nix
./Sovran_SystemsOS_File_Fixes_And_New_Services.nix
./synapse.nix
./coturn.nix
./wordpress.nix
./nextcloud.nix
./btcpayserver.nix
./vaultwarden.nix
./haven.nix
./bip110.nix
./element-calling.nix
./mempool.nix
./bitcoin-core.nix
./rdp.nix
./bitcoinecosystem.nix
];
}

224
modules/nextcloud.nix Normal file
View File

@@ -0,0 +1,224 @@
{ config, pkgs, lib, ... }:
let
cfg = config.sovran_systemsOS.services.nextcloud;
in
{
options.sovran_systemsOS.services.nextcloud = {
enable = lib.mkEnableOption "Nextcloud (raw PHP served by Caddy)";
};
config = lib.mkIf cfg.enable {
# ── Caddy vhost is now handled centrally in caddy.nix ─────
# ── PostgreSQL database ───────────────────────────────────
services.postgresql = {
enable = true;
};
# ── Auto-generate DB password and initialize ──────────────
systemd.services.nextcloud-db-init = {
description = "Initialize Nextcloud PostgreSQL database with auto-generated password";
after = [ "postgresql.service" ];
requires = [ "postgresql.service" ];
before = [ "nextcloud-init.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [ config.services.postgresql.package pkgs.pwgen pkgs.coreutils ];
script = ''
set -euo pipefail
SECRET_FILE="/var/lib/secrets/nextclouddb"
# Existing machines already have this file leave it alone
if [ ! -f "$SECRET_FILE" ]; then
mkdir -p /var/lib/secrets
pwgen -s 64 1 > "$SECRET_FILE"
chmod 600 "$SECRET_FILE"
fi
DB_PASS=$(cat "$SECRET_FILE")
# Create role if it doesn't exist, update password either way
psql -U postgres <<SQL
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'ncusr') THEN
CREATE ROLE "ncusr" WITH LOGIN PASSWORD '$DB_PASS';
ELSE
ALTER ROLE "ncusr" WITH LOGIN PASSWORD '$DB_PASS';
END IF;
END
\$\$;
SQL
# Create database if it doesn't exist
if ! psql -U postgres -lqt | cut -d \| -f 1 | grep -qw "nextclouddb"; then
psql -U postgres -c "CREATE DATABASE nextclouddb WITH OWNER ncusr TEMPLATE template0 LC_COLLATE = 'C' LC_CTYPE = 'C';"
fi
'';
};
# ── Fully automated Nextcloud setup ───────────────────────
systemd.services.nextcloud-init = {
description = "Download, extract, and fully configure Nextcloud";
after = [ "network-online.target" "postgresql.service" "phpfpm-mypool.service" "nextcloud-db-init.service" ];
wants = [ "network-online.target" ];
requires = [ "postgresql.service" "nextcloud-db-init.service" ];
wantedBy = [ "multi-user.target" ];
unitConfig = {
ConditionPathExists = "!/var/lib/www/nextcloud/config/config.php";
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = with pkgs; [ curl unzip php pwgen coreutils ];
script = ''
set -euo pipefail
INSTALL_DIR="/var/lib/www/nextcloud"
DATA_DIR="/var/lib/www/nextcloud-data"
DOMAIN=$(cat /var/lib/domains/nextcloud)
DB_NAME="nextclouddb"
DB_USER="ncusr"
DB_PASS=$(cat /var/lib/secrets/nextclouddb)
DB_HOST="localhost"
ADMIN_USER=$(pwgen -s 16 1)
ADMIN_PASS=$(pwgen -s 24 1)
echo ""
echo " Nextcloud Automated Installation"
echo ""
# Download
if [ ! -f "$INSTALL_DIR/occ" ]; then
echo "Downloading Nextcloud..."
TEMP_DIR=$(mktemp -d)
curl -L -o "$TEMP_DIR/nextcloud.zip" "https://download.nextcloud.com/server/releases/latest.zip"
unzip -q "$TEMP_DIR/nextcloud.zip" -d "$TEMP_DIR"
mkdir -p "$INSTALL_DIR"
cp -a "$TEMP_DIR/nextcloud/"* "$INSTALL_DIR/"
rm -rf "$TEMP_DIR"
echo "Download complete."
fi
# Create data directory
mkdir -p "$DATA_DIR"
# Set permissions
chown -R caddy:root "$INSTALL_DIR"
chown -R caddy:root "$DATA_DIR"
find "$INSTALL_DIR" -type d -exec chmod 750 {} \;
find "$INSTALL_DIR" -type f -exec chmod 640 {} \;
chmod -R 770 "$INSTALL_DIR/apps"
chmod -R 770 "$INSTALL_DIR/config"
chmod -R 770 "$DATA_DIR"
# Wait for database
echo "Waiting for PostgreSQL..."
for i in $(seq 1 30); do
if su -s /bin/sh caddy -c "php -r \"new PDO('pgsql:host=$DB_HOST;dbname=$DB_NAME', '$DB_USER', '$DB_PASS');\"" 2>/dev/null; then
echo "Database ready."
break
fi
sleep 2
done
# Run Nextcloud install via occ
echo "Running Nextcloud installation..."
su -s /bin/sh caddy -c "
php $INSTALL_DIR/occ maintenance:install \
--database 'pgsql' \
--database-name '$DB_NAME' \
--database-user '$DB_USER' \
--database-pass '$DB_PASS' \
--database-host '$DB_HOST' \
--admin-user '$ADMIN_USER' \
--admin-pass '$ADMIN_PASS' \
--data-dir '$DATA_DIR'
"
# Configure trusted domains
echo "Configuring trusted domains..."
su -s /bin/sh caddy -c "
php $INSTALL_DIR/occ config:system:set trusted_domains 0 --value='$DOMAIN'
php $INSTALL_DIR/occ config:system:set overwrite.cli.url --value='https://$DOMAIN'
php $INSTALL_DIR/occ config:system:set overwriteprotocol --value='https'
"
# Set recommended settings <EFBFBD><EFBFBD>
echo "Applying recommended settings..."
su -s /bin/sh caddy -c "
php $INSTALL_DIR/occ config:system:set default_phone_region --value='US'
php $INSTALL_DIR/occ config:system:set memcache.local --value='\OC\Memcache\APCu'
php $INSTALL_DIR/occ background:cron
"
# Install default apps
echo "Installing default apps..."
su -s /bin/sh caddy -c "
php $INSTALL_DIR/occ app:install calendar || true
php $INSTALL_DIR/occ app:install contacts || true
php $INSTALL_DIR/occ app:install tasks || true
php $INSTALL_DIR/occ app:install notes || true
php $INSTALL_DIR/occ app:install deck || true
php $INSTALL_DIR/occ app:enable calendar || true
php $INSTALL_DIR/occ app:enable contacts || true
php $INSTALL_DIR/occ app:enable tasks || true
php $INSTALL_DIR/occ app:enable notes || true
php $INSTALL_DIR/occ app:enable deck || true
"
# Save admin credentials
CREDS_FILE="/var/lib/secrets/nextcloud-admin"
cat > "$CREDS_FILE" << CREDS
Nextcloud Admin Credentials
URL: https://$DOMAIN/
Username: $ADMIN_USER
Password: $ADMIN_PASS
CREDS
chmod 600 "$CREDS_FILE"
echo ""
echo ""
echo " Nextcloud installation complete!"
echo ""
echo " URL: https://$DOMAIN/"
echo " Username: $ADMIN_USER"
echo " Password: $ADMIN_PASS"
echo ""
echo " Installed apps: Calendar, Contacts, Tasks,"
echo " Notes, Deck"
echo ""
echo " Credentials saved to: $CREDS_FILE"
echo ""
'';
};
# ── Cron ──────────────────────────────────────────────────
services.cron.systemCronJobs = [
"*/5 * * * * caddy /run/current-system/sw/bin/php -f /var/lib/www/nextcloud/cron.php"
];
# ── Ensure directories ────────────────────────────────────
systemd.tmpfiles.rules = [
"d /var/lib/www 0755 caddy root -"
"d /var/lib/www/nextcloud 0750 caddy root -"
"d /var/lib/www/nextcloud-data 0770 caddy root -"
];
environment.systemPackages = with pkgs; [
unzip
];
};
}

24
modules/personalization.nix Executable file
View File

@@ -0,0 +1,24 @@
{
matrix_url = builtins.readFile /var/lib/domains/matrix;
wordpress_url = builtins.readFile /var/lib/domains/wordpress;
nextcloud_url = builtins.readFile /var/lib/domains/nextcloud;
btcpayserver_url = builtins.readFile /var/lib/domains/btcpayserver;
caddy_email_for_acme = builtins.readFile /var/lib/domains/sslemail;
vaultwarden_url = builtins.readFile /var/lib/domains/vaultwarden;
haven_url = builtins.readFile /var/lib/domains/haven;
element-calling_url = builtins.readFile /var/lib/domains/element-calling;
##
external_ip_secret = builtins.readFile /var/lib/secrets/external_ip;
coturn_static_auth_secret = builtins.readFile /var/lib/secrets/turn;
##
matrixdb = builtins.readFile /var/lib/secrets/matrixdb;
nextclouddb = builtins.readFile /var/lib/secrets/nextclouddb;
wordpressdb = builtins.readFile /var/lib/secrets/wordpressdb;
}

66
modules/php.nix Executable file
View File

@@ -0,0 +1,66 @@
{ config, pkgs, lib, ... }:
let
custom-php = pkgs.php83.buildEnv {
extensions = { enabled, all }: enabled ++ (with all; [ bz2 apcu redis imagick memcached ]);
extraConfig = ''
display_errors = On
display_startup_errors = On
max_execution_time = 10000
max_input_time = 3000
memory_limit = 1G;
opcache.enable=1;
opcache.memory_consumption=512;
opcache_revalidate_freq = 240;
opcache.max_accelerated_files=20000;
post_max_size = 3G
upload_max_filesize = 3G
apc.enable_cli=1
opcache.interned_strings_buffer = 192
redis.session.locking_enabled=1
redis.session.lock_retries=-1
redis.session.lock_wait_time=10000
'';
};
in
{
users.users = {
php = {
isSystemUser = true;
createHome = false;
uid = 7777;
};
};
users.users.php.group = "php";
users.groups.php = {};
environment.systemPackages = with pkgs; [
custom-php
];
services.phpfpm.pools = {
mypool = {
user = "caddy";
group = "php";
phpPackage = custom-php;
settings = {
"pm" = "dynamic";
"pm.max_children" = 75;
"pm.start_servers" = 10;
"pm.min_spare_servers" = 5;
"pm.max_spare_servers" = 20;
"pm.max_requests" = 500;
"clear_env" = "no";
};
};
};
}

107
modules/rdp.nix Executable file
View File

@@ -0,0 +1,107 @@
{ config, pkgs, lib, ... }:
lib.mkIf config.sovran_systemsOS.features.rdp {
services.gnome.gnome-remote-desktop.enable = true;
networking.firewall.allowedTCPPorts = [ 3389 ];
environment.systemPackages = with pkgs; [
freerdp
];
# The NixOS module installs the unit but doesn't enable it — we just need to start it and order it
systemd.services.gnome-remote-desktop = {
wantedBy = [ "graphical.target" ];
after = [ "gnome-remote-desktop-setup.service" ];
wants = [ "gnome-remote-desktop-setup.service" ];
};
systemd.tmpfiles.rules = [
"d /var/lib/gnome-remote-desktop 0750 gnome-remote-desktop gnome-remote-desktop -"
"d /var/lib/gnome-remote-desktop/.local 0750 gnome-remote-desktop gnome-remote-desktop -"
"d /var/lib/gnome-remote-desktop/.local/share 0750 gnome-remote-desktop gnome-remote-desktop -"
"d /var/lib/gnome-remote-desktop/.local/share/gnome-remote-desktop 0750 gnome-remote-desktop gnome-remote-desktop -"
];
systemd.services.gnome-remote-desktop-setup = {
description = "Configure GNOME Remote Desktop RDP";
wantedBy = [ "multi-user.target" ];
before = [ "gnome-remote-desktop.service" ];
after = [ "systemd-tmpfiles-setup.service" "network-online.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [
pkgs.gnome-remote-desktop
pkgs.polkit
pkgs.openssl
pkgs.hostname
pkgs.gawk
];
script = ''
# Ensure directory structure exists
mkdir -p /var/lib/gnome-remote-desktop/.local/share/gnome-remote-desktop
chown -R gnome-remote-desktop:gnome-remote-desktop /var/lib/gnome-remote-desktop
TLS_DIR="/var/lib/gnome-remote-desktop/tls"
CRED_FILE="/var/lib/gnome-remote-desktop/rdp-credentials"
# Generate TLS certificate if it doesn't exist
if [ ! -f "$TLS_DIR/rdp-tls.crt" ]; then
mkdir -p "$TLS_DIR"
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
-sha256 -nodes -days 3650 \
-keyout "$TLS_DIR/rdp-tls.key" \
-out "$TLS_DIR/rdp-tls.crt" \
-subj "/CN=gnome-remote-desktop"
chown -R gnome-remote-desktop:gnome-remote-desktop "$TLS_DIR"
chmod 600 "$TLS_DIR/rdp-tls.key"
chmod 644 "$TLS_DIR/rdp-tls.crt"
echo "Generated RDP TLS certificate"
fi
# Configure TLS certificate
grdctl --system rdp set-tls-cert "$TLS_DIR/rdp-tls.crt"
grdctl --system rdp set-tls-key "$TLS_DIR/rdp-tls.key"
# Generate password on first boot only
PASSWORD=""
if [ ! -f /var/lib/gnome-remote-desktop/rdp-password ]; then
PASSWORD=$(openssl rand -base64 16)
echo "$PASSWORD" > /var/lib/gnome-remote-desktop/rdp-password
chmod 600 /var/lib/gnome-remote-desktop/rdp-password
else
PASSWORD=$(cat /var/lib/gnome-remote-desktop/rdp-password)
fi
# Get current IP address
LOCAL_IP=$(hostname -I | awk '{print $1}')
# Always rewrite the credentials file with the current IP
cat > "$CRED_FILE" <<EOF
========================================
GNOME Remote Desktop (RDP) Credentials
========================================
Username: sovran
Password: $PASSWORD
Connect from any RDP client to:
$LOCAL_IP:3389
========================================
EOF
chmod 600 "$CRED_FILE"
# Enable RDP backend and set credentials
grdctl --system rdp enable
grdctl --system rdp set-credentials sovran "$PASSWORD"
echo "GNOME Remote Desktop RDP configured successfully"
'';
};
}

136
modules/synapse.nix Normal file
View File

@@ -0,0 +1,136 @@
{ config, pkgs, lib, ... }:
{
# ── PostgreSQL database for Matrix ──────────────────────────
services.postgresql = {
enable = true;
ensureDatabases = [ "matrix-synapse" ];
ensureUsers = [
{
name = "matrix-synapse";
ensureDBOwnership = true;
}
];
};
# ── Auto-generate DB password and initialize ────────────────
systemd.services.matrix-synapse-db-init = {
description = "Initialize Matrix Synapse PostgreSQL database with auto-generated password";
after = [ "postgresql.service" ];
requires = [ "postgresql.service" ];
before = [ "matrix-synapse.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [ config.services.postgresql.package pkgs.pwgen pkgs.coreutils ];
script = ''
SECRET_DIR="/var/lib/secrets"
SECRET_FILE="$SECRET_DIR/matrix_db_secret"
mkdir -p "$SECRET_DIR"
if [ ! -f "$SECRET_FILE" ]; then
pwgen -s 64 1 > "$SECRET_FILE"
chmod 600 "$SECRET_FILE"
chown matrix-synapse:matrix-synapse "$SECRET_FILE"
fi
DB_PASS=$(cat "$SECRET_FILE")
psql -U postgres -c "ALTER ROLE \"matrix-synapse\" WITH LOGIN PASSWORD '$DB_PASS';"
if ! psql -U postgres -lqt | cut -d \| -f 1 | grep -qw "matrix-synapse"; then
psql -U postgres -c "CREATE DATABASE \"matrix-synapse\" WITH OWNER \"matrix-synapse\" TEMPLATE template0 LC_COLLATE = 'C' LC_CTYPE = 'C';"
fi
'';
};
# ── Generate Synapse runtime config from /var/lib/domains ───
systemd.services.matrix-synapse-runtime-config = {
description = "Generate Matrix Synapse runtime config from domain files";
before = [ "matrix-synapse.service" ];
after = [ "matrix-synapse-db-init.service" ];
requiredBy = [ "matrix-synapse.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [ pkgs.coreutils ];
script = ''
MATRIX=$(cat /var/lib/domains/matrix)
RUNTIME_DIR="/run/matrix-synapse"
mkdir -p "$RUNTIME_DIR"
cat > "$RUNTIME_DIR/runtime-config.yaml" <<EOF
server_name: "$MATRIX"
EOF
chown matrix-synapse:matrix-synapse "$RUNTIME_DIR/runtime-config.yaml"
chmod 640 "$RUNTIME_DIR/runtime-config.yaml"
'';
};
# ── Synapse service ─────────────────────────────────────────
lib.mkIf config.sovran_systemsOS.features.synapse {
services.matrix-synapse = {
enable = true;
extraConfigFiles = [ "/run/matrix-synapse/runtime-config.yaml" ];
settings = {
push.include_content = false;
group_unread_count_by_room = false;
encryption_enabled_by_default_for_room_type = "invite";
allow_profile_lookup_over_federation = false;
allow_device_name_lookup_over_federation = false;
# server_name is injected at runtime via extraConfigFiles
url_preview_enabled = true;
max_upload_size = "1024M";
url_preview_ip_range_blacklist = [
"10.0.0.0/8"
"100.64.0.0/10"
"169.254.0.0/16"
"172.16.0.0/12"
"192.0.0.0/24"
"192.0.2.0/24"
"192.168.0.0/16"
"192.88.99.0/24"
"198.18.0.0/15"
"198.51.100.0/24"
"2001:db8::/32"
"203.0.113.0/24"
"224.0.0.0/4"
"::1/128"
"fc00::/7"
"fe80::/10"
"fec0::/10"
"ff00::/8"
];
url_preview_ip_ranger_whitelist = [ "127.0.0.1" ];
presence.enabled = true;
enable_registration = false;
registration_shared_secret = config.age.secrets.matrix_reg_secret.path;
listeners = [
{
port = 8008;
bind_addresses = [ "::1" ];
type = "http";
tls = false;
x_forwarded = true;
resources = [
{
names = [ "client" ];
compress = true;
}
{
names = [ "federation" ];
compress = false;
}
];
}
];
};
};
}
}

47
modules/vaultwarden.nix Executable file
View File

@@ -0,0 +1,47 @@
{ config, pkgs, lib, ... }:
lib.mkIf config.sovran_systemsOS.features.vaultwarden {
# ── Caddy vhost is now handled centrally in caddy.nix ─────
# ── Generate Vaultwarden runtime config from domain files ──
systemd.services.vaultwarden-runtime-config = {
description = "Generate Vaultwarden runtime config from domain files";
before = [ "vaultwarden.service" ];
requiredBy = [ "vaultwarden.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [ pkgs.coreutils ];
script = ''
VAULTWARDEN=$(cat /var/lib/domains/vaultwarden)
mkdir -p /run/vaultwarden
cat > /run/vaultwarden/runtime.env <<EOF
DOMAIN=https://$VAULTWARDEN
EOF
chmod 640 /run/vaultwarden/runtime.env
'';
};
services.vaultwarden = {
enable = true;
config = {
# DOMAIN injected at runtime via EnvironmentFile
SIGNUPS_ALLOWED = false;
ROCKET_ADDRESS = "127.0.0.1";
ROCKET_PORT = 8777;
ROCKET_LOG = "critical";
};
dbBackend = "sqlite";
environmentFile = "/var/lib/secrets/vaultwarden/vaultwarden.env";
};
systemd.services.vaultwarden.serviceConfig.EnvironmentFile = lib.mkAfter [
"/run/vaultwarden/runtime.env"
];
}

198
modules/wordpress.nix Normal file
View File

@@ -0,0 +1,198 @@
{ config, pkgs, lib, ... }:
let
cfg = config.sovran_systemsOS.services.wordpress;
in
{
options.sovran_systemsOS.services.wordpress = {
enable = lib.mkEnableOption "WordPress (raw PHP served by Caddy)";
};
config = lib.mkIf cfg.enable {
# ── Caddy vhost is now handled centrally in caddy.nix ─────
# ── MariaDB database ──────────────────────────────────────
services.mysql = {
enable = true;
package = pkgs.mariadb;
};
# ── Auto-generate DB password and initialize ──────────────
systemd.services.wordpress-db-init = {
description = "Initialize WordPress MariaDB database with auto-generated password";
after = [ "mysql.service" ];
requires = [ "mysql.service" ];
before = [ "wordpress-init.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [ config.services.mysql.package pkgs.pwgen pkgs.coreutils ];
script = ''
set -euo pipefail
SECRET_FILE="/var/lib/secrets/wordpressdb"
# Existing machines already have this file leave it alone
if [ ! -f "$SECRET_FILE" ]; then
mkdir -p /var/lib/secrets
pwgen -s 64 1 > "$SECRET_FILE"
chmod 600 "$SECRET_FILE"
fi
DB_PASS=$(cat "$SECRET_FILE")
mysql -u root <<SQL
CREATE DATABASE IF NOT EXISTS wordpressdb;
CREATE USER IF NOT EXISTS 'wpusr'@'localhost' IDENTIFIED BY '$DB_PASS';
ALTER USER 'wpusr'@'localhost' IDENTIFIED BY '$DB_PASS';
GRANT ALL ON wordpressdb.* TO 'wpusr'@'localhost';
FLUSH PRIVILEGES;
SQL
'';
};
# ── Fully automated WordPress setup ───────────────────────
systemd.services.wordpress-init = {
description = "Download, extract, and fully configure WordPress";
after = [ "network-online.target" "mysql.service" "phpfpm-mypool.service" "wordpress-db-init.service" ];
wants = [ "network-online.target" ];
requires = [ "mysql.service" "wordpress-db-init.service" ];
wantedBy = [ "multi-user.target" ];
unitConfig = {
ConditionPathExists = "!/var/lib/www/wordpress/wp-config.php";
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = with pkgs; [ curl unzip wp-cli pwgen php coreutils ];
script = ''
set -euo pipefail
INSTALL_DIR="/var/lib/www/wordpress"
DOMAIN=$(cat /var/lib/domains/wordpress)
DB_NAME="wordpressdb"
DB_USER="wpusr"
DB_PASS=$(cat /var/lib/secrets/wordpressdb)
DB_HOST="localhost"
ADMIN_USER=$(pwgen -s 16 1)
ADMIN_PASS=$(pwgen -s 24 1)
ADMIN_EMAIL="$ADMIN_USER@''${DOMAIN#*.}"
echo ""
echo " WordPress Automated Installation"
echo ""
# Download
if [ ! -f "$INSTALL_DIR/wp-includes/version.php" ]; then
echo "Downloading WordPress..."
TEMP_DIR=$(mktemp -d)
curl -L -o "$TEMP_DIR/wordpress.zip" "https://wordpress.org/latest.zip"
unzip -q "$TEMP_DIR/wordpress.zip" -d "$TEMP_DIR"
mkdir -p "$INSTALL_DIR"
cp -a "$TEMP_DIR/wordpress/"* "$INSTALL_DIR/"
rm -rf "$TEMP_DIR"
echo "Download complete."
fi
# Set permissions
chown -R caddy:root "$INSTALL_DIR"
find "$INSTALL_DIR" -type d -exec chmod 755 {} \;
find "$INSTALL_DIR" -type f -exec chmod 644 {} \;
chmod -R 775 "$INSTALL_DIR/wp-content"
# Generate wp-config.php
echo "Generating wp-config.php..."
cd "$INSTALL_DIR"
su -s /bin/sh caddy -c "
wp config create \
--dbname='$DB_NAME' \
--dbuser='$DB_USER' \
--dbpass='$DB_PASS' \
--dbhost='$DB_HOST' \
--skip-check
"
# Wait for database to be ready
echo "Waiting for database..."
for i in $(seq 1 30); do
if su -s /bin/sh caddy -c "wp db check" 2>/dev/null; then
break
fi
sleep 2
done
# Run WordPress install
echo "Running WordPress core install..."
su -s /bin/sh caddy -c "
wp core install \
--url='https://$DOMAIN' \
--title='Sovran_SystemsOS' \
--admin_user='$ADMIN_USER' \
--admin_password='$ADMIN_PASS' \
--admin_email='$ADMIN_EMAIL' \
--skip-email
"
# Configure WordPress settings
echo "Configuring WordPress..."
su -s /bin/sh caddy -c "
wp option update blogdescription 'Powered by Sovran_SystemsOS'
wp option update permalink_structure '/%postname%/'
wp option update default_ping_status 'closed'
wp option update default_comment_status 'closed'
wp rewrite flush
"
# Security hardening
echo "Applying security settings..."
su -s /bin/sh caddy -c "
wp config set DISALLOW_FILE_EDIT true --raw
wp config set WP_AUTO_UPDATE_CORE true --raw
wp config set FORCE_SSL_ADMIN true --raw
"
# Save admin credentials
CREDS_FILE="/var/lib/secrets/wordpress-admin"
cat > "$CREDS_FILE" << CREDS
WordPress Admin Credentials
URL: https://$DOMAIN/wp-admin/
Username: $ADMIN_USER
Password: $ADMIN_PASS
Email: $ADMIN_EMAIL
CREDS
chmod 600 "$CREDS_FILE"
echo ""
echo ""
echo " WordPress installation complete!"
echo ""
echo " URL: https://$DOMAIN/wp-admin/"
echo " Username: $ADMIN_USER"
echo " Password: $ADMIN_PASS"
echo ""
echo " Credentials saved to: $CREDS_FILE"
echo ""
'';
};
# ── Ensure directories ────────────────────────────────────
systemd.tmpfiles.rules = [
"d /var/lib/www 0755 caddy root -"
"d /var/lib/www/wordpress 0755 caddy root -"
];
environment.systemPackages = with pkgs; [
wp-cli
unzip
];
};
}