mirror of
https://github.com/harivansh-afk/nix.git
synced 2026-04-17 07:03:30 +00:00
split netty configuration.nix into per-service modules, remove sandbox-agent
Break the monolithic 495-line configuration.nix into focused modules: - forgejo.nix: Forgejo service, git user, mirror sync timer - betternas.nix: control-plane + node agent services - vaultwarden.nix: Vaultwarden service - nginx.nix: ACME + all Nginx virtualHosts Remove sandbox-agent entirely (service, CORS proxy, package). Keep netty.harivan.sh vhost reserved for future use.
This commit is contained in:
parent
c97726766a
commit
fd79908ad2
8 changed files with 297 additions and 4260 deletions
|
|
@ -34,4 +34,4 @@ The VPS has a declarative service bundle:
|
||||||
- services only listen on 127.0.0.1 (runs behind nginx with ACME)
|
- services only listen on 127.0.0.1 (runs behind nginx with ACME)
|
||||||
- Self hosts Forgejo mirroring to GitHub (git.harivan.sh)
|
- Self hosts Forgejo mirroring to GitHub (git.harivan.sh)
|
||||||
- Self hosts VaultWarden
|
- Self hosts VaultWarden
|
||||||
- Runs sandbox agent behind a CORS proxy
|
- betterNAS control-plane and node agent (api.betternas.com)
|
||||||
|
|
|
||||||
68
hosts/netty/betternas.nix
Normal file
68
hosts/netty/betternas.nix
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
username,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
betternasDomain = "api.betternas.com";
|
||||||
|
betternasRepoDir = "/home/${username}/Documents/GitHub/betterNAS/betterNAS";
|
||||||
|
betternasNodeEnvFile = "/var/lib/betternas/node-agent/node-agent.env";
|
||||||
|
betternasNodeBinary = "${betternasRepoDir}/apps/node-agent/dist/betternas-node";
|
||||||
|
betternasNodeExportPath = "/home/${username}/Documents";
|
||||||
|
betternasNodeEnvCheck = pkgs.writeShellScript "betternas-node-env-check" ''
|
||||||
|
[ -f "${betternasNodeEnvFile}" ] && [ -x "${betternasNodeBinary}" ] && [ -d "${betternasNodeExportPath}" ]
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /var/lib/betternas/node-agent 0750 ${username} users -"
|
||||||
|
"z ${betternasNodeEnvFile} 0600 ${username} users -"
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.services.betternas-control-plane = {
|
||||||
|
description = "betterNAS Control Plane";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
User = username;
|
||||||
|
Group = "users";
|
||||||
|
WorkingDirectory = "/var/lib/betternas/control-plane";
|
||||||
|
ExecStart = "${betternasRepoDir}/apps/control-plane/dist/control-plane";
|
||||||
|
EnvironmentFile = "/var/lib/betternas/control-plane/control-plane.env";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 5;
|
||||||
|
StateDirectory = "betternas/control-plane";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.betternas-node = {
|
||||||
|
description = "betterNAS Node";
|
||||||
|
after = [
|
||||||
|
"betternas-control-plane.service"
|
||||||
|
"network-online.target"
|
||||||
|
];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
environment = {
|
||||||
|
PORT = "8090";
|
||||||
|
BETTERNAS_CONTROL_PLANE_URL = "http://127.0.0.1:3100";
|
||||||
|
BETTERNAS_NODE_DIRECT_ADDRESS = "https://${betternasDomain}";
|
||||||
|
BETTERNAS_EXPORT_PATH = betternasNodeExportPath;
|
||||||
|
BETTERNAS_NODE_DISPLAY_NAME = "netty";
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
User = username;
|
||||||
|
Group = "users";
|
||||||
|
WorkingDirectory = "/var/lib/betternas/node-agent";
|
||||||
|
ExecCondition = betternasNodeEnvCheck;
|
||||||
|
ExecStart = betternasNodeBinary;
|
||||||
|
EnvironmentFile = "-${betternasNodeEnvFile}";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 5;
|
||||||
|
StateDirectory = "betternas/node-agent";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -9,103 +9,15 @@
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
packageSets = import ../../lib/package-sets.nix { inherit inputs lib pkgs; };
|
packageSets = import ../../lib/package-sets.nix { inherit inputs lib pkgs; };
|
||||||
sandboxDomain = "netty.harivan.sh";
|
|
||||||
forgejoDomain = "git.harivan.sh";
|
|
||||||
vaultDomain = "vault.harivan.sh";
|
|
||||||
betternasDomain = "api.betternas.com";
|
|
||||||
forgejoApiUrl = "http://127.0.0.1:19300";
|
|
||||||
betternasRepoDir = "/home/${username}/Documents/GitHub/betterNAS/betterNAS";
|
|
||||||
betternasNodeEnvFile = "/var/lib/betternas/node-agent/node-agent.env";
|
|
||||||
betternasNodeBinary = "${betternasRepoDir}/apps/node-agent/dist/betternas-node";
|
|
||||||
betternasNodeExportPath = "/home/${username}/Documents";
|
|
||||||
sandboxAgentPackage = pkgs.callPackage ../../pkgs/sandbox-agent { };
|
|
||||||
sandboxAgentDir = "/home/${username}/.config/sandbox-agent";
|
|
||||||
sandboxAgentPath =
|
|
||||||
packageSets.core
|
|
||||||
++ packageSets.extras
|
|
||||||
++ [
|
|
||||||
pkgs.bubblewrap
|
|
||||||
pkgs.git
|
|
||||||
pkgs.nodejs
|
|
||||||
pkgs.pnpm
|
|
||||||
sandboxAgentPackage
|
|
||||||
];
|
|
||||||
sandboxAgentEnvCheck = pkgs.writeShellScript "sandbox-agent-env-check" ''
|
|
||||||
[ -f "${sandboxAgentDir}/agent.env" ] && [ -f "${sandboxAgentDir}/public.env" ]
|
|
||||||
'';
|
|
||||||
betternasNodeEnvCheck = pkgs.writeShellScript "betternas-node-env-check" ''
|
|
||||||
[ -f "${betternasNodeEnvFile}" ] && [ -x "${betternasNodeBinary}" ] && [ -d "${betternasNodeExportPath}" ]
|
|
||||||
'';
|
|
||||||
sandboxAgentWrapper = pkgs.writeShellScript "sandbox-agent-public" ''
|
|
||||||
set -euo pipefail
|
|
||||||
set -a
|
|
||||||
. "${sandboxAgentDir}/public.env"
|
|
||||||
. "${sandboxAgentDir}/agent.env"
|
|
||||||
set +a
|
|
||||||
exec sandbox-agent server \
|
|
||||||
--host 127.0.0.1 \
|
|
||||||
--port "''${SANDBOX_AGENT_PORT}" \
|
|
||||||
--token "''${SANDBOX_AGENT_TOKEN}"
|
|
||||||
'';
|
|
||||||
sandboxCorsProxy = pkgs.writeText "sandbox-agent-cors-proxy.mjs" ''
|
|
||||||
import http from "node:http";
|
|
||||||
|
|
||||||
const listenHost = "127.0.0.1";
|
|
||||||
const listenPort = 2468;
|
|
||||||
const targetHost = "127.0.0.1";
|
|
||||||
const targetPort = 2470;
|
|
||||||
|
|
||||||
function setCorsHeaders(headers, req) {
|
|
||||||
headers["access-control-allow-origin"] = "*";
|
|
||||||
headers["access-control-allow-methods"] = "GET,POST,PUT,PATCH,DELETE,OPTIONS";
|
|
||||||
headers["access-control-allow-headers"] =
|
|
||||||
req.headers["access-control-request-headers"] || "authorization,content-type";
|
|
||||||
headers["access-control-max-age"] = "86400";
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
const server = http.createServer((req, res) => {
|
|
||||||
if (req.method === "OPTIONS") {
|
|
||||||
res.writeHead(204, setCorsHeaders({}, req));
|
|
||||||
res.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const proxyReq = http.request(
|
|
||||||
{
|
|
||||||
host: targetHost,
|
|
||||||
port: targetPort,
|
|
||||||
method: req.method,
|
|
||||||
path: req.url,
|
|
||||||
headers: {
|
|
||||||
...req.headers,
|
|
||||||
host: `''${targetHost}:''${targetPort}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
(proxyRes) => {
|
|
||||||
res.writeHead(
|
|
||||||
proxyRes.statusCode || 502,
|
|
||||||
setCorsHeaders({ ...proxyRes.headers }, req),
|
|
||||||
);
|
|
||||||
proxyRes.pipe(res);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
proxyReq.on("error", () => {
|
|
||||||
res.writeHead(502, setCorsHeaders({ "content-type": "text/plain" }, req));
|
|
||||||
res.end("Upstream request failed");
|
|
||||||
});
|
|
||||||
|
|
||||||
req.pipe(proxyReq);
|
|
||||||
});
|
|
||||||
|
|
||||||
server.listen(listenPort, listenHost);
|
|
||||||
'';
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
./disk-config.nix
|
./disk-config.nix
|
||||||
|
./nginx.nix
|
||||||
|
./vaultwarden.nix
|
||||||
|
./forgejo.nix
|
||||||
|
./betternas.nix
|
||||||
../../modules/base.nix
|
../../modules/base.nix
|
||||||
(modulesPath + "/profiles/minimal.nix")
|
(modulesPath + "/profiles/minimal.nix")
|
||||||
(modulesPath + "/profiles/headless.nix")
|
(modulesPath + "/profiles/headless.nix")
|
||||||
|
|
@ -196,300 +108,9 @@ in
|
||||||
virtualisation.docker.enable = true;
|
virtualisation.docker.enable = true;
|
||||||
|
|
||||||
environment.systemPackages = packageSets.extras ++ [
|
environment.systemPackages = packageSets.extras ++ [
|
||||||
pkgs.bubblewrap
|
|
||||||
pkgs.pnpm
|
|
||||||
pkgs.nodejs
|
|
||||||
pkgs.php
|
pkgs.php
|
||||||
sandboxAgentPackage
|
|
||||||
];
|
];
|
||||||
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
"L /usr/bin/bwrap - - - - ${pkgs.bubblewrap}/bin/bwrap"
|
|
||||||
"d /var/lib/betternas/node-agent 0750 ${username} users -"
|
|
||||||
"z ${betternasNodeEnvFile} 0600 ${username} users -"
|
|
||||||
"z /var/lib/vaultwarden/vaultwarden.env 0600 vaultwarden vaultwarden -"
|
|
||||||
];
|
|
||||||
|
|
||||||
# --- ACME / Let's Encrypt ---
|
|
||||||
security.acme = {
|
|
||||||
acceptTerms = true;
|
|
||||||
defaults.email = "rathiharivansh@gmail.com";
|
|
||||||
};
|
|
||||||
|
|
||||||
# --- Nginx reverse proxy ---
|
|
||||||
services.nginx = {
|
|
||||||
enable = true;
|
|
||||||
recommendedProxySettings = true;
|
|
||||||
recommendedTlsSettings = true;
|
|
||||||
clientMaxBodySize = "512m";
|
|
||||||
|
|
||||||
virtualHosts.${sandboxDomain} = {
|
|
||||||
enableACME = true;
|
|
||||||
forceSSL = true;
|
|
||||||
locations."/".proxyPass = "http://127.0.0.1:2470";
|
|
||||||
};
|
|
||||||
|
|
||||||
virtualHosts.${forgejoDomain} = {
|
|
||||||
enableACME = true;
|
|
||||||
forceSSL = true;
|
|
||||||
locations."/".proxyPass = "http://127.0.0.1:19300";
|
|
||||||
};
|
|
||||||
|
|
||||||
virtualHosts.${vaultDomain} = {
|
|
||||||
enableACME = true;
|
|
||||||
forceSSL = true;
|
|
||||||
locations."/".proxyPass = "http://127.0.0.1:8222";
|
|
||||||
};
|
|
||||||
|
|
||||||
virtualHosts.${betternasDomain} = {
|
|
||||||
enableACME = true;
|
|
||||||
forceSSL = true;
|
|
||||||
locations."/".proxyPass = "http://127.0.0.1:3100";
|
|
||||||
locations."/dav/".proxyPass = "http://127.0.0.1:8090";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# --- Vaultwarden ---
|
|
||||||
services.vaultwarden = {
|
|
||||||
enable = true;
|
|
||||||
backupDir = "/var/backup/vaultwarden";
|
|
||||||
environmentFile = "/var/lib/vaultwarden/vaultwarden.env";
|
|
||||||
config = {
|
|
||||||
DOMAIN = "https://${vaultDomain}";
|
|
||||||
SIGNUPS_ALLOWED = false;
|
|
||||||
ROCKET_ADDRESS = "127.0.0.1";
|
|
||||||
ROCKET_PORT = 8222;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# --- Forgejo ---
|
|
||||||
users.users.git = {
|
|
||||||
isSystemUser = true;
|
|
||||||
home = "/var/lib/forgejo";
|
|
||||||
group = "git";
|
|
||||||
shell = "${pkgs.bash}/bin/bash";
|
|
||||||
};
|
|
||||||
users.groups.git = { };
|
|
||||||
|
|
||||||
services.forgejo = {
|
|
||||||
enable = true;
|
|
||||||
user = "git";
|
|
||||||
group = "git";
|
|
||||||
settings = {
|
|
||||||
repository = {
|
|
||||||
FORCE_PRIVATE = true;
|
|
||||||
DEFAULT_PRIVATE = "private";
|
|
||||||
DEFAULT_PUSH_CREATE_PRIVATE = true;
|
|
||||||
};
|
|
||||||
server = {
|
|
||||||
DOMAIN = forgejoDomain;
|
|
||||||
ROOT_URL = "https://${forgejoDomain}/";
|
|
||||||
HTTP_PORT = 19300;
|
|
||||||
SSH_DOMAIN = forgejoDomain;
|
|
||||||
};
|
|
||||||
service = {
|
|
||||||
DISABLE_REGISTRATION = true;
|
|
||||||
REQUIRE_SIGNIN_VIEW = true;
|
|
||||||
};
|
|
||||||
session.COOKIE_SECURE = true;
|
|
||||||
mirror = {
|
|
||||||
DEFAULT_INTERVAL = "1h";
|
|
||||||
MIN_INTERVAL = "10m";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# --- Forgejo mirror sync (hourly) ---
|
|
||||||
systemd.services.forgejo-mirror-sync = {
|
|
||||||
description = "Sync GitHub mirrors to Forgejo";
|
|
||||||
after = [ "forgejo.service" ];
|
|
||||||
requires = [ "forgejo.service" ];
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
EnvironmentFile = "/etc/forgejo-mirror.env";
|
|
||||||
};
|
|
||||||
path = [
|
|
||||||
pkgs.curl
|
|
||||||
pkgs.jq
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.gnused
|
|
||||||
];
|
|
||||||
script = ''
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
api_call() {
|
|
||||||
local response http_code body
|
|
||||||
response=$(curl -sS -w "\n%{http_code}" "$@")
|
|
||||||
http_code=$(printf '%s\n' "$response" | tail -n1)
|
|
||||||
body=$(printf '%s\n' "$response" | sed '$d')
|
|
||||||
if [ "$http_code" -ge 400 ]; then
|
|
||||||
printf '[forgejo-mirror-sync] HTTP %s\n' "$http_code" >&2
|
|
||||||
printf '%s\n' "$body" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
printf '%s' "$body"
|
|
||||||
}
|
|
||||||
|
|
||||||
gh_user=$(api_call -H "Authorization: token $GITHUB_TOKEN" \
|
|
||||||
"https://api.github.com/user" | jq -r '.login')
|
|
||||||
|
|
||||||
repos_file=$(mktemp)
|
|
||||||
trap 'rm -f "$repos_file"' EXIT
|
|
||||||
|
|
||||||
page=1
|
|
||||||
while true; do
|
|
||||||
batch=$(api_call -H "Authorization: token $GITHUB_TOKEN" \
|
|
||||||
"https://api.github.com/user/repos?per_page=100&page=$page&visibility=all&affiliation=owner,organization_member")
|
|
||||||
count=$(printf '%s' "$batch" | jq length)
|
|
||||||
[ "$count" -eq 0 ] && break
|
|
||||||
printf '%s' "$batch" | jq -r '.[] | [.full_name, .clone_url] | @tsv' >> "$repos_file"
|
|
||||||
page=$((page + 1))
|
|
||||||
done
|
|
||||||
|
|
||||||
sort -u "$repos_file" -o "$repos_file"
|
|
||||||
|
|
||||||
while IFS=$'\t' read -r full_name clone_url; do
|
|
||||||
repo_owner="''${full_name%%/*}"
|
|
||||||
repo_name="''${full_name#*/}"
|
|
||||||
|
|
||||||
if [ "$repo_owner" = "$gh_user" ]; then
|
|
||||||
forgejo_repo_name="$repo_name"
|
|
||||||
else
|
|
||||||
forgejo_repo_name="$repo_owner--$repo_name"
|
|
||||||
fi
|
|
||||||
|
|
||||||
status=$(curl -sS -o /dev/null -w '%{http_code}' \
|
|
||||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
|
||||||
"${forgejoApiUrl}/api/v1/repos/$FORGEJO_OWNER/$forgejo_repo_name" || true)
|
|
||||||
|
|
||||||
if [ "$status" = "404" ]; then
|
|
||||||
api_call -X POST \
|
|
||||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${forgejoApiUrl}/api/v1/repos/migrate" \
|
|
||||||
-d "$(jq -n \
|
|
||||||
--arg addr "$clone_url" \
|
|
||||||
--arg name "$forgejo_repo_name" \
|
|
||||||
--arg owner "$FORGEJO_OWNER" \
|
|
||||||
--arg token "$GITHUB_TOKEN" \
|
|
||||||
'{
|
|
||||||
clone_addr: $addr,
|
|
||||||
repo_name: $name,
|
|
||||||
repo_owner: $owner,
|
|
||||||
private: true,
|
|
||||||
mirror: true,
|
|
||||||
auth_token: $token,
|
|
||||||
service: "github"
|
|
||||||
}')" \
|
|
||||||
> /dev/null
|
|
||||||
echo "Created mirror: $full_name -> $FORGEJO_OWNER/$forgejo_repo_name"
|
|
||||||
else
|
|
||||||
if ! api_call -X POST \
|
|
||||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
|
||||||
"${forgejoApiUrl}/api/v1/repos/$FORGEJO_OWNER/$forgejo_repo_name/mirror-sync" \
|
|
||||||
> /dev/null; then
|
|
||||||
echo "Failed mirror sync: $full_name -> $FORGEJO_OWNER/$forgejo_repo_name" >&2
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
echo "Synced mirror: $full_name -> $FORGEJO_OWNER/$forgejo_repo_name"
|
|
||||||
fi
|
|
||||||
done < "$repos_file"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.timers.forgejo-mirror-sync = {
|
|
||||||
wantedBy = [ "timers.target" ];
|
|
||||||
timerConfig = {
|
|
||||||
OnCalendar = "hourly";
|
|
||||||
Persistent = true;
|
|
||||||
RandomizedDelaySec = "5m";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# --- Sandbox Agent (declarative systemd services) ---
|
|
||||||
systemd.services.sandbox-agent = {
|
|
||||||
description = "Sandbox Agent";
|
|
||||||
after = [ "network-online.target" ];
|
|
||||||
wants = [ "network-online.target" ];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
path = sandboxAgentPath;
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "simple";
|
|
||||||
User = username;
|
|
||||||
Group = "users";
|
|
||||||
WorkingDirectory = "/home/${username}";
|
|
||||||
ExecCondition = sandboxAgentEnvCheck;
|
|
||||||
ExecStart = sandboxAgentWrapper;
|
|
||||||
Restart = "on-failure";
|
|
||||||
RestartSec = 5;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.sandbox-cors-proxy = {
|
|
||||||
description = "Sandbox CORS Proxy";
|
|
||||||
after = [ "sandbox-agent.service" ];
|
|
||||||
requires = [ "sandbox-agent.service" ];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "simple";
|
|
||||||
User = username;
|
|
||||||
Group = "users";
|
|
||||||
WorkingDirectory = "/home/${username}";
|
|
||||||
ExecCondition = sandboxAgentEnvCheck;
|
|
||||||
ExecStart = "${pkgs.nodejs}/bin/node ${sandboxCorsProxy}";
|
|
||||||
Restart = "on-failure";
|
|
||||||
RestartSec = 5;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# --- betterNAS control-plane ---
|
|
||||||
systemd.services.betternas-control-plane = {
|
|
||||||
description = "betterNAS Control Plane";
|
|
||||||
after = [ "network-online.target" ];
|
|
||||||
wants = [ "network-online.target" ];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "simple";
|
|
||||||
User = username;
|
|
||||||
Group = "users";
|
|
||||||
WorkingDirectory = "/var/lib/betternas/control-plane";
|
|
||||||
ExecStart = "/home/${username}/Documents/GitHub/betterNAS/betterNAS/apps/control-plane/dist/control-plane";
|
|
||||||
EnvironmentFile = "/var/lib/betternas/control-plane/control-plane.env";
|
|
||||||
Restart = "on-failure";
|
|
||||||
RestartSec = 5;
|
|
||||||
StateDirectory = "betternas/control-plane";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.betternas-node = {
|
|
||||||
description = "betterNAS Node";
|
|
||||||
after = [
|
|
||||||
"betternas-control-plane.service"
|
|
||||||
"network-online.target"
|
|
||||||
];
|
|
||||||
wants = [ "network-online.target" ];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
environment = {
|
|
||||||
PORT = "8090";
|
|
||||||
BETTERNAS_CONTROL_PLANE_URL = "http://127.0.0.1:3100";
|
|
||||||
BETTERNAS_NODE_DIRECT_ADDRESS = "https://${betternasDomain}";
|
|
||||||
BETTERNAS_EXPORT_PATH = betternasNodeExportPath;
|
|
||||||
BETTERNAS_NODE_DISPLAY_NAME = "netty";
|
|
||||||
};
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "simple";
|
|
||||||
User = username;
|
|
||||||
Group = "users";
|
|
||||||
WorkingDirectory = "/var/lib/betternas/node-agent";
|
|
||||||
ExecCondition = betternasNodeEnvCheck;
|
|
||||||
ExecStart = betternasNodeBinary;
|
|
||||||
EnvironmentFile = "-${betternasNodeEnvFile}";
|
|
||||||
Restart = "on-failure";
|
|
||||||
RestartSec = 5;
|
|
||||||
StateDirectory = "betternas/node-agent";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
system.configurationRevision = self.rev or self.dirtyRev or null;
|
system.configurationRevision = self.rev or self.dirtyRev or null;
|
||||||
system.stateVersion = "24.11";
|
system.stateVersion = "24.11";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
153
hosts/netty/forgejo.nix
Normal file
153
hosts/netty/forgejo.nix
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
username,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
forgejoDomain = "git.harivan.sh";
|
||||||
|
forgejoApiUrl = "http://127.0.0.1:19300";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
users.users.git = {
|
||||||
|
isSystemUser = true;
|
||||||
|
home = "/var/lib/forgejo";
|
||||||
|
group = "git";
|
||||||
|
shell = "${pkgs.bash}/bin/bash";
|
||||||
|
};
|
||||||
|
users.groups.git = { };
|
||||||
|
|
||||||
|
services.forgejo = {
|
||||||
|
enable = true;
|
||||||
|
user = "git";
|
||||||
|
group = "git";
|
||||||
|
settings = {
|
||||||
|
repository = {
|
||||||
|
FORCE_PRIVATE = true;
|
||||||
|
DEFAULT_PRIVATE = "private";
|
||||||
|
DEFAULT_PUSH_CREATE_PRIVATE = true;
|
||||||
|
};
|
||||||
|
server = {
|
||||||
|
DOMAIN = forgejoDomain;
|
||||||
|
ROOT_URL = "https://${forgejoDomain}/";
|
||||||
|
HTTP_PORT = 19300;
|
||||||
|
SSH_DOMAIN = forgejoDomain;
|
||||||
|
};
|
||||||
|
service = {
|
||||||
|
DISABLE_REGISTRATION = true;
|
||||||
|
REQUIRE_SIGNIN_VIEW = true;
|
||||||
|
};
|
||||||
|
session.COOKIE_SECURE = true;
|
||||||
|
mirror = {
|
||||||
|
DEFAULT_INTERVAL = "1h";
|
||||||
|
MIN_INTERVAL = "10m";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# --- Forgejo mirror sync (hourly) ---
|
||||||
|
systemd.services.forgejo-mirror-sync = {
|
||||||
|
description = "Sync GitHub mirrors to Forgejo";
|
||||||
|
after = [ "forgejo.service" ];
|
||||||
|
requires = [ "forgejo.service" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
EnvironmentFile = "/etc/forgejo-mirror.env";
|
||||||
|
};
|
||||||
|
path = [
|
||||||
|
pkgs.curl
|
||||||
|
pkgs.jq
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.gnused
|
||||||
|
];
|
||||||
|
script = ''
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
api_call() {
|
||||||
|
local response http_code body
|
||||||
|
response=$(curl -sS -w "\n%{http_code}" "$@")
|
||||||
|
http_code=$(printf '%s\n' "$response" | tail -n1)
|
||||||
|
body=$(printf '%s\n' "$response" | sed '$d')
|
||||||
|
if [ "$http_code" -ge 400 ]; then
|
||||||
|
printf '[forgejo-mirror-sync] HTTP %s\n' "$http_code" >&2
|
||||||
|
printf '%s\n' "$body" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
printf '%s' "$body"
|
||||||
|
}
|
||||||
|
|
||||||
|
gh_user=$(api_call -H "Authorization: token $GITHUB_TOKEN" \
|
||||||
|
"https://api.github.com/user" | jq -r '.login')
|
||||||
|
|
||||||
|
repos_file=$(mktemp)
|
||||||
|
trap 'rm -f "$repos_file"' EXIT
|
||||||
|
|
||||||
|
page=1
|
||||||
|
while true; do
|
||||||
|
batch=$(api_call -H "Authorization: token $GITHUB_TOKEN" \
|
||||||
|
"https://api.github.com/user/repos?per_page=100&page=$page&visibility=all&affiliation=owner,organization_member")
|
||||||
|
count=$(printf '%s' "$batch" | jq length)
|
||||||
|
[ "$count" -eq 0 ] && break
|
||||||
|
printf '%s' "$batch" | jq -r '.[] | [.full_name, .clone_url] | @tsv' >> "$repos_file"
|
||||||
|
page=$((page + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
sort -u "$repos_file" -o "$repos_file"
|
||||||
|
|
||||||
|
while IFS=$'\t' read -r full_name clone_url; do
|
||||||
|
repo_owner="''${full_name%%/*}"
|
||||||
|
repo_name="''${full_name#*/}"
|
||||||
|
|
||||||
|
if [ "$repo_owner" = "$gh_user" ]; then
|
||||||
|
forgejo_repo_name="$repo_name"
|
||||||
|
else
|
||||||
|
forgejo_repo_name="$repo_owner--$repo_name"
|
||||||
|
fi
|
||||||
|
|
||||||
|
status=$(curl -sS -o /dev/null -w '%{http_code}' \
|
||||||
|
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||||
|
"${forgejoApiUrl}/api/v1/repos/$FORGEJO_OWNER/$forgejo_repo_name" || true)
|
||||||
|
|
||||||
|
if [ "$status" = "404" ]; then
|
||||||
|
api_call -X POST \
|
||||||
|
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${forgejoApiUrl}/api/v1/repos/migrate" \
|
||||||
|
-d "$(jq -n \
|
||||||
|
--arg addr "$clone_url" \
|
||||||
|
--arg name "$forgejo_repo_name" \
|
||||||
|
--arg owner "$FORGEJO_OWNER" \
|
||||||
|
--arg token "$GITHUB_TOKEN" \
|
||||||
|
'{
|
||||||
|
clone_addr: $addr,
|
||||||
|
repo_name: $name,
|
||||||
|
repo_owner: $owner,
|
||||||
|
private: true,
|
||||||
|
mirror: true,
|
||||||
|
auth_token: $token,
|
||||||
|
service: "github"
|
||||||
|
}')" \
|
||||||
|
> /dev/null
|
||||||
|
echo "Created mirror: $full_name -> $FORGEJO_OWNER/$forgejo_repo_name"
|
||||||
|
else
|
||||||
|
if ! api_call -X POST \
|
||||||
|
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||||
|
"${forgejoApiUrl}/api/v1/repos/$FORGEJO_OWNER/$forgejo_repo_name/mirror-sync" \
|
||||||
|
> /dev/null; then
|
||||||
|
echo "Failed mirror sync: $full_name -> $FORGEJO_OWNER/$forgejo_repo_name" >&2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo "Synced mirror: $full_name -> $FORGEJO_OWNER/$forgejo_repo_name"
|
||||||
|
fi
|
||||||
|
done < "$repos_file"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.forgejo-mirror-sync = {
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "hourly";
|
||||||
|
Persistent = true;
|
||||||
|
RandomizedDelaySec = "5m";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
48
hosts/netty/nginx.nix
Normal file
48
hosts/netty/nginx.nix
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
sandboxDomain = "netty.harivan.sh";
|
||||||
|
forgejoDomain = "git.harivan.sh";
|
||||||
|
vaultDomain = "vault.harivan.sh";
|
||||||
|
betternasDomain = "api.betternas.com";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
security.acme = {
|
||||||
|
acceptTerms = true;
|
||||||
|
defaults.email = "rathiharivansh@gmail.com";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx = {
|
||||||
|
enable = true;
|
||||||
|
recommendedProxySettings = true;
|
||||||
|
recommendedTlsSettings = true;
|
||||||
|
clientMaxBodySize = "512m";
|
||||||
|
|
||||||
|
# Reserved for future use - nothing listening on this port yet
|
||||||
|
virtualHosts.${sandboxDomain} = {
|
||||||
|
enableACME = true;
|
||||||
|
forceSSL = true;
|
||||||
|
locations."/".proxyPass = "http://127.0.0.1:2470";
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualHosts.${forgejoDomain} = {
|
||||||
|
enableACME = true;
|
||||||
|
forceSSL = true;
|
||||||
|
locations."/".proxyPass = "http://127.0.0.1:19300";
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualHosts.${vaultDomain} = {
|
||||||
|
enableACME = true;
|
||||||
|
forceSSL = true;
|
||||||
|
locations."/".proxyPass = "http://127.0.0.1:8222";
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualHosts.${betternasDomain} = {
|
||||||
|
enableACME = true;
|
||||||
|
forceSSL = true;
|
||||||
|
locations."/".proxyPass = "http://127.0.0.1:3100";
|
||||||
|
locations."/dav/".proxyPass = "http://127.0.0.1:8090";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
23
hosts/netty/vaultwarden.nix
Normal file
23
hosts/netty/vaultwarden.nix
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
vaultDomain = "vault.harivan.sh";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"z /var/lib/vaultwarden/vaultwarden.env 0600 vaultwarden vaultwarden -"
|
||||||
|
];
|
||||||
|
|
||||||
|
services.vaultwarden = {
|
||||||
|
enable = true;
|
||||||
|
backupDir = "/var/backup/vaultwarden";
|
||||||
|
environmentFile = "/var/lib/vaultwarden/vaultwarden.env";
|
||||||
|
config = {
|
||||||
|
DOMAIN = "https://${vaultDomain}";
|
||||||
|
SIGNUPS_ALLOWED = false;
|
||||||
|
ROCKET_ADDRESS = "127.0.0.1";
|
||||||
|
ROCKET_PORT = 8222;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
3832
pkgs/sandbox-agent/Cargo.lock
generated
3832
pkgs/sandbox-agent/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,44 +0,0 @@
|
||||||
{
|
|
||||||
lib,
|
|
||||||
fetchFromGitHub,
|
|
||||||
openssl,
|
|
||||||
pkg-config,
|
|
||||||
rustPlatform,
|
|
||||||
}:
|
|
||||||
rustPlatform.buildRustPackage {
|
|
||||||
pname = "sandbox-agent";
|
|
||||||
version = "0.5.0-rc.1";
|
|
||||||
|
|
||||||
src = fetchFromGitHub {
|
|
||||||
owner = "rivet-dev";
|
|
||||||
repo = "sandbox-agent";
|
|
||||||
rev = "v0.5.0-rc.1";
|
|
||||||
hash = "sha256-oeOpWjaQlQZZzwQGts4yJgL3STDCd3Hz2qbOJ4N2HBM=";
|
|
||||||
};
|
|
||||||
|
|
||||||
cargoLock.lockFile = ./Cargo.lock;
|
|
||||||
|
|
||||||
prePatch = ''
|
|
||||||
cp ${./Cargo.lock} Cargo.lock
|
|
||||||
'';
|
|
||||||
|
|
||||||
nativeBuildInputs = [ pkg-config ];
|
|
||||||
|
|
||||||
buildInputs = [ openssl ];
|
|
||||||
|
|
||||||
cargoBuildFlags = [
|
|
||||||
"-p"
|
|
||||||
"sandbox-agent"
|
|
||||||
];
|
|
||||||
|
|
||||||
env.SANDBOX_AGENT_SKIP_INSPECTOR = "1";
|
|
||||||
doCheck = false;
|
|
||||||
|
|
||||||
meta = with lib; {
|
|
||||||
description = "Universal API for coding agents in sandboxes";
|
|
||||||
homepage = "https://sandboxagent.dev";
|
|
||||||
license = licenses.asl20;
|
|
||||||
mainProgram = "sandbox-agent";
|
|
||||||
platforms = platforms.unix;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue