diff --git a/README.md b/README.md index 49401aa..de37612 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,15 @@ # mediawiki -Config for wiki.ctbk.de and the upcoming Orgawiki deployment. +Config for [wiki.ctbk.de](https://wiki.ctbk.de) and the upcoming Orgawiki deployment. For deployment documentation refer to [the Wiki](https://wiki.ctbk.de/Dienste/Wiki). -Files for the wiki.ctbk.de public deployment are found in `public_mediawiki`. Files for the internal deployment (not yet in production) are found in `orga_mediawiki`. Some common files are found in the root directory. +Files for the [wiki.ctbk.de](https://wiki.ctbk.de) public deployment are found in `public_mediawiki`. Files for [orgawiki.ctbk.de](https://orgawiki.ctbk.de) are found in `orga_mediawiki`. Some common files are found in the root directory. Note that while some files may look identical between the deployments, they are not shared to allow easier modifications to both deployments independently. + +The [`install.sh`](install.sh) script replaces all relevant system files with symlinks to the files in this repository. + +## License + +The files in this repository are licensed under the BSD 2-clause license. The contents of the Wiki have different license(s), [see here](https://wiki.ctbk.de/Wiki:Urheberrechte). diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..c2561e3 --- /dev/null +++ b/install.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +scriptdir=$(dirname $(realpath $0)) + +set +e + +ln -fs "$scriptdir/smw-jobs.sh" /usr/local/bin/smw-jobs +ln -fs "$scriptdir/nginx.conf" /etc/nginx/nginx.conf +ln -fs "$scriptdir/fastcgi.conf" /etc/nginx/fastcgi.conf +ln -fs "$scriptdir/robots.txt" /etc/nginx/robots.txt +ln -fs "$scriptdir/public_mediawiki/public_mediawiki.conf" /etc/nginx/sites-enabled/public_mediawiki.conf +ln -fs "$scriptdir/orga_mediawiki/orga_mediawiki.conf" /etc/nginx/sites-enabled/orga_mediawiki.conf +ln -fs "$scriptdir/pgtune.conf" "/etc/postgresql/15/main/conf.d/pgtune.conf" + +for file in "$scriptdir/system"/*; do + filename=$(basename "$file") + ln -fs "$file" "/etc/systemd/system/$filename" +done + +for file in "$scriptdir/public_mediawiki"/*; do + filename=$(basename "$file") + ln -fs "$file" "/etc/public_mediawiki/$filename" +done + +for file in "$scriptdir/orga_mediawiki"/*; do + filename=$(basename "$file") + ln -fs "$file" "/etc/orga_mediawiki/$filename" +done + diff --git a/nginx.conf b/nginx.conf index bac1998..4532678 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,7 +1,6 @@ user www-data; worker_processes auto; pid /run/nginx.pid; -error_log /var/log/nginx/error.log; include /etc/nginx/modules-enabled/*.conf; events { @@ -37,7 +36,12 @@ http { # Logging Settings ## - access_log /var/log/nginx/access.log; + log_format main '$http_x_forwarded_for [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log; ## # Gzip Settings diff --git a/orga_mediawiki/LocalSettings.php b/orga_mediawiki/LocalSettings.php index ecda7c4..a620443 100644 --- a/orga_mediawiki/LocalSettings.php +++ b/orga_mediawiki/LocalSettings.php @@ -29,6 +29,16 @@ require_once "/etc/orga_mediawiki/SecretSettings.php"; $wgSitename = "CTBK Orgawiki"; $wgMetaNamespace = "Wiki"; +#### Namespace config +define('NS_FSCK', 100); +define('NS_FSCK_TALK', 101); +define('NS_VORSTAND', 200); +define('NS_VORSTAND_TALK', 201); +$wgExtraNamespaces[NS_FSCK] = 'FSCK'; +$wgExtraNamespaces[NS_FSCK_TALK] = 'FSCK_Diskussion'; +$wgExtraNamespaces[NS_VORSTAND] = 'Vorstand'; +$wgExtraNamespaces[NS_VORSTAND_TALK] = 'Vorstand_Diskussion'; + ## The URL base path to the directory containing the wiki; ## defaults for all runtime URL paths are based off of this. ## For more information on customizing the URLs @@ -40,19 +50,16 @@ $wgUsePathInfo = true; $wgScriptExtension = ".php"; ## The protocol and server name to use in fully-qualified URLs -# TODO: should be the public domain eventually -#$wgServer = "https://orgawiki.ctbk.de"; -$wgServer = "http://wiki.chaos:81"; +$wgServer = "https://orgawiki.ctbk.de"; ## The URL path to static resources (images, scripts, etc.) $wgResourceBasePath = $wgScriptPath; ## The URL paths to the logo. Make sure you change this from the default, ## or else you'll overwrite your logo when you upgrade! -# TODO: Chaostreff logo $wgLogos = [ - '1x' => "$wgResourceBasePath/resources/assets/change-your-logo.svg", - 'icon' => "$wgResourceBasePath/resources/assets/change-your-logo.svg", + '1x' => "$wgResourceBasePath/resources/assets/logo.svg", + 'icon' => "$wgResourceBasePath/resources/assets/logo.svg", ]; ## UPO means: this is also a user preference option @@ -162,7 +169,11 @@ wfLoadExtension( 'OpenIDConnect' ); wfLoadExtension( 'SemanticMediaWiki' ); enableSemantics( 'orgawiki.ctbk.de' ); -# Add more configuration options below. +wfLoadExtension( 'Lockdown' ); +wfLoadExtension( 'UserFunctions' ); + + +#### Permissions # Disable account creation - we only use SSO accounts $wgGroupPermissions['*']['autocreateaccount'] = true; @@ -172,8 +183,67 @@ $wgGroupPermissions['sysop']['createaccount'] = true; # Also disable reading/editing by non-logged-in users, making the wiki properly private $wgGroupPermissions['*']['read'] = false; $wgGroupPermissions['*']['edit'] = false; +$wgGroupPermissions['*']['createpage'] = false; +$wgGroupPermissions['*']['createtalk'] = false; +# Remove tons of permissions from standard users +$wgGroupPermissions['user']['edit'] = false; +$wgGroupPermissions['user']['read'] = false; +$wgGroupPermissions['user']['createpage'] = false; +$wgGroupPermissions['user']['createtalk'] = false; +$wgGroupPermissions['user']['upload'] = false; +$wgGroupPermissions['user']['reupload'] = false; +$wgGroupPermissions['user']['reupload-shared'] = false; +$wgGroupPermissions['user']['movefile'] = false; +$wgGroupPermissions['user']['move-rootuserpages'] = false; +$wgGroupPermissions['user']['move-categorypages'] = false; +$wgGroupPermissions['user']['move-subpages'] = false; +$wgGroupPermissions['user']['move'] = false; +# give all the user groups basic rights -- taken away by Lockdown again mostly, but Lockdown cannot give permissions that don’t exist on the user +$wgGroupPermissions['orga-users']['edit'] = true; +$wgGroupPermissions['orga-users']['read'] = true; +$wgGroupPermissions['orga-users']['createpage'] = true; +$wgGroupPermissions['orga-fsck']['edit'] = true; +$wgGroupPermissions['orga-fsck']['read'] = true; +$wgGroupPermissions['orga-fsck']['createpage'] = true; +$wgGroupPermissions['orga-vorstand']['edit'] = true; +$wgGroupPermissions['orga-vorstand']['read'] = true; +$wgGroupPermissions['orga-vorstand']['createpage'] = true; +$wgGroupPermissions['orga-verein']['edit'] = true; +$wgGroupPermissions['orga-verein']['read'] = true; +$wgGroupPermissions['orga-verein']['createpage'] = true; -# SSO config +# sysop rights +$wgGroupPermissions['sysop']['edit'] = true; +$wgGroupPermissions['sysop']['read'] = true; +$wgGroupPermissions['sysop']['createpage'] = true; + +#### Lockdown configuration +$wgSpecialPageLockdown['Export'] = ['user']; +$wgSpecialPageLockdown['Recentchanges'] = ['user']; +$wgNamespacePermissionLockdown[NS_MAIN]['read'] = ['orga-users']; + +# remove most namespace permissions +$wgNamespacePermissionLockdown['*']['read'] = ['sysop']; +$wgNamespacePermissionLockdown['*']['edit'] = ['sysop']; +$wgNamespacePermissionLockdown['*']['createpage'] = ['sysop']; + +# limit template workaround +$wgNonincludableNamespaces[] = NS_MAIN; +$wgNonincludableNamespaces[] = NS_PROJECT; +$wgNonincludableNamespaces[] = NS_VORSTAND; +$wgNonincludableNamespaces[] = NS_FSCK; + +# FSCK namespace +$wgNamespacePermissionLockdown[NS_FSCK]['read'] = [ 'orga-fsck' ]; +$wgNamespacePermissionLockdown[NS_FSCK]['edit'] = [ 'orga-fsck' ]; +$wgNamespacePermissionLockdown[NS_FSCK]['createpage'] = [ 'orga-fsck' ]; + +# Verein namespace +$wgNamespacePermissionLockdown[NS_VORSTAND]['read'] = [ 'orga-vorstand' ]; +$wgNamespacePermissionLockdown[NS_VORSTAND]['edit'] = [ 'orga-vorstand' ]; +$wgNamespacePermissionLockdown[NS_VORSTAND]['createpage'] = [ 'orga-vorstand' ]; + +#### SSO config # necessary to allow admin user(s) to login $wgPluggableAuth_EnableLocalLogin = true; $wgPluggableAuth_Config["Chaostreff Backnang IdP"] = [ @@ -181,12 +251,60 @@ $wgPluggableAuth_Config["Chaostreff Backnang IdP"] = [ 'data' => [ 'providerURL' => 'https://idp.ctbk.de/realms/ctbk/', 'clientID' => 'orga_mediawiki', + 'scope' => [ 'openid', 'profile', 'email', 'groups' ], 'clientsecret' => $ctbkClientSecret + ], + 'groupsyncs' => [ + [ + 'type' => 'mapped', + 'map' => [ + 'sysop' => [ 'groups' => '/mediawiki/admins' ], + 'bureaucrat' => [ 'groups' => '/mediawiki/admins' ], + 'interface-admin' => [ 'groups' => '/mediawiki/admins' ], + 'orga-users' => [ 'groups' => '/orgawiki/users' ], + 'orga-fsck' => [ 'groups' => '/ctbk/fsck' ], + 'orga-vorstand' => [ 'groups' => '/ctbk/vorstand' ], + 'orga-verein' => [ 'groups' => '/ctbk/members' ] + ] + ] ] ]; +# keep users logged in for extended amounts of time +$wgObjectCacheSessionExpiry = 5 * 24 * 60 * 60; +$wgExtendedLoginCookieExpiration = 365 * 24 * 60 * 60; + +# interwiki config +$wgGroupPermissions['sysop']['interwiki'] = true; +$wgInterwikiMagic = true; +$wgHideInterlanguageLinks = false; + # for better error reporting - disable while in production #error_reporting( -1 ); #ini_set( 'display_errors', 1 ); #$wgShowExceptionDetails = true; #$wgShowDBErrorBacktrace = true; + +$wgUrlProtocols[] = 'webcal://'; +$smwgURITypeSchemeList = array_merge($smwgURITypeSchemeList, ['matrix']); + +### Namespace attributes +$wgNamespacesWithSubpages[NS_MAIN] = true; +$wgNamespacesWithSubpages[NS_FSCK] = true; +$wgNamespacesWithSubpages[NS_VORSTAND] = true; +$wgNamespacesWithSubpages[NS_TEMPLATE] = true; +# SMW enabled on custom namespaces +$smwgNamespacesWithSemanticLinks[NS_FSCK] = true; +$smwgNamespacesWithSemanticLinks[NS_FSCK_TALK] = true; +$smwgNamespacesWithSemanticLinks[NS_VORSTAND] = true; +$smwgNamespacesWithSemanticLinks[NS_VORSTAND_TALK] = true; +$wgContentNamespaces[] = NS_FSCK; +$wgContentNamespaces[] = NS_VORSTAND; + +# Allow user functions in all namespaces, needed for main page based on group membership +$wgUFAllowedNamespaces = array_fill( 0, 300, true ); + +# use proxy ip addresses -- we’re behind (at least) one reverse proxy that sets X-Forwarded-For +$wgUsePrivateIPs = true; +# ingress haproxy +$wgCdnServersNoPurge = [ '10.140.0.1' ]; diff --git a/orga_mediawiki/composer.local.json b/orga_mediawiki/composer.local.json index 489afd2..d38bb2c 100644 --- a/orga_mediawiki/composer.local.json +++ b/orga_mediawiki/composer.local.json @@ -1,21 +1,55 @@ { - "repositories": [ - { - "type": "vcs", - "url": "https://gerrit.wikimedia.org/r/mediawiki/extensions/OpenIDConnect" - } - ], - "require": { - "starcitizentools/citizen-skin": "^2.39", - "mediawiki/pluggable-auth": "^7", - "mediawiki/openidconnect": "^8", - "mediawiki/semantic-compound-queries": "^2.2", - "mediawiki/semantic-extra-special-properties": "^3", - "mediawiki/semantic-media-wiki": "^4.2", - "mediawiki/semantic-result-formats": "^4.2" - }, - "config": { - "preferred-install": "source", - "optimize-autoloader": true - } + "repositories": [ + { + "type": "vcs", + "url": "https://gerrit.wikimedia.org/r/mediawiki/extensions/OpenIDConnect" + }, + { + "type": "package", + "package": { + "name": "x-mediawiki/lockdown", + "version": "1.1.0", + "type": "mediawiki-extension", + "extra": { + "installer-name": "Lockdown" + }, + "source": { + "type": "git", + "url": "https://gerrit.wikimedia.org/r/mediawiki/extensions/Lockdown", + "reference": "REL1_43" + } + } + }, + { + "type": "package", + "package": { + "name": "x-mediawiki/userfunctions", + "version": "2.8.1", + "type": "mediawiki-extension", + "extra": { + "installer-name": "UserFunctions" + }, + "source": { + "type": "git", + "url": "https://gerrit.wikimedia.org/r/mediawiki/extensions/UserFunctions", + "reference": "REL1_43" + } + } + } + ], + "require": { + "starcitizentools/citizen-skin": "^3", + "mediawiki/pluggable-auth": "^7", + "mediawiki/openidconnect": "^8.3", + "mediawiki/semantic-compound-queries": "^3", + "mediawiki/semantic-extra-special-properties": "^4", + "mediawiki/semantic-media-wiki": "^6", + "mediawiki/semantic-result-formats": "^5", + "x-mediawiki/lockdown": "^1.1", + "x-mediawiki/userfunctions": "^2.8" + }, + "config": { + "preferred-install": "source", + "optimize-autoloader": true + } } diff --git a/orga_mediawiki/orga_mediawiki.conf b/orga_mediawiki/orga_mediawiki.conf index e7fb257..904933f 100644 --- a/orga_mediawiki/orga_mediawiki.conf +++ b/orga_mediawiki/orga_mediawiki.conf @@ -1,4 +1,4 @@ -# Public Chaostreff MediaWiki +# Orga Chaostreff MediaWiki server { listen 81; @@ -9,11 +9,17 @@ server { client_body_timeout 60; index index.php index.html index.htm; - location ~ \.ht { - deny all; - } + location ~ \.ht { + deny all; + } + + location /robots.txt { + root /etc/nginx; + try_files /robots.txt =404; + } location / { + add_header 'X-Content-Type-Options' 'nosniff'; try_files $uri $uri/ @rewrite; } diff --git a/public_mediawiki/LocalSettings.php b/public_mediawiki/LocalSettings.php index ee29bf5..bef90d6 100755 --- a/public_mediawiki/LocalSettings.php +++ b/public_mediawiki/LocalSettings.php @@ -1,4 +1,5 @@ [ 'providerURL' => 'https://idp.ctbk.de/realms/ctbk/', 'clientID' => 'public_mediawiki', + 'scope' => [ 'openid', 'profile', 'email', 'groups' ], 'clientsecret' => $ctbkClientSecret + ], + # use Keycloak group definitions to manage groups centrally + 'groupsyncs' => [ + [ + 'type' => 'mapped', + 'map' => [ + 'sysop' => [ 'groups' => '/mediawiki/admins' ], + 'buerocrat' => [ 'groups' => '/mediawiki/admins' ], + 'interface-admin' => [ 'groups' => '/mediawiki/admins' ] + ] + ] ] ]; +# keep users logged in for extended amounts of time +$wgObjectCacheSessionExpiry = 5 * 24 * 60 * 60; +$wgExtendedLoginCookieExpiration = 365 * 24 * 60 * 60; + +$wgRCMaxAge = 180 * 24 * 3600; + +$wgDefaultUserOptions['usecodemirror'] = 1; + # interwiki config $wgGroupPermissions['sysop']['interwiki'] = true; $wgInterwikiMagic = true; @@ -203,7 +241,26 @@ $wgHideInterlanguageLinks = false; # uncomment this if Semantic MediaWiki property locking is broken #$smwgChangePropagationProtection = false; +# Allow webcal:// URLs +$wgUrlProtocols[] = 'webcal://'; +$smwgURITypeSchemeList = array_merge($smwgURITypeSchemeList, ['matrix']); + # SVG config $wgFileExtensions[] = 'svg'; # This extension will no longer be needed in MediaWiki >= 1.41, then $wgSVGNativeRendering can be used wfLoadExtension( 'NativeSvgHandler' ); + +# do not sanitize my CSS +#$wgTemplateStylesAutoParseContent = false; +$wgTemplateStylesExtenderEnablePrefersColorScheme = true; +$wgTemplateStylesExtenderEnableCssVars = true; + +# enable namespaces everywhere we need them +$wgNamespacesWithSubpages[NS_MAIN] = true; +$wgNamespacesWithSubpages[NS_TEMPLATE] = true; + +# use proxy ip addresses -- we’re behind (at least) one reverse proxy that sets X-Forwarded-For +$wgUsePrivateIPs = true; +# ingress haproxy +$wgCdnServersNoPurge = [ '10.140.0.1' ]; + diff --git a/public_mediawiki/composer.local.json b/public_mediawiki/composer.local.json index 489afd2..1c8044e 100644 --- a/public_mediawiki/composer.local.json +++ b/public_mediawiki/composer.local.json @@ -3,19 +3,60 @@ { "type": "vcs", "url": "https://gerrit.wikimedia.org/r/mediawiki/extensions/OpenIDConnect" + }, + { + "type": "package", + "package": { + "name": "mediawiki/codemirror", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/wikimedia/mediawiki-extensions-CodeMirror.git", + "reference": "REL1_43" + } + } + }, + { + "type": "package", + "package": { + "name": "mediawiki/templatestyles", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://gerrit.wikimedia.org/r/mediawiki/extensions/TemplateStyles", + "reference": "REL1_43" + } + } + }, + { + "type": "package", + "package": { + "name": "mediawiki/widgets", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://gerrit.wikimedia.org/r/mediawiki/extensions/Widgets", + "reference": "1.7.0" + } + } } ], "require": { - "starcitizentools/citizen-skin": "^2.39", + "starcitizentools/citizen-skin": "^3", "mediawiki/pluggable-auth": "^7", - "mediawiki/openidconnect": "^8", - "mediawiki/semantic-compound-queries": "^2.2", - "mediawiki/semantic-extra-special-properties": "^3", - "mediawiki/semantic-media-wiki": "^4.2", - "mediawiki/semantic-result-formats": "^4.2" + "mediawiki/openidconnect": "^8.3", + "mediawiki/semantic-compound-queries": "^3", + "mediawiki/semantic-extra-special-properties": "^4", + "mediawiki/semantic-media-wiki": "^6", + "mediawiki/semantic-result-formats": "^5", + "mediawiki/codemirror": "^6", + "mediawiki/templatestyles": "^1", + "octfx/template-styles-extender": "^2.1", + "mediawiki/widgets": "^1.7" }, "config": { "preferred-install": "source", - "optimize-autoloader": true + "optimize-autoloader": true, + "process-timeout": 3000 } } diff --git a/public_mediawiki/public_mediawiki.conf b/public_mediawiki/public_mediawiki.conf index 8d36cb6..81521d2 100644 --- a/public_mediawiki/public_mediawiki.conf +++ b/public_mediawiki/public_mediawiki.conf @@ -9,11 +9,17 @@ server { client_body_timeout 60; index index.php index.html index.htm; - location ~ \.ht { - deny all; - } + location ~ \.ht { + deny all; + } + + location /robots.txt { + root /etc/nginx; + try_files /robots.txt =404; + } location / { + add_header 'X-Content-Type-Options' 'nosniff'; try_files $uri $uri/ @rewrite; } diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..b93e3f5 --- /dev/null +++ b/robots.txt @@ -0,0 +1,3 @@ +User-Agent: * +Disallow: / + diff --git a/smw-jobs.sh b/smw-jobs.sh new file mode 100755 index 0000000..000cf0e --- /dev/null +++ b/smw-jobs.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +# ignore single script failures +set +e + +SMW_PATH=$1 + +set -x + +# not needed in our current config according to the documentation +#php "${SMW_PATH}/maintenance/updateSpecialPages.php" --quiet + +# recommended daily jobs +php "${SMW_PATH}/maintenance/rebuildData.php" --shallow-update +php "${SMW_PATH}/maintenance/disposeOutdatedEntities.php" +php "${SMW_PATH}/maintenance/rebuildPropertyStatistics.php" +php "${SMW_PATH}/maintenance/rebuildConceptCache.php" --update --create + +# recommended weekly jobs — we still run them daily to simplify the timers +php "${SMW_PATH}/maintenance/rebuildData.php" -d 100 +php "${SMW_PATH}/maintenance/setupStore.php" --skip-import + +# recommended monthly jobs +php "${SMW_PATH}/maintenance/removeDuplicateEntities.php" + diff --git a/system/mediawiki-jobrunner@.service b/system/mediawiki-jobrunner@.service new file mode 100644 index 0000000..4de5700 --- /dev/null +++ b/system/mediawiki-jobrunner@.service @@ -0,0 +1,18 @@ +[Unit] +Description=MediaWiki job runner %I +Documentation=https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:RunJobs.php + +[Service] +User=www-data +Group=www-data +ExecStart=/usr/bin/php /var/lib/%i/maintenance/runJobs.php --wait --maxjobs=50 +Restart=always +RestartSec=15 +RuntimeMaxSec=300 +PrivateDevices=true +PrivateTmp=true +ProtectHome=read-only + +[Install] +WantedBy=multi-user.target + diff --git a/system/semantic-mediawiki-jobs@.service b/system/semantic-mediawiki-jobs@.service new file mode 100644 index 0000000..c0b8181 --- /dev/null +++ b/system/semantic-mediawiki-jobs@.service @@ -0,0 +1,16 @@ +[Unit] +Description=Semantic MediaWiki job runner %I +Documentation=https://www.semantic-mediawiki.org/wiki/Help:Cron_jobs + +[Service] +User=www-data +Group=www-data +ExecStart=/usr/local/bin/smw-jobs /var/lib/%i/extensions/SemanticMediaWiki +RestartSec=15 +PrivateDevices=true +PrivateTmp=true +ProtectHome=read-only + +[Install] +WantedBy=default.target + diff --git a/system/semantic-mediawiki-jobs@.timer b/system/semantic-mediawiki-jobs@.timer new file mode 100644 index 0000000..9b15214 --- /dev/null +++ b/system/semantic-mediawiki-jobs@.timer @@ -0,0 +1,13 @@ +[Unit] +Description=Semantic MediaWiki job timer %I +Documentation=https://www.semantic-mediawiki.org/wiki/Help:Cron_jobs + +[Timer] +# run the jobs in the morning, after the backups happen +OnCalendar=*-*-* 04:00:00 +RandomizedDelaySec=1h +Unit=semantic-mediawiki-jobs@%i.service + +[Install] +WantedBy=timers.target +