# GPLX – Deployment Runbook: IONOS VPS (native Apache)

This document records the **exact steps taken** to deploy GPLX on an IONOS VPS on 2026-06-05.
It replaces a broken Docker-based MediaWiki install with a native Apache + PHP + MariaDB stack.
Use it verbatim for a repeat deployment or hand it to another AI assistant.

---

## Environment

| Item                 | Value                            |
| -------------------- | -------------------------------- |
| Server IP            | `31.70.94.35`                    |
| OS                   | Ubuntu 24.04 LTS                 |
| Web root             | `/var/www/gplx/mediawiki`        |
| Git bare repo        | `/var/repo/gplx.git`             |
| DB name/user         | `gplx` / `gplx`                  |
| MW admin user        | `Gxplex_admin`                   |
| MW admin pass        | `TempAdmin123` ← **change this** |
| HTTP Basic Auth user | `testUser`                       |

---

## Starting situation

- Two Docker containers were running on the server:
  - `mediawiki:1.41` on `0.0.0.0:8080->80`
  - `mysql:8.0` on `3306/tcp`
- `nginx` was running on port 80 (served something else, now disabled)
- `/var/www/gplx` did **not** exist
- Git remote `ionos` pointed at `ssh://root@31.70.94.35/var/www/gplx` (wrong — no bare repo)

---

## Step 1 – Create bare git repo on the server

Run locally:

```powershell
ssh root@31.70.94.35 "git init --bare /var/repo/gplx.git && mkdir -p /var/www/gplx && printf '#!/bin/bash\nGIT_WORK_TREE=/var/www/gplx git checkout -f main\n' > /var/repo/gplx.git/hooks/post-receive && chmod +x /var/repo/gplx.git/hooks/post-receive && echo 'Done'"
```

This creates a bare repo at `/var/repo/gplx.git` and a `post-receive` hook that checks out
the code into `/var/www/gplx` on every push.

---

## Step 2 – Fix local git remote and push

```powershell
git remote set-url ionos ssh://root@31.70.94.35/var/repo/gplx.git
git push ionos main
```

The hook fires automatically: output shows `Switched to branch 'main'`.

---

## Step 3 – Install system packages

All packages were already present on this server. If doing a fresh install:

```bash
ssh root@<ip>
apt update && apt install -y apache2 php8.3 php8.3-mysql php8.3-xml \
  php8.3-mbstring php8.3-intl php8.3-curl php8.3-gd php8.3-zip \
  mariadb-server git apache2-utils
```

---

## Step 4 – Stop nginx (was holding port 80)

```bash
systemctl stop nginx
systemctl disable nginx
```

---

## Step 5 – Create database and user

```bash
mysql -u root <<'SQL'
CREATE DATABASE IF NOT EXISTS gplx CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS 'gplx'@'localhost' IDENTIFIED BY 'YOUR_DB_PASSWORD';
GRANT ALL PRIVILEGES ON gplx.* TO 'gplx'@'localhost';
FLUSH PRIVILEGES;
SQL
```

If the user already exists with a different password, reset it:

```bash
mysql -u root <<'SQL'
ALTER USER 'gplx'@'localhost' IDENTIFIED BY 'YOUR_DB_PASSWORD';
FLUSH PRIVILEGES;
SQL
```

---

## Step 6 – Configure Apache vhost with HTTP Basic Auth

```bash
cat > /etc/apache2/sites-available/gplx.conf << 'EOF'
<VirtualHost *:80>
    ServerName 31.70.94.35
    DocumentRoot /var/www/gplx/mediawiki

    <Directory /var/www/gplx/mediawiki>
        AllowOverride All
        AuthType Basic
        AuthName "GxPlex – restricted access"
        AuthUserFile /etc/apache2/.htpasswd
        Require valid-user
    </Directory>
</VirtualHost>
EOF

a2enmod rewrite
a2ensite gplx
a2dissite 000-default
systemctl start apache2
```

> **Important:** Do NOT include `Require all granted` alongside `Require valid-user` —
> they act as OR, which lets anonymous traffic through.

