diff --git a/.env.example b/.env.example
new file mode 100644
index 00000000..7626471c
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,25 @@
+# Database connection setting
+# This uses the Index library's DSN syntax
+# Currently Misuzu only supports MariaDB, or Null of course since that does nothing
+#
+# For normal TCP connection you can use the following syntax:
+#DATABASE_DSN="mariadb://username:password@hostname/dbname?charset=utf8mb4"
+#
+# For a UNIX socket connection you can use the following syntax:
+#DATABASE_DSN="mariadb://username:password@:unix:/dbname?socket=/path/to/mysqld.sock&charset=utf8mb4"
+#
+# And here's your unsensible default:
+DATABASE_DSN="null:"
+
+# Sentry error reporting setting
+# I have not idea this works, just shove the value Sentry gives you in here.
+# You can also leave it commented.
+#SENTRY_DSN="https://6e41e3a2507d1542fd1e9aaf54f05d87@o4505858016870400.ingest.sentry.io/4505858023751680"
+
+# Domain roles
+# This assigns what domain has what role, domains can also have multiple roles!
+# Pairs are split by ;, domain and role pairs are split by =, roles are split by , and if you want a prefix use :
+# The example below maps everything to localhost for development.
+# But to make things more understandable, the value for Flashii is also included
+#DOMAIN_ROLES="flashii.net=main; fii.moe=redirect"
+DOMAIN_ROLES="localhost=main,redirect:/go"
diff --git a/.gitignore b/.gitignore
index 49f1e43d..706011ad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@
 /composer.local.json
 
 # Configuration
+/.env
 /config/config.cfg
 /config/github.cfg
 /config/config.ini
diff --git a/VERSION b/VERSION
index 11e801e0..039de05f 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-20250209
+20250210
diff --git a/composer.json b/composer.json
index e82149b2..f05be536 100644
--- a/composer.json
+++ b/composer.json
@@ -10,7 +10,9 @@
         "symfony/mailer": "~7.2",
         "matomo/device-detector": "~6.4",
         "sentry/sdk": "~4.0",
-        "nesbot/carbon": "~3.8"
+        "nesbot/carbon": "~3.8",
+        "vlucas/phpdotenv": "~5.6",
+        "filp/whoops": "~2.17"
     },
     "autoload": {
         "classmap": [
@@ -27,7 +29,6 @@
         ]
     },
     "require-dev": {
-        "phpstan/phpstan": "~2.1",
-        "filp/whoops": "~2.17"
+        "phpstan/phpstan": "~2.1"
     }
 }
diff --git a/composer.lock b/composer.lock
index 693b9a9e..ee8ee9c4 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "3cc1fc369aa12ce9ebbf324bc13a7d9d",
+    "content-hash": "ba6f5f293f0f3e3c7ad9b5335aee2b36",
     "packages": [
         {
             "name": "carbonphp/carbon-doctrine-types",
@@ -428,6 +428,77 @@
             },
             "time": "2019-12-30T22:54:17+00:00"
         },
+        {
+            "name": "filp/whoops",
+            "version": "2.17.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/filp/whoops.git",
+                "reference": "075bc0c26631110584175de6523ab3f1652eb28e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/filp/whoops/zipball/075bc0c26631110584175de6523ab3f1652eb28e",
+                "reference": "075bc0c26631110584175de6523ab3f1652eb28e",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1 || ^8.0",
+                "psr/log": "^1.0.1 || ^2.0 || ^3.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "^1.0",
+                "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3",
+                "symfony/var-dumper": "^4.0 || ^5.0"
+            },
+            "suggest": {
+                "symfony/var-dumper": "Pretty print complex values better with var-dumper available",
+                "whoops/soap": "Formats errors as SOAP responses"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.7-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Whoops\\": "src/Whoops/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Filipe Dobreira",
+                    "homepage": "https://github.com/filp",
+                    "role": "Developer"
+                }
+            ],
+            "description": "php error handling for cool kids",
+            "homepage": "https://filp.github.io/whoops/",
+            "keywords": [
+                "error",
+                "exception",
+                "handling",
+                "library",
+                "throwable",
+                "whoops"
+            ],
+            "support": {
+                "issues": "https://github.com/filp/whoops/issues",
+                "source": "https://github.com/filp/whoops/tree/2.17.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/denis-sokolov",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-01-25T12:00:00+00:00"
+        },
         {
             "name": "flashii/rpcii",
             "version": "v4.0.0",
@@ -522,6 +593,68 @@
             "homepage": "https://railgun.sh/index",
             "time": "2025-01-22T12:38:11+00:00"
         },
+        {
+            "name": "graham-campbell/result-type",
+            "version": "v1.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/GrahamCampbell/Result-Type.git",
+                "reference": "3ba905c11371512af9d9bdd27d99b782216b6945"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945",
+                "reference": "3ba905c11371512af9d9bdd27d99b782216b6945",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "phpoption/phpoption": "^1.9.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "GrahamCampbell\\ResultType\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                }
+            ],
+            "description": "An Implementation Of The Result Type",
+            "keywords": [
+                "Graham Campbell",
+                "GrahamCampbell",
+                "Result Type",
+                "Result-Type",
+                "result"
+            ],
+            "support": {
+                "issues": "https://github.com/GrahamCampbell/Result-Type/issues",
+                "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-07-20T21:45:45+00:00"
+        },
         {
             "name": "guzzlehttp/psr7",
             "version": "2.7.0",
@@ -926,6 +1059,81 @@
             ],
             "time": "2024-12-27T09:25:35+00:00"
         },
+        {
+            "name": "phpoption/phpoption",
+            "version": "1.9.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/schmittjoh/php-option.git",
+                "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54",
+                "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                },
+                "branch-alias": {
+                    "dev-master": "1.9-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpOption\\": "src/PhpOption/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Johannes M. Schmitt",
+                    "email": "schmittjoh@gmail.com",
+                    "homepage": "https://github.com/schmittjoh"
+                },
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                }
+            ],
+            "description": "Option Type for PHP",
+            "keywords": [
+                "language",
+                "option",
+                "php",
+                "type"
+            ],
+            "support": {
+                "issues": "https://github.com/schmittjoh/php-option/issues",
+                "source": "https://github.com/schmittjoh/php-option/tree/1.9.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-07-20T21:41:07+00:00"
+        },
         {
             "name": "psr/clock",
             "version": "1.0.0",
@@ -2274,6 +2482,86 @@
             ],
             "time": "2024-09-09T11:45:10+00:00"
         },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.31.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
+                "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-09-09T11:45:10+00:00"
+        },
         {
             "name": "symfony/polyfill-php81",
             "version": "v1.31.0",
@@ -2829,80 +3117,93 @@
                 }
             ],
             "time": "2025-01-29T07:06:14+00:00"
-        }
-    ],
-    "packages-dev": [
+        },
         {
-            "name": "filp/whoops",
-            "version": "2.17.0",
+            "name": "vlucas/phpdotenv",
+            "version": "v5.6.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/filp/whoops.git",
-                "reference": "075bc0c26631110584175de6523ab3f1652eb28e"
+                "url": "https://github.com/vlucas/phpdotenv.git",
+                "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/filp/whoops/zipball/075bc0c26631110584175de6523ab3f1652eb28e",
-                "reference": "075bc0c26631110584175de6523ab3f1652eb28e",
+                "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2",
+                "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.1 || ^8.0",
-                "psr/log": "^1.0.1 || ^2.0 || ^3.0"
+                "ext-pcre": "*",
+                "graham-campbell/result-type": "^1.1.3",
+                "php": "^7.2.5 || ^8.0",
+                "phpoption/phpoption": "^1.9.3",
+                "symfony/polyfill-ctype": "^1.24",
+                "symfony/polyfill-mbstring": "^1.24",
+                "symfony/polyfill-php80": "^1.24"
             },
             "require-dev": {
-                "mockery/mockery": "^1.0",
-                "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3",
-                "symfony/var-dumper": "^4.0 || ^5.0"
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "ext-filter": "*",
+                "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
             },
             "suggest": {
-                "symfony/var-dumper": "Pretty print complex values better with var-dumper available",
-                "whoops/soap": "Formats errors as SOAP responses"
+                "ext-filter": "Required to use the boolean validator."
             },
             "type": "library",
             "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                },
                 "branch-alias": {
-                    "dev-master": "2.7-dev"
+                    "dev-master": "5.6-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "Whoops\\": "src/Whoops/"
+                    "Dotenv\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "MIT"
+                "BSD-3-Clause"
             ],
             "authors": [
                 {
-                    "name": "Filipe Dobreira",
-                    "homepage": "https://github.com/filp",
-                    "role": "Developer"
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Vance Lucas",
+                    "email": "vance@vancelucas.com",
+                    "homepage": "https://github.com/vlucas"
                 }
             ],
-            "description": "php error handling for cool kids",
-            "homepage": "https://filp.github.io/whoops/",
+            "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
             "keywords": [
-                "error",
-                "exception",
-                "handling",
-                "library",
-                "throwable",
-                "whoops"
+                "dotenv",
+                "env",
+                "environment"
             ],
             "support": {
-                "issues": "https://github.com/filp/whoops/issues",
-                "source": "https://github.com/filp/whoops/tree/2.17.0"
+                "issues": "https://github.com/vlucas/phpdotenv/issues",
+                "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1"
             },
             "funding": [
                 {
-                    "url": "https://github.com/denis-sokolov",
+                    "url": "https://github.com/GrahamCampbell",
                     "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
+                    "type": "tidelift"
                 }
             ],
-            "time": "2025-01-25T12:00:00+00:00"
-        },
+            "time": "2024-07-20T21:52:34+00:00"
+        }
+    ],
+    "packages-dev": [
         {
             "name": "phpstan/phpstan",
             "version": "2.1.3",
diff --git a/config/config.example.cfg b/config/config.example.cfg
deleted file mode 100644
index 21422f97..00000000
--- a/config/config.example.cfg
+++ /dev/null
@@ -1,11 +0,0 @@
-# Example configuration for Misuzu
-# and ; can be used at the start of a line for comments.
-
-database:dsn mariadb://<user>:<pass>@<host>/<name>?charset=utf8mb4
-
-;sentry:dsn          https://sentry dsn here
-;sentry:tracesRate   1.0
-;sentry:profilesRate 1.0
-
-domain:localhost main redirect
-domain:localhost:redirect:path /go
diff --git a/misuzu.php b/misuzu.php
index d60d1c6c..b4501519 100644
--- a/misuzu.php
+++ b/misuzu.php
@@ -1,26 +1,28 @@
 <?php
 namespace Misuzu;
 
-use Index\Config\Fs\FsConfig;
-
 define('MSZ_STARTUP', microtime(true));
 define('MSZ_ROOT', __DIR__);
 
 require_once __DIR__ . '/vendor/autoload.php';
 
-$env = FsConfig::fromFile(Misuzu::PATH_CONFIG . '/config.cfg');
+\Dotenv\Dotenv::createImmutable(Misuzu::PATH_ROOT)->load();
 
-if($env->hasValues('sentry:dsn'))
-    (function($env) {
-        \Sentry\init([
-            'dsn' => $env->getString('dsn'),
-            'traces_sample_rate' => $env->getFloat('tracesRate', 0.2),
-            'profiles_sample_rate' => $env->getFloat('profilesRate', 0.2),
-        ]);
+if(!empty($_ENV['SENTRY_DSN']))
+    \Sentry\init(['dsn' => $_ENV['SENTRY_DSN']]);
 
-        set_exception_handler(function(\Throwable $ex) {
-            \Sentry\captureException($ex);
-        });
-    })($env->scopeTo('sentry'));
+(function(\Whoops\RunInterface $whoops) {
+    if(Misuzu::cli())
+        $whoops->pushHandler(new Whoops\SentryPlainTextHandler);
+    elseif(Misuzu::debug())
+        $whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler);
+    else
+        $whoops->pushHandler(new Whoops\SentryPageHandler);
 
-$msz = new MisuzuContext($env);
+    $whoops->register();
+})(new \Whoops\Run);
+
+$msz = new MisuzuContext(
+    $_ENV['DATABASE_DSN'] ?? 'null:',
+    $_ENV['DOMAIN_ROLES'] ?? 'localhost=main,redirect:/go'
+);
diff --git a/public/index.php b/public/index.php
index ef83817c..5171431e 100644
--- a/public/index.php
+++ b/public/index.php
@@ -9,23 +9,6 @@ require_once __DIR__ . '/../misuzu.php';
 if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
     die('Misuzu is not initialised.');
 
-if(class_exists(\Whoops\Run::class))
-    (function($whoops) {
-        $whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler);
-        $whoops->register();
-    })(new \Whoops\Run);
-else
-    set_exception_handler(function(\Throwable $ex) {
-        \Sentry\captureException($ex);
-
-        http_response_code(500);
-        ob_clean();
-
-        header('Content-Type: text/html; charset=utf-8');
-        header('X-Accel-Redirect: /error-500.html');
-        exit;
-    });
-
 // The whole wall of shit before the router setup and dispatch should be worked away
 // Lockdown things should be middleware when there's no more legacy files
 
@@ -128,7 +111,7 @@ CSRF::init(
 $router = $msz->createRouting($request);
 $msz->startTemplating();
 
-if(in_array('main', $msz->env->getArray(sprintf('domain:%s', $request->getHeaderLine('Host'))))) {
+if($msz->domainRoles->hasRole($request->getHeaderLine('Host'), 'main')) {
     $mszRequestPath = substr($request->path, 1);
     $mszLegacyPathPrefix = Misuzu::PATH_PUBLIC_LEGACY . '/';
     $mszLegacyPath = $mszLegacyPathPrefix . $mszRequestPath;
diff --git a/src/DatabaseContext.php b/src/DatabaseContext.php
index 24f9a65b..83db6ff3 100644
--- a/src/DatabaseContext.php
+++ b/src/DatabaseContext.php
@@ -11,8 +11,10 @@ class DatabaseContext implements RouteHandler {
 
     public private(set) DbConnection $conn;
 
-    public function __construct(Config $config) {
-        $this->conn = DbBackends::create($config->getString('dsn', 'null:'));
+    public function __construct(
+        #[\SensitiveParameter] string $dsn
+    ) {
+        $this->conn = DbBackends::create($dsn);
         $this->conn->execute(<<<SQL
             SET SESSION
                 time_zone = '+00:00',
diff --git a/src/DomainRoles.php b/src/DomainRoles.php
new file mode 100644
index 00000000..40d4090e
--- /dev/null
+++ b/src/DomainRoles.php
@@ -0,0 +1,52 @@
+<?php
+namespace Misuzu;
+
+use Index\XArray;
+
+class DomainRoles {
+    /** @var array<string, array<string, string>> */
+    private array $domainRoles;
+
+    /** @param string|array<string, array<string, string>> $domainRoles */
+    public function __construct(string|array $domainRoles) {
+        if(is_string($domainRoles)) {
+            $domains = explode(';', $domainRoles);
+            $domainRoles = [];
+            foreach($domains as $domain) {
+                [$domain, $rolesRaw] = XArray::select(explode('=', $domain, 2), fn($part) => trim($part));
+
+                $roles = [];
+                $rolesRaw = explode(',', $rolesRaw);
+                foreach($rolesRaw as $roleRaw) {
+                    $role = XArray::select(explode(':', $roleRaw, 2), fn($part) => trim($part));
+                    $roles[$role[0]] = empty($role[1]) ? '' : $role[1];
+                }
+
+                $domainRoles[$domain] = $roles;
+            }
+        }
+
+        $this->domainRoles = $domainRoles;
+    }
+
+    public function hasRole(string $domain, string $role): bool {
+        return array_key_exists($domain, $this->domainRoles)
+            && array_key_exists($role, $this->domainRoles[$domain]);
+    }
+
+    /** @return string[] */
+    public function getRoles(string $domain): array {
+        return array_key_exists($domain, $this->domainRoles)
+            ? array_keys($this->domainRoles[$domain])
+            : [];
+    }
+
+    public function getPrefix(string $domain, string $role): ?string {
+        if(!array_key_exists($domain, $this->domainRoles))
+            return null;
+        if(!array_key_exists($role, $this->domainRoles[$domain]))
+            return null;
+
+        return $this->domainRoles[$domain][$role];
+    }
+}
diff --git a/src/Misuzu.php b/src/Misuzu.php
index 91699354..7ad32fe1 100644
--- a/src/Misuzu.php
+++ b/src/Misuzu.php
@@ -39,4 +39,8 @@ final class Misuzu {
     public static function debug(): bool {
         return !empty(ini_get('display_errors'));
     }
+
+    public static function cli(): bool {
+        return php_sapi_name() === 'cli';
+    }
 }
diff --git a/src/MisuzuContext.php b/src/MisuzuContext.php
index 16a8b1fd..ed8203c9 100644
--- a/src/MisuzuContext.php
+++ b/src/MisuzuContext.php
@@ -18,6 +18,7 @@ class MisuzuContext {
     public private(set) Dependencies $deps;
 
     public private(set) Config $config;
+    public private(set) DomainRoles $domainRoles;
     public private(set) TplEnvironment $templating;
 
     public private(set) AuditLog\AuditLogData $auditLog;
@@ -47,17 +48,18 @@ class MisuzuContext {
     public private(set) UrlRegistry $urls;
 
     public function __construct(
-        public private(set) Config $env
+        #[\SensitiveParameter] string $dsn,
+        string $domainRoles
     ) {
         $this->deps = new Dependencies;
         $this->deps->register($this);
         $this->deps->register($this->deps);
-        $this->deps->register($this->env);
 
-        $this->deps->register($this->dbCtx = new DatabaseContext($this->env->scopeTo('database')));
+        $this->deps->register($this->dbCtx = new DatabaseContext($dsn));
         $this->deps->register($this->dbCtx->conn);
 
         $this->deps->register($this->config = $this->deps->constructLazy(DbConfig::class, tableName: 'msz_config'));
+        $this->deps->register($this->domainRoles = $this->deps->constructLazy(DomainRoles::class, domainRoles: $domainRoles));
 
         $this->deps->register($this->perms = $this->deps->constructLazy(Perms\PermissionsData::class));
         $this->deps->register($this->authInfo = $this->deps->constructLazy(Auth\AuthInfo::class));
@@ -138,9 +140,8 @@ class MisuzuContext {
     }
 
     public function createRouting(HttpRequest $request): BackedRoutingContext {
-        $prefix = sprintf('domain:%s', $request->getHeaderLine('Host'));
-        $hostInfo = $this->env->scopeTo($prefix);
-        $purposes = $this->env->getArray($prefix);
+        $host = $request->getHeaderLine('Host');
+        $roles = $this->domainRoles->getRoles($host);
 
         $routingCtx = new BackedRoutingContext;
         $this->deps->register($this->urls = $routingCtx->urls);
@@ -150,15 +151,15 @@ class MisuzuContext {
             new HmacVerificationProvider(fn() => $this->config->getString('aleister.secret'))
         ));
 
-        if(in_array('main', $purposes))
+        if(in_array('main', $roles))
             $this->registerMainRoutes(
-                $routingCtx->scopeTo($hostInfo->getString('main:path')),
+                $routingCtx->scopeTo($this->domainRoles->getPrefix($host, 'main')),
                 $rpcServer
             );
 
-        if(in_array('redirect', $purposes))
+        if(in_array('redirect', $roles))
             $this->registerRedirectorRoutes(
-                $routingCtx->scopeTo($hostInfo->getString('redirect:path'))
+                $routingCtx->scopeTo($this->domainRoles->getPrefix($host, 'redirect'))
             );
 
         return $routingCtx;
diff --git a/src/Perms/PermissionsData.php b/src/Perms/PermissionsData.php
index 3eb24bce..ddf9ac2e 100644
--- a/src/Perms/PermissionsData.php
+++ b/src/Perms/PermissionsData.php
@@ -6,6 +6,7 @@ use InvalidArgumentException;
 use RuntimeException;
 use Index\XDateTime;
 use Index\Db\{DbConnection,DbStatement,DbStatementCache,DbTools};
+use Misuzu\Misuzu;
 use Misuzu\Forum\{ForumCategoriesData,ForumCategoryInfo};
 use Misuzu\Users\{RoleInfo,UserInfo};
 
@@ -381,7 +382,7 @@ class PermissionsData {
      * @param bool|float|int|string|null ...$args
      */
     private static function precalculatePermissionsLog(string $fmt, ...$args): void {
-        if(php_sapi_name() === 'cli')
+        if(!Misuzu::cli())
             return;
 
         echo XDateTime::now()->format('[H:i:s.u] ');
diff --git a/src/Whoops/SentryPageHandler.php b/src/Whoops/SentryPageHandler.php
new file mode 100644
index 00000000..12f81a58
--- /dev/null
+++ b/src/Whoops/SentryPageHandler.php
@@ -0,0 +1,38 @@
+<?php
+namespace Misuzu\Whoops;
+
+use Throwable;
+use Misuzu\Misuzu;
+use Sentry\EventId;
+use Whoops\Handler\Handler;
+
+class SentryPageHandler extends Handler {
+    /** @return int */
+    public function handle() {
+        $throwable = $this->getException();
+        $eventId = $throwable instanceof Throwable ? \Sentry\captureException($throwable) : null;
+        $path = Misuzu::PATH_PUBLIC . DIRECTORY_SEPARATOR . 'error-500.html';
+        $message = $eventId instanceof EventId
+            ? sprintf("Error has been reported: <code>%s</code>", (string)$eventId)
+            : 'Error could not be reported.';
+
+        http_response_code(500);
+        if(is_file($path)) {
+            $body = file_get_contents($path);
+            if(is_string($body))
+                $body = str_replace(
+                    '<span class=error-additional></span>',
+                    sprintf('<br>%s', $message),
+                    $body
+                );
+        }
+
+        if(empty($body) || !is_string($body))
+            $body = '<!doctype html>Something went very wrong. Please report what you were trying to do to a developer. ' . $message;
+
+        header('Content-Type: text/html; charset=utf-8');
+        echo $body;
+
+        return Handler::QUIT;
+    }
+}
diff --git a/src/Whoops/SentryPlainTextHandler.php b/src/Whoops/SentryPlainTextHandler.php
new file mode 100644
index 00000000..f001c063
--- /dev/null
+++ b/src/Whoops/SentryPlainTextHandler.php
@@ -0,0 +1,19 @@
+<?php
+namespace Misuzu\Whoops;
+
+use Throwable;
+use Sentry\EventId;
+use Whoops\Handler\PlainTextHandler;
+
+class SentryPlainTextHandler extends PlainTextHandler {
+    #[\Override]
+    public function generateResponse() {
+        $throwable = $this->getException();
+        $eventId = $throwable instanceof Throwable ? \Sentry\captureException($throwable) : null;
+        $message = $eventId instanceof EventId
+            ? sprintf('Error has been reported: %s', (string)$eventId)
+            : 'Error could not be reported.';
+
+        return parent::generateResponse() . $message . "\n";
+    }
+}
diff --git a/templates/errors/master.twig b/templates/errors/master.twig
index 89db9d99..a532bbff 100644
--- a/templates/errors/master.twig
+++ b/templates/errors/master.twig
@@ -38,7 +38,7 @@
                         <i class="{{ error_icon }}"></i>
                     </div>
                     <h1 class="error-title">{{ error_string }}</h1>
-                    <p class="error-blerb">{{ error_blerb }}</p>
+                    <p class="error-blerb">{{ error_blerb }}<span class=error-additional></span></p>
                 </article>
                 <footer class="error-footer">
                     <p>