diff --git a/.gitignore b/.gitignore index d3ef7ada..e088092e 100644 --- a/.gitignore +++ b/.gitignore @@ -20,12 +20,18 @@ node_modules/ # User configuration .env +.envrc pluralkit.conf pluralkit.*.conf *.DotSettings.user # Generated +.direnv logs/ .version recipe.json -.docker-bin/ \ No newline at end of file +.docker-bin/ + +# nix +.nix-process-compose +result diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..a13615a1 --- /dev/null +++ b/flake.lock @@ -0,0 +1,329 @@ +{ + "nodes": { + "crane": { + "flake": false, + "locked": { + "lastModified": 1727316705, + "narHash": "sha256-/mumx8AQ5xFuCJqxCIOFCHTVlxHkMT21idpbgbm/TIE=", + "owner": "ipetkov", + "repo": "crane", + "rev": "5b03654ce046b5167e7b0bccbd8244cb56c16f0e", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "ref": "v0.19.0", + "repo": "crane", + "type": "github" + } + }, + "d2n": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "purescript-overlay": "purescript-overlay", + "pyproject-nix": "pyproject-nix" + }, + "locked": { + "lastModified": 1734729217, + "narHash": "sha256-UaBik0h7veLw+VqsK5EP2ucC68BEkHLDJkcfmY+wEuY=", + "owner": "nix-community", + "repo": "dream2nix", + "rev": "98c1c2e934995a2c6ce740d4ff43ce0daa19b79f", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "dream2nix", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "revCount": 57, + "type": "tarball", + "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" + } + }, + "mk-naked-shell": { + "flake": false, + "locked": { + "lastModified": 1681286841, + "narHash": "sha256-3XlJrwlR0nBiREnuogoa5i1b4+w/XPe0z8bbrJASw0g=", + "owner": "yusdacra", + "repo": "mk-naked-shell", + "rev": "7612f828dd6f22b7fb332cc69440e839d7ffe6bd", + "type": "github" + }, + "original": { + "owner": "yusdacra", + "repo": "mk-naked-shell", + "type": "github" + } + }, + "nci": { + "inputs": { + "crane": "crane", + "dream2nix": [ + "d2n" + ], + "mk-naked-shell": "mk-naked-shell", + "nixpkgs": [ + "nixpkgs" + ], + "parts": [ + "parts" + ], + "rust-overlay": "rust-overlay", + "treefmt": [ + "treefmt" + ] + }, + "locked": { + "lastModified": 1734953472, + "narHash": "sha256-zWPAJFo7NNhSXbOc6YRAXbrWzcJGxNPtutKTTZz46Bs=", + "owner": "yusdacra", + "repo": "nix-cargo-integration", + "rev": "e71c873cf3b0dfa52e9550d580531e41eb4b4c6a", + "type": "github" + }, + "original": { + "owner": "yusdacra", + "repo": "nix-cargo-integration", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1734435836, + "narHash": "sha256-kMBQ5PRiFLagltK0sH+08aiNt3zGERC2297iB6vrvlU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4989a246d7a390a859852baddb1013f825435cee", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixpkgs-unstable", + "type": "indirect" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1733096140, + "narHash": "sha256-1qRH7uAUsyQI7R1Uwl4T+XvdNv778H0Nb5njNrqvylY=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz" + } + }, + "parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1733312601, + "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "process-compose": { + "locked": { + "lastModified": 1733325752, + "narHash": "sha256-79tzPuXNRo1NUllafYW6SjeLtjqfnLGq7tHCM7cAXNg=", + "owner": "Platonic-Systems", + "repo": "process-compose-flake", + "rev": "1012530b582f1bd3b102295c799358d95abf42d7", + "type": "github" + }, + "original": { + "owner": "Platonic-Systems", + "repo": "process-compose-flake", + "type": "github" + } + }, + "purescript-overlay": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": [ + "d2n", + "nixpkgs" + ], + "slimlock": "slimlock" + }, + "locked": { + "lastModified": 1728546539, + "narHash": "sha256-Sws7w0tlnjD+Bjck1nv29NjC5DbL6nH5auL9Ex9Iz2A=", + "owner": "thomashoneyman", + "repo": "purescript-overlay", + "rev": "4ad4c15d07bd899d7346b331f377606631eb0ee4", + "type": "github" + }, + "original": { + "owner": "thomashoneyman", + "repo": "purescript-overlay", + "type": "github" + } + }, + "pyproject-nix": { + "flake": false, + "locked": { + "lastModified": 1702448246, + "narHash": "sha256-hFg5s/hoJFv7tDpiGvEvXP0UfFvFEDgTdyHIjDVHu1I=", + "owner": "davhau", + "repo": "pyproject.nix", + "rev": "5a06a2697b228c04dd2f35659b4b659ca74f7aeb", + "type": "github" + }, + "original": { + "owner": "davhau", + "ref": "dream2nix", + "repo": "pyproject.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "d2n": "d2n", + "flake-compat": "flake-compat_2", + "nci": "nci", + "nixpkgs": "nixpkgs", + "parts": "parts", + "process-compose": "process-compose", + "services": "services", + "systems": "systems", + "treefmt": "treefmt" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nci", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1734834660, + "narHash": "sha256-bm8V+Cu8rWJA+vKQnc94mXTpSDgvedyoDKxTVi/uJfw=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "b070e6030118680977bc2388868c4b3963872134", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "services": { + "locked": { + "lastModified": 1734242477, + "narHash": "sha256-u+fkdD8+0/0J8k0/YKDc3ReUcYZZGiftGL+Sz2wdRqM=", + "owner": "juspay", + "repo": "services-flake", + "rev": "acc7f3f9f30621b469ca3ee511592a68a4437312", + "type": "github" + }, + "original": { + "owner": "juspay", + "repo": "services-flake", + "type": "github" + } + }, + "slimlock": { + "inputs": { + "nixpkgs": [ + "d2n", + "purescript-overlay", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1688756706, + "narHash": "sha256-xzkkMv3neJJJ89zo3o2ojp7nFeaZc2G0fYwNXNJRFlo=", + "owner": "thomashoneyman", + "repo": "slimlock", + "rev": "cf72723f59e2340d24881fd7bf61cb113b4c407c", + "type": "github" + }, + "original": { + "owner": "thomashoneyman", + "repo": "slimlock", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1680978846, + "narHash": "sha256-Gtqg8b/v49BFDpDetjclCYXm8mAnTrUzR0JnE2nv5aw=", + "owner": "nix-systems", + "repo": "x86_64-linux", + "rev": "2ecfcac5e15790ba6ce360ceccddb15ad16d08a8", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "x86_64-linux", + "type": "github" + } + }, + "treefmt": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1734704479, + "narHash": "sha256-MMi74+WckoyEWBRcg/oaGRvXC9BVVxDZNRMpL+72wBI=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "65712f5af67234dad91a5a4baee986a8b62dbf8f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..5f3989a7 --- /dev/null +++ b/flake.nix @@ -0,0 +1,233 @@ +{ + description = "flake for pluralkit"; + + inputs = { + nixpkgs.url = "nixpkgs/nixpkgs-unstable"; + parts.url = "github:hercules-ci/flake-parts"; + systems.url = "github:nix-systems/x86_64-linux"; + # process compose + process-compose.url = "github:Platonic-Systems/process-compose-flake"; + services.url = "github:juspay/services-flake"; + # rust + d2n.url = "github:nix-community/dream2nix"; + d2n.inputs.nixpkgs.follows = "nixpkgs"; + nci.url = "github:yusdacra/nix-cargo-integration"; + nci.inputs.parts.follows = "parts"; + nci.inputs.nixpkgs.follows = "nixpkgs"; + nci.inputs.dream2nix.follows = "d2n"; + nci.inputs.treefmt.follows = "treefmt"; + # misc + treefmt.url = "github:numtide/treefmt-nix"; + treefmt.inputs.nixpkgs.follows = "nixpkgs"; + flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"; + }; + + outputs = + inp: + inp.parts.lib.mkFlake { inputs = inp; } { + systems = import inp.systems; + imports = [ + inp.process-compose.flakeModule + inp.nci.flakeModule + inp.treefmt.flakeModule + ]; + perSystem = + { + config, + self', + pkgs, + lib, + system, + ... + }: + let + # this is used as devshell for bot, and in the process-compose processes as environment + mkBotEnv = + cmd: + pkgs.buildFHSEnv { + name = "env"; + targetPkgs = + pkgs: with pkgs; [ + coreutils + git + dotnet-sdk_8 + gcc + protobuf + omnisharp-roslyn + bashInteractive + ]; + runScript = cmd; + }; + + rustOutputs = config.nci.outputs; + composeCfg = config.process-compose."dev"; + in + { + # _module.args.pkgs = import inp.nixpkgs { + # inherit system; + # config.permittedInsecurePackages = [ "dotnet-sdk-6.0.428" ]; + # }; + + treefmt = { + projectRootFile = "flake.nix"; + programs.nixfmt.enable = true; + }; + + nci.toolchainConfig = { + channel = "nightly"; + }; + nci.projects."pluralkit-services" = { + path = ./.; + export = false; + }; + nci.crates."gateway" = { + depsDrvConfig.mkDerivation = { + nativeBuildInputs = [ pkgs.protobuf ]; + }; + drvConfig.mkDerivation = { + nativeBuildInputs = [ pkgs.protobuf ]; + }; + }; + + # TODO: expose other rust packages after it's verified they build and work properly + packages = lib.genAttrs ["gateway"] (name: rustOutputs.${name}.packages.release); + # TODO: package the bot itself (dotnet) + + devShells = { + services = rustOutputs."pluralkit-services".devShell; + bot = (mkBotEnv "bash").env; + }; + + process-compose."dev" = let + dataDir = ".nix-process-compose"; + pluralkitConfCheck = '' + [[ -f "pluralkit.conf" ]] || (echo "pluralkit config not found, please copy pluralkit.conf.example to pluralkit.conf and edit it" && exit 1) + ''; + sourceDotenv = '' + [[ -f ".env" ]] && echo "sourcing .env file..." && export "$(xargs < .env)" + ''; + in { + imports = [ inp.services.processComposeModules.default ]; + + settings.log_location = "${dataDir}/log"; + + settings.environment = { + DOTNET_CLI_TELEMETRY_OPTOUT = "1"; + NODE_OPTIONS = "--openssl-legacy-provider"; + }; + + services.redis."redis" = { + enable = true; + dataDir = "${dataDir}/redis"; + }; + services.postgres."postgres" = { + enable = true; + dataDir = "${dataDir}/postgres"; + initialScript.before = '' + CREATE DATABASE pluralkit; + CREATE USER postgres WITH password 'postgres'; + GRANT ALL PRIVILEGES ON DATABASE pluralkit TO postgres; + ALTER DATABASE pluralkit OWNER TO postgres; + ''; + }; + + settings.processes = + let + procCfg = composeCfg.settings.processes; + mkServiceInitProcess = + { + name, + inputs ? [ ], + ... + }: + let + shell = rustOutputs.${name}.devShell; + in + { + command = pkgs.writeShellApplication { + name = "pluralkit-${name}-init"; + runtimeInputs = + (with pkgs; [ + coreutils + shell.stdenv.cc + ]) + ++ shell.nativeBuildInputs + ++ inputs; + text = '' + ${sourceDotenv} + set -x + ${pluralkitConfCheck} + exec cargo build --package ${name} + ''; + }; + }; + in + { + ### bot ### + pluralkit-bot-init = { + command = pkgs.writeShellApplication { + name = "pluralkit-bot-init"; + runtimeInputs = [ + pkgs.coreutils + pkgs.git + ]; + text = '' + ${sourceDotenv} + set -x + ${pluralkitConfCheck} + exec ${mkBotEnv "dotnet build -c Release -o obj/"}/bin/env + ''; + }; + }; + pluralkit-bot = { + command = pkgs.writeShellApplication { + name = "pluralkit-bot"; + runtimeInputs = [ pkgs.coreutils ]; + text = '' + ${sourceDotenv} + set -x + exec ${mkBotEnv "dotnet obj/PluralKit.Bot.dll"}/bin/env + ''; + }; + depends_on.pluralkit-bot-init.condition = "process_completed_successfully"; + depends_on.postgres.condition = "process_healthy"; + depends_on.redis.condition = "process_healthy"; + depends_on.pluralkit-gateway.condition = "process_healthy"; + # TODO: add liveness check + ready_log_line = "Received Ready"; + }; + ### gateway ### + pluralkit-gateway-init = mkServiceInitProcess { + name = "gateway"; + }; + pluralkit-gateway = { + command = pkgs.writeShellApplication { + name = "pluralkit-gateway"; + runtimeInputs = with pkgs; [ + coreutils + curl + gnugrep + ]; + text = '' + ${sourceDotenv} + set -x + exec target/debug/gateway + ''; + }; + depends_on.postgres.condition = "process_healthy"; + depends_on.redis.condition = "process_healthy"; + depends_on.pluralkit-gateway-init.condition = "process_completed_successfully"; + # configure health checks + # TODO: don't assume port? + liveness_probe.exec.command = ''curl -s -o /dev/null -w "%{http_code}" http://localhost:5000/stats | grep "302"''; + liveness_probe.period_seconds = 5; + readiness_probe.exec.command = procCfg.pluralkit-gateway.liveness_probe.exec.command; + readiness_probe.period_seconds = 5; + readiness_probe.initial_delay_seconds = 3; + }; + # TODO: add the rest of the services + }; + }; + }; + }; +} diff --git a/shell.nix b/shell.nix index a9ca81ea..459e1526 100644 --- a/shell.nix +++ b/shell.nix @@ -1,15 +1,12 @@ -{ pkgs ? import {} }: - -pkgs.mkShellNoCC { - packages = with pkgs; [ - cargo rust-analyzer rustfmt - gcc - protobuf - dotnet-sdk_8 - omnisharp-roslyn - go - nodejs yarn - ]; - - NODE_OPTIONS = "--openssl-legacy-provider"; -} +(import ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + nodeName = lock.nodes.root.inputs.flake-compat; + in + fetchTarball { + url = + lock.nodes.${nodeName}.locked.url + or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.${nodeName}.locked.rev}.tar.gz"; + sha256 = lock.nodes.${nodeName}.locked.narHash; + } +) { src = ./.; }).shellNix