Create the htpasswd file:

```bash
htpasswd -c /etc/apache2/.htpasswd testUser
# enter password at the prompt
```

---

## Step 7 – Install Composer dependencies

MediaWiki core:

```bash
cd /var/www/gplx/mediawiki
composer install --no-dev
```

SemanticMediaWiki (has its own vendor tree):

```bash
composer install --no-dev -d extensions/SemanticMediaWiki
```

---

## Step 8 – Deploy LocalSettings.php

`LocalSettings.php` is gitignored and must be transferred separately.

**On your local machine**, edit `mediawiki/LocalSettings.php` and set:

```php
$wgServer     = "http://31.70.94.35";   // or https://yourdomain.de once TLS is live
$wgDBserver   = "localhost";
$wgDBname     = "gplx";
$wgDBuser     = "gplx";
$wgDBpassword = "YOUR_DB_PASSWORD";
$wgSecretKey  = "<output of: openssl rand -hex 32>";
$wgShowExceptionDetails = false;
// in the ExtensionFunctions hook:
enableSemantics( '31.70.94.35' );  // change to domain once live
```

Then copy to the server:

```powershell
scp mediawiki/LocalSettings.php root@31.70.94.35:/var/www/gplx/mediawiki/LocalSettings.php
```

Or patch it in-place on the server with `sed`:

```bash
sed -i \
  -e 's|$wgServer = "http://127.0.0.1:8080";|$wgServer = "http://31.70.94.35";|' \
  -e 's|$wgDBserver = "127.0.0.1";|$wgDBserver = "localhost";|' \
  -e 's|$wgDBname = "gflex_test";|$wgDBname = "gplx";|' \
  -e 's|$wgDBuser = "root";|$wgDBuser = "gplx";|' \
  -e "s|\$wgDBpassword = \"\";|\$wgDBpassword = \"YOUR_DB_PASSWORD\";|" \
  -e "s|enableSemantics( '127.0.0.1:8080' );|enableSemantics( '31.70.94.35' );|" \
  -e 's|$wgShowExceptionDetails = true;|$wgShowExceptionDetails = false;|' \
  /var/www/gplx/mediawiki/LocalSettings.php
```

---

## Step 9 – Create images directory

```bash
mkdir -p /var/www/gplx/mediawiki/images
chown -R www-data:www-data /var/www/gplx/mediawiki/images
```

---

## Step 10 – Install MediaWiki database tables

Because the database is empty, `update.php` fails with "Can not upgrade from versions older
than 1.35". Use `install.php` instead, temporarily hiding `LocalSettings.php`:

```bash
cd /var/www/gplx/mediawiki
mv LocalSettings.php LocalSettings.php.bak
php maintenance/run.php install \
  --dbuser gplx \
  --dbpass YOUR_DB_PASSWORD \
  --dbname gplx \
  --dbserver localhost \
  --server 'http://31.70.94.35' \
  --scriptpath '' \
  --lang en \
  --pass TempAdmin123 \
  'GxPlex' 'Gxplex_admin'
mv LocalSettings.php.bak LocalSettings.php
```

Then run update to pick up extension schema changes:

```bash
php maintenance/run.php update --quick
```

---

## Step 11 – Set up SMW tables

```bash
php extensions/SemanticMediaWiki/maintenance/setupStore.php --nochecks
```

---

## Step 12 – Smoke test

```bash
# Should return 401 (Basic Auth gate)
curl -s -o /dev/null -w '%{http_code}' http://31.70.94.35/

# Should return 200 with correct credentials
curl -sL -o /dev/null -w '%{http_code}' -u testUser:YOUR_HTPASSWD_PASSWORD http://31.70.94.35/
```

Open in browser: `http://31.70.94.35` → login prompt → MediaWiki main page.

---

## Step 13 – Import local database dump

The local MediaWiki DB was exported as `gflex_test.sql` (≈280 MB) and contains all user
accounts, pages, and content from the dev instance.

Upload the dump to the server:

```powershell
scp D:\Work\Projekte\gplx\gflex_test.sql root@31.70.94.35:/root/gflex_test.sql
```

