Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 71 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<!-- Note: it would be great to have `click` links be relative rather than absolute, but that isn't possible: https://github.com/orgs/community/discussions/46096 -->
```mermaid
flowchart TD;
classDef volume fill:#dddddd,stroke:#333,stroke-width:1px
classDef external fill:#eeeebb
classDef folder fill:#bbeeee

subgraph app["app <small>(Single VM)</small>"]
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["<code>backupreader</code> users"]
class backupreader external
click backupreader "https://github.com/RopeWiki/app/tree/master/backup_manager/pubkeys"
GMail
class GMail external

luca_server["luca server<br>(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<br>/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

Expand Down Expand Up @@ -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=<The password for the `ropewiki` DB user>
export RW_ROOT_DB_PASSWORD=<The password for the `root` DB user>
export RW_SMTP_USERNAME=<The username for logging into the smtp relay>
Expand Down
4 changes: 4 additions & 0 deletions backup_manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions backup_manager/pubkeys/README.md
Original file line number Diff line number Diff line change
@@ -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.
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.
11 changes: 11 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we pin this more specifically? latest tags often break things on a schedule we don't control

hostname: ropewiki_tileserver
restart: unless-stopped
ports:
- "7979:7979"
volumes:
- /mnt/rwimages/tiles:/data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a new requirement to have for a site deployment -- let's add documentation describing how someone would get the appropriate data since someone couldn't bring up a working site instance without it. These instructions should be complete and sufficient to bring up a deployment of the site, assuming the user has access to the right things (database backup, images folder)

command: -c config.json -p 7979
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this config.json file in the /mnt/rwimages/tiles folder? If so, it seems like it should be tracked in this repo and probably we should build an image based on maptiler/tileserver-gl


volumes:
ropewiki_database_storage:
name: ropewiki_database_storage
Expand Down
9 changes: 9 additions & 0 deletions reverse_proxy/services.conf
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ server {
return 204;
}



# This line logs accesses to this service
access_log /logs/nginx/access.log combined;
}
Expand All @@ -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;
}
}
2 changes: 2 additions & 0 deletions webserver/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 /

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
--- 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
);