From b3c0113d8a593a8793b954fc9a9e31f4084198e7 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Thu, 13 Aug 2026 22:43:21 +0200 Subject: [PATCH] Hotfix posgresql (mediawiki pkg et WSOAuth), rollback popups Fix pass tealc --- hosts/vm/mediakiwi/WSOAuth.patch | 11 + hosts/vm/mediakiwi/mediawiki-patch.nix | 852 ++++++++++++++++++++++++ hosts/vm/mediakiwi/mediawiki.nix | 9 +- secrets/mediakiwi/mediawiki-db-pass.age | 60 +- 4 files changed, 900 insertions(+), 32 deletions(-) create mode 100644 hosts/vm/mediakiwi/mediawiki-patch.nix diff --git a/hosts/vm/mediakiwi/WSOAuth.patch b/hosts/vm/mediakiwi/WSOAuth.patch index 5da4498..829e86c 100644 --- a/hosts/vm/mediakiwi/WSOAuth.patch +++ b/hosts/vm/mediakiwi/WSOAuth.patch @@ -1,3 +1,14 @@ +diff --git a/sql/postgres/table_wsoauth_multiauth_mappings.sql b/sql/postgres/table_wsoauth_multiauth_mappings.sql +index d2917db..b903ba7 100644 +--- a/sql/postgres/table_wsoauth_multiauth_mappings.sql ++++ b/sql/postgres/table_wsoauth_multiauth_mappings.sql +@@ -1,5 +1,5 @@ + CREATE TABLE /*_*/wsoauth_multiauth_mappings ( +- wsoauth_user int unsigned NOT NULL, ++ wsoauth_user int NOT NULL, + wsoauth_remote_name varchar(512) NOT NULL, + wsoauth_provider_id varchar(255) NOT NULL, + PRIMARY KEY (wsoauth_remote_name, wsoauth_provider_id) diff --git a/src/WSOAuth.php b/src/WSOAuth.php index 3a94c87..e077b9e 100644 --- a/src/WSOAuth.php diff --git a/hosts/vm/mediakiwi/mediawiki-patch.nix b/hosts/vm/mediakiwi/mediawiki-patch.nix new file mode 100644 index 0000000..9e6228f --- /dev/null +++ b/hosts/vm/mediakiwi/mediawiki-patch.nix @@ -0,0 +1,852 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + + inherit (lib) + mkDefault + mkEnableOption + mkPackageOption + mkForce + mkIf + mkMerge + mkOption + ; + inherit (lib) + concatStringsSep + literalExpression + mapAttrsToList + optional + optionals + optionalString + types + ; + + cfg = config.services.mediawiki; + fpm = config.services.phpfpm.pools.mediawiki; + user = "mediawiki"; + group = + if cfg.webserver == "apache" then + config.services.httpd.group + else if cfg.webserver == "nginx" then + config.services.nginx.group + else + "mediawiki"; + + cacheDir = "/var/cache/mediawiki"; + stateDir = "/var/lib/mediawiki"; + + toolsPath = pkgs.symlinkJoin { + name = "mediawiki-path"; + paths = cfg.path; + }; + + pkg = pkgs.stdenv.mkDerivation rec { + pname = "mediawiki-full"; + inherit (src) version; + src = cfg.package; + + installPhase = '' + mkdir -p $out + cp -r * $out/ + + substituteInPlace $out/share/mediawiki/includes/config-schema.php \ + --replace-fail "/usr/bin/" "${toolsPath}/bin/" \ + --replace-fail "\$path/" "${toolsPath}/bin/" + + # try removing directories before symlinking to allow overwriting any builtin extension or skin + ${concatStringsSep "\n" ( + mapAttrsToList (k: v: '' + rm -rf $out/share/mediawiki/skins/${k} + ln -s ${v} $out/share/mediawiki/skins/${k} + '') cfg.skins + )} + + ${concatStringsSep "\n" ( + mapAttrsToList (k: v: '' + rm -rf $out/share/mediawiki/extensions/${k} + ln -s ${ + if v != null then v else "$src/share/mediawiki/extensions/${k}" + } $out/share/mediawiki/extensions/${k} + '') cfg.extensions + )} + ''; + }; + + mediawikiScripts = + pkgs.runCommand "mediawiki-scripts" + { + nativeBuildInputs = [ pkgs.makeWrapper ]; + preferLocalBuild = true; + } + '' + mkdir -p $out/bin + makeWrapper ${cfg.phpPackage}/bin/php $out/bin/mediawiki-maintenance \ + --set MEDIAWIKI_CONFIG ${mediawikiConfig} \ + --add-flags ${pkg}/share/mediawiki/maintenance/run.php + + for i in changePassword createAndPromote deleteUserEmail renameUser resetUserEmail userOptions edit nukePage update importDump run; do + script="$out/bin/mediawiki-$i" + cat <<'EOF' >"$script" + #!${pkgs.runtimeShell} + become=(exec) + if [[ "$(id -u)" != ${user} ]]; then + become=(exec /run/wrappers/bin/sudo -u ${user} --) + fi + "${"$"}{become[@]}" ${placeholder "out"}/bin/mediawiki-maintenance \ + EOF + if [[ "$i" != "run" ]]; then + echo " ${pkg}/share/mediawiki/maintenance/$i.php \"\$@\"" >>"$script" + else + echo " ${pkg}/share/mediawiki/maintenance/\$1.php \"\''${@:2}\"" >>"$script" + fi + chmod +x "$script" + done + ''; + + dbAddr = + if cfg.database.type == "postgres" then + "${if cfg.database.socket == null then cfg.database.host else cfg.database.socket}" + else if cfg.database.socket == null then + "${cfg.database.host}:${toString cfg.database.port}" + else if cfg.database.type == "mysql" then + "${cfg.database.host}:${cfg.database.socket}" + else + throw "Unsupported database type: ${cfg.database.type} for socket: ${cfg.database.socket}"; + + mediawikiConfig = pkgs.writeTextFile { + name = "LocalSettings.php"; + checkPhase = '' + ${cfg.phpPackage}/bin/php --syntax-check "$target" + ''; + text = + let + dbSettings = + if cfg.database.type == "sqlite" then + '' + $wgSQLiteDataDir = "${cfg.database.path}"; + '' + else + '' + $wgDBserver = "${dbAddr}"; + $wgDBport = "${toString cfg.database.port}"; + $wgDBuser = "${cfg.database.user}"; + ${optionalString ( + cfg.database.passwordFile != null + ) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"} + + ${optionalString (cfg.database.type == "mysql" && cfg.database.tablePrefix != null) '' + # MySQL specific settings + $wgDBprefix = "${cfg.database.tablePrefix}"; + ''} + + ${optionalString (cfg.database.type == "mysql") '' + # MySQL table options to use during installation or update + $wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary"; + ''} + ''; + in + '' + . + ''; + }; + + socket = mkOption { + type = types.nullOr types.path; + default = + if (cfg.database.type == "mysql" && cfg.database.createLocally) then + "/run/mysqld/mysqld.sock" + else if (cfg.database.type == "postgres" && cfg.database.createLocally) then + "/run/postgresql" + else + null; + defaultText = literalExpression "/run/mysqld/mysqld.sock"; + description = "Path to the unix socket file to use for authentication. Used only if database type is not SQLite."; + }; + + createLocally = mkOption { + type = types.bool; + default = cfg.database.type == "mysql" || cfg.database.type == "postgres"; + defaultText = literalExpression "true"; + description = '' + Create the database and database user locally. + This currently only applies if database type "mysql" or "postgres" is selected. + ''; + }; + }; + + nginx.hostName = mkOption { + type = types.str; + example = literalExpression "wiki.example.com"; + default = "localhost"; + description = '' + The hostname to use for the nginx virtual host. + This is used to generate the nginx configuration. + ''; + }; + + httpd.virtualHost = mkOption { + type = types.submodule { }; # (import ../web-servers/apache-httpd/vhost-options.nix); + example = literalExpression '' + { + hostName = "mediawiki.example.org"; + adminAddr = "webmaster@example.org"; + forceSSL = true; + enableACME = true; + } + ''; + description = '' + Apache configuration can be done by adapting {option}`services.httpd.virtualHosts`. + See [](#opt-services.httpd.virtualHosts) for further information. + ''; + }; + + poolConfig = mkOption { + type = + with types; + attrsOf (oneOf [ + str + int + bool + ]); + default = { + "pm" = "dynamic"; + "pm.max_children" = 32; + "pm.start_servers" = 2; + "pm.min_spare_servers" = 2; + "pm.max_spare_servers" = 4; + "pm.max_requests" = 500; + }; + description = '' + Options for the MediaWiki PHP pool. See the documentation on `php-fpm.conf` + for details on configuration directives. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + description = '' + Any additional text to be appended to MediaWiki's + LocalSettings.php configuration file. For configuration + settings, see . + ''; + default = ""; + example = '' + $wgEnableEmail = false; + ''; + }; + + }; + }; + + imports = [ + (lib.mkRenamedOptionModule + [ "services" "mediawiki" "virtualHost" ] + [ "services" "mediawiki" "httpd" "virtualHost" ] + ) + ]; + + config = mkIf cfg.enable { + + assertions = [ + { + assertion = + cfg.database.createLocally -> (cfg.database.type == "mysql" || cfg.database.type == "postgres"); + message = "services.mediawiki.createLocally is currently only supported for database type 'mysql' and 'postgres'"; + } + { + assertion = + cfg.database.createLocally -> cfg.database.user == user && cfg.database.name == cfg.database.user; + message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true"; + } + { + assertion = cfg.database.createLocally -> cfg.database.socket != null; + message = "services.mediawiki.database.socket must be set if services.mediawiki.database.createLocally is set to true"; + } + { + assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; + message = "a password cannot be specified if services.mediawiki.database.createLocally is set to true"; + } + ]; + + warnings = + lib.optional + ( + cfg.database.type == "sqlite" + && ( + cfg.database.host != null + || cfg.database.port != null + || cfg.database.user != null + || cfg.database.passwordFile != null + || cfg.database.socket != null + ) + ) + '' + The services.mediawiki.database options host, port, user, passwordFile, and socket will be ignored because services.mediawiki.database.type is "sqlite". + '' + ++ lib.optional (cfg.database.type != "sqlite" && cfg.database.path != null) '' + The services.mediawiki.database.path option will be ignored because services.mediawiki.database.type is not "sqlite". + '' + ++ lib.optional (cfg.database.type != "mysql" && cfg.database.tablePrefix != null) '' + The services.mediawiki.database.tablePrefix option has no effect when the services.mediawiki.database.type is not "mysql". + ''; + + services.mediawiki = { + path = with pkgs; [ + diffutils + imagemagick + ]; + skins = { + MonoBook = "${cfg.package}/share/mediawiki/skins/MonoBook"; + Timeless = "${cfg.package}/share/mediawiki/skins/Timeless"; + Vector = "${cfg.package}/share/mediawiki/skins/Vector"; + }; + }; + + services.mysql = mkIf (cfg.database.type == "mysql" && cfg.database.createLocally) { + enable = true; + package = mkDefault pkgs.mariadb; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { + name = cfg.database.user; + ensurePermissions = { + "${cfg.database.name}.*" = "ALL PRIVILEGES"; + }; + } + ]; + }; + + services.postgresql = mkIf (cfg.database.type == "postgres" && cfg.database.createLocally) { + enable = true; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { + name = cfg.database.user; + ensureDBOwnership = true; + } + ]; + }; + + services.phpfpm.pools.mediawiki = { + inherit user group; + phpEnv.MEDIAWIKI_CONFIG = "${mediawikiConfig}"; + phpPackage = cfg.phpPackage; + settings = + ( + if (cfg.webserver == "apache") then + { + "listen.owner" = config.services.httpd.user; + "listen.group" = config.services.httpd.group; + } + else if (cfg.webserver == "nginx") then + { + "listen.owner" = config.services.nginx.user; + "listen.group" = config.services.nginx.group; + } + else + { + "listen.owner" = user; + "listen.group" = group; + } + ) + // cfg.poolConfig; + }; + + services.httpd = lib.mkIf (cfg.webserver == "apache") { + enable = true; + extraModules = [ "proxy_fcgi" ]; + virtualHosts.${cfg.httpd.virtualHost.hostName} = mkMerge [ + cfg.httpd.virtualHost + { + documentRoot = mkForce "${pkg}/share/mediawiki"; + extraConfig = '' + + + + SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" + + + + Require all granted + DirectoryIndex index.php + AllowOverride All + + '' + + optionalString (cfg.uploadsDir != null) '' + Alias "/images" "${cfg.uploadsDir}" + + Require all granted + + ''; + } + ]; + }; + # inspired by https://www.mediawiki.org/wiki/Manual:Short_URL/Nginx + services.nginx = lib.mkIf (cfg.webserver == "nginx") { + enable = true; + virtualHosts.${config.services.mediawiki.nginx.hostName} = { + root = "${pkg}/share/mediawiki"; + locations = { + "~ ^/w/(index|load|api|thumb|opensearch_desc|rest|img_auth)\\.php$".extraConfig = '' + rewrite ^/w/(.*) /$1 break; + include ${config.services.nginx.package}/conf/fastcgi.conf; + fastcgi_index index.php; + fastcgi_pass unix:${config.services.phpfpm.pools.mediawiki.socket}; + ''; + "/w/images/".alias = withTrailingSlash cfg.uploadsDir; + # Deny access to deleted images folder + "/w/images/deleted".extraConfig = '' + deny all; + ''; + # MediaWiki assets (usually images) + "~ ^/w/resources/(assets|lib|src)".extraConfig = '' + rewrite ^/w(/.*) $1 break; + add_header Cache-Control "public"; + expires 7d; + ''; + # Assets, scripts and styles from skins and extensions + "~ ^/w/(skins|extensions)/.+\\.(css|js|gif|jpg|jpeg|png|svg|wasm|ttf|woff|woff2)$".extraConfig = '' + rewrite ^/w(/.*) $1 break; + add_header Cache-Control "public"; + expires 7d; + ''; + + # Handling for Mediawiki REST API, see [[mw:API:REST_API]] + "/w/rest.php/".tryFiles = "$uri $uri/ /w/rest.php?$query_string"; + + # Handling for the article path (pretty URLs) + "/wiki/".extraConfig = '' + rewrite ^/wiki/(?.*)$ /w/index.php; + ''; + + # Explicit access to the root website, redirect to main page (adapt as needed) + "= /".extraConfig = '' + return 301 /wiki/; + ''; + + # Every other entry point will be disallowed. + # Add specific rules for other entry points/images as needed above this + "/".extraConfig = '' + return 404; + ''; + }; + }; + }; + + systemd.tmpfiles.rules = [ + "d '${stateDir}' 0750 ${user} ${group} - -" + "d '${cacheDir}' 0750 ${user} ${group} - -" + ] + ++ optionals (cfg.uploadsDir != null) [ + "d '${cfg.uploadsDir}' 0750 ${user} ${group} - -" + "Z '${cfg.uploadsDir}' 0750 ${user} ${group} - -" + ]; + + systemd.services.mediawiki-init = { + wantedBy = [ "multi-user.target" ]; + before = [ "phpfpm-mediawiki.service" ]; + after = + optional (cfg.database.type == "mysql" && cfg.database.createLocally) "mysql.service" + ++ optional (cfg.database.type == "postgres" && cfg.database.createLocally) "postgresql.target"; + script = + let + dbOptions = + if cfg.database.type == "sqlite" then + '' + --dbpath ${lib.escapeShellArg cfg.database.path} \ + --dbname ${lib.escapeShellArg cfg.database.name} \ + '' + else + '' + --dbserver ${lib.escapeShellArg dbAddr} \ + --dbport ${toString cfg.database.port} \ + --dbname ${lib.escapeShellArg cfg.database.name} \ + ${ + optionalString ( + cfg.database.tablePrefix != null + ) "--dbprefix ${lib.escapeShellArg cfg.database.tablePrefix}" + } \ + --dbuser ${lib.escapeShellArg cfg.database.user} \ + ${ + optionalString ( + cfg.database.passwordFile != null + ) "--dbpassfile ${lib.escapeShellArg cfg.database.passwordFile}" + } \ + ''; + in + '' + if ! test -e "${stateDir}/secret.key"; then + tr -dc A-Za-z0-9 /dev/null | head -c 64 > ${stateDir}/secret.key + fi + + ${optionalString (cfg.database.type == "sqlite") "mkdir -p ${cfg.database.path}"} + + echo "exit( \$this->getPrimaryDB()->tableExists( 'user' ) ? 1 : 0 );" | \ + ${cfg.phpPackage}/bin/php ${pkg}/share/mediawiki/maintenance/run.php eval --conf ${mediawikiConfig} && \ + ${cfg.phpPackage}/bin/php ${pkg}/share/mediawiki/maintenance/run.php ${pkg}/share/mediawiki/maintenance/install.php \ + --confpath /tmp \ + --scriptpath / \ + ${dbOptions} \ + --dbtype ${cfg.database.type} \ + --passfile ${lib.escapeShellArg cfg.passwordFile} \ + ${lib.escapeShellArg cfg.name} \ + admin + + ${cfg.phpPackage}/bin/php ${pkg}/share/mediawiki/maintenance/update.php --conf ${mediawikiConfig} --quick --skip-external-dependencies + ''; + + serviceConfig = { + Type = "oneshot"; + User = user; + Group = group; + PrivateTmp = true; + }; + }; + + systemd.services.httpd.after = + optional ( + cfg.webserver == "apache" && cfg.database.createLocally && cfg.database.type == "mysql" + ) "mysql.service" + ++ optional ( + cfg.webserver == "apache" && cfg.database.createLocally && cfg.database.type == "postgres" + ) "postgresql.target"; + + users.users.${user} = { + inherit group; + isSystemUser = true; + }; + users.groups.${group} = { }; + + environment.systemPackages = [ mediawikiScripts ]; + }; +} diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index a578d46..17faefe 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -8,6 +8,10 @@ let phpExtensions = config.services.mediawiki.phpPackage.extensions; in { + # Mauvaise config posgresql, on fixe à la mano temporairement + disabledModules = [ "services/web-apps/mediawiki.nix" ]; + imports = [ ./mediawiki-patch.nix ]; + age.secrets.mediawiki-admin-passwd = { file = ../../../secrets/mediakiwi/mediawiki-admin-passwd.age; owner = "mediawiki"; @@ -23,6 +27,7 @@ in owner = "mediawiki"; }; + # Attention : il ne faut pas mettre de retour à la ligne à la fin du mot de passe ! age.secrets.mediawiki-db = { file = ../../../secrets/mediakiwi/mediawiki-db-pass.age; owner = "mediawiki"; @@ -302,10 +307,10 @@ in name = "Popups"; owner = "wikimedia"; repo = "mediawiki-extensions-Popups"; - rev = "cfe78f2c07fa5cf6edcb109eadd8b4b01df58d92"; + rev = "82742d6e42750bf5b8000c90fc894b304aa95235"; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-lE64Tl0KmUoNHnN2bkNYvhexz5s1wMQ1bDvWSoWTdZU="; + sha256 = "sha256-mqiDU7w3B3imMLqoTvwgnyk/vCDWeAiTEX1y4dWmNDk="; }; # Auth diff --git a/secrets/mediakiwi/mediawiki-db-pass.age b/secrets/mediakiwi/mediawiki-db-pass.age index 7b86501..9c1c9e7 100644 --- a/secrets/mediakiwi/mediawiki-db-pass.age +++ b/secrets/mediakiwi/mediawiki-db-pass.age @@ -1,33 +1,33 @@ age-encryption.org/v1 --> ssh-ed25519 ZpDcxw XrXXRdePOdCNAkEtYg5mBAxKKu1EkJOrrD0cUT3JAUc -s7VMCZGoJS5BD8v8/JPQxrLw1GYs01uj9LdR6oL9zsM --> piv-p256 ewCc3w A5Qzmn7RYXKT13rdPG6MRWX5nvjAgnbLv7oNqbei2kQJ -lHc9/3C/aD5wzm3AQlYqIddEEBtke478hPSem2pZdvA --> piv-p256 6CL/Pw A+ZT4TG2h/1B1sxTGsz0dk0JI8Aa03rN7JZa7wcLuY2q -WBVW6R65Qm54777ENzWCmIiBKelfF9PjWZEU9QsAPMk --> ssh-ed25519 eOAUSg u95RB+i9FQ+k6V4IUXgVGBcDNIfExZxpnNQ13iQtM0s -rznvSYERswBrYsoBGOzo67vs7wd6FHSZG8YJlAiNYNk +-> ssh-ed25519 ZpDcxw EYeeHlk7G0Ce8Zjz7DOA6umVE58ZNeRb1ao86/romSA +iCLH6+fsNCeM5heK2QFh9kEY44BiDOQ8um4MCNUDeuI +-> piv-p256 ewCc3w A+QtLSWeA4j0756rlETtyvJRiZR3y70+UMs/X1/u0fqq +FslsT1yQ+NNHK8m/eBLWeNcsPu2BnubNO1h/AO95UxY +-> piv-p256 6CL/Pw AxHghSnLJmHJz9rXlP1IQDoigFTkFUQiE7h3c0EMeLsc +bmI1TbflKxJ6Axg/TgDEtCV0pw7tY6V/pMl4CJGjvL8 +-> ssh-ed25519 eOAUSg TPhT5qmh2Y9b0eBa41uTdIDdZEcDZtR6tEV8Jo1MuCk +gBD5CQlnxMYohS13RvzoDDQeGDhYOEcS18Xv1S/5f+M -> ssh-rsa REaZBA -jX3byskGzK07aZRbZCUnv8ma8Wwnnav1LszzuKjN5ULBem6tVGk33ak2oRVnNfsc -Vj8jHVySN9b+px1SQk/a/7o/717Xejvoa71xeIMacNuvGNr1VgaISF9niKI5J0Gd -O8KSRcRdkxqBdQ+WIVj1KKjabV9kBxxwf6r1CjH4U1sGqpxCvHT98v20DPYrbzpm -1rR8R+z9v/d75bCmQ/RCzkAfxNhuQkQ0h1xZYuCnlolnRK2TvFbUAO9CcfKCwgia -dwj+R8tt+Pcu5LcSatt7tqqm8X3CKbyb4SJxCHZRq8uz7kDJ5pKKqRIpFL1Gv5jc -2jZEK5tZoKEtnymE7T145Dww3hUdK7lx8Upnm825Unr62DLRNY3JzWnsMpVw0Wkn -tzbul4wLwIkja5388cmpQgHywrTiqtm7uLCs2AeKY6oq/7afVPf2bkgwW+J7bWfH -bDEBavJu/PJa3dYhLR4gdiH3i+rSivbfMbQgfdRCx94liniv2adi6SmpW6dystoL +NtWXPRN/qPJ5XUZNXt64Rt3brMfbXxP2ARvXo3wpnB0ed0QPnX29OTyVm/O2VNrY +5jPouFaxWwDBuwyJ/HyJJB5SdrE188u7du/M0Z8l1/H8i2lpkp98XS64AEXkvrHo +oWFsRloCWyeIU3m1MtUdqu/c3pnqyiE1SlCnajb6MUUrhDe2CdEEgJxFsIyMwXpW +mkwtHxe9ngVebRLY3vw9OVhtqrKeKrNvSGGCE+IFn6kWuNoGD/rDI52kx76sNa0D +531FFAdsiPt1VKZi0YBaUfRP407sc6rw8R258jFCeYpa58Zud62BWsT0d464gPkD +tJQUZecCPRW0jflpVWzqZHIVurtp3Dqjp/oovmMMJ303/ywL0VdZR6PeMndGOkf+ +ame4zqTwOyQoxSkU+62D/KeSzM0QlVqAZhL+lMfYkFCYizZYQZSlLAHi7nF3704W +S+nrv+FNNxLYT6zlq0hHokP9fIfUct/eFfMmV2ff9E77Tow/79TpgQnfxxdtJWJG --> ssh-ed25519 J/iReg zCuMlBFN4eVMnh5vHKqvlJlLg6ZcwAy7wHlvVnQABjw -elWuCugOpDB80l/uEOJnNKEem5xoePkyCD3pmELJ8cs --> ssh-ed25519 GNhSGw Wv9wMTi6QUexDlbucYb9SGS4jNFNFs+hoWa5u5HGmzY -lrswEeBFRfSkHf0hQ4YcEFmV6ytusrk0zdIsMD+QbQ8 --> ssh-ed25519 eXMAtA 2BfG6GmISd1+jHa9OruAWnNjbOWJ1kSEAABMAWRA/kI -BB3JmIEP6ALQuB41BKQK2Wp8ZZw4XEnP2Boh7icE+Hw --> ssh-ed25519 5hXocQ hZbub90RRPk0Vas8CCLwdTFxw9DNLbw3o2YRNrKsxAU -VVFz46gOtFCQIQKDeKiuxedT+dt+P9fhY8sTix5uLxY --> ssh-ed25519 bRHVVA pdEbXrfSgJ2rBKLuHXH0LLnKQy8BbTw0siNm5iLglgY -yRe+LVzvN0l13j4u4K3lGrzn+HokcL2kRw91n1/JIEY --> ssh-ed25519 HgW9eA tPNgD0elE7Bqh87hYKRlrU0HPgTHlNyqMEMqQfkhlgQ -AtAAtoU/DYVBaQJKPwAcAKrr2KXyJ/iyLUV8LFSmCF0 ---- nTU2XNZl52MtMZI+39D+eVeoX55RSWRERVgJNKti3Qk -J` 긌20RO7 ssh-ed25519 J/iReg ZAxHEQjYmqQkMFth5Lp344f0JqwfGh4CO6DEG+uMmys +qlLlf9eLNNw3/o++6LVpy2Anj3TWkNbcJoJ/rP4JKsI +-> ssh-ed25519 GNhSGw 44kEvp1o41BaNRwFnAjKB4K3otIYhN7QN9FgCd+eSQ4 +fqZM/JCIderSDDrxckv4VLZu64Dfx275UVmfKsozpKU +-> ssh-ed25519 eXMAtA mPn1SPpt7RkhMBLqUZ4mWGvqNeGtUN+pcA9jjKKGPGU +bGyck1ZRHRlzk+HVmL3e99MiVFxjTuC7v/6WHjRjH8A +-> ssh-ed25519 5hXocQ bT0MsqVcYEcB/ckkuAC0zbH5+pfs4iPso9MHhVUyx2U +jZQOFmGWnZMtdTS5ePKIvnQFiOVsZTC/A09ZmXzveho +-> ssh-ed25519 bRHVVA NSnH4xuAWnDgUIpi+csZOvq0K6u99Dt1IUGDrzZGcgQ +L9WOetohzpDPAcF/1jGPmWnBhazSDyIU/0or+il2/ug +-> ssh-ed25519 HgW9eA LdB9v8WGmExJffyQa8KuudNr97nyxshWj9j0YMDJKlI +0BCRYHWmulVjwtlYUR2Xi4rdnLSRQTLL2jn8N5rubSo +--- y4i6KM8sEq07Sx594lC7WX9hEtq9ce4Vm9tTU8loJtc +2Ջr};,"ív&d}8U_VYnhZħg`Nf` \ No newline at end of file