Your one-stop-cake-shop for everything Freshly Baked has to offer

feat(pm/wiki): factor into ingredient

I want to have a private copy of the wiki on umber and a public copy on
teal. It would be good to share configs between them, so we should use
an ingredient for this

authored by Skyler Grey and committed by tangled.org e00fc6fa d9c77d71

+2
packetmix/systems/default.nix
··· 90 90 ingredients = [ 91 91 "freshlybakedcake" 92 92 "server" 93 + "wiki" 93 94 ]; 94 95 args = { 95 96 system = "x86_64-linux"; ··· 101 102 ingredients = [ 102 103 "freshlybakedcake" 103 104 "server" 105 + "wiki" 104 106 ]; 105 107 args = { 106 108 system = "x86_64-linux";
+6
packetmix/systems/teal/headscale.nix
··· 125 125 type = "A"; 126 126 value = "100.64.0.37"; 127 127 } 128 + { 129 + # wiki.starrysky.fyi -> umber 130 + name = "wiki.starrysky.fyi"; 131 + type = "A"; 132 + value = "100.64.0.48"; 133 + } 128 134 ]; 129 135 nameservers.global = [ 130 136 "1.1.1.1"
+6 -348
packetmix/systems/teal/wiki.nix
··· 1 1 # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # SPDX-FileCopyrightText: 2026 Collabora Productivity Limited 2 3 # 3 4 # SPDX-License-Identifier: MIT 4 5 5 6 { 6 - project, 7 - system, 8 - config, 9 - pkgs, 10 - lib, 11 - ... 12 - }: 13 - { 14 - clicks.storage.impermanence.persist.directories = [ 15 - { 16 - directory = "/var/lib/mediawiki"; 17 - mode = "0700"; 18 - user = "mediawiki"; 19 - defaultPerms.mode = "0700"; 20 - } 21 - { 22 - directory = "/var/lib/private/opensearch"; 23 - mode = "0700"; 24 - user = "opensearch"; 25 - defaultPerms.mode = "0700"; 26 - } 27 - ]; 28 - 29 - services.mediawiki = { 30 - enable = true; 31 - package = project.inputs.nixos-unstable.result.${system}.mediawiki; # header auth master requires mediawiki unstable - header auth stable is broken on missing Hooks (recently removed in stable MW version) 32 - phpPackage = pkgs.php83.withExtensions ({ enabled, all }: enabled ++ [ all.luasandbox ]); 33 - database.type = "postgres"; 34 - path = [ 35 - pkgs.diffutils 36 - pkgs.imagemagick 37 - pkgs.python3Packages.pygments 38 - ]; 39 - extensions = { 40 - AdvancedSearch = project.inputs.AdvancedSearch.src; 41 - Auth_remoteuser = project.inputs.Auth_remoteuser.src; # header auth 42 - AutoCreateCategoryPages = project.inputs.AutoCreateCategoryPages.src; 43 - Cargo = project.inputs.Cargo.src; # queries and soforth 44 - CategoryTree = null; 45 - CheckUser = null; 46 - Cite = null; 47 - CiteThisPage = null; 48 - CirrusSearch = "${ 49 - pkgs.php.buildComposerProject { 50 - pname = "CirrusSearch"; 51 - version = "0.0.3665"; 52 - src = project.inputs.CirrusSearch.src; 53 - vendorHash = "sha256-MLD/3hvzX1aqR4knajJ1amb6K5SVtxlfy+UZWoSi1Bk="; 54 - composerLock = ./wiki/CirrusSearch.composer.lock; 55 - } 56 - }/share/php/CirrusSearch"; # needed for advancedsearch 57 - CodeEditor = null; 58 - DiscussionTools = null; 59 - Echo = null; 60 - EditNotify = project.inputs.EditNotify.src; 61 - Elastica = "${ 62 - pkgs.php.buildComposerProject { 63 - pname = "Elastica"; 64 - version = "0.0.3665"; 65 - src = project.inputs.Elastica.src; 66 - vendorHash = "sha256-4kp8njLTqPeFCREnGharCB/pmYBnXLJR4TdD6EH6WCI="; 67 - composerLock = ./wiki/Elastica.composer.lock; 68 - } 69 - }/share/php/Elastica"; # needed for cirrussearch 70 - Linter = null; 71 - Math = null; 72 - MobileFrontend = project.inputs.MobileFrontend.src; 73 - NamespacePreload = project.inputs.NamespacePreload.src; 74 - Network = "${ 75 - config.services.phpfpm.pools.mediawiki.phpPackage.buildComposerProject { 76 - pname = "Network"; 77 - version = "0.0.3665"; 78 - src = project.inputs.Network.src; 79 - vendorHash = "sha256-JHa6PW5xO3pcwn/2jbGXM0wGhr6UmtqFdxaGCgpaYb0="; 80 - composerLock = ./wiki/Network.composer.lock; 81 - } 82 - }/share/php/Network"; # for page connection graphs 83 - OpenIDConnect = "${ 84 - pkgs.php.buildComposerProject { 85 - pname = "OpenIDConnect"; 86 - version = "0.0.3665"; 87 - src = project.inputs.OpenIDConnect.src; 88 - vendorHash = "sha256-DjxyOK21tbBEj6hFfhVNDxeNu4a26hvMRHgD/u24ZT0="; 89 - composerLock = ./wiki/OpenIDConnect.composer.lock; 90 - 91 - postInstall = '' 92 - cat sql/postgres/ChangePrimaryKey.sql | sed 's/DROP INDEX "primary"/ALTER TABLE openid_connect DROP CONSTRAINT openid_connect_pkey/' > $out/share/php/OpenIDConnect/sql/postgres/ChangePrimaryKey.sql 93 - ''; 94 - } 95 - }/share/php/OpenIDConnect"; 96 - ParserFunctions = null; 97 - PluggableAuth = project.inputs.PluggableAuth.src; # needed for OIDC 98 - Poem = null; 99 - ReplaceText = null; 100 - Scribunto = null; 101 - SecureLinkFixer = null; 102 - SimpleTooltip = project.inputs.SimpleTooltip.src; 103 - SyntaxHighlight_GeSHi = null; 104 - TemplateData = null; 105 - TemplateStyles = null; 106 - Thanks = null; 107 - UserMerge = project.inputs.UserMerge.src; 108 - VisualEditor = null; 109 - WikiEditor = null; 110 - }; 111 - extraConfig = '' 112 - $wgMaxUploadSize = 1024*1024*1024*8; 113 - $wgGroupPermissions['autoconfirmed']['upload_by_url'] = true; 114 - $wgGroupPermissions['autoconfirmed']['interwiki'] = true; // https://wiki.freshly.space/wiki/Special:Interwiki - edit shortlink prefixes, crazy-strong permission but we trust our friends 115 - $wgAllowCopyUploads = true; 116 - $wgCopyUploadsFromSpecialUpload = true; 117 - 118 - $wgSMTP = [ 119 - 'host' => 'ssl://mail.freshly.space', 120 - 'IDHost' => 'wiki.freshly.space', 121 - 'localhost' => 'wiki.freshly.space', 122 - 'port' => 465, 123 - 'auth' => true, 124 - 'username' => 'automated@freshly.space', 125 - 'password' => trim(file_get_contents('/secrets/mediawiki/mail_password.txt')) 126 - ]; 127 - $wgLocalInterwikis = [ 128 - 'fbc' 129 - ]; 130 - 131 - $wgWhitelistReadRegexp = [ 132 - '/^Main Page$/', 133 - '/^Public:/', 134 - '/^User:/' 135 - ]; 136 - $wgGroupPermissions['*']['read'] = false; 137 - $wgGroupPermissions['*']['edit'] = false; 138 - $wgGroupPermissions['*']['createaccount'] = false; 139 - $wgGroupPermissions['*']['autocreateaccount'] = true; 140 - 141 - $wgGroupPermissions['bureaucrat']['usermerge'] = true; 142 - 143 - $wgAuthRemoteuserUserName = function () { 144 - if (!isset($_SERVER['HTTP_X_WEBAUTH_LOGIN'])) { 145 - return ""; 146 - } 147 - 148 - if ($_SERVER['HTTP_X_WEBAUTH_LOGIN'] === 'hyperneutrino') { 149 - return 'HyperNeutrino'; 150 - } 151 - 152 - return $_SERVER['HTTP_X_WEBAUTH_LOGIN']; 153 - }; 154 - $wgAuthRemoteuserPriority = MediaWiki\Session\SessionInfo::MAX_PRIORITY; 155 - 156 - $wgUseCdn = true; 157 - $wgCdnServersNoPurge = [ 158 - '127.0.0.1' 159 - ]; 160 - $wgUsePrivateIPs = true; 161 - 162 - $wgUseInstantCommons = true; 163 - $wgPingback = false; 164 - 165 - $wgPluggableAuth_Config = [ 166 - 'Freshly Baked Cake Kanidm' => [ 167 - 'plugin' => 'OpenIDConnect', 168 - 'data' => [ 169 - 'providerURL' => 'https://idm.freshly.space/oauth2/openid/mediawiki', 170 - 'clientID' => 'mediawiki', 171 - 'clientsecret' => trim(file_get_contents('/secrets/mediawiki/oidc_client_secret.txt')), 172 - 'codeChallengeMethod' => 'S256' 173 - ] 174 - ] 175 - ]; 176 - 177 - $wgOpenIDConnect_MigrateUsersByUserName = true; 178 - 179 - $wgLogos = [ 180 - 'icon' => '/icon.svg', 181 - 'svg' => '/icon.svg' 182 - ]; 183 - 184 - $wgPygmentizePath = '${pkgs.python3Packages.pygments}/bin/pygmentize'; 185 - 186 - $wgScribuntoDefaultEngine = 'luasandbox'; 187 - 188 - $wgNamespacesWithSubpages[NS_MAIN] = true; 189 - 190 - $wgNamespacePreloadDoExpansion = false; // This can't expand {{PAGENAME}} (or like) correctly, making it very nearly useless 191 - 192 - $wgCirrusSearchServers = [ 193 - [ 194 - "host" => '127.0.0.1', 195 - "port" => 1037 196 - ] 197 - ]; 198 - $wgSearchType = 'CirrusSearch'; 199 - $wgNamespacesToBeSearchedDefault[NS_CATEGORY] = true; 200 - 201 - $wgUrlProtocols[] = "rad:"; 202 - 203 - $wgSVGNativeRendering = true; 204 - 205 - $wgRCWatchCategoryMembership = true; 206 - 207 - $wgCargoPageDataColumns[] = 'creationDate'; 208 - $wgCargoPageDataColumns[] = 'modificationDate'; 209 - $wgCargoPageDataColumns[] = 'creator'; 210 - $wgCargoPageDataColumns[] = 'lastEditor'; 211 - $wgCargoPageDataColumns[] = 'displayTitle'; 212 - $wgCargoPageDataColumns[] = 'categories'; 213 - $wgCargoPageDataColumns[] = 'numRevisions'; 214 - $wgCargoPageDataColumns[] = 'outgoingLinks'; 215 - $wgCargoPageDataColumns[] = 'isRedirect'; 216 - $wgCargoPageDataColumns[] = 'pageNameOrRedirect'; 217 - $wgCargoPageDataColumns[] = 'pageIDOrRedirect'; 218 - 219 - $wgCargoFileDataColumns[] = 'mediaType'; 220 - $wgCargoFileDataColumns[] = 'path'; 221 - $wgCargoFileDataColumns[] = 'lastUploadDate'; 222 - 223 - $wgFixDoubleRedirects = true; 224 - 225 - $wgMFAutodetectMobileView = true; 226 - $wgMFEnableMobilePreferences = true; 227 - wfLoadSkin( 'MinervaNeue' ); 228 - 229 - $wgShowExceptionDetails = true; 230 - $wgDevelopmentWarnings = true; 231 - ''; 232 - webserver = "nginx"; 233 - url = "https://wiki.freshly.space"; 234 - nginx.hostName = "wiki.freshly.space"; 235 - name = "Freshly Wiki"; 236 - database.createLocally = true; 237 - 238 - passwordSender = "wiki@freshly.space"; 239 - 240 - passwordFile = "/secrets/mediawiki/initial_admin_password.txt"; 241 - }; 242 - 243 - systemd.timers.mediawiki-maintenance = { 244 - wantedBy = [ "timers.target" ]; 245 - timerConfig = { 246 - OnUnitActiveSec = "30"; 247 - OnBootSec = "30"; 248 - Persistent = false; 249 - Unit = "mediawiki-maintenance.service"; 250 - }; 251 - }; 252 - 253 - systemd.services.mediawiki-maintenance = { 254 - script = '' 255 - ${config.services.phpfpm.pools.mediawiki.phpPackage}/bin/php ${config.services.mediawiki.finalPackage}/share/mediawiki/maintenance/run.php runJobs --memory-limit 1G --maxtime 30 256 - ''; 257 - serviceConfig = { 258 - Type = "oneshot"; 259 - RemainAfterExit = false; 260 - User = "mediawiki"; 261 - Group = "nginx"; 262 - PrivateTmp = true; 263 - Environment = "MEDIAWIKI_CONFIG=${config.services.phpfpm.pools.mediawiki.phpEnv.MEDIAWIKI_CONFIG}"; 264 - }; 265 - }; 266 - 267 - services.opensearch = { 268 - # needed for cirrussearch 269 - enable = true; 270 - package = project.packages.opensearch.result.${system}; 271 - settings = { 272 - "http.port" = 1037; 273 - "path.data" = "/var/lib/private/opensearch/data"; 274 - "path.logs" = "/var/lib/private/opensearch/logs"; 275 - }; 276 - }; 277 - 278 - services.nginx.enable = true; 279 7 services.headscale.settings.dns.extra_records = [ 280 8 { 281 9 # wiki.freshly.space -> teal ··· 284 12 value = "100.64.0.5"; 285 13 } 286 14 ]; 287 - services.nginx.virtualHosts."wiki.freshly.space" = { 288 - listen = [ 289 - { 290 - addr = "127.0.0.1"; 291 - port = 1036; 292 - } 293 - ]; 294 15 295 - locations = { 296 - "= /" = lib.mkForce { 297 - extraConfig = '' 298 - return 301 https://wiki.freshly.space/wiki/; 299 - ''; # overriding nixpkgs /wiki/ redirect since as our double-proxy makes it redirect to :1036 300 - }; 301 - "= /favicon.ico".alias = ./wiki/favicon.ico; 302 - "= /icon.svg".alias = ./wiki/icon.svg; 303 - }; 304 - 305 - extraConfig = '' 306 - client_max_body_size 1024M; 307 - ''; 308 - }; 309 - services.nginx.virtualHosts."external.wiki.freshly.space" = { 310 - listenAddresses = [ 311 - "0.0.0.0" 312 - "[::0]" 313 - ]; 314 - 315 - serverName = "wiki.freshly.space"; 316 - 317 - addSSL = true; 318 - enableACME = true; 319 - acmeRoot = null; 320 - 321 - locations."/" = { 322 - proxyPass = "http://127.0.0.1:1036"; 323 - recommendedProxySettings = true; 324 - proxyWebsockets = true; 325 - 326 - extraConfig = '' 327 - proxy_set_header X-Webauth-Login ""; 328 - proxy_cache off; 329 - ''; 330 - }; 331 - 332 - extraConfig = '' 333 - client_max_body_size 1024M; 334 - ''; 335 - }; 336 - services.nginx.virtualHosts."internal.wiki.freshly.space" = { 337 - listenAddresses = [ "localhost.tailscale" ]; 338 - 339 - serverName = "wiki.freshly.space"; 340 - 341 - addSSL = true; 342 - enableACME = true; 343 - acmeRoot = null; 344 - 345 - locations."/" = { 346 - proxyPass = "http://127.0.0.1:1036"; 347 - recommendedProxySettings = true; 348 - proxyWebsockets = true; 349 - 350 - extraConfig = '' 351 - proxy_cache off; 352 - ''; 353 - }; 354 - 355 - extraConfig = '' 356 - client_max_body_size 1024M; 357 - ''; 358 - }; 359 - 360 - services.nginx.tailscaleAuth = { 361 - enable = true; 362 - virtualHosts = [ "internal.wiki.freshly.space" ]; 16 + ingredient.wiki.wiki = { 17 + hostname = "wiki.freshly.space"; 18 + email = "wiki@freshly.space"; 19 + enablePublicInternet = true; 20 + enableAutoRegistration = true; 363 21 }; 364 22 }
packetmix/systems/teal/wiki/CirrusSearch.composer.lock packetmix/systems/wiki/wiki/CirrusSearch.composer.lock
packetmix/systems/teal/wiki/CirrusSearch.composer.lock.license packetmix/systems/wiki/wiki/CirrusSearch.composer.lock.license
packetmix/systems/teal/wiki/Elastica.composer.lock packetmix/systems/wiki/wiki/Elastica.composer.lock
packetmix/systems/teal/wiki/Elastica.composer.lock.license packetmix/systems/wiki/wiki/Elastica.composer.lock.license
packetmix/systems/teal/wiki/Network.composer.lock packetmix/systems/wiki/wiki/Network.composer.lock
packetmix/systems/teal/wiki/Network.composer.lock.license packetmix/systems/wiki/wiki/Network.composer.lock.license
packetmix/systems/teal/wiki/OpenIDConnect.composer.lock packetmix/systems/wiki/wiki/OpenIDConnect.composer.lock
packetmix/systems/teal/wiki/OpenIDConnect.composer.lock.license packetmix/systems/wiki/wiki/OpenIDConnect.composer.lock.license
packetmix/systems/teal/wiki/favicon.ico packetmix/systems/wiki/wiki/favicon.ico
packetmix/systems/teal/wiki/favicon.ico.license packetmix/systems/wiki/wiki/favicon.ico.license
packetmix/systems/teal/wiki/icon.svg packetmix/systems/wiki/wiki/icon.svg
packetmix/systems/teal/wiki/icon.svg.license packetmix/systems/wiki/wiki/icon.svg.license
+9
packetmix/systems/umber/postgresql.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + clicks.storage.impermanence.persist.directories = [ 7 + "/var/lib/postgresql" 8 + ]; 9 + }
+11
packetmix/systems/umber/wiki.nix
··· 1 + # SPDX-FileCopyrightText: 2026 Collabora Productivity Limited 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + ingredient.wiki.wiki = { 7 + name = "Starry Sky Wiki"; 8 + hostname = "wiki.starrysky.fyi"; 9 + email = "wiki@starrysky.fyi"; 10 + }; 11 + }
+407
packetmix/systems/wiki/wiki.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # SPDX-FileCopyrightText: 2026 Collabora Productivity Limited 3 + # 4 + # SPDX-License-Identifier: MIT 5 + 6 + # This ingredient has some default values (including OIDC config/etc.) that make it probably unsuitable for use outside of Freshly Baked. Sorry. 7 + { 8 + project, 9 + system, 10 + config, 11 + pkgs, 12 + lib, 13 + ... 14 + }: 15 + { 16 + options.ingredient.wiki.wiki = { 17 + name = lib.mkOption { 18 + type = lib.types.str; 19 + description = "What should your wiki be called"; 20 + default = "Freshly Wiki"; 21 + }; 22 + hostname = lib.mkOption { 23 + type = lib.types.str; 24 + description = "Where your wiki should be hosted"; 25 + }; 26 + email = lib.mkOption { 27 + type = lib.types.str; 28 + description = "What email should notifications/password resets/etc. come from"; 29 + }; 30 + enablePublicInternet = lib.mkEnableOption "Allow access from the public internet with authentication via OIDC"; 31 + enableAutoRegistration = lib.mkEnableOption "Allow unregistered users to automatically register via OIDC or Tailscale"; 32 + }; 33 + 34 + config = { 35 + clicks.storage.impermanence.persist.directories = [ 36 + { 37 + directory = "/var/lib/mediawiki"; 38 + mode = "0700"; 39 + user = "mediawiki"; 40 + defaultPerms.mode = "0700"; 41 + } 42 + { 43 + directory = "/var/lib/private/opensearch"; 44 + mode = "0700"; 45 + user = "opensearch"; 46 + defaultPerms.mode = "0700"; 47 + } 48 + ]; 49 + 50 + services.mediawiki = { 51 + enable = true; 52 + package = project.inputs.nixos-unstable.result.${system}.mediawiki; # header auth master requires mediawiki unstable - header auth stable is broken on missing Hooks (recently removed in stable MW version) 53 + phpPackage = pkgs.php83.withExtensions ({ enabled, all }: enabled ++ [ all.luasandbox ]); 54 + database.type = "postgres"; 55 + path = [ 56 + pkgs.diffutils 57 + pkgs.imagemagick 58 + pkgs.python3Packages.pygments 59 + ]; 60 + extensions = { 61 + AdvancedSearch = project.inputs.AdvancedSearch.src; 62 + Auth_remoteuser = project.inputs.Auth_remoteuser.src; # header auth 63 + AutoCreateCategoryPages = project.inputs.AutoCreateCategoryPages.src; 64 + Cargo = project.inputs.Cargo.src; # queries and soforth 65 + CategoryTree = null; 66 + CheckUser = null; 67 + Cite = null; 68 + CiteThisPage = null; 69 + CirrusSearch = "${ 70 + pkgs.php.buildComposerProject { 71 + pname = "CirrusSearch"; 72 + version = "0.0.3665"; 73 + src = project.inputs.CirrusSearch.src; 74 + vendorHash = "sha256-MLD/3hvzX1aqR4knajJ1amb6K5SVtxlfy+UZWoSi1Bk="; 75 + composerLock = ./wiki/CirrusSearch.composer.lock; 76 + } 77 + }/share/php/CirrusSearch"; # needed for advancedsearch 78 + CodeEditor = null; 79 + DiscussionTools = null; 80 + Echo = null; 81 + EditNotify = project.inputs.EditNotify.src; 82 + Elastica = "${ 83 + pkgs.php.buildComposerProject { 84 + pname = "Elastica"; 85 + version = "0.0.3665"; 86 + src = project.inputs.Elastica.src; 87 + vendorHash = "sha256-4kp8njLTqPeFCREnGharCB/pmYBnXLJR4TdD6EH6WCI="; 88 + composerLock = ./wiki/Elastica.composer.lock; 89 + } 90 + }/share/php/Elastica"; # needed for cirrussearch 91 + Linter = null; 92 + Math = null; 93 + MobileFrontend = project.inputs.MobileFrontend.src; 94 + NamespacePreload = project.inputs.NamespacePreload.src; 95 + Network = "${ 96 + config.services.phpfpm.pools.mediawiki.phpPackage.buildComposerProject { 97 + pname = "Network"; 98 + version = "0.0.3665"; 99 + src = project.inputs.Network.src; 100 + vendorHash = "sha256-JHa6PW5xO3pcwn/2jbGXM0wGhr6UmtqFdxaGCgpaYb0="; 101 + composerLock = ./wiki/Network.composer.lock; 102 + } 103 + }/share/php/Network"; # for page connection graphs 104 + OpenIDConnect = lib.mkIf config.ingredient.wiki.wiki.enablePublicInternet "${ 105 + pkgs.php.buildComposerProject { 106 + pname = "OpenIDConnect"; 107 + version = "0.0.3665"; 108 + src = project.inputs.OpenIDConnect.src; 109 + vendorHash = "sha256-DjxyOK21tbBEj6hFfhVNDxeNu4a26hvMRHgD/u24ZT0="; 110 + composerLock = ./wiki/OpenIDConnect.composer.lock; 111 + 112 + postInstall = '' 113 + cat sql/postgres/ChangePrimaryKey.sql | sed 's/DROP INDEX "primary"/ALTER TABLE openid_connect DROP CONSTRAINT openid_connect_pkey/' > $out/share/php/OpenIDConnect/sql/postgres/ChangePrimaryKey.sql 114 + ''; 115 + } 116 + }/share/php/OpenIDConnect"; 117 + ParserFunctions = null; 118 + PluggableAuth = lib.mkIf config.ingredient.wiki.wiki.enablePublicInternet project.inputs.PluggableAuth.src; # needed for OIDC 119 + Poem = null; 120 + ReplaceText = null; 121 + Scribunto = null; 122 + SecureLinkFixer = null; 123 + SimpleTooltip = project.inputs.SimpleTooltip.src; 124 + SyntaxHighlight_GeSHi = null; 125 + TemplateData = null; 126 + TemplateStyles = null; 127 + Thanks = null; 128 + UserMerge = project.inputs.UserMerge.src; 129 + VisualEditor = null; 130 + WikiEditor = null; 131 + }; 132 + extraConfig = '' 133 + $wgMaxUploadSize = 1024*1024*1024*8; 134 + $wgGroupPermissions['autoconfirmed']['upload_by_url'] = true; 135 + $wgGroupPermissions['autoconfirmed']['interwiki'] = true; // Special:Interwiki - edit shortlink prefixes, crazy-strong permission but we trust our friends 136 + $wgAllowCopyUploads = true; 137 + $wgCopyUploadsFromSpecialUpload = true; 138 + 139 + $wgSMTP = [ 140 + 'host' => 'ssl://mail.freshly.space', 141 + 'IDHost' => '${config.ingredient.wiki.wiki.hostname}', 142 + 'localhost' => '${config.ingredient.wiki.wiki.hostname}', 143 + 'port' => 465, 144 + 'auth' => true, 145 + 'username' => 'automated@freshly.space', 146 + 'password' => trim(file_get_contents('/secrets/mediawiki/mail_password.txt')) 147 + ]; 148 + $wgLocalInterwikis = [ 149 + 'fbc' 150 + ]; 151 + 152 + $wgWhitelistReadRegexp = [ 153 + '/^Main Page$/', 154 + '/^Public:/', 155 + '/^User:/' 156 + ]; 157 + $wgGroupPermissions['*']['read'] = false; 158 + $wgGroupPermissions['*']['edit'] = false; 159 + $wgGroupPermissions['*']['createaccount'] = false; 160 + $wgGroupPermissions['*']['autocreateaccount'] = ${ 161 + if config.ingredient.wiki.wiki.enableAutoRegistration then "true" else "false" 162 + }; 163 + 164 + $wgGroupPermissions['bureaucrat']['usermerge'] = true; 165 + 166 + $wgAuthRemoteuserUserName = function () { 167 + if (!isset($_SERVER['HTTP_X_WEBAUTH_LOGIN'])) { 168 + return ""; 169 + } 170 + 171 + if ($_SERVER['HTTP_X_WEBAUTH_LOGIN'] === 'hyperneutrino') { 172 + return 'HyperNeutrino'; 173 + } 174 + 175 + return $_SERVER['HTTP_X_WEBAUTH_LOGIN']; 176 + }; 177 + $wgAuthRemoteuserPriority = MediaWiki\Session\SessionInfo::MAX_PRIORITY; 178 + 179 + $wgUseCdn = true; 180 + $wgCdnServersNoPurge = [ 181 + '127.0.0.1' 182 + ]; 183 + $wgUsePrivateIPs = true; 184 + 185 + $wgUseInstantCommons = true; 186 + $wgPingback = false; 187 + 188 + ${ 189 + if config.ingredient.wiki.wiki.enablePublicInternet then 190 + '' 191 + $wgPluggableAuth_Config = [ 192 + 'Freshly Baked Cake Kanidm' => [ 193 + 'plugin' => 'OpenIDConnect', 194 + 'data' => [ 195 + 'providerURL' => 'https://idm.freshly.space/oauth2/openid/mediawiki', 196 + 'clientID' => 'mediawiki', 197 + 'clientsecret' => trim(file_get_contents('/secrets/mediawiki/oidc_client_secret.txt')), 198 + 'codeChallengeMethod' => 'S256' 199 + ] 200 + ] 201 + ]; 202 + '' 203 + else 204 + "" 205 + } 206 + 207 + $wgOpenIDConnect_MigrateUsersByUserName = true; 208 + 209 + $wgLogos = [ 210 + 'icon' => '/icon.svg', 211 + 'svg' => '/icon.svg' 212 + ]; 213 + 214 + $wgPygmentizePath = '${pkgs.python3Packages.pygments}/bin/pygmentize'; 215 + 216 + $wgScribuntoDefaultEngine = 'luasandbox'; 217 + 218 + $wgNamespacesWithSubpages[NS_MAIN] = true; 219 + 220 + $wgNamespacePreloadDoExpansion = false; // This can't expand {{PAGENAME}} (or like) correctly, making it very nearly useless 221 + 222 + $wgCirrusSearchServers = [ 223 + [ 224 + "host" => '127.0.0.1', 225 + "port" => 1037 226 + ] 227 + ]; 228 + $wgSearchType = 'CirrusSearch'; 229 + $wgNamespacesToBeSearchedDefault[NS_CATEGORY] = true; 230 + 231 + $wgUrlProtocols[] = "rad:"; 232 + 233 + $wgSVGNativeRendering = true; 234 + 235 + $wgRCWatchCategoryMembership = true; 236 + 237 + $wgCargoPageDataColumns[] = 'creationDate'; 238 + $wgCargoPageDataColumns[] = 'modificationDate'; 239 + $wgCargoPageDataColumns[] = 'creator'; 240 + $wgCargoPageDataColumns[] = 'lastEditor'; 241 + $wgCargoPageDataColumns[] = 'displayTitle'; 242 + $wgCargoPageDataColumns[] = 'categories'; 243 + $wgCargoPageDataColumns[] = 'numRevisions'; 244 + $wgCargoPageDataColumns[] = 'outgoingLinks'; 245 + $wgCargoPageDataColumns[] = 'isRedirect'; 246 + $wgCargoPageDataColumns[] = 'pageNameOrRedirect'; 247 + $wgCargoPageDataColumns[] = 'pageIDOrRedirect'; 248 + 249 + $wgCargoFileDataColumns[] = 'mediaType'; 250 + $wgCargoFileDataColumns[] = 'path'; 251 + $wgCargoFileDataColumns[] = 'lastUploadDate'; 252 + 253 + $wgFixDoubleRedirects = true; 254 + 255 + $wgMFAutodetectMobileView = true; 256 + $wgMFEnableMobilePreferences = true; 257 + wfLoadSkin( 'MinervaNeue' ); 258 + 259 + $wgShowExceptionDetails = true; 260 + $wgDevelopmentWarnings = true; 261 + ''; 262 + webserver = "nginx"; 263 + url = "https://${config.ingredient.wiki.wiki.hostname}"; 264 + nginx.hostName = config.ingredient.wiki.wiki.hostname; 265 + inherit (config.ingredient.wiki.wiki) name; 266 + database = { 267 + passwordFile = builtins.toFile "unused-mediawiki-postgress-password" "userpass"; # This isn't actually needed for running a wiki, but some of the initialization scripts do require it. It's not a real password. 268 + createLocally = false; # We can't use createLocally with passwordFile, which is needed during initialization... we'll ensure the database ourself below :( 269 + 270 + socket = "/run/postgresql"; 271 + }; 272 + 273 + passwordSender = config.ingredient.wiki.wiki.email; 274 + 275 + passwordFile = "/secrets/mediawiki/initial_admin_password.txt"; 276 + }; 277 + 278 + services.postgresql = { 279 + enable = true; 280 + ensureDatabases = [ config.services.mediawiki.database.name ]; 281 + ensureUsers = [ 282 + { 283 + name = config.services.mediawiki.database.user; 284 + ensureDBOwnership = true; 285 + } 286 + ]; 287 + }; 288 + systemd.services.mediawiki-init.after = [ "postgresql.target" ]; 289 + systemd.services.httpd.after = [ "postgresql.target" ]; 290 + 291 + systemd.timers.mediawiki-maintenance = { 292 + wantedBy = [ "timers.target" ]; 293 + timerConfig = { 294 + OnUnitActiveSec = "30"; 295 + OnBootSec = "30"; 296 + Persistent = false; 297 + Unit = "mediawiki-maintenance.service"; 298 + }; 299 + }; 300 + 301 + systemd.services.mediawiki-maintenance = { 302 + script = '' 303 + ${config.services.phpfpm.pools.mediawiki.phpPackage}/bin/php ${config.services.mediawiki.finalPackage}/share/mediawiki/maintenance/run.php runJobs --memory-limit 1G --maxtime 30 304 + ''; 305 + serviceConfig = { 306 + Type = "oneshot"; 307 + RemainAfterExit = false; 308 + User = "mediawiki"; 309 + Group = "nginx"; 310 + PrivateTmp = true; 311 + Environment = "MEDIAWIKI_CONFIG=${config.services.phpfpm.pools.mediawiki.phpEnv.MEDIAWIKI_CONFIG}"; 312 + }; 313 + }; 314 + 315 + services.opensearch = { 316 + # needed for cirrussearch 317 + enable = true; 318 + package = project.packages.opensearch.result.${system}; 319 + settings = { 320 + "http.port" = 1037; 321 + "path.data" = "/var/lib/private/opensearch/data"; 322 + "path.logs" = "/var/lib/private/opensearch/logs"; 323 + }; 324 + }; 325 + 326 + services.nginx.enable = true; 327 + services.nginx.virtualHosts.${config.ingredient.wiki.wiki.hostname} = { 328 + listen = [ 329 + { 330 + addr = "127.0.0.1"; 331 + port = 1036; 332 + } 333 + ]; 334 + 335 + locations = { 336 + "= /" = lib.mkForce { 337 + extraConfig = '' 338 + return 301 https://${config.ingredient.wiki.wiki.hostname}/wiki/; 339 + ''; # overriding nixpkgs /wiki/ redirect since as our double-proxy makes it redirect to :1036 340 + }; 341 + "= /favicon.ico".alias = ./wiki/favicon.ico; 342 + "= /icon.svg".alias = ./wiki/icon.svg; 343 + }; 344 + 345 + extraConfig = '' 346 + client_max_body_size 1024M; 347 + ''; 348 + }; 349 + services.nginx.virtualHosts."external.${config.ingredient.wiki.wiki.hostname}" = 350 + lib.mkIf config.ingredient.wiki.wiki.enablePublicInternet 351 + { 352 + listenAddresses = [ 353 + "0.0.0.0" 354 + "[::0]" 355 + ]; 356 + 357 + serverName = config.ingredient.wiki.wiki.hostname; 358 + 359 + addSSL = true; 360 + enableACME = true; 361 + acmeRoot = null; 362 + 363 + locations."/" = { 364 + proxyPass = "http://127.0.0.1:1036"; 365 + recommendedProxySettings = true; 366 + proxyWebsockets = true; 367 + 368 + extraConfig = '' 369 + proxy_set_header X-Webauth-Login ""; 370 + proxy_cache off; 371 + ''; 372 + }; 373 + 374 + extraConfig = '' 375 + client_max_body_size 1024M; 376 + ''; 377 + }; 378 + services.nginx.virtualHosts."internal.${config.ingredient.wiki.wiki.hostname}" = { 379 + listenAddresses = [ "localhost.tailscale" ]; 380 + 381 + serverName = config.ingredient.wiki.wiki.hostname; 382 + 383 + addSSL = true; 384 + enableACME = true; 385 + acmeRoot = null; 386 + 387 + locations."/" = { 388 + proxyPass = "http://127.0.0.1:1036"; 389 + recommendedProxySettings = true; 390 + proxyWebsockets = true; 391 + 392 + extraConfig = '' 393 + proxy_cache off; 394 + ''; 395 + }; 396 + 397 + extraConfig = '' 398 + client_max_body_size 1024M; 399 + ''; 400 + }; 401 + 402 + services.nginx.tailscaleAuth = { 403 + enable = true; 404 + virtualHosts = [ "internal.${config.ingredient.wiki.wiki.hostname}" ]; 405 + }; 406 + }; 407 + }