diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 1a2d75307496..28a8ffbce691 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1120,6 +1120,7 @@ ./services/web-apps/baget.nix ./services/web-apps/bookstack.nix ./services/web-apps/calibre-web.nix + ./services/web-apps/coder.nix ./services/web-apps/changedetection-io.nix ./services/web-apps/cloudlog.nix ./services/web-apps/code-server.nix diff --git a/nixos/modules/services/web-apps/coder.nix b/nixos/modules/services/web-apps/coder.nix new file mode 100644 index 000000000000..469a29bc3aa8 --- /dev/null +++ b/nixos/modules/services/web-apps/coder.nix @@ -0,0 +1,217 @@ +{ config, lib, options, pkgs, ... }: + +with lib; + +let + cfg = config.services.coder; + name = "coder"; +in { + options = { + services.coder = { + enable = mkEnableOption (lib.mdDoc "Coder service"); + + user = mkOption { + type = types.str; + default = "coder"; + description = lib.mdDoc '' + User under which the coder service runs. + + ::: {.note} + If left as the default value this user will automatically be created + on system activation, otherwise it needs to be configured manually. + ::: + ''; + }; + + group = mkOption { + type = types.str; + default = "coder"; + description = lib.mdDoc '' + Group under which the coder service runs. + + ::: {.note} + If left as the default value this group will automatically be created + on system activation, otherwise it needs to be configured manually. + ::: + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.coder; + description = lib.mdDoc '' + Package to use for the service. + ''; + defaultText = literalExpression "pkgs.coder"; + }; + + homeDir = mkOption { + type = types.str; + description = lib.mdDoc '' + Home directory for coder user. + ''; + default = "/var/lib/coder"; + }; + + listenAddress = mkOption { + type = types.str; + description = lib.mdDoc '' + Listen address. + ''; + default = "127.0.0.1:3000"; + }; + + accessUrl = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc '' + Access URL should be a external IP address or domain with DNS records pointing to Coder. + ''; + default = null; + example = "https://coder.example.com"; + }; + + wildcardAccessUrl = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc '' + If you are providing TLS certificates directly to the Coder server, you must use a single certificate for the root and wildcard domains. + ''; + default = null; + example = "*.coder.example.com"; + }; + + database = { + createLocally = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Create the database and database user locally. + ''; + }; + + host = mkOption { + type = types.str; + default = "/run/postgresql"; + description = lib.mdDoc '' + Hostname hosting the database. + ''; + }; + + database = mkOption { + type = types.str; + default = "coder"; + description = lib.mdDoc '' + Name of database. + ''; + }; + + username = mkOption { + type = types.str; + default = "coder"; + description = lib.mdDoc '' + Username for accessing the database. + ''; + }; + + password = mkOption { + type = types.nullOr types.str; + default = null; + description = lib.mdDoc '' + Password for accessing the database. + ''; + }; + + sslmode = mkOption { + type = types.nullOr types.str; + default = "disable"; + description = lib.mdDoc '' + Password for accessing the database. + ''; + }; + }; + + tlsCert = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc '' + The path to the TLS certificate. + ''; + default = null; + }; + + tlsKey = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc '' + The path to the TLS key. + ''; + default = null; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { assertion = cfg.database.createLocally -> cfg.database.username == name; + message = "services.coder.database.username must be set to ${user} if services.coder.database.createLocally is set true"; + } + ]; + + systemd.services.coder = { + description = "Coder - Self-hosted developer workspaces on your infra"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + environment = { + CODER_ACCESS_URL = cfg.accessUrl; + CODER_WILDCARD_ACCESS_URL = cfg.wildcardAccessUrl; + CODER_PG_CONNECTION_URL = "user=${cfg.database.username} ${optionalString (cfg.database.password != null) "password=${cfg.database.password}"} database=${cfg.database.database} host=${cfg.database.host} ${optionalString (cfg.database.sslmode != null) "sslmode=${cfg.database.sslmode}"}"; + CODER_ADDRESS = cfg.listenAddress; + CODER_TLS_ENABLE = optionalString (cfg.tlsCert != null) "1"; + CODER_TLS_CERT_FILE = cfg.tlsCert; + CODER_TLS_KEY_FILE = cfg.tlsKey; + }; + + serviceConfig = { + ProtectSystem = "full"; + PrivateTmp = "yes"; + PrivateDevices = "yes"; + SecureBits = "keep-caps"; + AmbientCapabilities = "CAP_IPC_LOCK CAP_NET_BIND_SERVICE"; + CacheDirectory = "coder"; + CapabilityBoundingSet = "CAP_SYSLOG CAP_IPC_LOCK CAP_NET_BIND_SERVICE"; + KillSignal = "SIGINT"; + KillMode = "mixed"; + NoNewPrivileges = "yes"; + Restart = "on-failure"; + ExecStart = "${cfg.package}/bin/coder server"; + User = cfg.user; + Group = cfg.group; + }; + }; + + services.postgresql = lib.mkIf cfg.database.createLocally { + enable = true; + ensureDatabases = [ + cfg.database.database + ]; + ensureUsers = [{ + name = cfg.database.username; + ensurePermissions = { + "DATABASE \"${cfg.database.database}\"" = "ALL PRIVILEGES"; + }; + } + ]; + }; + + users.groups = optionalAttrs (cfg.group == name) { + "${cfg.group}" = {}; + }; + users.users = optionalAttrs (cfg.user == name) { + ${name} = { + description = "Coder service user"; + group = cfg.group; + home = cfg.homeDir; + createHome = true; + isSystemUser = true; + }; + }; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 7b1159d66715..f61cb1d9f9b5 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -135,6 +135,7 @@ in { cloudlog = handleTest ./cloudlog.nix {}; cntr = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cntr.nix {}; cockroachdb = handleTestOn ["x86_64-linux"] ./cockroachdb.nix {}; + coder = handleTest ./coder.nix {}; collectd = handleTest ./collectd.nix {}; connman = handleTest ./connman.nix {}; consul = handleTest ./consul.nix {}; diff --git a/nixos/tests/coder.nix b/nixos/tests/coder.nix new file mode 100644 index 000000000000..12813827284b --- /dev/null +++ b/nixos/tests/coder.nix @@ -0,0 +1,24 @@ +import ./make-test-python.nix ({ pkgs, ... }: { + name = "coder"; + meta = with pkgs.lib.maintainers; { + maintainers = [ shyim ghuntley ]; + }; + + nodes.machine = + { pkgs, ... }: + { + services.coder = { + enable = true; + accessUrl = "http://localhost:3000"; + }; + }; + + testScript = '' + machine.start() + machine.wait_for_unit("postgresql.service") + machine.wait_for_unit("coder.service") + machine.wait_for_open_port(3000) + + machine.succeed("curl --fail http://localhost:3000") + ''; +}) diff --git a/pkgs/applications/misc/coder/default.nix b/pkgs/applications/misc/coder/default.nix deleted file mode 100644 index 264aa979e383..000000000000 --- a/pkgs/applications/misc/coder/default.nix +++ /dev/null @@ -1,76 +0,0 @@ -{ lib, pkgs, fetchFromGitHub, installShellFiles, buildGoModule, fetchYarnDeps }: - -let - pname = "coder"; - version = "0.10.2"; - - src = fetchFromGitHub { - owner = "coder"; - repo = "coder"; - rev = "v${version}"; - sha256 = "sha256-h5bN75agNocRAjShbufRCJr45huYJOzCBd4OcGpF4C4="; - }; - - offlineCache = fetchYarnDeps { - yarnLock = src + "/site/yarn.lock"; - sha256 = "sha256-uDNPRQTpsgxyC5ks+2Qq/wiKjkbjWwSO+cJc5X6qmAA="; - }; - - yarn16 = pkgs.yarn.override { nodejs = pkgs.nodejs-16_x; }; - nodePackages16 = pkgs.nodePackages.override { nodejs = pkgs.nodejs-16_x; }; -in -buildGoModule rec { - inherit pname version src; - - subPackages = [ "cmd/coder" ]; - - vendorSha256 = "sha256-+3Zy0zArCXkvD4ogfKdu9W9gJXveAhwFXKG1VRDvOkI="; - - # Flags as provided by the build automation of the project: - # https://github.com/coder/coder/blob/075e891f287b27cdb481a48e129f20a1e6a7de12/scripts/build_go.sh#L89 - ldflags = [ - "-s" - "-w" - "-X github.com/coder/coder/buildinfo.tag=${version}" - ]; - - preBuild = '' - export HOME=$(mktemp -d) - mkdir -p $HOME - - cd site - yarn config --offline set yarn-offline-mirror ${offlineCache} - fixup_yarn_lock yarn.lock - - # node-gyp tries to download always the headers and fails: https://github.com/NixOS/nixpkgs/issues/195404 - yarn remove --offline jest-canvas-mock canvas - - NODE_ENV=production node node_modules/.bin/vite build - cd .. - ''; - - tags = [ "embed" ]; - - nativeBuildInputs = with pkgs; [ - fixup_yarn_lock - nodejs-16_x - yarn16 - nodePackages16.node-pre-gyp - python3 - pkg-config - installShellFiles - ]; - - postInstall = '' - installShellCompletion --cmd coder \ - --bash <($out/bin/coder completion bash) \ - --zsh <($out/bin/coder completion zsh) - ''; - - meta = with lib; { - description = "Software development on any infrastructure"; - license = licenses.agpl3Only; - homepage = "https://coder.com/"; - maintainers = with maintainers; [ shyim ]; - }; -} diff --git a/pkgs/development/tools/coder/default.nix b/pkgs/development/tools/coder/default.nix index 30d22bc183dc..c81cdce753ec 100644 --- a/pkgs/development/tools/coder/default.nix +++ b/pkgs/development/tools/coder/default.nix @@ -1,8 +1,18 @@ -{ buildGoModule +{ lib , fetchFromGitHub , installShellFiles -, lib +, makeWrapper +, buildGoModule +, fetchYarnDeps +, fixup_yarn_lock +, pkg-config +, nodejs +, yarn +, nodePackages +, python3 +, terraform }: + buildGoModule rec { pname = "coder"; version = "0.16.0"; @@ -14,18 +24,59 @@ buildGoModule rec { hash = "sha256-3rGpyJzGkZYUEvKKDzj2I5sqrUImmmX7cXWM9UClPLY="; }; - # integration tests require network access - doCheck = false; + offlineCache = fetchYarnDeps { + yarnLock = src + "/site/yarn.lock"; + hash = "sha256-4GbM7GNZ3wHIZJIJuHw1v/SwjUNc1vi8IHRGaGwPGZQ="; + }; + + subPackages = [ "cmd/..." ]; vendorHash = "sha256-bb9jBno7elO6qKGjacpX3rxgrpJpGpTxMQtdBYjBzMk="; - nativeBuildInputs = [ installShellFiles ]; + # integration tests require network access + doCheck = false; + + ldflags = [ + "-s" + "-w" + "-X github.com/coder/coder/buildinfo.tag=${version}" + ]; + + preBuild = '' + export HOME=$TEMPDIR + + pushd site + yarn config --offline set yarn-offline-mirror ${offlineCache} + fixup_yarn_lock yarn.lock + + # node-gyp tries to download always the headers and fails: https://github.com/NixOS/nixpkgs/issues/195404 + yarn remove --offline jest-canvas-mock canvas + + NODE_ENV=production node node_modules/.bin/vite build + + popd + ''; + + tags = [ "embed" ]; + + nativeBuildInputs = [ + fixup_yarn_lock + installShellFiles + makeWrapper + nodePackages.node-pre-gyp + nodejs + pkg-config + python3 + yarn + ]; postInstall = '' installShellCompletion --cmd coder \ --bash <($out/bin/coder completion bash) \ --fish <($out/bin/coder completion fish) \ --zsh <($out/bin/coder completion zsh) + + wrapProgram $out/bin/coder --prefix PATH : ${lib.makeBinPath [ terraform ]} ''; meta = with lib; { diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 2d5ff0e522b8..d3fd46c92d9d 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -4174,8 +4174,6 @@ with pkgs; electron = electron_11; }; - coder = callPackage ../applications/misc/coder {}; - contrast = callPackage ../applications/accessibility/contrast { }; cplex = callPackage ../applications/science/math/cplex (config.cplex or {});