Skip to content

About

Mirror of git.pop.coop/Keel-Linux/keel-wordpress. Issues and PRs welcome here or there. WordPress - Blog Publishing Platform

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

 
 

Latest commit

 

History

275 Commits

Folders and files

Repository files navigation

WordPress - Blog Publishing Platform

WordPress is a state-of-the-art publishing platform with a focus on aesthetics, web standards, and usability. It is one of the worlds most popular blog publishing applications, includes tons of powerful core functionality, extendable via literally thousands of plugins, and supports full theming.

This appliance includes all the standard features in TurnKey Core, and on top of that:

  • WordPress configurations:

    • Installed from upstream source code to /var/www/wordpress
    • Integrated upgrade mechanism: get WordPress updates straight from WordPress' creator Automattic.
    • Uploading of media such as images, videos, etc.
    • Permalinks configuration supported through admin console (convenience)
    • Plugin and theme management remains available through WordPress. These directories intentionally contain web-writable executable code; install only updates and extensions you trust.
    • WordPress core is root-owned and does not update automatically. Apply a supervised core update as root with keel-wordpress-update. The command verifies official WordPress core checksums and restores the appliance ownership boundary after updating.
  • Landing page provides links to WordPress plugin search, plus a number of useful and popular Wordpress plugins (none pre-installed):

  • SSL support out of the box.

  • Postfix MTA (bound to localhost) to allow sending of email (e.g., password recovery).

  • Webmin modules for configuring PHP, MySQL (MariaDB) and Postfix.

See the WordPress docs for further details (including multisite howto).

Keel Linux: the layer, the first boot and the update path

This appliance is built as a Keel layer, not as a whole root filesystem:

bt-layer wordpress --parent lnmp

The parent is the ``lnmp`` layer (handbook decision 0059: nginx is the only web server in Keel, and Apache is retired). The chain is Core, Web, LNMP and WordPress. Web brings nginx; LNMP brings PHP-FPM 8.4 and MariaDB 11.8 and the first boot hooks of the database. What this recipe adds is the PHP modules that WordPress wants, WordPress and wp-cli, its route, its first boot hook and its manifest, and nothing else.

LNMP also brings a stack check: the first boot hook 40lnmp-check, which makes a test database keel_check, and the page /keel-check/ that reads it. A WordPress product ships no test database and no page that shows the stack, so conf.d/main removes the hook, the route 50-keel-lnmp.conf and the page. The build records each removal in the layer, and keel assemble removes the path from the image. The build check of the image and the boot test prove that none is left; /keel-check/ is a URL of WordPress here and answers its 404.

The site is /etc/nginx/keel-default.d/60-keel-wordpress.conf, in the include directory of Keel Web, so it answers in the simple mode and behind Anubis. The default site of Keel Web already has a location /, so the site is made of regular expressions, which nginx tries after the prefixes and in file order, and the last one is the catch-all (try_files with a fallback to index.php, which is what pretty permalinks need). It refuses wp-config.php, PHP in uploads and the other directories the web user writes, the PHP files of wp-includes, hidden files except .well-known, and log and dump files. xmlrpc.php is reachable, as it was under Apache.

The appliance has a manifest, wordpress.yaml (base lnmp), so an instance description names it with appliance.name: wordpress. The database account is lnmp's option db_user: set it to wordpress.

Behind Anubis and Coraza (the cloud modes) two files of this layer make the site work. /etc/nginx/keel-front.d/60-keel-wordpress.conf sets the body limit of the front site of Keel Web to 64 MB, as PHP has it (the front site has 1 MB without it, and answers 413 to a media upload). It needs keel-web 0.3.2. The files of an upload are no argument for Coraza (keel-overlay-coraza 0.1.4), so a worker keeps its memory: an upload of 32 MiB costs about 10 MiB. And /etc/coreruleset/plugins/keel-wordpress-before.conf keeps the Core Rule Set from reading the content fields of a JSON write to /wp-json/, because the rules take the HTML of the blocks of the editor as an attack.

A replica gets wp-config.php and nothing in the database. The WordPress schema is made by the first boot of a primary or a standalone node and not by the image, because keel replaces a replica's databases with a copy of the primary and refuses to do it without --destroy-local-database when the server holds a schema that is not its own.

Nothing is decided at build time

Upstream's conf.d/main installs WordPress while the image is being built, with DB_PASS=turnkey and ADMIN_PASS=turnkey written in the recipe. A layer is published once and reused by every machine built on it, so those are the same two passwords on every one of them. This recipe leaves the layer with no credential at all: no wp-config.php, and a database account created with a password hash that no input produces.

The installation happens on the first boot, from /etc/keel/instance.yaml, and the WordPress installer is never offered to a visitor. firstboot.d/40wordpress reads:

Every secret is a file reference in the instance description, never a literal. Neither password is ever an argument of another process: wp-config.php is written by a shell function, and the administrator's password is set through the environment of one wp-cli call, and the statements that give the database account its password reach mysql on its standard input. The hook finishes by asking the site to authenticate that account, and to refuse a wrong password, before it lets the site serve anything.

The hook owns its database account. Under keel-init the inithooks conf is empty, so the parent layer's 35mysqlpass sets its own account admin, and 40wordpress sets wordpress with the password that it asks.

