init
This commit is contained in:
commit
642869ce9b
27 changed files with 1414 additions and 0 deletions
31
modules/caddy.nix
Normal file
31
modules/caddy.nix
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{ config, hostMeta, ... }:
|
||||
{
|
||||
services.caddy = {
|
||||
enable = true;
|
||||
virtualHosts.${hostMeta.publicDomain}.extraConfig = ''
|
||||
encode zstd gzip
|
||||
|
||||
handle /health {
|
||||
respond "ok" 200
|
||||
}
|
||||
|
||||
${
|
||||
if hostMeta.role == "replica" then
|
||||
''
|
||||
header {
|
||||
X-Wiki-Mode "read-only"
|
||||
}
|
||||
|
||||
''
|
||||
else
|
||||
""
|
||||
}php_fastcgi unix//run/phpfpm/mediawiki.sock {
|
||||
root ${config.services.mediawiki.finalPackage}/share/mediawiki
|
||||
}
|
||||
|
||||
file_server {
|
||||
root ${config.services.mediawiki.finalPackage}/share/mediawiki
|
||||
}
|
||||
'';
|
||||
};
|
||||
}
|
||||
43
modules/common.nix
Normal file
43
modules/common.nix
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
age.identityPaths = [ "/var/lib/agenix/host.age" ];
|
||||
|
||||
nix.settings = {
|
||||
experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
trusted-users = [
|
||||
"root"
|
||||
"@wheel"
|
||||
];
|
||||
auto-optimise-store = true;
|
||||
max-jobs = "auto";
|
||||
cores = 0;
|
||||
};
|
||||
|
||||
nix.gc = {
|
||||
automatic = true;
|
||||
dates = "weekly";
|
||||
options = "--delete-older-than 14d";
|
||||
};
|
||||
|
||||
time.timeZone = "America/Los_Angeles";
|
||||
i18n.defaultLocale = "en_US.UTF-8";
|
||||
|
||||
services.timesyncd.enable = true;
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/agenix 0700 root root -"
|
||||
"z /var/lib/agenix/host.age 0400 root root -"
|
||||
];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
vim
|
||||
git
|
||||
curl
|
||||
jq
|
||||
rsync
|
||||
mariadb.client
|
||||
];
|
||||
}
|
||||
109
modules/mediawiki-base.nix
Normal file
109
modules/mediawiki-base.nix
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
{
|
||||
config,
|
||||
hostMeta,
|
||||
siteConfig,
|
||||
...
|
||||
}:
|
||||
{
|
||||
services.mediawiki = {
|
||||
enable = true;
|
||||
name = siteConfig.wikiName;
|
||||
url = "https://${hostMeta.publicDomain}";
|
||||
webserver = "none";
|
||||
passwordFile = config.age.secrets.mediawiki-admin-password.path;
|
||||
|
||||
database = {
|
||||
type = "mysql";
|
||||
name = siteConfig.database.name;
|
||||
user = siteConfig.database.mediawikiUser;
|
||||
passwordFile = config.age.secrets.mysql-mediawiki.path;
|
||||
socket = "/run/mysqld/mysqld.sock";
|
||||
createLocally = false;
|
||||
};
|
||||
|
||||
extensions = {
|
||||
ParserFunctions = null;
|
||||
WikiEditor = null;
|
||||
};
|
||||
|
||||
extraConfig = ''
|
||||
$wgServer = "https://${hostMeta.publicDomain}";
|
||||
$wgSitename = "${siteConfig.wikiName}";
|
||||
$wgMetaNamespace = "${siteConfig.wikiName}";
|
||||
$wgScriptPath = "";
|
||||
$wgArticlePath = "/wiki/$1";
|
||||
$wgUsePathInfo = true;
|
||||
wfLoadSkin('Vector');
|
||||
|
||||
$wgEnableUploads = true;
|
||||
$wgEnableEmail = false;
|
||||
$wgShowIPinHeader = false;
|
||||
|
||||
$wgGroupPermissions['*']['edit'] = false;
|
||||
$wgGroupPermissions['*']['createpage'] = false;
|
||||
$wgGroupPermissions['*']['createtalk'] = false;
|
||||
$wgGroupPermissions['*']['writeapi'] = false;
|
||||
$wgGroupPermissions['*']['createaccount'] = false;
|
||||
|
||||
$wgGroupPermissions['user']['edit'] = true;
|
||||
$wgGroupPermissions['user']['createpage'] = true;
|
||||
$wgGroupPermissions['user']['createtalk'] = true;
|
||||
$wgGroupPermissions['user']['upload'] = true;
|
||||
$wgGroupPermissions['user']['writeapi'] = true;
|
||||
'';
|
||||
};
|
||||
|
||||
services.mediawiki.poolConfig =
|
||||
if hostMeta.role == "primary" then
|
||||
{
|
||||
"pm" = "dynamic";
|
||||
"pm.max_children" = 12;
|
||||
"pm.start_servers" = 3;
|
||||
"pm.min_spare_servers" = 2;
|
||||
"pm.max_spare_servers" = 4;
|
||||
"pm.max_requests" = 500;
|
||||
}
|
||||
else
|
||||
{
|
||||
"pm" = "dynamic";
|
||||
"pm.max_children" = 6;
|
||||
"pm.start_servers" = 2;
|
||||
"pm.min_spare_servers" = 1;
|
||||
"pm.max_spare_servers" = 3;
|
||||
"pm.max_requests" = 500;
|
||||
};
|
||||
|
||||
services.phpfpm.pools.mediawiki.phpOptions =
|
||||
if hostMeta.role == "primary" then
|
||||
''
|
||||
opcache.enable=1
|
||||
opcache.memory_consumption=192
|
||||
opcache.interned_strings_buffer=16
|
||||
opcache.max_accelerated_files=10000
|
||||
realpath_cache_size=4096K
|
||||
realpath_cache_ttl=600
|
||||
''
|
||||
else
|
||||
''
|
||||
opcache.enable=1
|
||||
opcache.memory_consumption=128
|
||||
opcache.interned_strings_buffer=16
|
||||
opcache.max_accelerated_files=10000
|
||||
realpath_cache_size=2048K
|
||||
realpath_cache_ttl=600
|
||||
'';
|
||||
|
||||
age.secrets.mediawiki-admin-password = {
|
||||
file = ../secrets/mediawiki-admin-password.age;
|
||||
owner = "mediawiki";
|
||||
group = "mediawiki";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
age.secrets.mysql-mediawiki = {
|
||||
file = ../secrets/mysql-mediawiki.age;
|
||||
owner = "mediawiki";
|
||||
group = "mediawiki";
|
||||
mode = "0400";
|
||||
};
|
||||
}
|
||||
18
modules/security.nix
Normal file
18
modules/security.nix
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{ ... }:
|
||||
{
|
||||
networking.firewall = {
|
||||
enable = true;
|
||||
allowedTCPPorts = [ 80 443 ];
|
||||
};
|
||||
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
openFirewall = false;
|
||||
settings = {
|
||||
PasswordAuthentication = false;
|
||||
KbdInteractiveAuthentication = false;
|
||||
PermitRootLogin = "prohibit-password";
|
||||
X11Forwarding = false;
|
||||
};
|
||||
};
|
||||
}
|
||||
32
modules/tailscale.nix
Normal file
32
modules/tailscale.nix
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
config,
|
||||
hostMeta,
|
||||
...
|
||||
}:
|
||||
{
|
||||
age.secrets.tailscale-auth = {
|
||||
file = ../secrets/tailscale-auth.age;
|
||||
owner = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
services.tailscale = {
|
||||
enable = true;
|
||||
authKeyFile = config.age.secrets.tailscale-auth.path;
|
||||
extraUpFlags = [ "--hostname=${hostMeta.tailscaleName}" ];
|
||||
};
|
||||
|
||||
networking.firewall.interfaces.tailscale0.allowedTCPPorts =
|
||||
if hostMeta.role == "primary" then
|
||||
[
|
||||
22
|
||||
3306
|
||||
]
|
||||
else
|
||||
[
|
||||
22
|
||||
873
|
||||
];
|
||||
networking.firewall.allowedUDPPorts = [ config.services.tailscale.port ];
|
||||
networking.firewall.checkReversePath = "loose";
|
||||
}
|
||||
29
modules/wiki-primary/mediawiki.nix
Normal file
29
modules/wiki-primary/mediawiki.nix
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{ pkgs, siteConfig, ... }:
|
||||
{
|
||||
systemd.services.mediawiki-image-sync = {
|
||||
description = "Sync MediaWiki uploads to the replica";
|
||||
after = [ "tailscale-autoconnect.service" ];
|
||||
wants = [ "tailscale-autoconnect.service" ];
|
||||
path = [ pkgs.rsync ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
${pkgs.rsync}/bin/rsync \
|
||||
-az \
|
||||
--delete \
|
||||
/var/lib/mediawiki/images/ \
|
||||
"rsync://${siteConfig.hosts.replica.tailscaleName}/mediawiki-images/"
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.timers.mediawiki-image-sync = {
|
||||
description = "Periodic upload sync from primary to replica";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = "hourly";
|
||||
Persistent = true;
|
||||
RandomizedDelaySec = "10m";
|
||||
};
|
||||
};
|
||||
}
|
||||
123
modules/wiki-primary/mysql.nix
Normal file
123
modules/wiki-primary/mysql.nix
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
{
|
||||
config,
|
||||
pkgs,
|
||||
siteConfig,
|
||||
...
|
||||
}:
|
||||
let
|
||||
dbName = siteConfig.database.name;
|
||||
wikiUser = siteConfig.database.mediawikiUser;
|
||||
replicationUser = siteConfig.database.replicationUser;
|
||||
mysqlCli = "${pkgs.mariadb}/bin/mysql -u root";
|
||||
bootstrapSql = pkgs.writeText "mysql-bootstrap.sql" ''
|
||||
SET @wiki_pass = FROM_BASE64('$wiki_pass_b64');
|
||||
SET @repl_pass = FROM_BASE64('$repl_pass_b64');
|
||||
|
||||
CREATE DATABASE IF NOT EXISTS \`${dbName}\`;
|
||||
|
||||
SET @create_wiki_user = CONCAT(
|
||||
"CREATE USER IF NOT EXISTS '${wikiUser}'@'localhost' IDENTIFIED BY ",
|
||||
QUOTE(@wiki_pass)
|
||||
);
|
||||
PREPARE stmt FROM @create_wiki_user;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @alter_wiki_user = CONCAT(
|
||||
"ALTER USER '${wikiUser}'@'localhost' IDENTIFIED BY ",
|
||||
QUOTE(@wiki_pass)
|
||||
);
|
||||
PREPARE stmt FROM @alter_wiki_user;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
GRANT ALL PRIVILEGES ON \`${dbName}\`.* TO '${wikiUser}'@'localhost';
|
||||
|
||||
SET @create_repl_user = CONCAT(
|
||||
"CREATE USER IF NOT EXISTS '${replicationUser}'@'%' IDENTIFIED BY ",
|
||||
QUOTE(@repl_pass)
|
||||
);
|
||||
PREPARE stmt FROM @create_repl_user;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @alter_repl_user = CONCAT(
|
||||
"ALTER USER '${replicationUser}'@'%' IDENTIFIED BY ",
|
||||
QUOTE(@repl_pass)
|
||||
);
|
||||
PREPARE stmt FROM @alter_repl_user;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO '${replicationUser}'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
'';
|
||||
in
|
||||
{
|
||||
services.mysql = {
|
||||
enable = true;
|
||||
package = pkgs.mariadb;
|
||||
|
||||
settings.mysqld = {
|
||||
bind-address = "0.0.0.0";
|
||||
server-id = 1;
|
||||
log_bin = "mysql-bin";
|
||||
log_slave_updates = 1;
|
||||
binlog_format = "ROW";
|
||||
gtid_strict_mode = 1;
|
||||
innodb_file_per_table = 1;
|
||||
innodb_buffer_pool_size = "4G";
|
||||
innodb_log_file_size = "512M";
|
||||
innodb_flush_method = "O_DIRECT";
|
||||
innodb_flush_neighbors = 0;
|
||||
innodb_io_capacity = 1000;
|
||||
innodb_io_capacity_max = 2000;
|
||||
max_connections = 80;
|
||||
thread_cache_size = 100;
|
||||
table_open_cache = 4000;
|
||||
tmp_table_size = "64M";
|
||||
max_heap_table_size = "64M";
|
||||
performance_schema = true;
|
||||
slow_query_log = 1;
|
||||
long_query_time = 1;
|
||||
skip_name_resolve = 1;
|
||||
};
|
||||
};
|
||||
|
||||
age.secrets.mysql-replication = {
|
||||
file = ../../secrets/mysql-replication.age;
|
||||
owner = "mysql";
|
||||
group = "mysql";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
systemd.services.mysql-bootstrap = {
|
||||
description = "Create MediaWiki database and users";
|
||||
after = [ "mysql.service" ];
|
||||
requires = [ "mysql.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
read_secret_b64() {
|
||||
tr -d '\n' < "$1" | base64 -w0
|
||||
}
|
||||
|
||||
wiki_pass_b64="$(read_secret_b64 ${config.age.secrets.mysql-mediawiki.path})"
|
||||
repl_pass_b64="$(read_secret_b64 ${config.age.secrets.mysql-replication.path})"
|
||||
|
||||
${mysqlCli} <<SQL
|
||||
$(< ${bootstrapSql})
|
||||
SQL
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.mediawiki-init = {
|
||||
after = [ "mysql-bootstrap.service" ];
|
||||
requires = [ "mysql-bootstrap.service" ];
|
||||
};
|
||||
}
|
||||
9
modules/wiki-replica/mediawiki.nix
Normal file
9
modules/wiki-replica/mediawiki.nix
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{ lib, ... }:
|
||||
{
|
||||
services.mediawiki.extraConfig = lib.mkAfter ''
|
||||
$wgReadOnly = "This wiki is a read-only replica.";
|
||||
$wgEnableUploads = false;
|
||||
'';
|
||||
|
||||
systemd.services.mediawiki-init.wantedBy = lib.mkForce [ ];
|
||||
}
|
||||
92
modules/wiki-replica/mysql.nix
Normal file
92
modules/wiki-replica/mysql.nix
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
config,
|
||||
pkgs,
|
||||
siteConfig,
|
||||
...
|
||||
}:
|
||||
let
|
||||
replicationUser = siteConfig.database.replicationUser;
|
||||
mysqlCli = "${pkgs.mariadb}/bin/mysql -u root";
|
||||
initReplicaSql = pkgs.writeText "mysql-init-replica.sql" ''
|
||||
STOP SLAVE;
|
||||
SET @repl_pass = FROM_BASE64('$repl_pass_b64');
|
||||
SET @change_master = CONCAT(
|
||||
"CHANGE MASTER TO ",
|
||||
"MASTER_HOST='${siteConfig.hosts.primary.tailscaleName}', ",
|
||||
"MASTER_USER='${replicationUser}', ",
|
||||
"MASTER_PASSWORD=", QUOTE(@repl_pass), ", ",
|
||||
"MASTER_USE_GTID=slave_pos"
|
||||
);
|
||||
PREPARE stmt FROM @change_master;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
START SLAVE;
|
||||
SHOW SLAVE STATUS\G
|
||||
'';
|
||||
in
|
||||
{
|
||||
services.mysql = {
|
||||
enable = true;
|
||||
package = pkgs.mariadb;
|
||||
|
||||
settings.mysqld = {
|
||||
bind-address = "127.0.0.1";
|
||||
server-id = 2;
|
||||
relay_log = "relay-bin";
|
||||
log_slave_updates = 1;
|
||||
read_only = 1;
|
||||
super_read_only = 1;
|
||||
gtid_strict_mode = 1;
|
||||
innodb_file_per_table = 1;
|
||||
innodb_buffer_pool_size = "2G";
|
||||
innodb_log_file_size = "256M";
|
||||
innodb_flush_method = "O_DIRECT";
|
||||
innodb_flush_neighbors = 0;
|
||||
innodb_io_capacity = 500;
|
||||
innodb_io_capacity_max = 1000;
|
||||
max_connections = 40;
|
||||
thread_cache_size = 50;
|
||||
table_open_cache = 2000;
|
||||
tmp_table_size = "32M";
|
||||
max_heap_table_size = "32M";
|
||||
performance_schema = true;
|
||||
slow_query_log = 1;
|
||||
long_query_time = 1;
|
||||
skip_name_resolve = 1;
|
||||
};
|
||||
};
|
||||
|
||||
age.secrets.mysql-replication = {
|
||||
file = ../../secrets/mysql-replication.age;
|
||||
owner = "mysql";
|
||||
group = "mysql";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
services.rsyncd = {
|
||||
enable = true;
|
||||
settings = {
|
||||
globalSection = {
|
||||
"use chroot" = false;
|
||||
};
|
||||
sections.mediawiki-images = {
|
||||
path = "/var/lib/mediawiki/images";
|
||||
comment = "MediaWiki upload mirror target";
|
||||
"read only" = false;
|
||||
list = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
(pkgs.writeShellScriptBin "init-replica" ''
|
||||
set -euo pipefail
|
||||
|
||||
repl_pass_b64="$(tr -d '\n' < ${config.age.secrets.mysql-replication.path} | base64 -w0)"
|
||||
|
||||
${mysqlCli} <<SQL
|
||||
$(< ${initReplicaSql})
|
||||
SQL
|
||||
'')
|
||||
];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue