diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md index dddb2b9241ad..858f1d2a6138 100644 --- a/nixos/doc/manual/release-notes/rl-2405.section.md +++ b/nixos/doc/manual/release-notes/rl-2405.section.md @@ -72,6 +72,8 @@ Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` for Pi +- [ownCloud Infinite Scale Stack](https://owncloud.com/infinite-scale-4-0/), a modern and scalable rewrite of ownCloud. + - [Handheld Daemon](https://github.com/hhd-dev/hhd), support for gaming handhelds like the Legion Go, ROG Ally, and GPD Win. Available as [services.handheld-daemon](#opt-services.handheld-daemon.enable). - [Guix](https://guix.gnu.org), a functional package manager inspired by Nix. Available as [services.guix](#opt-services.guix.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 914a31dfe1b3..178be2ab25c4 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1362,6 +1362,7 @@ ./services/web-apps/nexus.nix ./services/web-apps/nifi.nix ./services/web-apps/node-red.nix + ./services/web-apps/ocis.nix ./services/web-apps/onlyoffice.nix ./services/web-apps/openvscode-server.nix ./services/web-apps/mobilizon.nix diff --git a/nixos/modules/services/web-apps/ocis.md b/nixos/modules/services/web-apps/ocis.md new file mode 100644 index 000000000000..9156e927ed2d --- /dev/null +++ b/nixos/modules/services/web-apps/ocis.md @@ -0,0 +1,113 @@ +# ownCloud Infinite Scale {#module-services-ocis} + +[ownCloud Infinite Scale](https://owncloud.dev/ocis/) (oCIS) is an open-source, +modern file-sync and sharing platform. It is a ground-up rewrite of the well-known PHP based ownCloud server. + +The server setup can be automated using +[services.ocis](#opt-services.ocis.enable). The desktop client is packaged at +`pkgs.owncloud-client`. + +## Basic usage {#module-services-ocis-basic-usage} + +oCIS is a golang application and does not require an HTTP server (such as nginx) +in front of it, though you may optionally use one if you will. + +oCIS is configured using a combination of yaml and environment variables. It is +recommended to familiarize yourself with upstream's available configuration +options and deployment instructions: + +* [Getting Started](https://owncloud.dev/ocis/getting-started/) +* [Configuration](https://owncloud.dev/ocis/config/) +* [Basic Setup](https://owncloud.dev/ocis/deployment/basic-remote-setup/) + +A very basic configuration may look like this: +``` +{ pkgs, ... }: +{ + services.ocis = { + enable = true; + configDir = "/etc/ocis/config"; + }; +} +``` + +This will start the oCIS server and make it available at `https://localhost:9200` + +However to make this configuration work you will need generate a configuration. +You can do this with: + +```console +$ nix-shell -p ocis-bin +$ mkdir scratch/ +$ cd scratch/ +$ ocis init --config-path . --admin-password "changeme" +``` + +You may need to pass `--insecure true` or provide the `OCIS_INSECURE = true;` to +[`services.ocis.environment`][mod-envFile], if TLS certificates are generated +and managed externally (e.g. if you are using oCIS behind reverse proxy). + +If you want to manage the config file in your nix configuration, then it is +encouraged to use a secrets manager like sops-nix or agenix. + +Be careful not to write files containing secrets to the globally readable nix +store. + +Please note that current NixOS module for oCIS is configured to run in `fullstack` +mode, which starts all the services for owncloud on single instance. This will +start multiple ocis services and listen on multiple other ports. + +Current known services and their ports are as below: + +| Service | Group | Port | +|--------------------|---------|-------| +| gateway | api | 9142 | +| sharing | api | 9150 | +| app-registry | api | 9242 | +| ocdav | web | 45023 | +| auth-machine | api | 9166 | +| storage-system | api | 9215 | +| webdav | web | 9115 | +| webfinger | web | 46871 | +| storage-system | web | 9216 | +| web | web | 9100 | +| eventhistory | api | 33177 | +| ocs | web | 9110 | +| storage-publiclink | api | 9178 | +| settings | web | 9190 | +| ocm | api | 9282 | +| settings | api | 9191 | +| ocm | web | 9280 | +| app-provider | api | 9164 | +| storage-users | api | 9157 | +| auth-service | api | 9199 | +| thumbnails | web | 9186 | +| thumbnails | api | 9185 | +| storage-shares | api | 9154 | +| sse | sse | 46833 | +| userlog | userlog | 45363 | +| search | api | 9220 | +| proxy | web | 9200 | +| idp | web | 9130 | +| frontend | web | 9140 | +| groups | api | 9160 | +| graph | graph | 9120 | +| users | api | 9144 | +| auth-basic | api | 9146 | + +## Configuration via environment variables + +You can also eschew the config file entirely and pass everything to oCIS via +environment variables. For this make use of +[`services.ocis.environment`][mod-env] for non-sensitive +values, and +[`services.ocis.environmentFile`][mod-envFile] for +sensitive values. + +Configuration in (`services.ocis.environment`)[mod-env] overrides those from +[`services.ocis.environmentFile`][mod-envFile] and will have highest +precedence + + +[mod-env]: #opt-services.ocis.environment +[mod-envFile]: #opt-services.ocis.environmentFile diff --git a/nixos/modules/services/web-apps/ocis.nix b/nixos/modules/services/web-apps/ocis.nix new file mode 100644 index 000000000000..b3ffec9ad9c1 --- /dev/null +++ b/nixos/modules/services/web-apps/ocis.nix @@ -0,0 +1,201 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + inherit (lib) types; + cfg = config.services.ocis; + defaultUser = "ocis"; + defaultGroup = defaultUser; +in +{ + options = { + services.ocis = { + enable = lib.mkEnableOption "ownCloud Infinite Scale"; + + package = lib.mkPackageOption pkgs "ocis-bin" { }; + + configDir = lib.mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/lib/ocis/config"; + description = lib.mdDoc '' + Path to directory containing oCIS config file. + + Example config can be generated by `ocis init --config-path fileName --admin-password "adminPass"`. + Add `--insecure true` if SSL certificates are generated and managed externally (e.g. using oCIS behind reverse proxy). + + Note: This directory must contain at least a `ocis.yaml`. Ensure + [user](#opt-services.ocis.user) has read/write access to it. In some + circumstances you may need to add additional oCIS configuration files (e.g., + `proxy.yaml`) to this directory. + ''; + }; + + environmentFile = lib.mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/ocis.env"; + description = lib.mdDoc '' + An environment file as defined in {manpage}`systemd.exec(5)`. + + Configuration provided in this file will override those from [configDir](#opt-services.ocis.configDir)/ocis.yaml. + ''; + }; + + user = lib.mkOption { + type = types.str; + default = defaultUser; + example = "yourUser"; + description = lib.mdDoc '' + The user to run oCIS as. + By default, a user named `${defaultUser}` will be created whose home + directory is [stateDir](#opt-services.ocis.stateDir). + ''; + }; + + group = lib.mkOption { + type = types.str; + default = defaultGroup; + example = "yourGroup"; + description = lib.mdDoc '' + The group to run oCIS under. + By default, a group named `${defaultGroup}` will be created. + ''; + }; + + address = lib.mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Web interface address."; + }; + + port = lib.mkOption { + type = types.port; + default = 9200; + description = "Web interface port."; + }; + + url = lib.mkOption { + type = types.str; + default = "https://localhost:9200"; + example = "https://some-hostname-or-ip:9200"; + description = "Web interface address."; + }; + + stateDir = lib.mkOption { + default = "/var/lib/ocis"; + type = types.str; + description = "ownCloud data directory."; + }; + + environment = lib.mkOption { + type = types.attrsOf types.str; + default = { }; + description = lib.mdDoc '' + Extra config options. + + See [the documentation](https://doc.owncloud.com/ocis/next/deployment/services/services.html) for available options. + See [notes for environment variables](https://doc.owncloud.com/ocis/next/deployment/services/env-var-note.html) for more information. + + Note that all the attributes here will be copied to /nix/store/ and will be world readable. Options like *_PASSWORD or *_SECRET should be part of [environmentFile](#opt-services.ocis.environmentFile) instead, and are only provided here for illustrative purpose. + + Configuration here will override those from [environmentFile](#opt-services.ocis.environmentFile) and will have highest precedence, at the cost of security. Do NOT put security sensitive stuff here. + ''; + example = { + OCIS_INSECURE = "false"; + OCIS_LOG_LEVEL = "error"; + OCIS_JWT_SECRET = "super_secret"; + OCIS_TRANSFER_SECRET = "foo"; + OCIS_MACHINE_AUTH_API_KEY = "foo"; + OCIS_SYSTEM_USER_ID = "123"; + OCIS_MOUNT_ID = "123"; + OCIS_STORAGE_USERS_MOUNT_ID = "123"; + GATEWAY_STORAGE_USERS_MOUNT_ID = "123"; + CS3_ALLOW_INSECURE = "true"; + OCIS_INSECURE_BACKENDS = "true"; + TLS_INSECURE = "true"; + TLS_SKIP_VERIFY_CLIENT_CERT = "true"; + WEBDAV_ALLOW_INSECURE = "true"; + IDP_TLS = "false"; + GRAPH_APPLICATION_ID = "1234"; + IDM_IDPSVC_PASSWORD = "password"; + IDM_REVASVC_PASSWORD = "password"; + IDM_SVC_PASSWORD = "password"; + IDP_ISS = "https://localhost:9200"; + OCIS_LDAP_BIND_PASSWORD = "password"; + OCIS_SERVICE_ACCOUNT_ID = "foo"; + OCIS_SERVICE_ACCOUNT_SECRET = "foo"; + OCIS_SYSTEM_USER_API_KEY = "foo"; + STORAGE_USERS_MOUNT_ID = "123"; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + users.users.${defaultUser} = lib.mkIf (cfg.user == defaultUser) { + group = cfg.group; + home = cfg.stateDir; + isSystemUser = true; + createHome = true; + description = "ownCloud Infinite Scale daemon user"; + }; + + users.groups = lib.mkIf (cfg.group == defaultGroup) { ${defaultGroup} = { }; }; + + systemd = { + services.ocis = { + description = "ownCloud Infinite Scale Stack"; + wantedBy = [ "multi-user.target" ]; + environment = { + PROXY_HTTP_ADDR = "${cfg.address}:${toString cfg.port}"; + OCIS_URL = cfg.url; + OCIS_CONFIG_DIR = if (cfg.configDir == null) then "${cfg.stateDir}/config" else cfg.configDir; + OCIS_BASE_DATA_PATH = cfg.stateDir; + } // cfg.environment; + serviceConfig = { + Type = "simple"; + ExecStart = "${lib.getExe cfg.package} server"; + WorkingDirectory = cfg.stateDir; + User = cfg.user; + Group = cfg.group; + Restart = "always"; + EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile; + ReadWritePaths = [ cfg.stateDir ]; + ReadOnlyPaths = [ cfg.configDir ]; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectSystem = "strict"; + ProtectHome = true; + ProtectControlGroups = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectKernelLogs = true; + RestrictAddressFamilies = [ + "AF_UNIX" + "AF_INET" + "AF_INET6" + "AF_NETLINK" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + LockPersonality = true; + SystemCallArchitectures = "native"; + }; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ + bhankas + danth + ramblurr + ]; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 80edf70ee11c..cc8f5959f006 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -648,6 +648,7 @@ in { nvmetcfg = handleTest ./nvmetcfg.nix {}; nzbget = handleTest ./nzbget.nix {}; nzbhydra2 = handleTest ./nzbhydra2.nix {}; + ocis = handleTest ./ocis.nix {}; oh-my-zsh = handleTest ./oh-my-zsh.nix {}; ollama = handleTest ./ollama.nix {}; ombi = handleTest ./ombi.nix {}; diff --git a/nixos/tests/ocis.nix b/nixos/tests/ocis.nix new file mode 100644 index 000000000000..35461e246749 --- /dev/null +++ b/nixos/tests/ocis.nix @@ -0,0 +1,217 @@ +import ./make-test-python.nix ( + { lib, pkgs, ... }: + + let + # this is a demo user created by IDM_CREATE_DEMO_USERS=true + demoUser = "einstein"; + demoPassword = "relativity"; + + adminUser = "admin"; + adminPassword = "hunter2"; + testRunner = + pkgs.writers.writePython3Bin "test-runner" + { + libraries = [ pkgs.python3Packages.selenium ]; + flakeIgnore = [ "E501" ]; + } + '' + import sys + from selenium.webdriver.common.by import By + from selenium.webdriver import Firefox + from selenium.webdriver.firefox.options import Options + from selenium.webdriver.support.ui import WebDriverWait + from selenium.webdriver.support import expected_conditions as EC + + options = Options() + options.add_argument('--headless') + driver = Firefox(options=options) + + user = sys.argv[1] + password = sys.argv[2] + driver.implicitly_wait(20) + driver.get('https://localhost:9200/login') + wait = WebDriverWait(driver, 10) + wait.until(EC.title_contains("Sign in")) + driver.find_element(By.XPATH, '//*[@id="oc-login-username"]').send_keys(user) + driver.find_element(By.XPATH, '//*[@id="oc-login-password"]').send_keys(password) + driver.find_element(By.XPATH, '//*[@id="root"]//button').click() + wait.until(EC.title_contains("Personal")) + ''; + + # This was generated with `ocis init --config-path testconfig/ --admin-password "hunter2" --insecure true`. + testConfig = '' + token_manager: + jwt_secret: kaKYgfso*d9GA-yTM.&BTOUEuMz%Ai0H + machine_auth_api_key: sGWRG1JZ&qe&pe@N1HKK4#qH*B&@xLnO + system_user_api_key: h+m4aHPUtOtUJFKrc5B2=04C=7fDZaT- + transfer_secret: 4-R6AfUjQn0P&+h2+$skf0lJqmre$j=x + system_user_id: db180e0a-b38a-4edf-a4cd-a3d358248537 + admin_user_id: ea623f50-742d-4fd0-95bb-c61767b070d4 + graph: + application: + id: 11971eab-d560-4b95-a2d4-50726676bbd0 + events: + tls_insecure: true + spaces: + insecure: true + identity: + ldap: + bind_password: ^F&Vn7@mYGYGuxr$#qm^gGy@FVq=.w=y + service_account: + service_account_id: df39a290-3f3e-4e39-b67b-8b810ca2abac + service_account_secret: .demKypQ$=pGl+yRar!#YaFjLYCr4YwE + idp: + ldap: + bind_password: bv53IjS28x.nxth*%aRbE70%4TGNXbLU + idm: + service_user_passwords: + admin_password: hunter2 + idm_password: ^F&Vn7@mYGYGuxr$#qm^gGy@FVq=.w=y + reva_password: z-%@fWipLliR8lD#fl.0teC#9QbhJ^eb + idp_password: bv53IjS28x.nxth*%aRbE70%4TGNXbLU + proxy: + oidc: + insecure: true + insecure_backends: true + service_account: + service_account_id: df39a290-3f3e-4e39-b67b-8b810ca2abac + service_account_secret: .demKypQ$=pGl+yRar!#YaFjLYCr4YwE + frontend: + app_handler: + insecure: true + archiver: + insecure: true + service_account: + service_account_id: df39a290-3f3e-4e39-b67b-8b810ca2abac + service_account_secret: .demKypQ$=pGl+yRar!#YaFjLYCr4YwE + auth_basic: + auth_providers: + ldap: + bind_password: z-%@fWipLliR8lD#fl.0teC#9QbhJ^eb + auth_bearer: + auth_providers: + oidc: + insecure: true + users: + drivers: + ldap: + bind_password: z-%@fWipLliR8lD#fl.0teC#9QbhJ^eb + groups: + drivers: + ldap: + bind_password: z-%@fWipLliR8lD#fl.0teC#9QbhJ^eb + ocdav: + insecure: true + ocm: + service_account: + service_account_id: df39a290-3f3e-4e39-b67b-8b810ca2abac + service_account_secret: .demKypQ$=pGl+yRar!#YaFjLYCr4YwE + thumbnails: + thumbnail: + transfer_secret: 2%11!zAu*AYE&=d*8dfoZs8jK&5ZMm*% + webdav_allow_insecure: true + cs3_allow_insecure: true + search: + events: + tls_insecure: true + service_account: + service_account_id: df39a290-3f3e-4e39-b67b-8b810ca2abac + service_account_secret: .demKypQ$=pGl+yRar!#YaFjLYCr4YwE + audit: + events: + tls_insecure: true + settings: + service_account_ids: + - df39a290-3f3e-4e39-b67b-8b810ca2abac + sharing: + events: + tls_insecure: true + storage_users: + events: + tls_insecure: true + mount_id: ef72cb8b-809c-4592-bfd2-1df603295205 + service_account: + service_account_id: df39a290-3f3e-4e39-b67b-8b810ca2abac + service_account_secret: .demKypQ$=pGl+yRar!#YaFjLYCr4YwE + notifications: + notifications: + events: + tls_insecure: true + service_account: + service_account_id: df39a290-3f3e-4e39-b67b-8b810ca2abac + service_account_secret: .demKypQ$=pGl+yRar!#YaFjLYCr4YwE + nats: + nats: + tls_skip_verify_client_cert: true + gateway: + storage_registry: + storage_users_mount_id: ef72cb8b-809c-4592-bfd2-1df603295205 + userlog: + service_account: + service_account_id: df39a290-3f3e-4e39-b67b-8b810ca2abac + service_account_secret: .demKypQ$=pGl+yRar!#YaFjLYCr4YwE + auth_service: + service_account: + service_account_id: df39a290-3f3e-4e39-b67b-8b810ca2abac + service_account_secret: .demKypQ$=pGl+yRar!#YaFjLYCr4YwE + clientlog: + service_account: + service_account_id: df39a290-3f3e-4e39-b67b-8b810ca2abac + service_account_secret: .demKypQ$=pGl+yRar!#YaFjLYCr4YwE''; + in + + { + name = "ocis"; + + meta.maintainers = with lib.maintainers; [ + bhankas + ramblurr + ]; + + nodes.machine = + { config, ... }: + { + virtualisation.memorySize = 2048; + environment.systemPackages = [ + pkgs.firefox-unwrapped + pkgs.geckodriver + testRunner + ]; + + # if you do this in production, dont put secrets in this file because it will be written to the world readable nix store + environment.etc."ocis/ocis.env".text = '' + ADMIN_PASSWORD=${adminPassword} + IDM_CREATE_DEMO_USERS=true + ''; + + # if you do this in production, dont put secrets in this file because it will be written to the world readable nix store + environment.etc."ocis/config/ocis.yaml".text = testConfig; + + services.ocis = { + enable = true; + configDir = "/etc/ocis/config"; + environment = { + OCIS_INSECURE = "true"; + }; + environmentFile = "/etc/ocis/ocis.env"; + }; + }; + + testScript = '' + start_all() + machine.wait_for_unit("ocis.service") + machine.wait_for_open_port(9200) + # wait for ocis to fully come up + machine.sleep(5) + + with subtest("ocis bin works"): + machine.succeed("${lib.getExe pkgs.ocis-bin} version") + + with subtest("use the web interface to log in with a demo user"): + machine.succeed("PYTHONUNBUFFERED=1 systemd-cat -t test-runner test-runner ${demoUser} ${demoPassword}") + + with subtest("use the web interface to log in with the provisioned admin user"): + machine.succeed("PYTHONUNBUFFERED=1 systemd-cat -t test-runner test-runner ${adminUser} ${adminPassword}") + ''; + } +) diff --git a/pkgs/by-name/oc/ocis-bin/package.nix b/pkgs/by-name/oc/ocis-bin/package.nix new file mode 100644 index 000000000000..3b6988f078ea --- /dev/null +++ b/pkgs/by-name/oc/ocis-bin/package.nix @@ -0,0 +1,56 @@ +{ + fetchurl, + lib, + stdenv, + autoPatchelfHook, +}: + +stdenv.mkDerivation (finalAttrs: { + pname = "ocis-bin"; + version = "5.0.0"; + system = + if stdenv.isLinux && stdenv.isx86_64 then + "linux-amd64" + else if stdenv.isLinux && stdenv.isAarch64 then + "linux-arm64" + else + ""; + + src = fetchurl { + url = "https://github.com/owncloud/ocis/releases/download/v${finalAttrs.version}/ocis-${finalAttrs.version}-${finalAttrs.system}"; + + hash = + if stdenv.isLinux && stdenv.isAarch64 then + "sha256-xRgDNwmRovXbyGQ5sTUw5srsXMBDYyBFMpB9MoXoo+w=" + else if stdenv.isLinux && stdenv.isx86_64 then + "sha256-0lgDIHldW67OwinfYPATXkWUZVnR3PoXC4XLM1KkKmY=" + else + builtins.throw "Unsupported platform, please contact Nixpkgs maintainers for ocis package"; + }; + dontUnpack = true; + + nativeBuildInputs = [ autoPatchelfHook ]; + + installPhase = '' + runHook preInstall + install -D $src $out/bin/ocis + runHook postInstall + ''; + + meta = with lib; { + description = "ownCloud Infinite Scale Stack "; + homepage = "https://owncloud.dev/ocis/"; + changelog = "https://github.com/owncloud/ocis/releases/tag/v${version}"; + # oCIS is licensed under non-free EULA which can be found here : + # https://github.com/owncloud/ocis/releases/download/v5.0.0/End-User-License-Agreement-for-ownCloud-Infinite-Scale.pdf + license = licenses.unfree; + maintainers = with maintainers; [ + ramblurr + bhankas + danth + ramblurr + ]; + sourceProvenance = [ sourceTypes.binaryNativeCode ]; + mainProgram = "ocis"; + }; +})