Dump PostgreSQL pods whatever image they run

The dump assumed the official image's environment and the postgres
superuser. Bitnami keeps its passwords in files, and on this cluster the
superuser password no longer matches the database, so the Gitea database
backup would have failed. The collector now resolves the credentials from
either layout, probes them before dumping so a failed login cannot
truncate the archive, and falls back to a single-database dump when only
the application user works. Verified against both databases on the server.

Also adds the Nextcloud manifest that installs it on the cluster.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dennis Nemec
2026-09-03 21:32:43 +02:00
parent 8f5bc1755e
commit 1339ae864e
3 changed files with 284 additions and 4 deletions

View File

@ -10,6 +10,33 @@ use domain::DomainError;
use crate::host::CommandRunner;
/// Dumps a PostgreSQL pod without knowing which image it runs.
///
/// The official image exposes `POSTGRES_USER`/`POSTGRES_PASSWORD`, Bitnami's keeps the
/// passwords in files and names the superuser's separately. Credentials are probed before
/// dumping so a failed login cannot truncate the archive; a cluster dump is preferred and a
/// single-database dump is the fallback when only the application user works.
pub const POSTGRES_DUMP: &str = r#"
read_secret() { [ -n "$1" ] && [ -f "$1" ] && tr -d '\n' < "$1"; }
su_pw="${POSTGRES_POSTGRES_PASSWORD:-$(read_secret "$POSTGRES_POSTGRES_PASSWORD_FILE")}"
app_pw="${POSTGRES_PASSWORD:-${POSTGRESQL_PASSWORD:-$(read_secret "$POSTGRES_PASSWORD_FILE")}}"
app_user="${POSTGRES_USER:-${POSTGRESQL_USERNAME:-postgres}}"
app_db="${POSTGRES_DB:-${POSTGRES_DATABASE:-${POSTGRESQL_DATABASE:-$app_user}}}"
works() { PGPASSWORD="$2" psql -U "$1" -d postgres -c 'select 1' >/dev/null 2>&1; }
if [ -n "$su_pw" ] && works postgres "$su_pw"; then
PGPASSWORD="$su_pw" pg_dumpall -U postgres
elif [ -n "$app_pw" ] && works "$app_user" "$app_pw"; then
if [ "$(PGPASSWORD="$app_pw" psql -U "$app_user" -d postgres -tAc 'select usesuper from pg_user where usename=current_user')" = "t" ]; then
PGPASSWORD="$app_pw" pg_dumpall -U "$app_user"
else
PGPASSWORD="$app_pw" pg_dump -U "$app_user" -d "$app_db"
fi
else
echo "no usable postgres credentials in the pod environment" >&2
exit 1
fi
"#;
fn unavailable(what: &str, out: &crate::host::Output) -> DomainError {
let tail: Vec<&str> = out
.stderr
@ -653,6 +680,24 @@ mod tests {
);
}
#[test]
fn the_dump_command_copes_with_both_postgres_image_families() {
let cmd = POSTGRES_DUMP;
// the official image exposes the superuser and its password directly
assert!(cmd.contains("POSTGRES_PASSWORD"), "{cmd}");
assert!(cmd.contains("POSTGRES_USER"), "{cmd}");
// bitnami keeps them in files and names the superuser password separately
assert!(cmd.contains("POSTGRES_POSTGRES_PASSWORD_FILE"), "{cmd}");
assert!(cmd.contains("POSTGRES_PASSWORD_FILE"), "{cmd}");
// a cluster dump is preferred, with a single-database dump as the fallback
assert!(cmd.contains("pg_dumpall"), "{cmd}");
assert!(cmd.contains("pg_dump -U"), "{cmd}");
// credentials are probed before dumping, so a failure cannot truncate the output
assert!(cmd.contains("psql"), "{cmd}");
// and an unusable database fails loudly instead of writing an empty archive
assert!(cmd.contains("exit 1"), "{cmd}");
}
#[tokio::test]
async fn collector_builds_kubectl_and_tar_commands() {
let r = Arc::new(Rec::default());