diff --git a/flake.lock b/flake.lock index 06957f9..ba6aa30 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,26 @@ { "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1701498074, + "narHash": "sha256-UNYTZtBYa/4G5+dRzNNXNcEi1RVm6yOUQNHYkcRag2Q=", + "owner": "nix-community", + "repo": "fenix", + "rev": "ce8747b0d8d6605264651f9ab5c84db6d7a56728", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, "flake-compat": { "flake": false, "locked": { @@ -16,21 +37,18 @@ "type": "github" } }, - "flake-parts": { - "inputs": { - "nixpkgs-lib": "nixpkgs-lib" - }, + "flake-root": { "locked": { - "lastModified": 1696343447, - "narHash": "sha256-B2xAZKLkkeRFG5XcHHSXXcP7To9Xzr59KXeZiRf4vdQ=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "c9afaba3dfa4085dbd2ccb38dfade5141e33d9d4", + "lastModified": 1692742795, + "narHash": "sha256-f+Y0YhVCIJ06LemO+3Xx00lIcqQxSKJHXT/yk1RTKxw=", + "owner": "srid", + "repo": "flake-root", + "rev": "d9a70d9c7a5fd7f3258ccf48da9335e9b47c3937", "type": "github" }, "original": { - "owner": "hercules-ci", - "repo": "flake-parts", + "owner": "srid", + "repo": "flake-root", "type": "github" } }, @@ -73,13 +91,33 @@ "type": "github" } }, + "naersk": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1698420672, + "narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", + "owner": "nix-community", + "repo": "naersk", + "rev": "aeb58d5e8faead8980a807c840232697982d47b9", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "naersk", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1696757521, - "narHash": "sha256-cfgtLNCBLFx2qOzRLI6DHfqTdfWI+UbvsKYa3b3fvaA=", + "lastModified": 1701336116, + "narHash": "sha256-kEmpezCR/FpITc6yMbAh4WrOCiT2zg5pSjnKrq51h5Y=", "owner": "nixos", "repo": "nixpkgs", - "rev": "2646b294a146df2781b1ca49092450e8a32814e1", + "rev": "f5c27c6136db4d76c30e533c20517df6864c46ee", "type": "github" }, "original": { @@ -89,24 +127,6 @@ "type": "github" } }, - "nixpkgs-lib": { - "locked": { - "dir": "lib", - "lastModified": 1696019113, - "narHash": "sha256-X3+DKYWJm93DRSdC5M6K5hLqzSya9BjibtBsuARoPco=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "f5892ddac112a1e9b3612c39af1b72987ee5783a", - "type": "github" - }, - "original": { - "dir": "lib", - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, "nixpkgs-stable": { "locked": { "lastModified": 1685801374, @@ -123,6 +143,26 @@ "type": "github" } }, + "parts": { + "inputs": { + "nixpkgs-lib": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1701473968, + "narHash": "sha256-YcVE5emp1qQ8ieHUnxt1wCZCC3ZfAS+SRRWZ2TMda7E=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "34fed993f1674c8d06d58b37ce1e0fe5eebcb9f5", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "pre-commit-hooks": { "inputs": { "flake-compat": "flake-compat", @@ -134,11 +174,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1696846637, - "narHash": "sha256-0hv4kbXxci2+pxhuXlVgftj/Jq79VSmtAyvfabCCtYk=", + "lastModified": 1700922917, + "narHash": "sha256-ej2fch/T584b5K9sk1UhmZF7W6wEfDHuoUYpFN8dtvM=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "42e1b6095ef80a51f79595d9951eb38e91c4e6ca", + "rev": "e5ee5c5f3844550c01d2131096c7271cec5e9b78", "type": "github" }, "original": { @@ -147,11 +187,47 @@ "type": "github" } }, + "proc-flake": { + "locked": { + "lastModified": 1692742849, + "narHash": "sha256-Nv8SOX+O6twFfPnA9BfubbPLZpqc+UeK6JvIWnWkdb0=", + "owner": "srid", + "repo": "proc-flake", + "rev": "25291b6e3074ad5dd573c1cb7d96110a9591e10f", + "type": "github" + }, + "original": { + "owner": "srid", + "repo": "proc-flake", + "type": "github" + } + }, "root": { "inputs": { - "flake-parts": "flake-parts", + "fenix": "fenix", + "flake-root": "flake-root", + "naersk": "naersk", "nixpkgs": "nixpkgs", - "pre-commit-hooks": "pre-commit-hooks" + "parts": "parts", + "pre-commit-hooks": "pre-commit-hooks", + "proc-flake": "proc-flake" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1701447636, + "narHash": "sha256-WaCcxLNAqo/FAK0QtYqweKCUVTGcbKpFIHClc+k2YlI=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "e402c494b7c7d94a37c6d789a216187aaf9ccd3e", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" } }, "systems": { diff --git a/flake.nix b/flake.nix index 9b8d655..c311d1f 100644 --- a/flake.nix +++ b/flake.nix @@ -3,44 +3,41 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; - flake-parts.url = "github:hercules-ci/flake-parts"; + parts = { + url = "github:hercules-ci/flake-parts"; + inputs.nixpkgs-lib.follows = "nixpkgs"; + }; + + naersk = { + url = "github:nix-community/naersk"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + pre-commit-hooks = { url = "github:cachix/pre-commit-hooks.nix"; inputs.nixpkgs.follows = "nixpkgs"; }; + + flake-root.url = "github:srid/flake-root"; + proc-flake.url = "github:srid/proc-flake"; }; - outputs = { - flake-parts, - pre-commit-hooks, - ... - } @ inputs: - flake-parts.lib.mkFlake {inherit inputs;} { + outputs = {parts, ...} @ inputs: + parts.lib.mkFlake {inherit inputs;} { imports = [ - pre-commit-hooks.flakeModule - ]; + ./nix/dev.nix + ./nix/packages.nix + ./nix/deployment.nix - perSystem = { - config, - lib, - pkgs, - ... - }: { - pre-commit.settings.hooks = { - alejandra.enable = true; - prettier = { - enable = true; - excludes = ["flake.lock" "pnpm-lock.yaml"]; - }; - }; - devShells.default = pkgs.mkShell { - shellHook = '' - ${config.pre-commit.installationScript} - ''; - packages = with pkgs; [nodePackages.pnpm redis]; - }; - formatter = pkgs.alejandra; - }; + inputs.pre-commit-hooks.flakeModule + inputs.flake-root.flakeModule + inputs.proc-flake.flakeModule + ]; systems = [ "x86_64-linux" diff --git a/nix/deployment.nix b/nix/deployment.nix new file mode 100644 index 0000000..84044fc --- /dev/null +++ b/nix/deployment.nix @@ -0,0 +1,84 @@ +{ + inputs, + self, + ... +}: { + flake.nixosModules.default = import ./module.nix self; + + perSystem = { + lib, + pkgs, + system, + config, + inputs', + ... + }: let + crossPkgsFor = lib.fix (finalAttrs: { + "x86_64-linux" = { + "x86_64" = pkgs.pkgsStatic; + "aarch64" = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; + }; + + "aarch64-linux" = { + "x86_64" = pkgs.pkgsCross.musl64; + "aarch64" = pkgs.pkgsStatic; + }; + + "x86_64-darwin" = { + "x86_64" = pkgs.pkgsCross.musl64; + "aarch64" = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; + }; + + "aarch64-darwin" = finalAttrs."x86_64-darwin"; + }); + + exeFor = arch: let + target = "${arch}-unknown-linux-musl"; + target' = builtins.replaceStrings ["-"] ["_"] target; + targetUpper = lib.toUpper target'; + + toolchain = with inputs'.fenix.packages; + combine [ + minimal.cargo + minimal.rustc + targets.${target}.latest.rust-std + ]; + + naersk' = inputs.naersk.lib.${system}.override { + cargo = toolchain; + rustc = toolchain; + }; + + refraction = config.packages.refraction.override { + naersk = naersk'; + optimizeSize = true; + }; + + inherit (crossPkgsFor.${system}.${arch}.stdenv) cc; + in + lib.getExe ( + refraction.overrideAttrs (_: + lib.fix (finalAttrs: { + CARGO_BUILD_TARGET = target; + "CC_${target'}" = "${cc}/bin/${cc.targetPrefix}cc"; + "CARGO_TARGET_${targetUpper}_RUSTFLAGS" = "-C target-feature=+crt-static"; + "CARGO_TARGET_${targetUpper}_LINKER" = finalAttrs."CC_${target'}"; + })) + ); + + containerFor = arch: + pkgs.dockerTools.buildLayeredImage { + name = "refraction"; + tag = "latest-${arch}"; + contents = [pkgs.dockerTools.caCertificates]; + config.Cmd = [(exeFor arch)]; + + architecture = crossPkgsFor.${system}.${arch}.go.GOARCH; + }; + in { + legacyPackages = { + container-x86_64 = containerFor "x86_64"; + container-aarch64 = containerFor "aarch64"; + }; + }; +} diff --git a/nix/derivation.nix b/nix/derivation.nix new file mode 100644 index 0000000..f52b5a8 --- /dev/null +++ b/nix/derivation.nix @@ -0,0 +1,51 @@ +{ + lib, + stdenv, + naersk, + CoreFoundation, + Security, + SystemConfiguration, + version, + optimizeSize ? false, +}: let + filter = path: type: let + path' = toString path; + base = baseNameOf path'; + + dirBlocklist = ["nix"]; + + matches = lib.any (suffix: lib.hasSuffix suffix base) [".rs"]; + isCargo = base == "Cargo.lock" || base == "Cargo.toml"; + isAllowedDir = !(builtins.elem base dirBlocklist); + in + (type == "directory" && isAllowedDir) || matches || isCargo; + + filterSource = src: + lib.cleanSourceWith { + src = lib.cleanSource src; + inherit filter; + }; +in + naersk.buildPackage { + pname = "refraction"; + inherit version; + + src = filterSource ../.; + + buildInputs = lib.optionals stdenv.hostPlatform.isDarwin [ + CoreFoundation + Security + SystemConfiguration + ]; + + RUSTFLAGS = lib.optionalString optimizeSize "-C codegen-units=1 -C strip=symbols -C opt-level=z"; + + meta = with lib; { + mainProgram = "refraction"; + description = "Discord bot for Prism Launcher"; + homepage = "https://github.com/PrismLauncher/refraction"; + license = licenses.gpl3Plus; + platforms = with platforms; linux ++ darwin; + maintainers = with maintainers; [getchoo Scrumplex]; + }; + } diff --git a/nix/dev.nix b/nix/dev.nix new file mode 100644 index 0000000..9697dac --- /dev/null +++ b/nix/dev.nix @@ -0,0 +1,50 @@ +{ + perSystem = { + lib, + pkgs, + config, + ... + }: { + pre-commit.settings.hooks = { + actionlint.enable = true; + alejandra.enable = true; + rustfmt.enable = true; + nil.enable = true; + prettier = { + enable = true; + excludes = ["flake.lock"]; + }; + }; + + proc.groups.daemons.processes = { + redis.command = "${lib.getExe' pkgs.redis "redis-server"}"; + }; + + devShells.default = pkgs.mkShell { + shellHook = '' + ${config.pre-commit.installationScript} + ''; + + packages = with pkgs; [ + # general + actionlint + config.proc.groups.daemons.package + + # rust + cargo + rustc + clippy + rustfmt + rust-analyzer + + # nix + config.formatter + nil + ]; + + RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; + }; + + formatter = pkgs.alejandra; + }; +} diff --git a/nix/module.nix b/nix/module.nix new file mode 100644 index 0000000..baee5fe --- /dev/null +++ b/nix/module.nix @@ -0,0 +1,143 @@ +self: { + config, + lib, + pkgs, + ... +}: let + cfg = config.services.refraction; + defaultUser = "refraction"; + + inherit + (lib) + getExe + literalExpression + mdDoc + mkEnableOption + mkIf + mkOption + mkPackageOption + optionals + types + ; +in { + options.services.refraction = { + enable = mkEnableOption "refraction"; + package = mkPackageOption self.packages.${pkgs.stdenv.hostPlatform.system} "refraction" {}; + + user = mkOption { + description = mdDoc '' + User under which the service should run. If this is the default value, + the user will be created, with the specified group as the primary + group. + ''; + type = types.str; + default = defaultUser; + example = literalExpression '' + "bob" + ''; + }; + + group = mkOption { + description = mdDoc '' + Group under which the service should run. If this is the default value, + the group will be created. + ''; + type = types.str; + default = defaultUser; + example = literalExpression '' + "discordbots" + ''; + }; + + redisUrl = mkOption { + description = mdDoc '' + Connection to a redis server. If this needs to include credentials + that shouldn't be world-readable in the Nix store, set environmentFile + and override the `REDIS_URL` entry. + Pass the string `local` to setup a local Redis database. + ''; + type = types.str; + default = "local"; + example = literalExpression '' + "redis://localhost/" + ''; + }; + + environmentFile = mkOption { + description = mdDoc '' + Environment file as defined in {manpage}`systemd.exec(5)` + ''; + type = types.nullOr types.path; + default = null; + example = literalExpression '' + "/run/agenix.d/1/refraction" + ''; + }; + }; + + config = mkIf cfg.enable { + services.redis.servers.refraction = mkIf (cfg.redisUrl == "local") { + enable = true; + inherit (cfg) user; + port = 0; # disable tcp listener + }; + + systemd.services."refraction" = { + enable = true; + wantedBy = ["multi-user.target"]; + after = + ["network.target"] + ++ optionals (cfg.redisUrl == "local") ["redis-refraction.service"]; + + script = '' + ${getExe cfg.package} + ''; + + environment = { + REDIS_URL = + if cfg.redisUrl == "local" + then "unix:${config.services.redis.servers.refraction.unixSocket}" + else cfg.redisUrl; + }; + + serviceConfig = { + Type = "simple"; + Restart = "always"; + + EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile; + + User = cfg.user; + Group = cfg.group; + + # hardening + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectSystem = "strict"; + RestrictNamespaces = "uts ipc pid user cgroup"; + RestrictSUIDSGID = true; + }; + }; + + users = { + users = mkIf (cfg.user == defaultUser) { + ${defaultUser} = { + isSystemUser = true; + inherit (cfg) group; + }; + }; + + groups = mkIf (cfg.group == defaultUser) { + ${defaultUser} = {}; + }; + }; + }; +} diff --git a/nix/packages.nix b/nix/packages.nix new file mode 100644 index 0000000..e973cf9 --- /dev/null +++ b/nix/packages.nix @@ -0,0 +1,29 @@ +{ + self, + inputs, + ... +}: { + perSystem = { + pkgs, + system, + config, + ... + }: { + packages = { + refraction = pkgs.callPackage ./derivation.nix { + version = builtins.substring 0 8 self.lastModifiedDate or "dirty"; + + inherit + (pkgs.darwin.apple_sdk.frameworks) + CoreFoundation + Security + SystemConfiguration + ; + + naersk = inputs.naersk.lib.${system}; + }; + + default = config.packages.refraction; + }; + }; +}