The `gplx` DB already had tables from the fresh install (Step 10), so drop and recreate it
before importing — otherwise MySQL errors with "Table already exists":

```bash
mysql -u root <<'SQL'
DROP DATABASE gplx;
CREATE DATABASE gplx CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON gplx.* TO 'gplx'@'localhost';
FLUSH PRIVILEGES;
SQL
```

Import the dump:

```bash
mysql -ugplx -pYOUR_DB_PASSWORD gplx < /root/gflex_test.sql
```

Run update to apply any schema differences between the dump and the current MW version:

```bash
cd /var/www/gplx/mediawiki
php maintenance/run.php update --quick
```

All local user accounts and wiki content are now available on the server.

---

## Step 14 – Nutzeraccounts

Die Accounts aus dem SQL-Import (Step 13) sind direkt nutzbar — lokale Zugangsdaten
funktionieren ohne weitere Schritte.

**Vorhandene User prüfen:**

```bash
mysql -ugplx -pYOUR_DB_PASSWORD gplx \
  -e 'SELECT user_name, user_registration FROM user ORDER BY user_registration DESC LIMIT 20;'
```

**Neuen Account anlegen** (Passwort wird interaktiv abgefragt):

```bash
cd /var/www/gplx/mediawiki
php maintenance/run.php createAndPromote NeuUser
# mit Bureaucrat-Rechten:
php maintenance/run.php createAndPromote --bureaucrat NeuUser
```

**Passwort zurücksetzen:**

```bash
php maintenance/run.php changePassword --user="Benutzername" --password="NeuesPasswort"
```

**Account im Browser anlegen:** `http://31.70.94.35/index.php/Special:CreateAccount`
(nur sichtbar wenn `$wgGroupPermissions['*']['createaccount'] = true`, was der Standard ist).

---

## Next steps (not yet done)

- [ ] Change `Gxplex_admin` password via `Special:ChangePassword`
- [ ] Point domain DNS A-record to `31.70.94.35`
- [ ] Add TLS via Certbot: `certbot --apache -d yourdomain.de`
- [ ] Update `$wgServer` and `enableSemantics()` to use the domain
- [ ] Push wiki page templates: `Get-Content scripts/templates/<file>.wiki -Raw | php mediawiki/maintenance/run.php edit ...`
- [ ] Run `npm run a11y:all` against the live URL

---

## Gotchas encountered

| Problem                                                       | Cause                                                                          | Fix                                                                        |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
| `git push ionos main` failed (exit 1)                         | Remote pointed at non-bare working dir                                         | Created bare repo at `/var/repo/gplx.git` with post-receive hook           |
| `/var/www/gplx/mediawiki` not found after push                | Directory was checked out by hook — actually worked, just needed to look again | `ls /var/www/gplx/mediawiki` confirmed it was there                        |
| SMW fatal: `Class "Onoi\Cache\NullCache" not found`           | SMW vendor deps not installed                                                  | `composer install --no-dev -d extensions/SemanticMediaWiki`                |
| `update.php`: "Can not upgrade from versions older than 1.35" | Completely empty DB — MW misreads it                                           | Use `install.php` with `LocalSettings.php` temporarily moved away          |
| `install.php`: "A LocalSettings.php file has been detected"   | Installer refuses when file exists                                             | `mv LocalSettings.php LocalSettings.php.bak` before install, restore after |
| Apache won't start                                            | nginx was holding port 80                                                      | `systemctl stop nginx && systemctl disable nginx`                          |
| Basic Auth not blocking (returns 200 for anonymous)           | `Require all granted` + `Require valid-user` = OR logic                        | Remove `Require all granted`; only keep `Require valid-user`               |
| DB "Access denied for user gplx"                              | Password in LocalSettings.php didn't match DB                                  | `ALTER USER 'gplx'@'localhost' IDENTIFIED BY '...'` to sync them           |
| `ERROR 1050: Table 'actor' already exists` on SQL import      | Fresh MW install already created tables; importing on top fails                | `DROP DATABASE gplx` + recreate, then import                               |
