- assert encoding BEFORE role/password mutation on re-run, so an old
SQL_ASCII DB aborts with no side effects (codex finding 1)
- ensure_utf8_locale_active honours its locale argument consistently in
match, locale.gen line and export (codex finding 2)
- assert_db_encoding_utf8 uses argv-clean runuser psql with :'db' literal
binding instead of nested su -c shell; docs keep su - postgres -c
(codex finding 3)
A PostgreSQL cluster/database freezes its encoding at initdb / CREATE
DATABASE time; a C (non-UTF-8) locale yields a SQL_ASCII cluster, which
makes psycopg3 return bytes and crashes SQLAlchemy. Harden the installer
and add a reusable pattern for future DB installers:
- ensure_utf8_locale_active: generate AND activate en_US.UTF-8 for the
install process before the server package runs initdb; abort if the
locale is not actually available
- create the database explicitly with TEMPLATE template0 ENCODING 'UTF8'
LC_COLLATE/LC_CTYPE 'en_US.UTF-8' instead of inheriting the cluster
default
- assert_db_encoding_utf8: post-install guard, abort with an actionable
message if pg_encoding_to_char is not UTF8 (catches old SQL_ASCII DBs
on re-run too)
- credentials/README docs use su - postgres -c (minimal LXCs have no sudo)
LXC templates ship without a configured locale, so every apt/perl run
warned 'Setting locale failed'. setup_base_apt now exports C.UTF-8 for
the install run itself, installs the locales package, generates
en_US.UTF-8 and sets it as the system default via update-locale.
prompt_lxc_config asks 'SSH-Root-Login erlauben? [Y/n]' (env-presettable
via SSH_ROOT_LOGIN, validated, normalized to yes|no). The bootstrap passes
the value into the container; configure_ssh_root_login writes
/etc/ssh/sshd_config.d/zz-root-login.conf (yes -> PermitRootLogin yes,
no -> prohibit-password) and reloads sshd.
- CTID prompt re-prompts on invalid interactive input (was: abort)
- env-provided NAMESERVER is validated when non-empty ('' stays inherit)
- prompt_validated handles EOF (no infinite loop, clean abort under -e)
- 10# base forcing in vlan/cidr/ipv4 arithmetic (leading zeros are not
octal errors); is_clean_ascii rejects embedded newline/tab explicitly
(command substitution strips trailing newlines); is_ipv4_list checks
the whole string before word splitting
- nameref guard against reserved variable names in prompt_validated/
require_valid; source-check pattern documented as the repo contract
- 8 new test cases (41 total)
- validation helpers: sanitize_input trims CR/edge whitespace only;
embedded control/non-ASCII bytes FAIL validation and re-prompt with a
hint (2026-06-11 incident: invisible byte in a pasted VLAN tag broke
pct create mid-run) - never silently stripped
- prompt_lxc_config: every prompt validated (uint for CTID/disk/cores/
RAM, VLAN 1-4094, hostname/token formats, IP/CIDR/gateway, DNS list);
env-provided values are sanitized + validated too (abort, no re-prompt
loop in non-interactive use); helpers reusable for app prompts
- tests/test_validation.sh: 34 cases incl. the 2<0x80>0 repro, re-prompt
simulation, BASH_REMATCH clobbering regression (is_cidr), env dry-run
of prompt_lxc_config without PVE/TTY
- tests/check_ct_source.sh: every ct/*.sh must source build.func (bug
shipped twice); negative proof via prepared fixture in the test suite
- .gitea/workflows/ci.yml: bash -n over all scripts, source-check,
validation tests, shellcheck if present (documented skip otherwise)
- README: contributions via PR with cross-review (binding)
Adds apply_network_profile(), which looks up DNS servers for the entered
VLAN tag in lib/networks.conf and sets --nameserver accordingly — even
when IP is DHCP. Precedence: explicit env NAMESERVER > profile > static
prompt > inherit. Comma-separated DNS is normalised to spaces for pct.
Data file mapping a VLAN tag to its DNS servers and subnet, so build.func
can set the right resolvers from the tag entered at install time — even
with DHCP. New networks are a one-line addition here.
For DHCP setups DNS is delivered with the lease, but with a static IP the
container inherits /etc/resolv.conf from the PVE host - which is often
unreachable from the container's VLAN.
Changes:
- New NAMESERVER prompt (only shown for static IPs, defaults to gateway)
- pct create now passes --nameserver when set
- Network wait loop tests L3 and DNS separately so failures point at the
actual cause (no route to gateway vs. bad DNS server)
- Refactored pct create args into an array for cleaner conditional flags
HOSTNAME is a bash built-in always containing the host's name, so the
"-z HOSTNAME" check never fired and the prompt was silently skipped —
containers ended up named after the Proxmox host.
Also added an optional VLAN tag prompt (empty = no tag), and the network
wait loop now exits with an error if the network never comes up instead
of silently proceeding to a guaranteed-broken apt-get update.