diff --git a/flake.lock b/flake.lock index ebd170f..8bd1982 100755 --- a/flake.lock +++ b/flake.lock @@ -33,6 +33,21 @@ "type": "github" } }, + "crane": { + "locked": { + "lastModified": 1743908961, + "narHash": "sha256-e1idZdpnnHWuosI3KsBgAgrhMR05T2oqskXCmNzGPq0=", + "owner": "ipetkov", + "repo": "crane", + "rev": "80ceeec0dc94ef967c371dcdc56adb280328f591", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, "devshell": { "inputs": { "nixpkgs": [ @@ -286,6 +301,24 @@ "inputs": { "nixpkgs-lib": "nixpkgs-lib_2" }, + "locked": { + "lastModified": 1743550720, + "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "c621e8422220273271f52058f618c94e405bb0f5", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_4": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_3" + }, "locked": { "lastModified": 1756770412, "narHash": "sha256-+uWLQZccFHwqpGqr2Yt5VsW/PbeJVTn9Dk6SHWhNRPw=", @@ -300,9 +333,9 @@ "type": "github" } }, - "flake-parts_4": { + "flake-parts_5": { "inputs": { - "nixpkgs-lib": "nixpkgs-lib_3" + "nixpkgs-lib": "nixpkgs-lib_4" }, "locked": { "lastModified": 1754091436, @@ -318,7 +351,7 @@ "type": "github" } }, - "flake-parts_5": { + "flake-parts_6": { "inputs": { "nixpkgs-lib": [ "nur", @@ -339,7 +372,7 @@ "type": "github" } }, - "flake-parts_6": { + "flake-parts_7": { "inputs": { "nixpkgs-lib": [ "tuios", @@ -426,6 +459,28 @@ "type": "github" } }, + "gel": { + "inputs": { + "crane": "crane", + "flake-parts": "flake-parts_3", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1765769738, + "narHash": "sha256-24jPKah/aOATsxbE0AsXVRwir70tno2IQNtgOXicCGc=", + "owner": "geldata", + "repo": "packages-nix", + "rev": "030ed681858163b3bf54d13d56ff45af2b34be08", + "type": "github" + }, + "original": { + "owner": "geldata", + "repo": "packages-nix", + "type": "github" + } + }, "git-hooks-nix": { "inputs": { "flake-compat": [ @@ -974,7 +1029,7 @@ }, "linkpage": { "inputs": { - "flake-parts": "flake-parts_3", + "flake-parts": "flake-parts_4", "mkElmDerivation": "mkElmDerivation", "nixpkgs": "nixpkgs_3", "nixpkgs-stable": "nixpkgs-stable", @@ -1185,7 +1240,7 @@ "nixcord": { "inputs": { "flake-compat": "flake-compat_2", - "flake-parts": "flake-parts_4", + "flake-parts": "flake-parts_5", "nixpkgs": [ "nixpkgs" ] @@ -1236,6 +1291,21 @@ } }, "nixpkgs-lib_2": { + "locked": { + "lastModified": 1743296961, + "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs-lib_3": { "locked": { "lastModified": 1754788789, "narHash": "sha256-x2rJ+Ovzq0sCMpgfgGaaqgBSwY+LST+WbZ6TytnT9Rk=", @@ -1250,7 +1320,7 @@ "type": "github" } }, - "nixpkgs-lib_3": { + "nixpkgs-lib_4": { "locked": { "lastModified": 1753579242, "narHash": "sha256-zvaMGVn14/Zz8hnp4VWT9xVnhc8vuL3TStRqwk22biA=", @@ -1456,7 +1526,7 @@ }, "nur": { "inputs": { - "flake-parts": "flake-parts_5", + "flake-parts": "flake-parts_6", "nixpkgs": [ "nixpkgs" ] @@ -1547,6 +1617,7 @@ "inputs": { "filesorter": "filesorter", "flake-parts": "flake-parts_2", + "gel": "gel", "home-manager": "home-manager", "hyprland": "hyprland", "hyprland-portal": "hyprland-portal", @@ -1799,7 +1870,7 @@ "inputs": { "devshell": "devshell", "flake-compat": "flake-compat_4", - "flake-parts": "flake-parts_6", + "flake-parts": "flake-parts_7", "flake-root": "flake-root", "git-hooks-nix": "git-hooks-nix", "nix-fast-build": "nix-fast-build", diff --git a/flake.nix b/flake.nix index 0ceb621..c460270 100755 --- a/flake.nix +++ b/flake.nix @@ -76,6 +76,10 @@ url = "github:microvm-nix/microvm.nix"; inputs.nixpkgs.follows = "nixpkgs"; }; + gel = { + url = "github:geldata/packages-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; linkpage = { url = "git+ssh://git@gitlab.com/uprootnutrition/linkpage.git"; }; diff --git a/modules/config/default.nix b/modules/config/default.nix index 923b057..b741879 100755 --- a/modules/config/default.nix +++ b/modules/config/default.nix @@ -73,6 +73,7 @@ let ip = stringType; subdomain = stringType; wireguard = stringType; + instance = stringType; microvm = lib.mkOption { type = lib.types.submodule { options = { diff --git a/modules/config/instances/config/gel.nix b/modules/config/instances/config/gel.nix new file mode 100644 index 0000000..055263a --- /dev/null +++ b/modules/config/instances/config/gel.nix @@ -0,0 +1,41 @@ +{ moduleFunctions }: +let + inherit (moduleFunctions.instancesFunctions) dummy; + label = "Gel"; + name = "gel"; +in +{ + label = label; + name = name; + tags = [ + dummy + ]; + ports = { + }; + interfaces = { + interface0 = { + instance = "private"; + microvm = { + id = "vm-gelpri"; + mac = "02:00:00:00:81:81"; + idUser = "vmuser-gelpri"; + macUser = "02:00:00:00:00:81"; + ip = "192.168.50.181"; + gate = "192.168.50.1"; + ssh = 2801; + }; + }; + interface1 = { + instance = "public"; + microvm = { + id = "vm-gelpub"; + mac = "02:00:00:00:81:82"; + idUser = "vmuser-gelpub"; + macUser = "02:00:00:00:00:82"; + ip = "192.168.50.182"; + gate = "192.168.50.1"; + ssh = 2802; + }; + }; + }; +} diff --git a/modules/nixos/homelab/guests/gel/config/default.nix b/modules/nixos/homelab/guests/gel/config/default.nix new file mode 100644 index 0000000..717d58f --- /dev/null +++ b/modules/nixos/homelab/guests/gel/config/default.nix @@ -0,0 +1,287 @@ +{ + flake, + lib, + pkgs, + ... +}: +let + inherit (flake.config.people) user0; + inherit (flake.config.services.instances) gel; + inherit (flake.inputs.gel.packages."x86_64-linux") gel-cli gel-server; + inherit (flake.self.packages."x86_64-linux") tome; + serviceCfg = gel; +in +{ + gelVM = + { + user, + ip, + mac, + userMac, + host, + ssh, + mnt, + instance, + }: + { + microvm.vms = { + "${serviceCfg.name}-${user}" = { + autostart = true; + restartIfChanged = true; + config = { + system.stateVersion = "24.05"; + time.timeZone = "America/Winnipeg"; + users.users.root.openssh.authorizedKeys.keys = flake.config.people.users.${user0}.sshKeys; + + environment.systemPackages = [ + gel-cli + tome + ]; + + services = { + openssh = { + enable = true; + settings = { + PasswordAuthentication = false; + PermitRootLogin = "prohibit-password"; + }; + }; + }; + + users.users.gel = { + isSystemUser = true; + group = "gel"; + }; + users.groups.gel = { }; + + networking.firewall.allowedTCPPorts = [ + 22 + serviceCfg.ports.port0 + ]; + + systemd = { + services = { + gel-setup = { + description = "Setup gel directories and schema"; + wantedBy = [ "multi-user.target" ]; + before = [ "gel.service" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + # copy bootstrap script + mkdir -p /etc/gel + cp /run/secrets/bootstrap.edgesql /etc/gel/bootstrap.edgesql + chown gel:gel /etc/gel/bootstrap.edgesql + chmod 400 /etc/gel/bootstrap.edgesql + + # update schema files + if [ -z "$(ls -A /var/lib/gel-schema)" ]; then + # first boot: copy everything + cp -r ${tome}/* /var/lib/gel-schema/ + else + # subsequent boots: update config and schema files + rm -f /var/lib/gel-schema/gel.toml + cp ${tome}/gel.toml /var/lib/gel-schema/ + rm -f /var/lib/gel-schema/dbschema/*.gel + cp ${tome}/dbschema/*.gel /var/lib/gel-schema/dbschema/ + fi + + chmod -R u+w /var/lib/gel-schema + chown -R gel:gel /var/lib/gel-schema + ''; + }; + + gel = { + description = "Gel database server"; + after = [ + "network.target" + "run-secrets.mount" + ]; + requires = [ + "gel-setup.service" + "run-secrets.mount" + ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStartPre = '' + ${lib.getExe' gel-server "gel-server"} \ + --bootstrap-command-file=/etc/gel/bootstrap.edgesql \ + --bootstrap-only \ + --data-dir=/var/lib/${serviceCfg.name} \ + --instance-name=${instance} + ''; + ExecStart = '' + ${lib.getExe' gel-server "gel-server"} \ + --admin-ui=enabled \ + --bind-address=${ip} \ + --data-dir=/var/lib/${serviceCfg.name} \ + --instance-name=${instance} \ + --security=insecure_dev_mode + ''; + User = "gel"; + Group = "gel"; + Restart = "always"; + RestartSec = 5; + TimeoutStartSec = 300; + }; + }; + + gel-password-sync = { + description = "Sync gel admin password"; + after = [ "gel.service" ]; + requires = [ "gel.service" ]; + serviceConfig = { + Type = "oneshot"; + User = "gel"; + ExecStartPre = + "+" + + pkgs.writeShellScript "copy-password" '' + # copy password file to a location gel user can read + cp /run/secrets/${user}-pass /etc/gel/admin_password + chown gel:gel /etc/gel/admin_password + chmod 400 /etc/gel/admin_password + ''; + ExecStart = pkgs.writeShellScript "gel-password-sync" '' + # wait for admin socket to be ready + for i in {1..30}; do + if [ -S /var/lib/${serviceCfg.name}/.s.GEL.admin.5656 ]; then + break + fi + sleep 1 + done + + ${lib.getExe' gel-cli "gel"} \ + --admin \ + --unix-path /var/lib/${serviceCfg.name} \ + query "ALTER ROLE admin SET password := '$(cat /etc/gel/admin_password)'" + ''; + ExecStartPost = + "+" + + pkgs.writeShellScript "cleanup-password" '' + rm -f /etc/gel/admin_password + ''; + }; + }; + + gel-migrate = { + description = "Run gel migrations"; + after = [ "gel-password-sync.service" ]; + requires = [ "gel-password-sync.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = pkgs.writeShellScript "gel-migrate" '' + cd /var/lib/gel-schema + cat /run/secrets/${user}-pass | ${lib.getExe' gel-cli "gel"} \ + --host ${ip} \ + --password-from-stdin \ + --tls-security insecure \ + --user admin \ + migration create --non-interactive + cat /run/secrets/${user}-pass | ${lib.getExe' gel-cli "gel"} \ + --host ${ip} \ + --password-from-stdin \ + --tls-security insecure \ + --user admin \ + migrate + ''; + }; + }; + + systemd-networkd.wantedBy = [ "multi-user.target" ]; + }; + + network = { + enable = true; + networks."20-lan" = { + matchConfig.Name = "enp0s6"; + addresses = [ { Address = "${ip}/24"; } ]; + routes = [ + { + Destination = "0.0.0.0/0"; + Gateway = "192.168.50.1"; + } + ]; + dns = [ + "1.1.1.1" + "8.8.8.8" + ]; + }; + }; + }; + + microvm = { + vcpu = 2; + mem = 1024 * 4; + hypervisor = "qemu"; + interfaces = [ + { + type = "tap"; + id = "vm-gel-${user}"; + mac = mac; + } + { + type = "user"; + id = "vmuser-gel"; + mac = userMac; + } + ]; + + forwardPorts = [ + { + from = "host"; + host.port = ssh; + guest.port = 22; + } + ]; + + shares = [ + { + mountPoint = "/nix/.ro-store"; + proto = "virtiofs"; + source = "/nix/store"; + tag = "read_only_nix_store"; + } + { + mountPoint = "/var/lib/${serviceCfg.name}"; + proto = "virtiofs"; + source = "${mnt}/${serviceCfg.name}"; + tag = "${serviceCfg.name}_${user}_data"; + } + { + mountPoint = "/var/lib/gel-schema"; + proto = "virtiofs"; + source = "${mnt}/gel-schema"; + tag = "${serviceCfg.name}_${user}_schema"; + } + { + mountPoint = "/run/secrets"; + proto = "virtiofs"; + source = "/run/secrets/${serviceCfg.name}"; + tag = "host_secrets"; + } + ]; + }; + }; + }; + }; + + systemd.tmpfiles.rules = [ + "d ${mnt}/${serviceCfg.name} 0751 microvm wheel - -" + "d ${mnt}/gel-schema 0751 microvm wheel - -" + ]; + + sops.secrets = { + "${serviceCfg.name}/${user}-pass" = { + owner = "root"; + mode = "0600"; + }; + "${serviceCfg.name}/bootstrap.edgesql" = { + owner = "root"; + mode = "0600"; + }; + }; + }; +} diff --git a/modules/nixos/homelab/guests/gel/gelPrivate/default.nix b/modules/nixos/homelab/guests/gel/gelPrivate/default.nix new file mode 100644 index 0000000..0024c4a --- /dev/null +++ b/modules/nixos/homelab/guests/gel/gelPrivate/default.nix @@ -0,0 +1,28 @@ +{ + flake, + lib, + pkgs, + ... +}: +let + inherit (import ../../../helpers.nix { inherit flake; }) labHelpers; + inherit (labHelpers) mntPath; + inherit (import ../config { inherit flake lib pkgs; }) gelVM; + inherit (flake.config.people) user0; + inherit (flake.config.services) instances; + + interfaceCfg = instances.gel.interfaces.interface0; + + gelPrivate = gelVM { + user = user0; + ip = interfaceCfg.microvm.ip; + mac = interfaceCfg.microvm.mac; + userMac = interfaceCfg.microvm.macUser; + ssh = interfaceCfg.microvm.ssh; + mnt = mntPath; + host = interfaceCfg.domain; + instance = interfaceCfg.instance; + }; + +in +gelPrivate diff --git a/modules/nixos/homelab/guests/gel/gelPublic/default.nix b/modules/nixos/homelab/guests/gel/gelPublic/default.nix new file mode 100644 index 0000000..c0a4c81 --- /dev/null +++ b/modules/nixos/homelab/guests/gel/gelPublic/default.nix @@ -0,0 +1,28 @@ +{ + flake, + lib, + pkgs, + ... +}: +let + inherit (import ../../../helpers.nix { inherit flake; }) labHelpers; + inherit (labHelpers) mntPath; + inherit (import ../config { inherit flake lib pkgs; }) gelVM; + inherit (flake.config.people) user0; + inherit (flake.config.services) instances; + + interfaceCfg = instances.gel.interfaces.interface1; + + gelPublic = gelVM { + user = user0; + ip = interfaceCfg.microvm.ip; + mac = interfaceCfg.microvm.mac; + userMac = interfaceCfg.microvm.macUser; + ssh = interfaceCfg.microvm.ssh; + mnt = mntPath; + host = interfaceCfg.domain; + instance = interfaceCfg.istance; + }; + +in +gelPublic diff --git a/packages/tome/.envrc b/packages/tome/.envrc new file mode 100644 index 0000000..2b120b2 --- /dev/null +++ b/packages/tome/.envrc @@ -0,0 +1 @@ +use flake .#tome diff --git a/packages/tome/default.nix b/packages/tome/default.nix new file mode 100644 index 0000000..b1d24e3 --- /dev/null +++ b/packages/tome/default.nix @@ -0,0 +1,22 @@ +{ inputs, ... }: +{ + perSystem = + { pkgs, ... }: + { + devShells.tome = pkgs.mkShell { + buildInputs = builtins.attrValues { + inherit (inputs.gel.packages."x86_64-linux") gel-cli gel-ls; + }; + }; + + packages.tome = pkgs.stdenv.mkDerivation { + installPhase = '' + mkdir -p $out + cp -r dbschema $out/ + cp gel.toml $out/ + ''; + name = "tome"; + src = ./src; + }; + }; +} diff --git a/packages/tome/src/dbschema/default.gel b/packages/tome/src/dbschema/default.gel new file mode 100755 index 0000000..2919f5f --- /dev/null +++ b/packages/tome/src/dbschema/default.gel @@ -0,0 +1,42 @@ +module default { + scalar type SubscriptionStatus extending enum + Active, + Cancelled, + PastDue, + Trialing + >; + + type User { + required email: str { + constraint exclusive; + } + required password_hash: str; + required created_at: datetime { + default := datetime_of_statement(); + } + subscription: Subscription; + } + + type Subscription { + required user: User { + constraint exclusive; + } + required stripe_customer_id: str { + constraint exclusive; + } + required status: SubscriptionStatus; + required current_period_end: datetime; + plan_id: str; + } + + type Session { + required user: User; + required token: str { + constraint exclusive; + } + required expires_at: datetime; + required created_at: datetime { + default := datetime_of_statement(); + } + } +} diff --git a/packages/tome/src/gel.toml b/packages/tome/src/gel.toml new file mode 100755 index 0000000..9028f78 --- /dev/null +++ b/packages/tome/src/gel.toml @@ -0,0 +1,2 @@ +[instance] +server-version = "6.11"