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
+ );
+