installer: restore cell identity prompts and domain setup
Unit Tests / test (push) Successful in 15m39s
Unit Tests / test (push) Successful in 15m39s
Reverts 8d1ef39. The installer must collect cell name, domain mode, and
provider tokens before 'make install' so that DDNS registration,
availability checks, and Caddy TLS can be configured at first boot.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -510,6 +510,11 @@ class ConfigManager:
|
|||||||
cfg.setdefault('peer_exit_map', {})
|
cfg.setdefault('peer_exit_map', {})
|
||||||
return dict(cfg)
|
return dict(cfg)
|
||||||
|
|
||||||
|
def set_ddns_config(self, ddns_cfg: Dict[str, Any]) -> None:
|
||||||
|
"""Replace the top-level ddns section and persist."""
|
||||||
|
self.configs['ddns'] = ddns_cfg
|
||||||
|
self._save_all_configs()
|
||||||
|
|
||||||
def set_connectivity_field(self, field: str, value: Any) -> bool:
|
def set_connectivity_field(self, field: str, value: Any) -> bool:
|
||||||
"""Set a single field within the connectivity config and persist."""
|
"""Set a single field within the connectivity config and persist."""
|
||||||
cfg = self.configs.setdefault('connectivity', {'exits': {}, 'peer_exit_map': {}})
|
cfg = self.configs.setdefault('connectivity', {'exits': {}, 'peer_exit_map': {}})
|
||||||
|
|||||||
@@ -55,11 +55,4 @@ def complete_setup():
|
|||||||
payload = request.get_json(silent=True) or {}
|
payload = request.get_json(silent=True) or {}
|
||||||
result = sm.complete_setup(payload)
|
result = sm.complete_setup(payload)
|
||||||
status_code = 200 if result.get('success') else 400
|
status_code = 200 if result.get('success') else 400
|
||||||
|
|
||||||
# TODO (Phase 3): if result.get('success') and domain_mode == 'pic_ngo':
|
|
||||||
# from app import ddns_manager
|
|
||||||
# name = payload.get('cell_name', '')
|
|
||||||
# ip = payload.get('public_ip', '')
|
|
||||||
# ddns_manager.register(name, ip)
|
|
||||||
|
|
||||||
return jsonify(result), status_code
|
return jsonify(result), status_code
|
||||||
|
|||||||
+49
-3
@@ -60,6 +60,37 @@ VALID_DOMAIN_MODES = {'pic_ngo', 'cloudflare', 'duckdns', 'http01', 'lan'}
|
|||||||
CELL_NAME_RE = re.compile(r'^[a-z][a-z0-9-]{1,30}$')
|
CELL_NAME_RE = re.compile(r'^[a-z][a-z0-9-]{1,30}$')
|
||||||
|
|
||||||
|
|
||||||
|
DDNS_API_BASE = os.environ.get('DDNS_URL', 'https://ddns.pic.ngo/api/v1').replace('/api/v1', '')
|
||||||
|
DDNS_TOTP_SECRET = os.environ.get('DDNS_TOTP_SECRET', '')
|
||||||
|
|
||||||
|
|
||||||
|
def _build_ddns_config(domain_mode: str, cloudflare_api_token: str = '',
|
||||||
|
duckdns_token: str = '', duckdns_subdomain: str = '') -> dict:
|
||||||
|
"""Return the top-level ddns config dict for a given domain mode."""
|
||||||
|
if domain_mode == 'pic_ngo':
|
||||||
|
return {
|
||||||
|
'provider': 'pic_ngo',
|
||||||
|
'api_base_url': DDNS_API_BASE,
|
||||||
|
'totp_secret': DDNS_TOTP_SECRET,
|
||||||
|
'enabled': True,
|
||||||
|
}
|
||||||
|
if domain_mode == 'cloudflare':
|
||||||
|
cfg = {'provider': 'cloudflare', 'enabled': True}
|
||||||
|
if cloudflare_api_token:
|
||||||
|
cfg['api_token'] = cloudflare_api_token
|
||||||
|
return cfg
|
||||||
|
if domain_mode == 'duckdns':
|
||||||
|
cfg = {'provider': 'duckdns', 'enabled': True}
|
||||||
|
if duckdns_token:
|
||||||
|
cfg['token'] = duckdns_token
|
||||||
|
if duckdns_subdomain:
|
||||||
|
cfg['subdomain'] = duckdns_subdomain
|
||||||
|
return cfg
|
||||||
|
if domain_mode == 'http01':
|
||||||
|
return {'provider': 'http01', 'enabled': True}
|
||||||
|
return {'provider': 'none', 'enabled': False}
|
||||||
|
|
||||||
|
|
||||||
class SetupManager:
|
class SetupManager:
|
||||||
"""Manages the first-run setup wizard state and completion."""
|
"""Manages the first-run setup wizard state and completion."""
|
||||||
|
|
||||||
@@ -209,10 +240,25 @@ class SetupManager:
|
|||||||
if duckdns_token:
|
if duckdns_token:
|
||||||
self.config_manager.set_identity_field('duckdns_token', duckdns_token)
|
self.config_manager.set_identity_field('duckdns_token', duckdns_token)
|
||||||
|
|
||||||
logger.info(
|
# ── write top-level ddns section so DDNSManager can find provider ──
|
||||||
'DDNS registration deferred to Phase 3. '
|
duckdns_sub = domain_name.replace('.duckdns.org', '') if domain_mode == 'duckdns' else ''
|
||||||
f'ddns_provider={ddns_provider!r} domain_name={domain_name!r}'
|
ddns_cfg = _build_ddns_config(
|
||||||
|
domain_mode,
|
||||||
|
cloudflare_api_token=cloudflare_api_token,
|
||||||
|
duckdns_token=duckdns_token,
|
||||||
|
duckdns_subdomain=duckdns_sub,
|
||||||
)
|
)
|
||||||
|
self.config_manager.set_ddns_config(ddns_cfg)
|
||||||
|
|
||||||
|
# ── trigger DDNS registration for pic_ngo ─────────────────────────
|
||||||
|
if domain_mode == 'pic_ngo':
|
||||||
|
try:
|
||||||
|
from ddns_manager import DDNSManager
|
||||||
|
ddns_mgr = DDNSManager(self.config_manager)
|
||||||
|
ddns_mgr.register(cell_name, '')
|
||||||
|
logger.info(f'DDNS registered: {cell_name}.pic.ngo')
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(f'DDNS registration failed (will retry at next heartbeat): {exc}')
|
||||||
|
|
||||||
# ── mark setup complete (must be last) ─────────────────────────
|
# ── mark setup complete (must be last) ─────────────────────────
|
||||||
self.config_manager.set_identity_field('setup_complete', True)
|
self.config_manager.set_identity_field('setup_complete', True)
|
||||||
|
|||||||
+234
-9
@@ -79,7 +79,56 @@ log_error() { printf "\n${RED}${BOLD}ERROR:${RESET}${RED} %s${RESET}\n" "$1" >
|
|||||||
|
|
||||||
die() { log_error "$1"; exit 1; }
|
die() { log_error "$1"; exit 1; }
|
||||||
|
|
||||||
TOTAL_STEPS=7
|
# ---------------------------------------------------------------------------
|
||||||
|
# Interactive prompt helpers (use /dev/tty so they work even with piped stdin)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
prompt() {
|
||||||
|
# prompt <label> <default> <var>
|
||||||
|
local _label="$1" _default="$2" _var="$3" _inp=''
|
||||||
|
if [ -n "$_default" ]; then
|
||||||
|
printf " %s [%s]: " "$_label" "$_default" >/dev/tty
|
||||||
|
else
|
||||||
|
printf " %s: " "$_label" >/dev/tty
|
||||||
|
fi
|
||||||
|
read -r _inp </dev/tty || true
|
||||||
|
[ -z "$_inp" ] && _inp="$_default"
|
||||||
|
eval "${_var}=\${_inp}"
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_secret() {
|
||||||
|
# prompt_secret <label> <var>
|
||||||
|
local _label="$1" _var="$2" _inp=''
|
||||||
|
printf " %s: " "$_label" >/dev/tty
|
||||||
|
stty -echo </dev/tty 2>/dev/null || true
|
||||||
|
read -r _inp </dev/tty || true
|
||||||
|
stty echo </dev/tty 2>/dev/null || true
|
||||||
|
printf "\n" >/dev/tty
|
||||||
|
eval "${_var}=\${_inp}"
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_cf_token() {
|
||||||
|
local _token="$1" _result=''
|
||||||
|
_result=$(curl -fsSm 10 \
|
||||||
|
-H "Authorization: Bearer ${_token}" \
|
||||||
|
"https://api.cloudflare.com/client/v4/user/tokens/verify" 2>/dev/null) || true
|
||||||
|
echo "$_result" | grep -q '"success":true'
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_duckdns() {
|
||||||
|
local _sub="$1" _token="$2" _result=''
|
||||||
|
_result=$(curl -fsSm 10 \
|
||||||
|
"https://www.duckdns.org/update?domains=${_sub}&token=${_token}&ip=" 2>/dev/null) || true
|
||||||
|
[ "$_result" = "OK" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
check_pic_ngo_available() {
|
||||||
|
local _name="$1" _result=''
|
||||||
|
_result=$(curl -fsSm 10 \
|
||||||
|
"https://ddns.pic.ngo/api/v1/check/${_name}" 2>/dev/null) || true
|
||||||
|
echo "$_result" | grep -q '"available":true'
|
||||||
|
}
|
||||||
|
|
||||||
|
TOTAL_STEPS=8
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Must run as root
|
# Must run as root
|
||||||
@@ -253,13 +302,182 @@ chown -R "${REPO_OWNER}:${REPO_OWNER}" "$PIC_DIR"
|
|||||||
git config --system --add safe.directory "$PIC_DIR" 2>/dev/null || true
|
git config --system --add safe.directory "$PIC_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Step 5 — Run make install
|
# Step 5 — Configure cell identity
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
log_step 5 "Running 'make install'..."
|
log_step 5 "Configuring cell identity..."
|
||||||
|
|
||||||
|
if [ ! -c /dev/tty ]; then
|
||||||
|
die "No interactive terminal available. Re-run with a real terminal (not piped)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "\n" >/dev/tty
|
||||||
|
|
||||||
|
# ── Cell name ──────────────────────────────────────────────────────────────
|
||||||
|
PIC_CELL_NAME=''
|
||||||
|
while true; do
|
||||||
|
prompt "Cell name (e.g. myhome, alice, lab)" "" PIC_CELL_NAME
|
||||||
|
if echo "$PIC_CELL_NAME" | grep -qE '^[a-z][a-z0-9-]{1,30}$'; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
log_warn "Must start with a letter, use only lowercase letters/digits/hyphens, 2–31 chars."
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Domain / DDNS choice ───────────────────────────────────────────────────
|
||||||
|
printf "\n" >/dev/tty
|
||||||
|
printf " How will your cell be publicly reachable?\n" >/dev/tty
|
||||||
|
printf " 1) pic.ngo subdomain — free, %s.pic.ngo, fully automatic HTTPS\n" "$PIC_CELL_NAME" >/dev/tty
|
||||||
|
printf " 2) Cloudflare DNS-01 — your own domain (must use Cloudflare nameservers)\n" >/dev/tty
|
||||||
|
printf " 3) DuckDNS — free *.duckdns.org subdomain\n" >/dev/tty
|
||||||
|
printf " 4) HTTP-01 (any) — any domain, port 80 must be publicly reachable\n" >/dev/tty
|
||||||
|
printf " 5) Local only — no public domain, LAN/VPN access only\n" >/dev/tty
|
||||||
|
printf "\n" >/dev/tty
|
||||||
|
|
||||||
|
PIC_DOMAIN_MODE=''
|
||||||
|
_choice=''
|
||||||
|
while true; do
|
||||||
|
prompt "Choice" "1" _choice
|
||||||
|
case "$_choice" in
|
||||||
|
1) PIC_DOMAIN_MODE="pic_ngo"; break ;;
|
||||||
|
2) PIC_DOMAIN_MODE="cloudflare"; break ;;
|
||||||
|
3) PIC_DOMAIN_MODE="duckdns"; break ;;
|
||||||
|
4) PIC_DOMAIN_MODE="http01"; break ;;
|
||||||
|
5) PIC_DOMAIN_MODE="lan"; break ;;
|
||||||
|
*) log_warn "Enter a number from 1 to 5." ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
PIC_DOMAIN_NAME=''
|
||||||
|
PIC_CF_TOKEN=''
|
||||||
|
PIC_DDK_TOKEN=''
|
||||||
|
PIC_DDK_SUB=''
|
||||||
|
|
||||||
|
# ── pic.ngo ────────────────────────────────────────────────────────────────
|
||||||
|
if [ "$PIC_DOMAIN_MODE" = "pic_ngo" ]; then
|
||||||
|
PIC_DOMAIN_NAME="${PIC_CELL_NAME}.pic.ngo"
|
||||||
|
printf " Checking name availability at pic.ngo..." >/dev/tty
|
||||||
|
if check_pic_ngo_available "$PIC_CELL_NAME"; then
|
||||||
|
printf " available\n" >/dev/tty
|
||||||
|
log_ok "Will register: ${PIC_DOMAIN_NAME}"
|
||||||
|
else
|
||||||
|
printf "\n" >/dev/tty
|
||||||
|
log_warn "${PIC_DOMAIN_NAME} may already be taken or the server is unreachable."
|
||||||
|
log_warn "Registration will be retried at first boot."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Cloudflare ─────────────────────────────────────────────────────────────
|
||||||
|
if [ "$PIC_DOMAIN_MODE" = "cloudflare" ]; then
|
||||||
|
printf "\n" >/dev/tty
|
||||||
|
while true; do
|
||||||
|
prompt "Your domain name (e.g. home.example.com)" "" PIC_DOMAIN_NAME
|
||||||
|
if echo "$PIC_DOMAIN_NAME" | grep -qiE '^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z]{2,})+$'; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
log_warn "Enter a valid fully-qualified domain name (e.g. home.example.com)."
|
||||||
|
done
|
||||||
|
printf "\n" >/dev/tty
|
||||||
|
printf " Create an API token at: Cloudflare Dashboard → My Profile → API Tokens\n" >/dev/tty
|
||||||
|
printf " Required permission: Zone / DNS / Edit (set to all zones or your specific zone)\n" >/dev/tty
|
||||||
|
printf "\n" >/dev/tty
|
||||||
|
_attempts=0
|
||||||
|
while true; do
|
||||||
|
prompt_secret "Cloudflare API token" PIC_CF_TOKEN
|
||||||
|
if [ -z "$PIC_CF_TOKEN" ]; then
|
||||||
|
log_warn "Token cannot be empty."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
printf " Verifying token with Cloudflare..." >/dev/tty
|
||||||
|
if verify_cf_token "$PIC_CF_TOKEN"; then
|
||||||
|
printf " valid\n" >/dev/tty
|
||||||
|
log_ok "Cloudflare token verified"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
printf " invalid\n" >/dev/tty
|
||||||
|
_attempts=$((_attempts + 1))
|
||||||
|
log_warn "Verification failed — check the token has Zone / DNS / Edit permission."
|
||||||
|
if [ "$_attempts" -ge 2 ]; then
|
||||||
|
log_warn "Token failed twice. You can still continue — it will be tested again at first boot."
|
||||||
|
prompt "Press Enter to continue with this token, or Ctrl-C to abort and re-run" "" _dummy
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── DuckDNS ────────────────────────────────────────────────────────────────
|
||||||
|
if [ "$PIC_DOMAIN_MODE" = "duckdns" ]; then
|
||||||
|
printf "\n" >/dev/tty
|
||||||
|
printf " First create a subdomain at duckdns.org, then enter the details below.\n" >/dev/tty
|
||||||
|
printf "\n" >/dev/tty
|
||||||
|
while true; do
|
||||||
|
prompt "DuckDNS subdomain (e.g. myhome → myhome.duckdns.org)" "" PIC_DDK_SUB
|
||||||
|
if echo "$PIC_DDK_SUB" | grep -qE '^[a-z0-9][a-z0-9-]*$'; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
log_warn "Subdomain must be lowercase letters, digits, and hyphens only."
|
||||||
|
done
|
||||||
|
PIC_DOMAIN_NAME="${PIC_DDK_SUB}.duckdns.org"
|
||||||
|
printf "\n" >/dev/tty
|
||||||
|
_attempts=0
|
||||||
|
while true; do
|
||||||
|
prompt_secret "DuckDNS token (from duckdns.org account page)" PIC_DDK_TOKEN
|
||||||
|
if [ -z "$PIC_DDK_TOKEN" ]; then
|
||||||
|
log_warn "Token cannot be empty."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
printf " Verifying token with DuckDNS..." >/dev/tty
|
||||||
|
if verify_duckdns "$PIC_DDK_SUB" "$PIC_DDK_TOKEN"; then
|
||||||
|
printf " valid\n" >/dev/tty
|
||||||
|
log_ok "DuckDNS token verified (${PIC_DOMAIN_NAME})"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
printf " invalid\n" >/dev/tty
|
||||||
|
_attempts=$((_attempts + 1))
|
||||||
|
log_warn "Verification failed — make sure the subdomain exists at duckdns.org and the token is correct."
|
||||||
|
if [ "$_attempts" -ge 2 ]; then
|
||||||
|
log_warn "Token failed twice. You can still continue — it will be tested again at first boot."
|
||||||
|
prompt "Press Enter to continue with this token, or Ctrl-C to abort and re-run" "" _dummy
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── HTTP-01 ────────────────────────────────────────────────────────────────
|
||||||
|
if [ "$PIC_DOMAIN_MODE" = "http01" ]; then
|
||||||
|
printf "\n" >/dev/tty
|
||||||
|
while true; do
|
||||||
|
prompt "Your domain name (e.g. home.example.com)" "" PIC_DOMAIN_NAME
|
||||||
|
if echo "$PIC_DOMAIN_NAME" | grep -qiE '^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z]{2,})+$'; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
log_warn "Enter a valid fully-qualified domain name."
|
||||||
|
done
|
||||||
|
printf "\n" >/dev/tty
|
||||||
|
log_warn "HTTP-01 requires port 80 to be publicly reachable from the internet."
|
||||||
|
log_warn "Make sure your router forwards port 80 to this machine before completing setup."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Local only ─────────────────────────────────────────────────────────────
|
||||||
|
if [ "$PIC_DOMAIN_MODE" = "lan" ]; then
|
||||||
|
log_ok "Local-only mode — no public domain or DDNS will be configured."
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "\n" >/dev/tty
|
||||||
|
log_ok "Identity configured: cell=${PIC_CELL_NAME} mode=${PIC_DOMAIN_MODE}${PIC_DOMAIN_NAME:+ domain=${PIC_DOMAIN_NAME}}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Step 6 — Run make install
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
log_step 6 "Running 'make install'..."
|
||||||
|
|
||||||
cd "$PIC_DIR"
|
cd "$PIC_DIR"
|
||||||
|
|
||||||
if ! make install 2>&1 | sed 's/^/ /'; then
|
if ! CELL_NAME="$PIC_CELL_NAME" \
|
||||||
|
DOMAIN_MODE="$PIC_DOMAIN_MODE" \
|
||||||
|
CELL_DOMAIN_NAME="${PIC_DOMAIN_NAME:-}" \
|
||||||
|
CLOUDFLARE_API_TOKEN="${PIC_CF_TOKEN:-}" \
|
||||||
|
DUCKDNS_TOKEN="${PIC_DDK_TOKEN:-}" \
|
||||||
|
DUCKDNS_SUBDOMAIN="${PIC_DDK_SUB:-}" \
|
||||||
|
make install 2>&1 | sed 's/^/ /'; then
|
||||||
die "'make install' failed. Check the output above."
|
die "'make install' failed. Check the output above."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -270,9 +488,9 @@ chown -R "${REPO_OWNER}:${REPO_OWNER}" "$PIC_DIR"
|
|||||||
log_ok "'make install' complete"
|
log_ok "'make install' complete"
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Step 6 — Start core services
|
# Step 7 — Start core services
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
log_step 6 "Starting core services..."
|
log_step 7 "Starting core services..."
|
||||||
|
|
||||||
cd "$PIC_DIR"
|
cd "$PIC_DIR"
|
||||||
|
|
||||||
@@ -283,9 +501,9 @@ fi
|
|||||||
log_ok "Core services started"
|
log_ok "Core services started"
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Step 7 — Health check + print wizard URL
|
# Step 8 — Health check + print wizard URL
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
log_step 7 "Waiting for API health check (up to ${API_HEALTH_TIMEOUT}s)..."
|
log_step 8 "Waiting for API health check (up to ${API_HEALTH_TIMEOUT}s)..."
|
||||||
|
|
||||||
ELAPSED=0
|
ELAPSED=0
|
||||||
HEALTHY=0
|
HEALTHY=0
|
||||||
@@ -323,7 +541,14 @@ printf "\n${GREEN}${BOLD}=======================================================
|
|||||||
printf "${GREEN}${BOLD} PIC installed successfully!${RESET}\n"
|
printf "${GREEN}${BOLD} PIC installed successfully!${RESET}\n"
|
||||||
printf "${GREEN}${BOLD}============================================================${RESET}\n"
|
printf "${GREEN}${BOLD}============================================================${RESET}\n"
|
||||||
printf "\n"
|
printf "\n"
|
||||||
printf " Open the setup wizard to configure your cell:\n"
|
printf " Cell: ${BOLD}%s${RESET}\n" "$PIC_CELL_NAME"
|
||||||
|
if [ -n "$PIC_DOMAIN_NAME" ]; then
|
||||||
|
printf " Domain: ${BOLD}%s${RESET} (%s)\n" "$PIC_DOMAIN_NAME" "$PIC_DOMAIN_MODE"
|
||||||
|
else
|
||||||
|
printf " Domain: %s\n" "local only (LAN/VPN)"
|
||||||
|
fi
|
||||||
|
printf "\n"
|
||||||
|
printf " Open the setup wizard to set your admin password and choose services:\n"
|
||||||
printf "\n"
|
printf "\n"
|
||||||
printf " ${BOLD}http://${HOST_IP}:${WEBUI_PORT}/setup${RESET}\n"
|
printf " ${BOLD}http://${HOST_IP}:${WEBUI_PORT}/setup${RESET}\n"
|
||||||
printf "\n"
|
printf "\n"
|
||||||
|
|||||||
@@ -320,3 +320,60 @@ def test_get_setup_status_preconfigured_returns_installer_values(setup_manager,
|
|||||||
assert pre['domain_mode'] == 'pic_ngo'
|
assert pre['domain_mode'] == 'pic_ngo'
|
||||||
assert pre['domain_name'] == 'myhome.pic.ngo'
|
assert pre['domain_name'] == 'myhome.pic.ngo'
|
||||||
assert 'cloudflare_api_token' not in pre
|
assert 'cloudflare_api_token' not in pre
|
||||||
|
|
||||||
|
|
||||||
|
# ── _build_ddns_config ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
from setup_manager import _build_ddns_config
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_ddns_config_pic_ngo():
|
||||||
|
cfg = _build_ddns_config('pic_ngo')
|
||||||
|
assert cfg['provider'] == 'pic_ngo'
|
||||||
|
assert cfg['enabled'] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_ddns_config_cloudflare_includes_token():
|
||||||
|
cfg = _build_ddns_config('cloudflare', cloudflare_api_token='tok123')
|
||||||
|
assert cfg['provider'] == 'cloudflare'
|
||||||
|
assert cfg['api_token'] == 'tok123'
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_ddns_config_duckdns_includes_token_and_subdomain():
|
||||||
|
cfg = _build_ddns_config('duckdns', duckdns_token='duck', duckdns_subdomain='myhome')
|
||||||
|
assert cfg['provider'] == 'duckdns'
|
||||||
|
assert cfg['token'] == 'duck'
|
||||||
|
assert cfg['subdomain'] == 'myhome'
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_ddns_config_lan_disabled():
|
||||||
|
cfg = _build_ddns_config('lan')
|
||||||
|
assert cfg['provider'] == 'none'
|
||||||
|
assert cfg['enabled'] is False
|
||||||
|
|
||||||
|
|
||||||
|
# ── ddns config written on complete_setup ─────────────────────────────────────
|
||||||
|
|
||||||
|
def test_complete_setup_writes_ddns_config_section(
|
||||||
|
setup_manager, mock_config_manager, mock_auth_manager, tmp_path):
|
||||||
|
mock_config_manager.get_identity.return_value = {}
|
||||||
|
with patch.dict(os.environ, {'DATA_DIR': str(tmp_path)}):
|
||||||
|
setup_manager.complete_setup(_valid_payload(domain_mode='lan'))
|
||||||
|
mock_config_manager.set_ddns_config.assert_called_once()
|
||||||
|
ddns_arg = mock_config_manager.set_ddns_config.call_args[0][0]
|
||||||
|
assert ddns_arg['provider'] == 'none'
|
||||||
|
|
||||||
|
|
||||||
|
def test_complete_setup_writes_cloudflare_ddns_config(
|
||||||
|
setup_manager, mock_config_manager, mock_auth_manager, tmp_path):
|
||||||
|
mock_config_manager.get_identity.return_value = {}
|
||||||
|
payload = _valid_payload(
|
||||||
|
domain_mode='cloudflare',
|
||||||
|
domain_name='home.example.com',
|
||||||
|
cloudflare_api_token='cf-token-xyz',
|
||||||
|
)
|
||||||
|
with patch.dict(os.environ, {'DATA_DIR': str(tmp_path)}):
|
||||||
|
setup_manager.complete_setup(payload)
|
||||||
|
ddns_arg = mock_config_manager.set_ddns_config.call_args[0][0]
|
||||||
|
assert ddns_arg['provider'] == 'cloudflare'
|
||||||
|
assert ddns_arg['api_token'] == 'cf-token-xyz'
|
||||||
|
|||||||
Reference in New Issue
Block a user