A headless first boot that does not declare the two passwords asks nothing: nobody can answer tty1 of a container (inithooks' lib/console.sh). The hook records that it is deferred and finishes, the site answers 503, and the first login starts keel-init, which asks the passwords and completes the installation.

wp-config.php derives WP_HOME and WP_SITEURL from the Host header of each request rather than reading them from the database, so the site answers on whatever address or name reaches it. A WordPress whose recorded site URL is not the one it is reached by answers a permanent redirect to the recorded one, which for an appliance reached by its IPv6 literal means sending the visitor to a name that does not resolve. The recorded URL is the fallback for WP-CLI and wp-cron, which carry no Host header.

wp-config.php holds no password and no salt. The site logs in to MariaDB over the socket as www-data (unix_socket), and wp-cli as root logs in as root over the same socket. The salts are options of the database: the first boot stores them (lib/wordpress-set-salts.php), and wp_salt() reads them when wp-config.php defines none. On a cloud pair keel seeds the database to the replica and replicates it, so both nodes have one account and one set of salts, and a login stays a login after a failover. No salt is fetched from WordPress's API, because a first boot must not need the internet.

What our archive updates, and what it does not

The appliance ships /etc/apt/sources.list.d/keel.sources enabled for the signed trixie distribution of https://archive.keellinux.org, with Signed-By naming /usr/share/keyrings/keel-archive-keyring.gpg from the keel-archive-keyring package, and /etc/apt/preferences.d/keel pins that origin at 1001. So on a booted appliance:

apt-get update      # reads our archive and verifies its signature
apt-get upgrade     # takes newer Debian and newer project packages
apt-get install keel-transition   # a project package, from our archive

covers two things:

  • the system, from Debian and Debian security, as any Debian machine does;
  • the project's own packages: inithooks, confconsole, keel, keel-archive-keyring, keel-transition, fab.

The boot test proves that path rather than proving that a newer version happens to exist on the day it runs. It asserts that apt-get update verifies our archive's signature; that apt-cache policy shows a project package the image carries with our archive as the source of its candidate at that pin priority; that keel-transition, which the image does not carry, comes down from our archive against the digest the signed index holds; and that a project package the image does carry is downloaded again and put through dpkg. apt refuses an archive it cannot verify before it asks for a single byte, so those last two are the signature reaching real files and not only an index.

All of it stays inside our own archive on purpose. Installing keel-transition on a real appliance also pulls gpgv from Debian, which a Debian 13 image does not carry because apt verifies with sqv; that is fine on a machine with a route to Debian, and the CI runner has no IPv4 route out, so a test step that needed it would be measuring the runner's network instead of this appliance.

It does not cover WordPress core, and saying otherwise would be a lie in the documentation. Nobody packages WordPress for Debian and we do not package it either: it is unpacked from a release archive pinned by digest in conf.d/main and verified a second time against WordPress's own per file checksums. WordPress core therefore updates through WordPress's own mechanism, either from the dashboard or, preferably on this appliance, with:

keel-wordpress-update

which runs wp core update, verifies WordPress's checksums again and puts the ownership boundary back: core and wp-config.php root owned, and only wp-content/uploads, cache, upgrade, plugins and themes writable by the web server. Automatic core updates are off in wp-config.php for the same reason: a supervised update keeps that boundary, an unsupervised one does not.

One consequence of the pin worth knowing, because it is a priority that downgrades as well as upgrades: an image must not carry a project package that the signed archive has not got. If it does, apt-get upgrade replaces the newer installed package with the archive's older one. The boot test's policy check is where that shows up.

Plugins and themes are WordPress's business and are installed through WordPress. Those two directories are deliberately web writable, which means they hold executable code the web server can change: install only what you trust.

Ports, and what listens

80 and 443 are the site, on both address families, and 12321 is Webmin from Core. 3306 is closed to the network: this appliance reaches its own database over the loopback, and wp-config.php names [::1]. On a pair the manifest of lnmp makes 3306 a mesh port, open on the WireGuard interface only. /etc/cron.d/wordpress-cron runs the due events with wp cron event run as the web user, not through a request to wp-cron.php. Port 80 serves the site as 443 does while the certificate is the self-signed one of the first boot, and redirects to HTTPS when a certificate authority's is in place (keel-web 0.4.0, handbook decision 0079). The loopback addresses of port 80 always serve the site, so http://127.0.0.1/ reaches WordPress on the machine; /keel-health there is Keel Web's probe.

Adminer and the rest of turnkey/lamp are left out. The database is administered through webmin-mysql in the panel Core already carries. The parent layer's own admin database account is left unable to authenticate unless an instance declares it, which is the state that layer publishes it in.

Running one

keel/instance.example.yaml is the description to start from, and tests/README.md is how to boot and check one by hand. The appliance's own view of itself, on the machine:

keel diff --spec /etc/keel/instance.yaml

Credentials (passwords set at first boot)

Every password comes from the instance description; none is set here and none is typed at a console on a headless first boot.

  • Webmin and SSH: username root, from secrets.root_password
  • the MariaDB account of lnmp (app.options.db_user), from secrets.db_password; the site logs in as www-data over the socket, with no password
  • WordPress: username admin (app.options.admin_user), from secrets.app_password

Adminer is not installed, so it has no account here.

About

Mirror of git.pop.coop/Keel-Linux/keel-wordpress. Issues and PRs welcome here or there. WordPress - Blog Publishing Platform

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages