diff --git a/README.md b/README.md index 450355d..e37ed66 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,76 @@ contain the database content nor the `images` folder content of the real site). ### Site architecture -* The [MySQL database](database/README.md) stores all the non-file site data -* The [webserver](webserver/README.md) runs MediaWiki with extensions, and also stores all the file-based site - data/uploads - * Exposes port 8080 for direct access to the webserver that bypasses the reverse proxy -* The [reverse proxy](reverse_proxy/README.md) manages TLS termination and also redirects according to target site - * Exposes ports 80 & 443 as the external-facing webserver -* The [backup manager](backup_manager/README.md) exposes site data for backup - * Exposes port 22001 for SSH access to [`backupreader`](backup_manager/pubkeys/README.md) users + +```mermaid +flowchart TD; + classDef volume fill:#dddddd,stroke:#333,stroke-width:1px + classDef external fill:#eeeebb + classDef folder fill:#bbeeee + + subgraph app["app (Single VM)"] + ropewiki_db + click ropewiki_db "https://github.com/RopeWiki/app/tree/master/database" "MySQL database with non-file site data" + + ropewiki_webserver + click ropewiki_webserver "https://github.com/RopeWiki/app/tree/master/webserver" "MediaWiki server and file-based site data/uploads" + + ropewiki_reverse_proxy + click ropewiki_reverse_proxy "https://github.com/RopeWiki/app/tree/master/reverse_proxy" "TLS termination and redirects" + + ropewiki_backup_manager + click ropewiki_backup_manager "https://github.com/RopeWiki/app/tree/master/backup_manager" "Exposes site data for backup" + + ropewiki_mailserver + click ropewiki_mailserver "https://github.com/RopeWiki/app/tree/master/mailserver" "SMTP relay server to send email" + + ropewiki_database_storage@{shape:cyl} + class ropewiki_database_storage volume + ropewiki_proxy_certs@{shape:cyl} + class ropewiki_proxy_certs volume + ropewiki_proxy_logs@{shape:cyl} + class ropewiki_proxy_logs volume + + images["/rw/mount/images"]@{shape:cyl} + class images folder + sqlbackup["/rw/mount/sqlbackup"]@{shape:cyl} + class sqlbackup folder + end + + Internet["Public Internet"] + class Internet external + backupreader["backupreader users"] + class backupreader external + click backupreader "https://github.com/RopeWiki/app/tree/master/backup_manager/pubkeys" + GMail + class GMail external + + luca_server["luca server
(Windows VM)"] + click luca_server "https://github.com/RopeWiki/RWServer" + class luca_server external + + ropewiki_webserver -- MySQL 3306 --> ropewiki_db -- /var/lib/mysql --> ropewiki_database_storage + ropewiki_webserver -- /usr/share/nginx/html/ropewiki/images --> images + ropewiki_webserver -- SMTP 25 --> ropewiki_mailserver -- TLS SMTP 587 --> GMail + ropewiki_reverse_proxy -- HTTP 80 --> ropewiki_webserver + ropewiki_reverse_proxy -- HTTP 80
/luca/* --> luca_server + Internet -- HTTP 8080 --> ropewiki_webserver + Internet -- HTTPS 443 --> ropewiki_reverse_proxy + Internet -- HTTP 80 --> ropewiki_reverse_proxy + backupreader -- SSH 22001 --> ropewiki_backup_manager -- MySQL 3306 --> ropewiki_db + ropewiki_backup_manager -- mysqldump --> sqlbackup --> ropewiki_backup_manager + images --> ropewiki_backup_manager + ropewiki_reverse_proxy -- /etc/letsencrypt --> ropewiki_proxy_certs + ropewiki_reverse_proxy -- /logs --> ropewiki_proxy_logs + + subgraph Legend + Container["docker container"] + Volume["docker-managed volume"] + class Volume volume + Folder["Folder on host machine"] + class Folder folder + end +``` ## Site deployment @@ -65,7 +127,7 @@ the `PATH` (`python3 --version` to verify). Ignore all `apt` commands and instea 1. Create a shortcut to declare passwords: create `~/rw_passwords.sh` (or another location) with content like: ```shell #!/bin/bash -# These are the manditory environment variables needed to start a copy of the site +# These are the mandatory environment variables needed to start a copy of the site export WG_DB_PASSWORD= export RW_ROOT_DB_PASSWORD= export RW_SMTP_USERNAME= diff --git a/backup_manager/README.md b/backup_manager/README.md index fe347b3..2a03fc9 100644 --- a/backup_manager/README.md +++ b/backup_manager/README.md @@ -6,6 +6,10 @@ The RopeWiki backup manager automatically makes periodic backups, when enabled. follow the form `all-backup-YYYY-MM-DD-HHMMSS.sql.gz` and are stored in /home/backupreader/backups/ according to [backup_mysql.sh](scripts/backup_mysql.sh). +The `images` data (most of the file-based data uploaded to the site) can be found in +/home/backupreader/images/ according to the mounted volume in the +[docker-compose file](../docker-compose.yaml). + ## Copying backups offsite The database container is accessible via SSH directly at port 22001. Connect to the container using SSH via this port diff --git a/backup_manager/pubkeys/README.md b/backup_manager/pubkeys/README.md index 34f300f..33827c2 100644 --- a/backup_manager/pubkeys/README.md +++ b/backup_manager/pubkeys/README.md @@ -1,4 +1,3 @@ # Backup public keys -Users holding the private key to any of the public keys in this folder will be allowed to SSH into the database and -webserver containers as `backupreader` to copy backups off-site. \ No newline at end of file +Users holding the private key to any of the public keys in this folder will be allowed to SSH into the backup_manager container as `backupreader` to makes backups off-site. diff --git a/docker-compose.yaml b/docker-compose.yaml index 18e809e..3e1332f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -66,6 +66,7 @@ services: - WG_HOSTNAME=${WG_HOSTNAME:-localhost} depends_on: - ropewiki_webserver + - ropewiki_tileserver volumes: - ropewiki_proxy_certs:/etc/letsencrypt - ropewiki_proxy_logs:/logs @@ -111,6 +112,16 @@ services: - INBOUND_TLS=no # Don't require local clients to use TLS - ACCEPTED_NETWORKS=172.16.0.0/12 # Only accept connections from docker's internal network + ropewiki_tileserver: + image: maptiler/tileserver-gl:latest + hostname: ropewiki_tileserver + restart: unless-stopped + ports: + - "7979:7979" + volumes: + - /mnt/rwimages/tiles:/data + command: -c config.json -p 7979 + volumes: ropewiki_database_storage: name: ropewiki_database_storage diff --git a/reverse_proxy/services.conf b/reverse_proxy/services.conf index 98a50f8..a40d280 100644 --- a/reverse_proxy/services.conf +++ b/reverse_proxy/services.conf @@ -67,6 +67,8 @@ server { return 204; } + + # This line logs accesses to this service access_log /logs/nginx/access.log combined; } @@ -76,3 +78,10 @@ server { listen 80; return 301 https://{{WG_HOSTNAME}}$request_uri; } + +server { + server_name tiles.{{WG_HOSTNAME}}; + location / { + proxy_pass http://ropewiki_tileserver:7979; + } +} \ No newline at end of file diff --git a/webserver/Dockerfile b/webserver/Dockerfile index aaa56e0..5437bff 100644 --- a/webserver/Dockerfile +++ b/webserver/Dockerfile @@ -96,6 +96,8 @@ RUN composer require --no-update mediawiki/semantic-media-wiki 3.2.3 RUN composer require --no-update mediawiki/header-footer 3.0.1 RUN composer require --no-update mediawiki/maps 8.0.0 RUN composer require --no-update mediawiki/semantic-result-formats 3.2 +# Bypass warning regarding CVE-2022-48614 affecting SMW. See https://github.com/RopeWiki/app/issues/159 +RUN COMPOSER_ALLOW_SUPERUSER=yes composer config --no-plugins audit.ignore "PKSA-t71x-qf1n-7xwy" RUN COMPOSER_ALLOW_SUPERUSER=yes composer install WORKDIR / diff --git a/webserver/html/ropewiki/extensions/SphinxSearch/SphinxMWSearch.REL1_35.patch b/webserver/html/ropewiki/extensions/SphinxSearch/SphinxMWSearch.REL1_35.patch index 4ec1145..e28a855 100644 --- a/webserver/html/ropewiki/extensions/SphinxSearch/SphinxMWSearch.REL1_35.patch +++ b/webserver/html/ropewiki/extensions/SphinxSearch/SphinxMWSearch.REL1_35.patch @@ -24,14 +24,46 @@ $search_engine->setLimitOffset( $limit, $offset ); $result_set = $search_engine->searchText( '@page_title: ' . $term . '*' ); ---- SphinxMWSearchResult.php.orig 2025-05-29 21:18:03.424775847 +0000 -+++ SphinxMWSearchResult.php 2025-05-29 21:19:14.007250957 +0000 -@@ -25,7 +25,7 @@ - * - * @return string highlighted text snippet - */ -- public function getTextSnippet( $terms ) { -+ public function getTextSnippet( $terms = []) { - global $wgAdvancedSearchHighlighting, $wgSphinxSearchMWHighlighter, $wgSphinxSearch_index; - - $this->initText(); \ No newline at end of file +--- SphinxMWSearchResult.php.orig 2025-06-11 04:04:00.089677897 +0000 ++++ SphinxMWSearchResult.php 2025-06-24 01:20:29.530809534 +0000 +@@ -25,7 +25,20 @@ + * + * @return string highlighted text snippet + */ +- public function getTextSnippet( $terms ) { ++ public function getTextSnippet($terms = []) { ++ global $wgRequest; ++ ++ // If $terms wasn't provided, try and figure it out from the url parameters ++ if ( empty( $terms ) ) { ++ $query = trim( $wgRequest->getVal( 'search', '' ) ); ++ if ( $query === '' ) { ++ $query = trim( $wgRequest->getVal( 'q', '' ) ); ++ } ++ if ( $query !== '' ) { ++ $terms = preg_split( '/\s+/', $query ); ++ } ++ } ++ + global $wgAdvancedSearchHighlighting, $wgSphinxSearchMWHighlighter, $wgSphinxSearch_index; + + $this->initText(); +@@ -48,10 +61,17 @@ + "around" => $contextchars, + ]; + ++ $query = trim( implode( ' ', $terms ) ); ++ // It crashes searchd if the query is empty (null pointer), so fallback to ' '. ++ if ( $query === '' ) { ++ $query = ' '; ++ } ++ ++ wfDebugLog( 'SphinxSearch', 'BuildExcerpts query: ' . var_export( $terms, true ) ); + $excerpts = $this->sphinx_client->BuildExcerpts( + [ $this->mText ], + $wgSphinxSearch_index, +- implode( ' ', $terms ), ++ $query, + $excerpts_opt + ); +