fix: an upgrade keeps the configuration and asks nothing.
Release / release (push) Successful in 18s

This commit is contained in:
slawek committed 2026-10-24 02:08:23 +02:00
1 parent 08693f7e68
commit cda33fc4c7
4 files changed
+77 -33

No files matched your search

+4 -2
View File
@@ -85,8 +85,10 @@ that deploy a router on Azure or GCP.
## Configuration ## Configuration
`/etc/vpn-router/vpn-router.json` is JSON, mode 0600, and is created on first install. It `/etc/vpn-router/vpn-router.json` is JSON, mode 0600, and is created on first install. It
is managed with `ucf`, so it is yours to edit: a later `dpkg-reconfigure` applies what is managed with `ucf`, so it is yours to edit: a later `dpkg-reconfigure` applies the answers
changed and asks before touching anything you altered by hand. A complete example is given and asks before touching anything you altered by hand, and an upgrade leaves the file as
it is and asks nothing. Settings the file leaves out take their defaults when it is read, so a
file written by cloud-init or by hand needs only the settings that differ. A complete example is
installed at `/usr/share/doc/vpn-router/vpn-router.json.example`. installed at `/usr/share/doc/vpn-router/vpn-router.json.example`.
The file is an object of sections, each an object of settings. A section or setting that The file is an object of sections, each an object of settings. A section or setting that
+13
View File
@@ -1,3 +1,16 @@
vpn-router (0.5.8-1) unstable; urgency=medium
fix: an upgrade keeps the configuration and asks nothing.
* postinst passes on only the debconf answers that were given. The
defaults of questions never asked, such as platform none, no longer
replace settings of vpn-router.json.
* Without an answer that changes it, the configuration ucf is given is
vpn-router.json byte for byte, so an upgrade has no conflict to ask
about; settings the file leaves out keep their defaults when it is read.
-- Slawomir Koszewski <slawek@koszewscy.waw.pl> Sat, 24 Oct 2026 20:00:00 +0200
vpn-router (0.5.7-1) unstable; urgency=medium vpn-router (0.5.7-1) unstable; urgency=medium
feature: users have a first name, a last name and an e-mail address. feature: users have a first name, a last name and an e-mail address.
+30 -19
View File
@@ -2,28 +2,39 @@
set -e set -e
. /usr/share/debconf/confmodule . /usr/share/debconf/confmodule
# The answer to a question in ANSWER, or "" when the question has not been seen; the
# default debconf holds for an unseen question is not an answer.
answer() {
ANSWER=""
db_fget "$1" seen
if [ "$RET" = "true" ]; then
db_get "$1"
ANSWER="$RET"
fi
}
case "$1" in case "$1" in
configure) configure)
# --- Read the installer's answers --- # --- Read the installer's answers ---
db_get vpn-router/platform; VPN_ROUTER_PLATFORM="$RET" answer vpn-router/platform; VPN_ROUTER_PLATFORM="$ANSWER"
db_get vpn-router/mode; VPN_ROUTER_MODE="$RET" answer vpn-router/mode; VPN_ROUTER_MODE="$ANSWER"
db_get vpn-router/int_addr; VPN_ROUTER_INT_ADDR="$RET" answer vpn-router/int_addr; VPN_ROUTER_INT_ADDR="$ANSWER"
db_get vpn-router/external_interface; VPN_ROUTER_EXTERNAL_INTERFACE="$RET" answer vpn-router/external_interface; VPN_ROUTER_EXTERNAL_INTERFACE="$ANSWER"
db_get vpn-router/internal_interface; VPN_ROUTER_INTERNAL_INTERFACE="$RET" answer vpn-router/internal_interface; VPN_ROUTER_INTERNAL_INTERFACE="$ANSWER"
db_get vpn-router/local_fqdn; VPN_ROUTER_LOCAL_FQDN="$RET" answer vpn-router/local_fqdn; VPN_ROUTER_LOCAL_FQDN="$ANSWER"
db_get vpn-router/local_id_mode; VPN_ROUTER_LOCAL_ID_MODE="$RET" answer vpn-router/local_id_mode; VPN_ROUTER_LOCAL_ID_MODE="$ANSWER"
db_get vpn-router/local_cidrs; VPN_ROUTER_LOCAL_CIDRS="$RET" answer vpn-router/local_cidrs; VPN_ROUTER_LOCAL_CIDRS="$ANSWER"
db_get vpn-router/int_gateway_ip; VPN_ROUTER_INT_GATEWAY_IP="$RET" answer vpn-router/int_gateway_ip; VPN_ROUTER_INT_GATEWAY_IP="$ANSWER"
db_get vpn-router/remote_addrs; VPN_ROUTER_REMOTE_ADDRS="$RET" answer vpn-router/remote_addrs; VPN_ROUTER_REMOTE_ADDRS="$ANSWER"
db_get vpn-router/remote_id; VPN_ROUTER_REMOTE_ID="$RET" answer vpn-router/remote_id; VPN_ROUTER_REMOTE_ID="$ANSWER"
db_get vpn-router/remote_cidrs; VPN_ROUTER_REMOTE_CIDRS="$RET" answer vpn-router/remote_cidrs; VPN_ROUTER_REMOTE_CIDRS="$ANSWER"
db_get vpn-router/psk; VPN_ROUTER_PSK="$RET" answer vpn-router/psk; VPN_ROUTER_PSK="$ANSWER"
db_get vpn-router/p2s_enabled; VPN_ROUTER_P2S_ENABLED="$RET" answer vpn-router/p2s_enabled; VPN_ROUTER_P2S_ENABLED="$ANSWER"
db_get vpn-router/p2s_address_pool; VPN_ROUTER_P2S_ADDRESS_POOL="$RET" answer vpn-router/p2s_address_pool; VPN_ROUTER_P2S_ADDRESS_POOL="$ANSWER"
db_get vpn-router/p2s_ca_name; VPN_ROUTER_P2S_CA_NAME="$RET" answer vpn-router/p2s_ca_name; VPN_ROUTER_P2S_CA_NAME="$ANSWER"
db_get vpn-router/wg_enabled; VPN_ROUTER_WG_ENABLED="$RET" answer vpn-router/wg_enabled; VPN_ROUTER_WG_ENABLED="$ANSWER"
db_get vpn-router/wg_address; VPN_ROUTER_WG_ADDRESS="$RET" answer vpn-router/wg_address; VPN_ROUTER_WG_ADDRESS="$ANSWER"
db_get vpn-router/wg_listen_port; VPN_ROUTER_WG_LISTEN_PORT="$RET" answer vpn-router/wg_listen_port; VPN_ROUTER_WG_LISTEN_PORT="$ANSWER"
export VPN_ROUTER_PLATFORM VPN_ROUTER_MODE VPN_ROUTER_INT_ADDR \ export VPN_ROUTER_PLATFORM VPN_ROUTER_MODE VPN_ROUTER_INT_ADDR \
VPN_ROUTER_EXTERNAL_INTERFACE \ VPN_ROUTER_EXTERNAL_INTERFACE \
@@ -6,18 +6,15 @@ Called from postinst, which hands the result to ucf(1). ucf compares it with
this script never writes to /etc and never has to worry about destroying an this script never writes to /etc and never has to worry about destroying an
administrator's edits. administrator's edits.
The candidate is built in three layers, each overriding the one before: The candidate is the current /etc/vpn-router/vpn-router.json as it is, or the
shipped defaults when there is none, with every debconf answer that was given
1. the shipped defaults written over it. Without an answer that changes it, the candidate is the current
2. the current contents of /etc/vpn-router/vpn-router.json, if it exists file byte for byte, so
3. any debconf answer that was actually given an upgrade or a reconfiguration that changes nothing gives ucf nothing to ask;
defaults the file leaves out are applied by vpnrouter.load_config() when the
Layer 2 is what makes reconfiguration safe: a setting the administrator edited file is read. An empty answer means "leave alone", which is why clearing the
by hand, and that debconf has nothing to say about, is carried through pre-shared key from the debconf database after installation does not blank it
unchanged, so ucf only sees a diff where something really changed. An empty on the next dpkg-reconfigure.
answer means "leave alone", which is why clearing the pre-shared key from the
debconf database after installation does not blank it on the next
dpkg-reconfigure.
Answers arrive as VPN_ROUTER_* environment variables and are converted to the Answers arrive as VPN_ROUTER_* environment variables and are converted to the
type of the setting they fill: comma-separated lists become arrays, "true" and type of the setting they fill: comma-separated lists become arrays, "true" and
@@ -62,8 +59,21 @@ def main():
return 2 return 2
output = sys.argv[1] output = sys.argv[1]
try:
current = vpnrouter.CONFIG_FILE.read_text(encoding='utf-8')
except FileNotFoundError:
current = None
if current is None:
cfg = vpnrouter.load_config() cfg = vpnrouter.load_config()
else:
try:
cfg = json.loads(current)
except ValueError as exc:
vpnrouter.fail(f'cannot parse {vpnrouter.CONFIG_FILE}: {exc}')
if not isinstance(cfg, dict):
vpnrouter.fail(f'cannot parse {vpnrouter.CONFIG_FILE}: the top level is not an object')
answered = False
for variable, (section, option) in ANSWERS.items(): for variable, (section, option) in ANSWERS.items():
value = os.environ.get(variable) value = os.environ.get(variable)
if not value: if not value:
@@ -76,9 +86,17 @@ def main():
value = int(value) if value.isdigit() else value value = int(value) if value.isdigit() else value
elif isinstance(default, list): elif isinstance(default, list):
value = [item.strip() for item in value.split(',') if item.strip()] value = [item.strip() for item in value.split(',') if item.strip()]
if not isinstance(cfg.get(section, {}), dict):
vpnrouter.fail(f'cannot parse {vpnrouter.CONFIG_FILE}: {section} is not an object')
if cfg.setdefault(section, {}).get(option) != value:
cfg[section][option] = value cfg[section][option] = value
answered = True
# Without a change the candidate is the current file byte for byte, as ucf compares checksums.
with open(output, 'w', encoding='utf-8') as handle: with open(output, 'w', encoding='utf-8') as handle:
if current is not None and not answered:
handle.write(current)
else:
json.dump(cfg, handle, indent=2) json.dump(cfg, handle, indent=2)
handle.write('\n') handle.write('\n')
os.chmod(output, 0o600) os.chmod(output, 0o600)