Redis database layer for Keel appliances, built on core. Compatible with
TurnKey Linux appliances, and corresponding to the upstream appliance
turnkeylinux-apps/redis for
the database half of what that appliance is:
git clone --branch v1.0.4 \
https://github.com/keel-linux/unit-redis.git unit.d/redis
bt-layer redis --parent core
The server comes from the unit.d/redis component: keel-linux/unit-redis carries its plan, its overlay
and its conf script, fab applies it, and bt-layer records it in the layer
manifest as units redis@1.0.4. Materialising unit.d from the pin is
the assembly step decision 0010 names as work of the project and does not
exist yet, so the clone above is that step for now; unit.d/ is ignored by
git here.
It is a layer, not a product: any appliance that needs Redis is built on it, so the server is fetched, configured and measured once. Nothing is stopping it being run on its own; it boots, it listens on loopback, and Webmin administers the machine around it.
Redis is the third of the three engines decision 0013 settled on, and the one
where the topology modes are cheapest: replication is one directive,
replicaof with masterauth; failover has redis-sentinel, packaged
beside the server; and sharding is native in Redis Cluster with no extra
package at all, where PostgreSQL has none packaged and MariaDB needs Spider.
None of that is in this layer. This is the standalone appliance, and the
modes come next, in their own issue, on the reading this layer makes
possible.
Makefile |
the component under unit.d, this overlay, the firewall ports, the project package check copied in for the build |
plan/main |
the client and the project packages; the server is the component's plan |
conf.d/main |
the checks on what the component did, the project package upgrade from the Keel archive and its check |
bin/keel-project-packages |
each project package installed at apt's candidate, from the Keel archive, in the track's suite |
keel/instance.example.yaml |
the instance description an operator starts from |
tests/ |
bats for the shell, boot-test.sh for the machine |
Webmin comes from core and answers on 12321. Debian 13 packages
webmin-mysql and webmin-postgresql and no equivalent for Redis,
so unlike the other two database layers this one adds no module, and says so
in three places rather than leaving a silence: here, in conf.d/main,
which fails the build the day one is packaged, and in the boot test, which
checks the machine offers none.
An instance description declares the secret once:
secrets:
db_password:
file: /etc/keel/secrets/db_password
It renders to DB_PASS, the same variable the MariaDB and PostgreSQL
hooks read. On those engines it is a database account's password. Redis has
no database account. Its secret is one of two things, and the vocabulary of
decision 0013 leaves both open: requirepass, which is the password of the
built in default user, or an ACL user with a password of its own.
This layer's component uses an ACL user, named admin
(app.options.db_user renames it), so a client authenticates with a user
name and that password:
REDISCLI_AUTH=$(cat /etc/keel/secrets/db_password) \
redis-cli -h ::1 -p 6379 --user admin PING
Two consequences an operator will meet, and both are the reason for the choice rather than side effects of it:
- The server answers ``INFO`` to anybody who can reach it, and no key to
anybody without the secret.
keel inspectreadsdatabase.server.rolefromINFO replicationandINFO cluster, and it never reads a secret to get past a refusal. Withrequirepassset, this appliance could not say that it isstandalone, and it could not later say that it is aprimaryor areplicaeither, which is the whole of what the next issue is built on. Sodefaultis left with exactly one command,INFO, no key and no channel. - Nothing on the machine can print the secret back. An ACL rule takes the
SHA-256 of a password, so
/etc/redis/redis.conf.d/50-keel-secret.confholds a digest,root:redis 0640. The upstream appliance wroterequirepassinto/etc/redis/redis.conf, which the package shipsroot:root 0644, and shippedturnkey-redis-pw getand a confconsole plugin to print it out again into/root/redis_password.txt. Neither tool is here, because neither has anything to read.
Before the first boot the account does not exist. keel-mariadb
publishes its account with a password hash no input produces; the same shape
was tried here and Redis refuses it, because a user may be declared only
once across configuration files. So the account is declared once, by the
first boot, or not at all, and the property that mattered holds either way:
a layer is published once and reused by every appliance built on it, so a
password chosen at build time would be the same password everywhere, and a
random one would make the layer irreproducible (brief section 5.4).
One Redis behaviour is worth knowing before reading any of the checks:
redis-cli exits 0 when the server answers with an error. A wrong password
prints AUTH failed: WRONGPASS ... and exits 0. Every check in the
component, in this recipe and in the boot test therefore reads the answer.
The server listens on ::1 and 127.0.0.1, as literal addresses, and on
nothing else. Debian ships bind 127.0.0.1 -::1, where the dash marks the
address as optional, so a server that cannot bind ::1 starts anyway and
answers one family. A name is never used: Debian maps ::1 to
ip6-localhost and never to localhost, which is how the PostgreSQL
appliance shipped listening on IPv4 only (docs/traps.md).
The port is not opened to the network. This layer exists to be built on, and the appliance above it is on the same machine. An appliance that really has remote clients opens the port, says who may connect and terminates TLS; that is a decision an appliance makes, and the console's cloud modes are where it will be made.
Upstream's redis appliance also bundles Redis Commander on nginx with
a Node.js runtime and pm2, a landing page served by them,
turnkey-redis-pw and a confconsole plugin that writes the Redis password
to /root/redis_password.txt. None of that is here.
Redis Commander needs a web server and a Node.js runtime, and which of each
differs by context. Putting them in the database layer forces a choice that
the layers above would have to undo and make again, which is the same
argument that keeps Adminer out of keel-mariadb. So Redis Commander
arrives with a web stack, if the maintainer wants that artefact: it would be
a different one, built on this layer.
The two password tools go for a different reason: they read requirepass
out of a configuration file, and this appliance's configuration holds a
digest. A tool that cannot work is worse than no tool, and the secret an
operator needs is the file their own instance description points at.
Also not here: redis-sentinel, which Debian packages beside the server,
and Redis Cluster, which needs no package. Both are modes.
Debian 13 also ships valkey-server 8.1.1, the fork made after Redis
changed its licence, and it is recorded in decision 0013 as the ready escape
if Redis tightens further. Everything this layer does is Valkey's too: Valkey
8.1 keeps the ACL vocabulary, the include semantics, the bind syntax
and the INFO sections that keel inspect reads. What differs is names,
not behaviour: /etc/valkey/valkey.conf, valkey-server.service,
valkey-cli. So one unit could serve both, with a variable naming the
flavour, and keel would need valkey-server added beside
redis-server in its engine table. That is a change with its own issue,
not a line smuggled into this one. unit-redis's README sizes it.
tests/README.md has the detail. In short: tests/coverage.sh runs the
bats suite under kcov and gates the shell this layer writes;
tests/boot-test.sh assembles the published layer, boots it headless from
an instance description that declares secrets.db_password from a file,
authenticates over IPv6 with that secret, writes and reads a key, is refused
a key without the secret, checks Webmin over IPv6, reads the role back off
the server with keel inspect, and runs keel diff.
The first boot hook, its library and the component's conf script are measured
in keel-linux/unit-redis, which is where they live.