diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 52c6fe5028f1..0560caee6704 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1259,6 +1259,7 @@ ./services/web-apps/changedetection-io.nix ./services/web-apps/chatgpt-retrieval-plugin.nix ./services/web-apps/cloudlog.nix + ./services/web-apps/code-server.nix ./services/web-apps/convos.nix ./services/web-apps/dex.nix ./services/web-apps/discourse.nix diff --git a/nixos/modules/services/web-apps/code-server.nix b/nixos/modules/services/web-apps/code-server.nix new file mode 100644 index 000000000000..11601f6c3044 --- /dev/null +++ b/nixos/modules/services/web-apps/code-server.nix @@ -0,0 +1,259 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.code-server; + defaultUser = "code-server"; + defaultGroup = defaultUser; +in { + options = { + services.code-server = { + enable = lib.mkEnableOption (lib.mdDoc "code-server"); + + package = lib.mkPackageOptionMD pkgs "code-server" { + example = '' + pkgs.vscode-with-extensions.override { + vscode = pkgs.code-server; + vscodeExtensions = with pkgs.vscode-extensions; [ + bbenoist.nix + dracula-theme.theme-dracula + ]; + } + ''; + }; + + extraPackages = lib.mkOption { + default = [ ]; + description = lib.mdDoc '' + Additional packages to add to the code-server {env}`PATH`. + ''; + example = lib.literalExpression "[ pkgs.go ]"; + type = lib.types.listOf lib.types.package; + }; + + extraEnvironment = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + description = lib.mdDoc '' + Additional environment variables to pass to code-server. + ''; + default = { }; + example = { PKG_CONFIG_PATH = "/run/current-system/sw/lib/pkgconfig"; }; + }; + + extraArguments = lib.mkOption { + default = [ ]; + description = lib.mdDoc '' + Additional arguments to pass to code-server. + ''; + example = lib.literalExpression ''[ "--log=info" ]''; + type = lib.types.listOf lib.types.str; + }; + + host = lib.mkOption { + default = "localhost"; + description = lib.mdDoc '' + The host name or IP address the server should listen to. + ''; + type = lib.types.str; + }; + + port = lib.mkOption { + default = 4444; + description = lib.mdDoc '' + The port the server should listen to. + ''; + type = lib.types.port; + }; + + auth = lib.mkOption { + default = "password"; + description = lib.mdDoc '' + The type of authentication to use. + ''; + type = lib.types.enum [ "none" "password" ]; + }; + + hashedPassword = lib.mkOption { + default = ""; + description = lib.mdDoc '' + Create the password with: `echo -n 'thisismypassword' | npx argon2-cli -e`. + ''; + type = lib.types.str; + }; + + user = lib.mkOption { + default = defaultUser; + example = "yourUser"; + description = lib.mdDoc '' + The user to run code-server as. + By default, a user named `${defaultUser}` will be created. + ''; + type = lib.types.str; + }; + + group = lib.mkOption { + default = defaultGroup; + example = "yourGroup"; + description = lib.mdDoc '' + The group to run code-server under. + By default, a group named `${defaultGroup}` will be created. + ''; + type = lib.types.str; + }; + + extraGroups = lib.mkOption { + default = [ ]; + description = lib.mdDoc '' + An array of additional groups for the `${defaultUser}` user. + ''; + example = [ "docker" ]; + type = lib.types.listOf lib.types.str; + }; + + socket = lib.mkOption { + default = null; + example = "/run/code-server/socket"; + description = lib.mdDoc '' + Path to a socket (bind-addr will be ignored). + ''; + type = lib.types.nullOr lib.types.str; + }; + + socketMode = lib.mkOption { + default = null; + description = lib.mdDoc '' + File mode of the socket. + ''; + type = lib.types.nullOr lib.types.str; + }; + + userDataDir = lib.mkOption { + default = null; + description = lib.mdDoc '' + Path to the user data directory. + ''; + type = lib.types.nullOr lib.types.str; + }; + + extensionsDir = lib.mkOption { + default = null; + description = lib.mdDoc '' + Path to the extensions directory. + ''; + type = lib.types.nullOr lib.types.str; + }; + + proxyDomain = lib.mkOption { + default = null; + example = "code-server.lan"; + description = lib.mdDoc '' + Domain used for proxying ports. + ''; + type = lib.types.nullOr lib.types.str; + }; + + disableTelemetry = lib.mkOption { + default = false; + example = true; + description = lib.mdDoc '' + Disable telemetry. + ''; + type = lib.types.bool; + }; + + disableUpdateCheck = lib.mkOption { + default = false; + example = true; + description = lib.mdDoc '' + Disable update check. + Without this flag, code-server checks every 6 hours against the latest github release and + then notifies you once every week that a new release is available. + ''; + type = lib.types.bool; + }; + + disableFileDownloads = lib.mkOption { + default = false; + example = true; + description = lib.mdDoc '' + Disable file downloads from Code. + ''; + type = lib.types.bool; + }; + + disableWorkspaceTrust = lib.mkOption { + default = false; + example = true; + description = lib.mdDoc '' + Disable Workspace Trust feature. + ''; + type = lib.types.bool; + }; + + disableGettingStartedOverride = lib.mkOption { + default = false; + example = true; + description = lib.mdDoc '' + Disable the coder/coder override in the Help: Getting Started page. + ''; + type = lib.types.bool; + }; + + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.code-server = { + description = "Code server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + path = cfg.extraPackages; + environment = { + HASHED_PASSWORD = cfg.hashedPassword; + } // cfg.extraEnvironment; + serviceConfig = { + ExecStart = '' + ${lib.getExe cfg.package} \ + --auth=${cfg.auth} \ + --bind-addr=${cfg.host}:${toString cfg.port} \ + '' + lib.optionalString (cfg.socket != null) '' + --socket=${cfg.socket} \ + '' + lib.optionalString (cfg.userDataDir != null) '' + --user-data-dir=${cfg.userDataDir} \ + '' + lib.optionalString (cfg.extensionsDir != null) '' + --extensions-dir=${cfg.extensionsDir} \ + '' + lib.optionalString (cfg.disableTelemetry == true) '' + --disable-telemetry \ + '' + lib.optionalString (cfg.disableUpdateCheck == true) '' + --disable-update-check \ + '' + lib.optionalString (cfg.disableFileDownloads == true) '' + --disable-file-downloads \ + '' + lib.optionalString (cfg.disableWorkspaceTrust == true) '' + --disable-workspace-trust \ + '' + lib.optionalString (cfg.disableGettingStartedOverride == true) '' + --disable-getting-started-override \ + '' + lib.escapeShellArgs cfg.extraArguments; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + RuntimeDirectory = cfg.user; + User = cfg.user; + Group = cfg.group; + Restart = "on-failure"; + }; + }; + + users.users."${cfg.user}" = lib.mkMerge [ + (lib.mkIf (cfg.user == defaultUser) { + isNormalUser = true; + description = "code-server user"; + inherit (cfg) group; + }) + { + packages = cfg.extraPackages; + inherit (cfg) extraGroups; + } + ]; + + users.groups."${defaultGroup}" = lib.mkIf (cfg.group == defaultGroup) { }; + }; + + meta.maintainers = [ lib.maintainers.stackshadow ]; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 40b7dd83ddb4..1c0c7c538086 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -198,6 +198,7 @@ in { cntr = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cntr.nix {}; cockpit = handleTest ./cockpit.nix {}; cockroachdb = handleTestOn ["x86_64-linux"] ./cockroachdb.nix {}; + code-server = handleTest ./code-server.nix {}; coder = handleTest ./coder.nix {}; collectd = handleTest ./collectd.nix {}; connman = handleTest ./connman.nix {}; diff --git a/nixos/tests/code-server.nix b/nixos/tests/code-server.nix new file mode 100644 index 000000000000..7d523dfc617e --- /dev/null +++ b/nixos/tests/code-server.nix @@ -0,0 +1,22 @@ +import ./make-test-python.nix ({pkgs, lib, ...}: +{ + name = "code-server"; + + nodes = { + machine = {pkgs, ...}: { + services.code-server = { + enable = true; + auth = "none"; + }; + }; + }; + + testScript = '' + start_all() + machine.wait_for_unit("code-server.service") + machine.wait_for_open_port(4444) + machine.succeed("curl -k --fail http://localhost:4444", timeout=10) + ''; + + meta.maintainers = [ lib.maintainers.drupol ]; +}) diff --git a/pkgs/servers/code-server/build-vscode-nogit.patch b/pkgs/servers/code-server/build-vscode-nogit.patch new file mode 100644 index 000000000000..ec726c68d438 --- /dev/null +++ b/pkgs/servers/code-server/build-vscode-nogit.patch @@ -0,0 +1,20 @@ +diff --git a/ci/build/build-vscode.sh b/ci/build/build-vscode.sh +index a72549fb..3aed1ad5 100755 +--- a/ci/build/build-vscode.sh ++++ b/ci/build/build-vscode.sh +@@ -58,7 +58,6 @@ main() { + # telemetry available; telemetry can still be disabled by flag or setting). + # This needs to be done before building as Code will read this file and embed + # it into the client-side code. +- git checkout product.json # Reset in case the script exited early. + cp product.json product.original.json # Since jq has no inline edit. + jq --slurp '.[0] * .[1]' product.original.json <( + cat << EOF +@@ -105,7 +104,6 @@ EOF + # Reset so if you develop after building you will not be stuck with the wrong + # commit (the dev client will use `oss-dev` but the dev server will still use + # product.json which will have `stable-$commit`). +- git checkout product.json + + popd + diff --git a/pkgs/servers/code-server/default.nix b/pkgs/servers/code-server/default.nix new file mode 100644 index 000000000000..0c5cb2310852 --- /dev/null +++ b/pkgs/servers/code-server/default.nix @@ -0,0 +1,327 @@ +{ lib +, stdenv +, fetchFromGitHub +, buildGoModule +, makeWrapper +, cacert +, moreutils +, jq +, git +, rsync +, pkg-config +, yarn +, python3 +, esbuild +, nodejs +, node-gyp +, libsecret +, xorg +, ripgrep +, AppKit +, Cocoa +, CoreServices +, Security +, cctools +, xcbuild +, quilt +, nixosTests +}: + +let + system = stdenv.hostPlatform.system; + + python = python3; + yarn' = yarn.override { inherit nodejs; }; + defaultYarnOpts = [ ]; + + esbuild' = esbuild.override { + buildGoModule = args: buildGoModule (args // rec { + version = "0.16.17"; + src = fetchFromGitHub { + owner = "evanw"; + repo = "esbuild"; + rev = "v${version}"; + hash = "sha256-8L8h0FaexNsb3Mj6/ohA37nYLFogo5wXkAhGztGUUsQ="; + }; + vendorHash = "sha256-+BfxCyg0KkDQpHt/wycy/8CTG6YBA/VJvJFhhzUnSiQ="; + }); + }; + + # replaces esbuild's download script with a binary from nixpkgs + patchEsbuild = path: version: '' + mkdir -p ${path}/node_modules/esbuild/bin + jq "del(.scripts.postinstall)" ${path}/node_modules/esbuild/package.json | sponge ${path}/node_modules/esbuild/package.json + sed -i 's/${version}/${esbuild'.version}/g' ${path}/node_modules/esbuild/lib/main.js + ln -s -f ${esbuild'}/bin/esbuild ${path}/node_modules/esbuild/bin/esbuild + ''; + + # Comment from @code-asher, the code-server maintainer + # See https://github.com/NixOS/nixpkgs/pull/240001#discussion_r1244303617 + # + # If the commit is missing it will break display languages (Japanese, Spanish, + # etc). For some reason VS Code has a hard dependency on the commit being set + # for that functionality. + # The commit is also used in cache busting. Without the commit you could run + # into issues where the browser is loading old versions of assets from the + # cache. + # Lastly, it can be helpful for the commit to be accurate in bug reports + # especially when they are built outside of our CI as sometimes the version + # numbers can be unreliable (since they are arbitrarily provided). + # + # To compute the commit when upgrading this derivation, do: + # `$ git rev-parse ` where is the git revision of the `src` + # Example: `$ git rev-parse v4.16.1` + commit = "94ef3776ad7bebfb5780dfc9632e04d20d5c9a6c"; +in +stdenv.mkDerivation (finalAttrs: { + pname = "code-server"; + version = "4.16.1"; + + src = fetchFromGitHub { + owner = "coder"; + repo = "code-server"; + rev = "v${finalAttrs.version}"; + fetchSubmodules = true; + hash = "sha256-h4AooHHKV/EfN2S1z7CQKqnYW3uA3sKhSW4senlzjxI="; + }; + + yarnCache = stdenv.mkDerivation { + name = "${finalAttrs.pname}-${finalAttrs.version}-${system}-yarn-cache"; + inherit (finalAttrs) src; + + nativeBuildInputs = [ yarn' git cacert ]; + + buildPhase = '' + runHook preBuild + + export HOME=$PWD + export GIT_SSL_CAINFO="${cacert}/etc/ssl/certs/ca-bundle.crt" + + yarn --cwd "./vendor" install --modules-folder modules --ignore-scripts --frozen-lockfile + + yarn config set yarn-offline-mirror $out + find "$PWD" -name "yarn.lock" -printf "%h\n" | \ + xargs -I {} yarn --cwd {} \ + --frozen-lockfile --ignore-scripts --ignore-platform \ + --ignore-engines --no-progress --non-interactive + + find ./lib/vscode -name "yarn.lock" -printf "%h\n" | \ + xargs -I {} yarn --cwd {} \ + --ignore-scripts --ignore-engines + + runHook postBuild + ''; + + outputHashMode = "recursive"; + outputHashAlgo = "sha256"; + outputHash = "sha256-vkju+oxEYrEXFAnjz/Mf1g0ZhxBALLAaRuWE0swSWwM="; + }; + + nativeBuildInputs = [ + nodejs + yarn' + python + pkg-config + makeWrapper + git + rsync + jq + moreutils + quilt + ]; + + buildInputs = lib.optionals (!stdenv.isDarwin) [ libsecret ] + ++ (with xorg; [ libX11 libxkbfile ]) + ++ lib.optionals stdenv.isDarwin [ + AppKit + Cocoa + CoreServices + Security + cctools + xcbuild + ]; + + patches = [ + # Remove all git calls from the VS Code build script except `git rev-parse + # HEAD` which is replaced in postPatch with the commit. + ./build-vscode-nogit.patch + ]; + + postPatch = '' + export HOME=$PWD + + patchShebangs ./ci + + # inject git commit + substituteInPlace ./ci/build/build-vscode.sh \ + --replace '$(git rev-parse HEAD)' "${commit}" + substituteInPlace ./ci/build/build-release.sh \ + --replace '$(git rev-parse HEAD)' "${commit}" + ''; + + configurePhase = '' + runHook preConfigure + + # run yarn offline by default + echo '--install.offline true' >> .yarnrc + + # set default yarn opts + ${lib.concatMapStrings (option: '' + yarn --offline config set ${option} + '') defaultYarnOpts} + + # set offline mirror to yarn cache we created in previous steps + yarn --offline config set yarn-offline-mirror "${finalAttrs.yarnCache}" + + # skip unnecessary electron download + export ELECTRON_SKIP_BINARY_DOWNLOAD=1 + + # set nodedir to prevent node-gyp from downloading headers + # taken from https://nixos.org/manual/nixpkgs/stable/#javascript-tool-specific + mkdir -p $HOME/.node-gyp/${nodejs.version} + echo 9 > $HOME/.node-gyp/${nodejs.version}/installVersion + ln -sfv ${nodejs}/include $HOME/.node-gyp/${nodejs.version} + export npm_config_nodedir=${nodejs} + + # use updated node-gyp. fixes the following error on Darwin: + # PermissionError: [Errno 1] Operation not permitted: '/usr/sbin/pkgutil' + export npm_config_node_gyp=${node-gyp}/lib/node_modules/node-gyp/bin/node-gyp.js + + runHook postConfigure + ''; + + buildPhase = '' + runHook preBuild + + # install code-server dependencies + yarn --offline --ignore-scripts + + # apply patches + quilt push -a + + # patch shebangs of everything to allow binary packages to build + patchShebangs . + + export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 + export SKIP_SUBMODULE_DEPS=1 + export NODE_OPTIONS=--openssl-legacy-provider + + # rebuild binary packages now that scripts have been patched + echo "----- NPM rebuild" + npm rebuild --prefer-offline + + # Replicate ci/dev/postinstall.sh + echo "----- Replicate ci/dev/postinstall.sh" + yarn --cwd "./vendor" install --modules-folder modules --offline --ignore-scripts --frozen-lockfile + + # remove all built-in extensions, as these are 3rd party extensions that + # get downloaded from vscode marketplace + jq --slurp '.[0] * .[1]' "./lib/vscode/product.json" <( + cat << EOF + { + "builtInExtensions": [] + } + EOF + ) | sponge ./lib/vscode/product.json + + # disable automatic updates + sed -i '/update.mode/,/\}/{s/default:.*/default: "none",/g}' \ + lib/vscode/src/vs/platform/update/common/update.config.contribution.ts + + # Patch out remote download of nodejs from build script + patch -p1 -i ${./remove-node-download.patch} + + # Fetch packages for vscode + find ./lib/vscode -name "yarn.lock" -printf "%h\n" | \ + xargs -I {} yarn --cwd {} \ + --frozen-lockfile --ignore-scripts --ignore-engines + + # patch shebangs of everything to allow binary packages to build + patchShebangs . + + ${patchEsbuild "./lib/vscode/build" "0.12.6"} + ${patchEsbuild "./lib/vscode/extensions" "0.11.23"} + '' + lib.optionalString stdenv.isDarwin '' + # use prebuilt binary for @parcel/watcher, which requires macOS SDK 10.13+ + # (see issue #101229) + pushd ./lib/vscode/remote/node_modules/@parcel/watcher + mkdir -p ./build/Release + mv ./prebuilds/darwin-x64/node.napi.glibc.node ./build/Release/watcher.node + jq "del(.scripts) | .gypfile = false" ./package.json | sponge ./package.json + popd + '' + '' + + # put ripgrep binary into bin, so postinstall does not try to download it + find -name ripgrep -type d \ + -execdir mkdir -p {}/bin \; \ + -execdir ln -s ${ripgrep}/bin/rg {}/bin/rg \; + + # run postinstall scripts after patching + find ./lib/vscode \( -path "*/node_modules/*" -or -path "*/extensions/*" \) \ + -and -type f -name "yarn.lock" -printf "%h\n" | \ + xargs -I {} sh -c 'jq -e ".scripts.postinstall" {}/package.json >/dev/null && yarn --cwd {} postinstall --frozen-lockfile --offline || true' + + # build code-server + yarn build + + # build vscode + VERSION=${finalAttrs.version} yarn build:vscode + + # inject version into package.json + jq --slurp '.[0] * .[1]' ./package.json <( + cat << EOF + { + "version": "${finalAttrs.version}" + } + EOF + ) | sponge ./package.json + + # create release + yarn release + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/libexec/code-server $out/bin + + # copy release to libexec path + cp -R -T release "$out/libexec/code-server" + + # install only production dependencies + yarn --offline --cwd "$out/libexec/code-server" --production + + # create wrapper + makeWrapper "${nodejs}/bin/node" "$out/bin/code-server" \ + --add-flags "$out/libexec/code-server/out/node/entry.js" + + runHook postInstall + ''; + + passthru = { + prefetchYarnCache = lib.overrideDerivation finalAttrs.yarnCache (d: { + outputHash = lib.fakeSha256; + }); + tests = { + inherit (nixosTests) code-server; + }; + # vscode-with-extensions compatibility + executableName = "code-server"; + longName = "Visual Studio Code Server"; + }; + + meta = { + description = "Run VS Code on a remote server"; + longDescription = '' + code-server is VS Code running on a remote server, accessible through the + browser. + ''; + homepage = "https://github.com/coder/code-server"; + license = lib.licenses.mit; + maintainers = with lib.maintainers; [ offline henkery code-asher ]; + platforms = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" ]; + mainProgram = "code-server"; + }; +}) diff --git a/pkgs/servers/code-server/playwright.patch b/pkgs/servers/code-server/playwright.patch new file mode 100644 index 000000000000..95a74748189d --- /dev/null +++ b/pkgs/servers/code-server/playwright.patch @@ -0,0 +1,10 @@ +--- ./vendor/modules/code-oss-dev/node_modules/playwright/install.js ++++ ./vendor/modules/code-oss-dev/node_modules/playwright/install.js +@@ -14,6 +14,4 @@ + * limitations under the License. + */ + +-const { installDefaultBrowsersForNpmInstall } = require('playwright-core/lib/utils/registry'); +- +-installDefaultBrowsersForNpmInstall(); ++process.stdout.write('Browser install disabled by Nix build script\n'); diff --git a/pkgs/servers/code-server/remove-node-download.patch b/pkgs/servers/code-server/remove-node-download.patch new file mode 100644 index 000000000000..d19d4a2b5bf7 --- /dev/null +++ b/pkgs/servers/code-server/remove-node-download.patch @@ -0,0 +1,28 @@ +--- ./lib/vscode/build/gulpfile.reh.js ++++ ./lib/vscode/build/gulpfile.reh.js +@@ -268,9 +268,6 @@ + .pipe(util.stripSourceMappingURL()) + .pipe(jsFilter.restore); + +- const nodePath = `.build/node/v${nodeVersion}/${platform}-${arch}`; +- const node = gulp.src(`${nodePath}/**`, { base: nodePath, dot: true }); +- + let web = []; + if (type === 'reh-web') { + web = [ +@@ -287,7 +284,6 @@ + license, + sources, + deps, +- node, + ...web + ); + +@@ -385,7 +381,6 @@ + const destinationFolderName = `vscode-${type}${dashed(platform)}${dashed(arch)}`; + + const serverTaskCI = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}-ci`, task.series( +- gulp.task(`node-${platform}-${arch}`), + util.rimraf(path.join(BUILD_ROOT, destinationFolderName)), + packageTask(type, platform, arch, sourceFolderName, destinationFolderName) + )); diff --git a/pkgs/top-level/aliases.nix b/pkgs/top-level/aliases.nix index 8fa94e82377a..f033921cde5d 100644 --- a/pkgs/top-level/aliases.nix +++ b/pkgs/top-level/aliases.nix @@ -156,7 +156,6 @@ mapAliases ({ claws-mail-gtk3 = claws-mail; # Added 2021-07-10 clucene_core_1 = throw "'clucene_core_1' has been renamed to/replaced by 'clucene_core'"; # Added 2023-12-09 cntk = throw "'cntk' has been removed from nixpkgs, as it was broken and unmaintained"; # Added 2023-10-09 - code-server = throw "'code-server' has been removed from nixpkgs, as it was depending on EOL Node.js and is unmaintained."; # Added 2023-10-30 codimd = hedgedoc; # Added 2020-11-29 inherit (libsForQt5.mauiPackages) communicator; # added 2022-05-17 compton = throw "'compton' has been renamed to/replaced by 'picom'"; # Converted to throw 2023-09-10 diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 60b5802a02a5..cf6ad1c0c527 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -36357,6 +36357,13 @@ with pkgs; inherit (nodePackages) node-gyp; }; + code-server = callPackage ../servers/code-server { + nodejs = nodejs_16; + inherit (darwin.apple_sdk.frameworks) AppKit Cocoa CoreServices Security; + inherit (darwin) cctools; + inherit (nodePackages) node-gyp; + }; + vue = callPackage ../applications/misc/vue { }; vuze = callPackage ../applications/networking/p2p/vuze {