diff --git a/nixos-anywhere/configs/tmux/tmux.conf b/configs/tmux/tmux.conf similarity index 100% rename from nixos-anywhere/configs/tmux/tmux.conf rename to configs/tmux/tmux.conf diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..c57e831 --- /dev/null +++ b/flake.lock @@ -0,0 +1,194 @@ +{ + "nodes": { + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nixvim", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1749398372, + "narHash": "sha256-tYBdgS56eXYaWVW3fsnPQ/nFlgWi/Z2Ymhyu21zVM98=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "9305fe4e5c2a6fcf5ba6a3ff155720fbe4076569", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "home-manager": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1752544374, + "narHash": "sha256-ReX0NG6nIAEtQQjLqeu1vUU2jjZuMlpymNtb4VQYeus=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "2e00ed310c218127e02ffcf28ddd4e0f669fde3e", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "release-25.05", + "repo": "home-manager", + "type": "github" + } + }, + "ixx": { + "inputs": { + "flake-utils": [ + "nixvim", + "nuschtosSearch", + "flake-utils" + ], + "nixpkgs": [ + "nixvim", + "nuschtosSearch", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1748294338, + "narHash": "sha256-FVO01jdmUNArzBS7NmaktLdGA5qA3lUMJ4B7a05Iynw=", + "owner": "NuschtOS", + "repo": "ixx", + "rev": "cc5f390f7caf265461d4aab37e98d2292ebbdb85", + "type": "github" + }, + "original": { + "owner": "NuschtOS", + "ref": "v0.0.8", + "repo": "ixx", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1752436162, + "narHash": "sha256-Kt1UIPi7kZqkSc5HVj6UY5YLHHEzPBkgpNUByuyxtlw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "dfcd5b901dbab46c9c6e80b265648481aafb01f8", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixvim": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": [ + "nixpkgs" + ], + "nuschtosSearch": "nuschtosSearch", + "systems": "systems_2" + }, + "locked": { + "lastModified": 1752010420, + "narHash": "sha256-fboKrq2WeEC2Y4LaZNiiH2dptUYHtSbYhzE0FTN/u+M=", + "owner": "nix-community", + "repo": "nixvim", + "rev": "a11133507a930dfd235324cdf776bdb5e6ddd717", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "nixos-25.05", + "repo": "nixvim", + "type": "github" + } + }, + "nuschtosSearch": { + "inputs": { + "flake-utils": "flake-utils", + "ixx": "ixx", + "nixpkgs": [ + "nixvim", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1749730855, + "narHash": "sha256-L3x2nSlFkXkM6tQPLJP3oCBMIsRifhIDPMQQdHO5xWo=", + "owner": "NuschtOS", + "repo": "search", + "rev": "8dfe5879dd009ff4742b668d9c699bc4b9761742", + "type": "github" + }, + "original": { + "owner": "NuschtOS", + "repo": "search", + "type": "github" + } + }, + "root": { + "inputs": { + "home-manager": "home-manager", + "nixpkgs": "nixpkgs", + "nixvim": "nixvim" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..7f62b7b --- /dev/null +++ b/flake.nix @@ -0,0 +1,51 @@ +{ + description = "Multi-host NixOS configuration"; + + inputs = { + home-manager = { + url = "github:nix-community/home-manager/release-25.05"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; + nixvim = { + url = "github:nix-community/nixvim/nixos-25.05"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, home-manager, nixpkgs, nixvim }: + let + allowed-unfree-packages = [ + "corefonts" + ]; + configFiles = pkgs.stdenv.mkDerivation { + name = "config-files"; + src = ./configs; + installPhase = '' + mkdir -p $out + cp -r * $out/ + ''; + }; + pkgs = nixpkgs.legacyPackages.${system}; + system = "x86_64-linux"; + in { + nixosConfigurations = { + corp = nixpkgs.lib.nixosSystem { + modules = [ + ./host/corp/configuration.nix + ./modules + ]; + pkgs = import nixpkgs { + config = { + allowUnfreePredicate = pkg: builtins.elem (nixpkgs.lib.getName pkg) allowed-unfree-packages; + }; + system = "${system}"; + }; + specialArgs = { + inherit configFiles; + }; + system = "${system}"; + }; + }; + }; +} diff --git a/host/corp/configuration.nix b/host/corp/configuration.nix new file mode 100644 index 0000000..e399e57 --- /dev/null +++ b/host/corp/configuration.nix @@ -0,0 +1,158 @@ +{ config, lib, pkgs, ... }: { + disabledModules = [ + "services/web-apps/onlyoffice.nix" + ]; + imports = [ + ./hardware-configuration.nix + ./networking.nix # generated at runtime by nixos-infect + ]; + + boot.tmp.cleanOnBoot = true; + environment.systemPackages = with pkgs; [ + age + element-web + fish + git + htop + neovim + podman + postgresql + redis + slirp4netns + tmux + wget + ]; + i18n.defaultLocale = "en_US.UTF-8"; + myModules = { + onlyoffice.enable = true; + seafile.enable = true; + synapse.enable = true; + timecardbot.enable = true; + }; + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + programs.neovim.enable = true; + programs.neovim.defaultEditor = true; + security.acme = { + acceptTerms = true; + defaults.email = "eli@gleipnir.technology"; + }; + security.sudo.wheelNeedsPassword = false; + # The Digital Ocean droplet agent + services.do-agent.enable = true; + services.nginx = { + # This adds the 'recommendedProxyConfig' without actually adding it since if I do add it, + # it'll include $nginx-recommended-proxy_set_headers-headers.conf at the http level, outside + # a server block, which breaks everything. + appendHttpConfig = '' + proxy_redirect off; + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + proxy_http_version 1.1; + # don't let clients close the keep-alive connection to upstream. See the nginx blog for details: + # https://www.nginx.com/blog/avoiding-top-10-nginx-configuration-mistakes/#no-keepalives + proxy_set_header "Connection" ""; + ''; + enable = true; + recommendedGzipSettings = true; + recommendedProxySettings = false; + virtualHosts."auth.gleipnir.technology" = { + addSSL = true; + enableACME = true; + locations."/" = { + extraConfig = '' + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + include /etc/nginx/proxy.conf; + ''; + proxyPass = "http://127.0.0.1:10000"; + }; + root = "/var/www/auth"; + }; + virtualHosts."static.gleipnir.technology" = { + addSSL = true; + enableACME = true; + locations."/" = { + index = "index.html"; + }; + root = "/var/www/static"; + }; + virtualHosts."todo.gleipnir.technology" = { + addSSL = true; + enableACME = true; + locations."/" = { + extraConfig = '' + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + include /etc/nginx/proxy.conf; + ''; + proxyPass = "http://127.0.0.1:10010"; + }; + root = "/var/www/todo"; + }; + }; + services.openssh.enable = true; + services.redis = { + servers."" = { + bind = "127.0.0.1"; + enable = true; + }; + }; + services.swapspace.enable = true; + services.vikunja = { + enable = true; + frontendHostname = "todo.gleipnir.technology"; + frontendScheme = "https"; + }; + time.timeZone = "America/Phoenix"; + + users.groups.vikunja = {}; + users.users.deploy = { + extraGroups = [ "deploy" ]; + isNormalUser = true; + }; + users.users.eliribble = { + extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user. + initialHashedPassword = "$y$j9T$XYOMZR8RZEiTnpaF8lsxv1$H7YbWDpzbnYXTLN0ZMhvtKOlSMy64P7C/RdLBaeaNf/"; + isNormalUser = true; + openssh.authorizedKeys.keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBvhtF6nRWlA6PVs71Eek7p0p2PxTd3P6ZEGFV2t75MB eliribble@nixos"]; + }; + users.users.root.openssh.authorizedKeys.keys = [ + ''ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBvhtF6nRWlA6PVs71Eek7p0p2PxTd3P6ZEGFV2t75MB eliribble@nixos'' + ]; + users.users.vikunja = { + group = "vikunja"; + isNormalUser = false; + isSystemUser = true; + }; + virtualisation.podman.enable = true; + zramSwap.enable = true; + + # Copy the NixOS configuration file and link it from the resulting system + # (/run/current-system/configuration.nix). This is useful in case you + # accidentally delete configuration.nix. + # system.copySystemConfiguration = true; + + # This option defines the first version of NixOS you have installed on this particular machine, + # and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions. + # + # Most users should NEVER change this value after the initial install, for any reason, + # even if you've upgraded your system to a new NixOS release. + # + # This value does NOT affect the Nixpkgs version your packages and OS are pulled from, + # so changing it will NOT upgrade your system - see https://nixos.org/manual/nixos/stable/#sec-upgrading for how + # to actually do that. + # + # This value being lower than the current NixOS release does NOT mean your system is + # out of date, out of support, or vulnerable. + # + # Do NOT change this value unless you have manually inspected all the changes it would make to your configuration, + # and migrated your data accordingly. + # + # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion . + system.stateVersion = "23.11"; +} diff --git a/host/corp/hardware-configuration.nix b/host/corp/hardware-configuration.nix new file mode 100644 index 0000000..d634744 --- /dev/null +++ b/host/corp/hardware-configuration.nix @@ -0,0 +1,9 @@ +{ modulesPath, ... }: +{ + imports = [ (modulesPath + "/profiles/qemu-guest.nix") ]; + boot.loader.grub.device = "/dev/vda"; + boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "xen_blkfront" "vmw_pvscsi" ]; + boot.initrd.kernelModules = [ "nvme" ]; + fileSystems."/" = { device = "/dev/vda1"; fsType = "ext4"; }; + +} diff --git a/host/corp/networking.nix b/host/corp/networking.nix new file mode 100644 index 0000000..0511d20 --- /dev/null +++ b/host/corp/networking.nix @@ -0,0 +1,50 @@ +{ lib, ... }: { + # This file was populated at runtime with the networking + # details gathered from the active system. + networking = { + hostName = "corp"; + defaultGateway = "159.89.144.1"; + defaultGateway6 = { + address = "2604:a880:2:d1::1"; + interface = "eth0"; + }; + dhcpcd.enable = false; + domain = "gleipnir.technology"; + firewall.enable = false; + interfaces = { + eth0 = { + ipv4.addresses = [ + { address="159.89.154.99"; prefixLength=20; } + { address="10.46.0.5"; prefixLength=16; } + ]; + ipv4.routes = [ { address = "159.89.144.1"; prefixLength = 32; } ]; + ipv6.addresses = [ + { address="2604:a880:2:d1::7f9a:6001"; prefixLength=64; } + { address="fe80::d4a8:45ff:fe46:cd11"; prefixLength=64; } + ]; + ipv6.routes = [ { address = "2604:a880:2:d1::1"; prefixLength = 128; } ]; + }; + eth1 = { + ipv4.addresses = [ + { address="10.120.0.2"; prefixLength=20; } + ]; + ipv6.addresses = [ + { address="fe80::4ac:1fff:fe36:cb24"; prefixLength=64; } + ]; + }; + }; + nameservers = [ + "67.207.67.3" + "67.207.67.2" + "67.207.67.3" + "67.207.67.2" + "67.207.67.3" + "67.207.67.2" + ]; + usePredictableInterfaceNames = lib.mkForce false; + }; + services.udev.extraRules = '' + ATTR{address}=="d6:a8:45:46:cd:11", NAME="eth0" + ATTR{address}=="06:ac:1f:36:cb:24", NAME="eth1" + ''; +} diff --git a/modules/default.nix b/modules/default.nix new file mode 100644 index 0000000..520a646 --- /dev/null +++ b/modules/default.nix @@ -0,0 +1,5 @@ +{ + imports = [ + ./system + ]; +} diff --git a/nixos-anywhere/modules/system/cloud-init.nix b/modules/system/cloud-init.nix similarity index 100% rename from nixos-anywhere/modules/system/cloud-init.nix rename to modules/system/cloud-init.nix diff --git a/nixos-anywhere/modules/system/default.nix b/modules/system/default.nix similarity index 57% rename from nixos-anywhere/modules/system/default.nix rename to modules/system/default.nix index ac18b05..fc3e814 100644 --- a/nixos-anywhere/modules/system/default.nix +++ b/modules/system/default.nix @@ -3,7 +3,11 @@ ./cloud-init.nix ./do-agent.nix ./fish.nix + ./onlyoffice.nix + ./seafile.nix ./sudo.nix + ./synapse.nix + ./timecardbot.nix ./tmux.nix ]; } diff --git a/nixos-anywhere/modules/system/do-agent.nix b/modules/system/do-agent.nix similarity index 100% rename from nixos-anywhere/modules/system/do-agent.nix rename to modules/system/do-agent.nix diff --git a/nixos-anywhere/modules/system/fish.nix b/modules/system/fish.nix similarity index 100% rename from nixos-anywhere/modules/system/fish.nix rename to modules/system/fish.nix diff --git a/modules/system/onlyoffice.nix b/modules/system/onlyoffice.nix new file mode 100644 index 0000000..fbdc851 --- /dev/null +++ b/modules/system/onlyoffice.nix @@ -0,0 +1,347 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.services.onlyoffice; +in +{ + # Override built-in onlyoffice definition to enable WOPI + options.services.onlyoffice = { + enable = lib.mkEnableOption "OnlyOffice DocumentServer"; + + enableExampleServer = lib.mkEnableOption "OnlyOffice example server"; + + hostname = lib.mkOption { + type = lib.types.str; + default = "localhost"; + description = "FQDN for the OnlyOffice instance."; + }; + + jwtSecretFile = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + Path to a file that contains the secret to sign web requests using JSON Web Tokens. + If left at the default value null signing is disabled. + ''; + }; + + package = lib.mkPackageOption pkgs "onlyoffice-documentserver" { }; + + x2t = lib.mkPackageOption pkgs "x2t" { }; + + port = lib.mkOption { + type = lib.types.port; + default = 8000; + description = "Port the OnlyOffice document server should listen on."; + }; + + examplePort = lib.mkOption { + type = lib.types.port; + default = null; + description = "Port the OnlyOffice example server should listen on."; + }; + + postgresHost = lib.mkOption { + type = lib.types.str; + default = "/run/postgresql"; + description = "The Postgresql hostname or socket path OnlyOffice should connect to."; + }; + + postgresName = lib.mkOption { + type = lib.types.str; + default = "onlyoffice"; + description = "The name of database OnlyOffice should use."; + }; + + postgresPasswordFile = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + Path to a file that contains the password OnlyOffice should use to connect to Postgresql. + Unused when using socket authentication. + ''; + }; + + postgresUser = lib.mkOption { + type = lib.types.str; + default = "onlyoffice"; + description = '' + The username OnlyOffice should use to connect to Postgresql. + Unused when using socket authentication. + ''; + }; + + rabbitmqUrl = lib.mkOption { + type = lib.types.str; + default = "amqp://guest:guest@localhost:5672"; + description = "The Rabbitmq in amqp URI style OnlyOffice should connect to."; + }; + + wopi = lib.mkEnableOption "Enable WOPI support"; + }; + options.myModules.onlyoffice.enable = mkEnableOption "custom onlyoffice configuration"; + + config = mkIf config.myModules.onlyoffice.enable { + services = { + nginx = { + enable = lib.mkDefault true; + # misses text/csv, font/ttf, application/x-font-ttf, application/rtf, application/wasm + recommendedGzipSettings = lib.mkDefault true; + recommendedProxySettings = lib.mkDefault true; + + upstreams = { + # /etc/nginx/includes/http-common.conf + onlyoffice-docservice = { + servers = { + "localhost:${toString cfg.port}" = { }; + }; + }; + onlyoffice-example = lib.mkIf cfg.enableExampleServer { + servers = { + "localhost:${toString cfg.examplePort}" = { }; + }; + }; + }; + + virtualHosts.${cfg.hostname} = { + locations = { + # resources that are generated and thus cannot be taken from the cfg.package yet: + "~ ^(\\/[\\d]+\\.[\\d]+\\.[\\d]+[\\.|-][\\w]+)?\\/(sdkjs/common/AllFonts.js)$".extraConfig = '' + proxy_pass http://onlyoffice-docservice/$2$3; + ''; + "~ ^(\\/[\\d]+\\.[\\d]+\\.[\\d]+[\\.|-][\\w]+)?\\/(fonts/.*)$".extraConfig = '' + proxy_pass http://onlyoffice-docservice/$2$3; + ''; + # /etc/nginx/includes/ds-docservice.conf + # disable caching for api.js + "~ ^(\\/[\\d]+\\.[\\d]+\\.[\\d]+[\\.|-][\\w]+)?\\/(web-apps\\/apps\\/api\\/documents\\/api\\.js)$".extraConfig = + '' + expires -1; + # gzip_static on; + alias ${cfg.package}/var/www/onlyoffice/documentserver/$2; + ''; + "~ ^(\\/[\\d]+\\.[\\d]+\\.[\\d]+[\\.|-][\\w]+)?\\/(document_editor_service_worker\\.js)$".extraConfig = + '' + expires 365d; + alias ${cfg.package}/var/www/onlyoffice/documentserver/sdkjs/common/serviceworker/$2; + ''; + # suppress logging the unsupported locale error in web-apps + "~ ^(\\/[\\d]+\\.[\\d]+\\.[\\d]+[\\.|-][\\w]+)?\\/(web-apps)(\\/.*\\.json)$".extraConfig = '' + expires 365d; + error_log /dev/null crit; + alias ${cfg.package}/var/www/onlyoffice/documentserver/$2$3; + ''; + # suppress logging the unsupported locale error in plugins + "~ ^(\\/[\\d]+\\.[\\d]+\\.[\\d]+[\\.|-][\\w]+)?\\/(sdkjs-plugins)(\\/.*\\.json)$".extraConfig = '' + expires 365d; + error_log /dev/null crit; + alias ${cfg.package}/var/www/onlyoffice/documentserver/$2$3; + ''; + "~ ^(\\/[\\d]+\\.[\\d]+\\.[\\d]+[\\.|-][\\w]+)?\\/(web-apps|sdkjs|sdkjs-plugins|fonts|dictionaries)(\\/.*)$".extraConfig = + '' + expires 365d; + alias ${cfg.package}/var/www/onlyoffice/documentserver/$2$3; + ''; + "~* ^(\\/cache\\/files.*)(\\/.*)".extraConfig = '' + alias /var/lib/onlyoffice/documentserver/App_Data$1; + add_header Content-Disposition "attachment; filename*=UTF-8''$arg_filename"; + + set $secure_link_secret verysecretstring; + secure_link $arg_md5,$arg_expires; + secure_link_md5 "$secure_link_expires$uri$secure_link_secret"; + + if ($secure_link = "") { + return 403; + } + + if ($secure_link = "0") { + return 410; + } + ''; + # Allow "/internal" interface only from 127.0.0.1 + # Don't comment out the section below for the security reason! + "~* ^(\\/[\\d]+\\.[\\d]+\\.[\\d]+[\\.|-][\\w]+)?\\/(internal)(\\/.*)$".extraConfig = '' + allow 127.0.0.1; + deny all; + proxy_pass http://onlyoffice-docservice/$2$3; + ''; + # Allow "/info" interface only from 127.0.0.1 by default + # Comment out lines allow 127.0.0.1; and deny all; + # of below section to turn on the info page + "~* ^(\\/[\\d]+\\.[\\d]+\\.[\\d]+[\\.|-][\\w]+)?\\/(info)(\\/.*)$".extraConfig = '' + allow 127.0.0.1; + deny all; + proxy_pass http://onlyoffice-docservice/$2$3; + ''; + "/".extraConfig = '' + proxy_pass http://onlyoffice-docservice; + ''; + "~ ^(\\/[\\d]+\\.[\\d]+\\.[\\d]+[\\.|-][\\w]+)?(\\/(doc|downloadas)\\/.*)".extraConfig = '' + proxy_pass http://onlyoffice-docservice$2$is_args$args; + proxy_http_version 1.1; + ''; + # end of /etc/nginx/includes/ds-docservice.conf + "/${cfg.package.version}/".extraConfig = '' + proxy_pass http://onlyoffice-docservice/; + ''; + # /etc/nginx/includes/ds-example.conf + "~ ^(\\/welcome\\/.*)$".extraConfig = lib.mkIf cfg.enableExampleServer '' + expires 365d; + alias ${cfg.package}/var/www/onlyoffice/documentserver-example$1; + index docker.html; + ''; + "/example/".extraConfig = lib.mkIf cfg.enableExampleServer '' + proxy_pass http://onlyoffice-example/; + proxy_set_header X-Forwarded-Path /example; + ''; + }; + extraConfig = '' + rewrite ^/$ /welcome/ redirect; + rewrite ^\/OfficeWeb(\/apps\/.*)$ /${cfg.package.version}/web-apps$1 redirect; + rewrite ^(\/web-apps\/apps\/(?!api\/).*)$ /${cfg.package.version}$1 redirect; + + # based on https://github.com/ONLYOFFICE/document-server-package/blob/master/common/documentserver/nginx/includes/http-common.conf.m4#L29-L34 + # without variable indirection and correct variable names + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + # required for CSP to take effect + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # required for websocket + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + ''; + }; + }; + + rabbitmq.enable = lib.mkDefault true; + + postgresql = { + enable = lib.mkDefault true; + ensureDatabases = [ "onlyoffice" ]; + ensureUsers = [ + { + name = "onlyoffice"; + ensureDBOwnership = true; + } + ]; + }; + }; + + systemd.services = { + onlyoffice-converter = { + description = "onlyoffice converter"; + after = [ + "network.target" + "onlyoffice-docservice.service" + "postgresql.service" + ]; + requires = [ + "network.target" + "onlyoffice-docservice.service" + "postgresql.service" + ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${cfg.package.fhs}/bin/onlyoffice-wrapper FileConverter/converter /run/onlyoffice/config"; + Group = "onlyoffice"; + Restart = "always"; + RuntimeDirectory = "onlyoffice"; + StateDirectory = "onlyoffice"; + Type = "simple"; + User = "onlyoffice"; + }; + }; + + onlyoffice-docservice = + let + onlyoffice-prestart = pkgs.writeShellScript "onlyoffice-prestart" '' + PATH=$PATH:${ + lib.makeBinPath ( + with pkgs; + [ + jq + moreutils + config.services.postgresql.package + ] + ) + } + umask 077 + mkdir -p /run/onlyoffice/config/ /var/lib/onlyoffice/documentserver/sdkjs/{slide/themes,common}/ /var/lib/onlyoffice/documentserver/{fonts,server/FileConverter/bin}/ + cp -r ${cfg.package}/etc/onlyoffice/documentserver/* /run/onlyoffice/config/ + chmod u+w /run/onlyoffice/config/default.json + + # Allow members of the onlyoffice group to serve files under /var/lib/onlyoffice/documentserver/App_Data + chmod g+x /var/lib/onlyoffice/documentserver + + cp /run/onlyoffice/config/default.json{,.orig} + + # for a mapping of environment variables from the docker container to json options see + # https://github.com/ONLYOFFICE/Docker-DocumentServer/blob/master/run-document-server.sh + jq ' + .services.CoAuthoring.server.port = ${toString cfg.port} | + .services.CoAuthoring.sql.dbHost = "${cfg.postgresHost}" | + .services.CoAuthoring.sql.dbName = "${cfg.postgresName}" | + ${lib.optionalString (cfg.postgresPasswordFile != null) '' + .services.CoAuthoring.sql.dbPass = "'"$(cat ${cfg.postgresPasswordFile})"'" | + ''} + .services.CoAuthoring.sql.dbUser = "${cfg.postgresUser}" | + ${lib.optionalString (cfg.jwtSecretFile != null) '' + .services.CoAuthoring.token.enable.browser = true | + .services.CoAuthoring.token.enable.request.inbox = true | + .services.CoAuthoring.token.enable.request.outbox = true | + .services.CoAuthoring.secret.inbox.string = "'"$(cat ${cfg.jwtSecretFile})"'" | + .services.CoAuthoring.secret.outbox.string = "'"$(cat ${cfg.jwtSecretFile})"'" | + .services.CoAuthoring.secret.session.string = "'"$(cat ${cfg.jwtSecretFile})"'" | + ''} + .rabbitmq.url = "${cfg.rabbitmqUrl}" | + .wopi.enable = "${toString cfg.wopi}" + ' /run/onlyoffice/config/default.json | sponge /run/onlyoffice/config/default.json + + chmod u+w /run/onlyoffice/config/production-linux.json + jq '.FileConverter.converter.x2tPath = "${cfg.x2t}/bin/x2t"' \ + /run/onlyoffice/config/production-linux.json | sponge /run/onlyoffice/config/production-linux.json + + if psql -d onlyoffice -c "SELECT 'task_result'::regclass;" >/dev/null; then + psql -f ${cfg.package}/var/www/onlyoffice/documentserver/server/schema/postgresql/removetbl.sql + psql -f ${cfg.package}/var/www/onlyoffice/documentserver/server/schema/postgresql/createdb.sql + else + psql -f ${cfg.package}/var/www/onlyoffice/documentserver/server/schema/postgresql/createdb.sql + fi + ''; + in + { + description = "onlyoffice documentserver"; + after = [ + "network.target" + "postgresql.service" + ]; + requires = [ "postgresql.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${cfg.package.fhs}/bin/onlyoffice-wrapper DocService/docservice /run/onlyoffice/config"; + ExecStartPre = [ onlyoffice-prestart ]; + Group = "onlyoffice"; + Restart = "always"; + RuntimeDirectory = "onlyoffice"; + StateDirectory = "onlyoffice"; + Type = "simple"; + User = "onlyoffice"; + }; + }; + }; + + users.users = { + onlyoffice = { + description = "OnlyOffice Service"; + group = "onlyoffice"; + isSystemUser = true; + }; + + nginx.extraGroups = [ "onlyoffice" ]; + }; + + users.groups.onlyoffice = { }; + + }; +} diff --git a/modules/system/seafile.nix b/modules/system/seafile.nix new file mode 100644 index 0000000..cfc313d --- /dev/null +++ b/modules/system/seafile.nix @@ -0,0 +1,145 @@ +{ config, lib, pkgs, ... }: +with lib; +let + domain = "files.gleipnir.technology"; + domain2 = "filez.gleipnir.technology"; + stripTabs = text: let + # Whether all lines start with a tab (or is empty) + shouldStripTab = lines: builtins.all (line: (line == "") || (pkgs.lib.strings.hasPrefix " " line)) lines; + # Strip a leading tab from all lines + stripTab = lines: builtins.map (line: pkgs.lib.strings.removePrefix " " line) lines; + # Strip tabs recursively until there are none + stripTabs = lines: if (shouldStripTab lines) then (stripTabs (stripTab lines)) else lines; + in + # Split into lines. Strip leading tabs. Concat back to string. + builtins.concatStringsSep "\n" (stripTabs (pkgs.lib.strings.splitString "\n" text)); +in { + options.myModules.seafile.enable = mkEnableOption "custom seafile configuration"; + config = mkIf config.myModules.seafile.enable { + services.nginx = { + virtualHosts."${domain}" = { + enableACME = true; + forceSSL = true; + locations = { + "/" = { + proxyPass = "http://unix:/run/seahub/gunicorn.sock"; + extraConfig = '' + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $server_name; + proxy_set_header X-Forwarded-Proto https; + proxy_read_timeout 1200s; + client_max_body_size 0; + ''; + }; + "/seafhttp" = { + proxyPass = "http://unix:/run/seafile/server.sock"; + extraConfig = '' + rewrite ^/seafhttp(.*)$ $1 break; + client_max_body_size 0; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_connect_timeout 36000s; + proxy_read_timeout 36000s; + proxy_send_timeout 36000s; + send_timeout 36000s; + ''; + }; + }; + }; + virtualHosts."${domain2}" = { + enableACME = true; + forceSSL = true; + locations = { + "/" = { + proxyPass = "http://[::1]:10030"; + extraConfig = '' + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $server_name; + proxy_set_header X-Forwarded-Proto https; + proxy_read_timeout 1200s; + client_max_body_size 0; + ''; + }; + }; + }; + }; + services.seafile = { + adminEmail = "eli@gleipnir.technology"; + ccnetSettings = { + General.SERVICE_URL = "https://${domain}"; + }; + enable = true; + gc = { + enable = true; + dates = [ "Sun 03:00:00" ]; + }; + initialAdminPassword = "change this later!"; + seafileSettings = { + fileserver = { + host = "unix:/run/seafile/server.sock"; + use_go_fileserver = "false"; + }; + # Enable weekly collection of freed blocks + history.keep_days = "14"; # Remove deleted files after 14 days + quota.default = "50"; # Amount of GB allotted to users + }; + seahubExtraConf = stripTabs('' + ENABLE_OAUTH = True + ENABLE_ONLYOFFICE = True + + # If create new user when he/she logs in Seafile for the first time, defalut `True`. + OAUTH_CREATE_UNKNOWN_USER = True + + # If active new user when he/she logs in Seafile for the first time, defalut `True`. + OAUTH_ACTIVATE_USER_AFTER_CREATION = True + + # Usually OAuth works through SSL layer. If your server is not parametrized to allow HTTPS, some method will raise an "oauthlib.oauth2.rfc6749.errors.InsecureTransportError". Set this to `True` to avoid this error. + #OAUTH_ENABLE_INSECURE_TRANSPORT = True + + # Client id/secret generated by authorization server when you register your client application. + OAUTH_CLIENT_ID = "secret" + OAUTH_CLIENT_SECRET = "secret" + + # Callback url when user authentication succeeded. Note, the redirect url you input when you register your client application MUST be exactly the same as this value. + OAUTH_REDIRECT_URL = 'https://files.gleipnir.technology/oauth/callback/' + + # The following should NOT be changed if you are using Github as OAuth provider. + OAUTH_PROVIDER_DOMAIN = 'gleipnir.technology' + OAUTH_PROVIDER = 'Authentik' + + OAUTH_AUTHORIZATION_URL = 'https://auth.gleipnir.technology/application/o/authorize/' + OAUTH_TOKEN_URL = 'https://auth.gleipnir.technology/application/o/token/' + OAUTH_USER_INFO_URL = 'https://auth.gleipnir.technology/application/o/userinfo/' + OAUTH_SCOPE = ["openid", "profile", "email"] + OAUTH_ATTRIBUTE_MAP = { + "id": (False, "not used"), + "name": (True, "name"), + "email": (True, "email"), + } + SEAHUB_DATA_ROOT = "/var/lib/seafile/seahub/data" + + OFFICE_SERVER_TYPE = 'CollaboraOffice' + ENABLE_OFFICE_WEB_APP = True + OFFICE_WEB_APP_BASE_URL = 'https://docs.gleipnir.technology/hosting/discovery' + + # Expiration of WOPI access token + # WOPI access token is a string used by Seafile to determine the file's + # identity and permissions when use LibreOffice Online view it online + # And for security reason, this token should expire after a set time period + WOPI_ACCESS_TOKEN_EXPIRATION = 30 * 60 # seconds + + VERIFY_ONLYOFFICE_CERTIFICATE = False + ONLYOFFICE_APIJS_URL = 'https://docs.gleipnir.technology/web-apps/apps/api/documents/api.js' + ONLYOFFICE_FILE_EXTENSION = ('doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'odt', 'fodt', 'odp', 'fodp', 'ods', 'fods') + ONLYOFFICE_EDIT_FILE_EXTENSION = ('docx', 'pptx', 'xlsx') + ONLYOFFICE_FORCE_SAVE = True + + ''); + }; + }; +} + diff --git a/nixos-anywhere/modules/system/sudo.nix b/modules/system/sudo.nix similarity index 100% rename from nixos-anywhere/modules/system/sudo.nix rename to modules/system/sudo.nix diff --git a/modules/system/synapse.nix b/modules/system/synapse.nix new file mode 100644 index 0000000..689cdb3 --- /dev/null +++ b/modules/system/synapse.nix @@ -0,0 +1,115 @@ +{ config, lib, pkgs, ... }: +with lib; +let + fqdn = "matrix.gleipnir.technology"; + baseUrl = "https://${fqdn}"; + clientConfig."m.homeserver".base_url = baseUrl; + serverConfig."m.server" = "${fqdn}:443"; + mkWellKnown = data: '' + default_type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '${builtins.toJSON data}'; + ''; +in { + options.myModules.synapse.enable = mkEnableOption "custom synapse configuration"; + + config = mkIf config.myModules.synapse.enable { + services.nginx = { + virtualHosts."chat.gleipnir.technology" = { + enableACME = true; + forceSSL = true; + # Host element web client at the root + root = pkgs.element-web.override { + conf = { + default_server_config = clientConfig; + }; + }; + }; + virtualHosts."corp.gleipnir.technology" = { + enableACME = true; + forceSSL = true; + # This section is not needed if the server_name of matrix-synapse is equal to + # the domain (i.e. example.org from @foo:example.org) and the federation port + # is 8448. + # Further reference can be found in the docs about delegation under + # https://element-hq.github.io/synapse/latest/delegate.html + locations."= /.well-known/matrix/server".extraConfig = mkWellKnown serverConfig; + # This is usually needed for homeserver discovery (from e.g. other Matrix clients). + # Further reference can be found in the upstream docs at + # https://spec.matrix.org/latest/client-server-api/#getwell-knownmatrixclient + locations."= /.well-known/matrix/client".extraConfig = mkWellKnown clientConfig; + }; + virtualHosts."matrix.gleipnir.technology" = { + enableACME = true; + forceSSL = true; + # It's also possible to do a redirect here or something else, this vhost is not + # needed for Matrix. It's recommended though to *not put* element + # here, see also the section about Element. + locations."/".extraConfig = '' + return 404; + ''; + # Forward all Matrix API calls to the synapse Matrix homeserver. A trailing slash + # *must not* be used here. + locations."/_matrix" = { + extraConfig = '' + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + include /etc/nginx/proxy.conf; + ''; + proxyPass = "http://[::1]:8008"; + }; + + # Forward requests for e.g. SSO and password-resets. + locations."/_synapse/client" = { + extraConfig = '' + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + include /etc/nginx/proxy.conf; + ''; + proxyPass = "http://[::1]:8008"; + }; + }; + virtualHosts."matrix-bot.gleipnir.technology" = { + enableACME = true; + forceSSL = true; + locations."/" = { + extraConfig = '' + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + include /etc/nginx/proxy.conf; + ''; + proxyPass = "http://[::1]:10050"; + }; + }; + }; + + services.matrix-synapse = { + enable = true; + extras = ["oidc"]; + extraConfigFiles = [ + "/run/secrets/matrix" + ]; + log.root.level = "WARNING"; + settings = { + listeners = [ + { port = 8008; + bind_addresses = [ "::1" ]; + type = "http"; + tls = false; + x_forwarded = true; + resources = [ { + names = [ "client" "federation" ]; + compress = true; + } ]; + } + ]; + # The public base URL value must match the `base_url` value set in `clientConfig` above. + # The default value here is based on `server_name`, so if your `server_name` is different + # from the value of `fqdn` above, you will likely run into some mismatched domain names + # in client applications. + public_baseurl = baseUrl; + server_name = config.networking.domain; + }; + }; + }; +} diff --git a/modules/system/timecardbot.nix b/modules/system/timecardbot.nix new file mode 100644 index 0000000..b115182 --- /dev/null +++ b/modules/system/timecardbot.nix @@ -0,0 +1,21 @@ +{ pkgs, lib, config, ... }: +with lib; +let + timecardBotSrc = pkgs.fetchFromGitHub { + owner = "Gleipnir-Technology"; + repo = "timecard-bot"; + rev = "00b2850655295513c1e99a519d1d59c3b9847122"; + sha256 = "1f78jm3jgzwzc69q1h9nplmcbz5hb9l74phyhzkbfjb99n3vrf1q"; + }; + timecardBotFlake = (import timecardBotSrc); + timecardBotPackage = timecardBotFlake.packages.${pkgs.system}.default; +in +{ + options.myModules.timecardbot.enable = mkEnableOption "custom timecardbot configuration"; + + config = mkIf config.myModules.timecardbot.enable { + #environment.systemPackages = with pkgs; [ + #timecardBotPackage + #]; + }; +} diff --git a/nixos-anywhere/modules/system/tmux.nix b/modules/system/tmux.nix similarity index 100% rename from nixos-anywhere/modules/system/tmux.nix rename to modules/system/tmux.nix