Installer: interactive cell identity prompts with live token validation
Unit Tests / test (push) Successful in 15m24s
Unit Tests / test (push) Successful in 15m24s
install.sh now guides the user through the full identity setup before running make install: - Cell name prompt with format validation and pic.ngo availability check - Domain mode selection: pic.ngo / Cloudflare / DuckDNS / HTTP-01 / LAN - Cloudflare API token: collected and verified against CF tokens/verify API - DuckDNS: subdomain + token verified against duckdns.org/update - HTTP-01: domain name collected, port-80 warning shown - All collected values passed as env vars to make install - After two failed token attempts user can continue (re-verified at boot) - Final banner shows configured cell name and domain setup_cell.py: updated to handle all domain modes - Reads DOMAIN_MODE / CELL_DOMAIN_NAME / CLOUDFLARE_API_TOKEN / DUCKDNS_TOKEN / DUCKDNS_SUBDOMAIN from env - write_cell_config() now writes domain_mode + domain_name to _identity and builds the ddns section for each provider (not hardcoded to pic_ngo) - register_with_ddns() only called when DOMAIN_MODE == 'pic_ngo' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+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 at:\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"
|
||||||
|
|||||||
+64
-20
@@ -169,7 +169,8 @@ def write_wg0_conf(private_key: str, address: str, port: int):
|
|||||||
print(f'[CREATED] config/wireguard/wg0.conf address={address} port={port}')
|
print(f'[CREATED] config/wireguard/wg0.conf address={address} port={port}')
|
||||||
|
|
||||||
|
|
||||||
def write_cell_config(cell_name: str, domain: str, port: int):
|
def write_cell_config(cell_name: str, domain: str, port: int,
|
||||||
|
domain_mode: str, domain_name: str) -> None:
|
||||||
cfg_path = os.path.join(ROOT, 'config', 'api', 'cell_config.json')
|
cfg_path = os.path.join(ROOT, 'config', 'api', 'cell_config.json')
|
||||||
if os.path.exists(cfg_path):
|
if os.path.exists(cfg_path):
|
||||||
try:
|
try:
|
||||||
@@ -179,23 +180,46 @@ def write_cell_config(cell_name: str, domain: str, port: int):
|
|||||||
return
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
config = {
|
|
||||||
'_identity': {
|
ddns: dict = {}
|
||||||
'cell_name': cell_name,
|
if domain_mode == 'pic_ngo':
|
||||||
'domain': domain,
|
ddns = {
|
||||||
'ip_range': '172.20.0.0/16',
|
|
||||||
'wireguard_port': port,
|
|
||||||
},
|
|
||||||
'ddns': {
|
|
||||||
'provider': 'pic_ngo',
|
'provider': 'pic_ngo',
|
||||||
'api_base_url': DDNS_URL.replace('/api/v1', ''),
|
'api_base_url': DDNS_URL.replace('/api/v1', ''),
|
||||||
'totp_secret': DDNS_TOTP_SECRET,
|
'totp_secret': DDNS_TOTP_SECRET,
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
|
}
|
||||||
|
elif domain_mode == 'cloudflare':
|
||||||
|
ddns = {'provider': 'cloudflare', 'enabled': True}
|
||||||
|
if CLOUDFLARE_TOKEN:
|
||||||
|
ddns['api_token'] = CLOUDFLARE_TOKEN
|
||||||
|
elif domain_mode == 'duckdns':
|
||||||
|
ddns = {'provider': 'duckdns', 'enabled': True}
|
||||||
|
if DUCKDNS_TOKEN:
|
||||||
|
ddns['token'] = DUCKDNS_TOKEN
|
||||||
|
if DUCKDNS_SUBDOMAIN:
|
||||||
|
ddns['subdomain'] = DUCKDNS_SUBDOMAIN
|
||||||
|
elif domain_mode == 'http01':
|
||||||
|
ddns = {'provider': 'http01', 'enabled': True}
|
||||||
|
else: # lan
|
||||||
|
ddns = {'provider': 'none', 'enabled': False}
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'_identity': {
|
||||||
|
'cell_name': cell_name,
|
||||||
|
'domain': domain,
|
||||||
|
'domain_mode': domain_mode,
|
||||||
|
'domain_name': domain_name,
|
||||||
|
'ip_range': '172.20.0.0/16',
|
||||||
|
'wireguard_port': port,
|
||||||
},
|
},
|
||||||
|
'ddns': ddns,
|
||||||
}
|
}
|
||||||
with open(cfg_path, 'w') as f:
|
with open(cfg_path, 'w') as f:
|
||||||
json.dump(config, f, indent=2)
|
json.dump(config, f, indent=2)
|
||||||
print(f'[CREATED] config/api/cell_config.json name={cell_name} domain={domain}')
|
os.chmod(cfg_path, 0o600)
|
||||||
|
print(f'[CREATED] config/api/cell_config.json name={cell_name} mode={domain_mode}'
|
||||||
|
+ (f' domain={domain_name}' if domain_name else ''))
|
||||||
|
|
||||||
|
|
||||||
def write_compose_env(ip_range: str):
|
def write_compose_env(ip_range: str):
|
||||||
@@ -244,8 +268,13 @@ def ensure_session_secret():
|
|||||||
print('[CREATED] data/api/.session_secret')
|
print('[CREATED] data/api/.session_secret')
|
||||||
|
|
||||||
|
|
||||||
DDNS_URL = os.environ.get('DDNS_URL', 'https://ddns.pic.ngo/api/v1')
|
DDNS_URL = os.environ.get('DDNS_URL', 'https://ddns.pic.ngo/api/v1')
|
||||||
DDNS_TOTP_SECRET = os.environ.get('DDNS_TOTP_SECRET', 'S6UMA464YIKM74QHXWL5WELDIO3HFZ6K')
|
DDNS_TOTP_SECRET = os.environ.get('DDNS_TOTP_SECRET', 'S6UMA464YIKM74QHXWL5WELDIO3HFZ6K')
|
||||||
|
DOMAIN_MODE = os.environ.get('DOMAIN_MODE', 'pic_ngo')
|
||||||
|
CELL_DOMAIN_NAME = os.environ.get('CELL_DOMAIN_NAME', '')
|
||||||
|
CLOUDFLARE_TOKEN = os.environ.get('CLOUDFLARE_API_TOKEN', '')
|
||||||
|
DUCKDNS_TOKEN = os.environ.get('DUCKDNS_TOKEN', '')
|
||||||
|
DUCKDNS_SUBDOMAIN= os.environ.get('DUCKDNS_SUBDOMAIN', '')
|
||||||
|
|
||||||
|
|
||||||
def register_with_ddns(cell_name: str) -> None:
|
def register_with_ddns(cell_name: str) -> None:
|
||||||
@@ -353,15 +382,28 @@ def bootstrap_admin_password():
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
cell_name = os.environ.get('CELL_NAME', 'mycell')
|
cell_name = os.environ.get('CELL_NAME', 'mycell')
|
||||||
domain = os.environ.get('CELL_DOMAIN', 'cell')
|
domain_mode = DOMAIN_MODE # module-level, read from env
|
||||||
|
domain_name = CELL_DOMAIN_NAME
|
||||||
|
|
||||||
|
# Derive the legacy 'domain' TLD field and fill in domain_name if empty
|
||||||
|
if domain_mode == 'pic_ngo':
|
||||||
|
domain = 'pic.ngo'
|
||||||
|
if not domain_name:
|
||||||
|
domain_name = f'{cell_name}.pic.ngo'
|
||||||
|
elif domain_mode == 'lan':
|
||||||
|
domain = os.environ.get('CELL_DOMAIN', 'cell')
|
||||||
|
domain_name = ''
|
||||||
|
else:
|
||||||
|
# cloudflare / duckdns / http01 — domain_name is the full FQDN
|
||||||
|
domain = domain_name
|
||||||
|
|
||||||
vpn_address = os.environ.get('VPN_ADDRESS', '10.0.0.1/24')
|
vpn_address = os.environ.get('VPN_ADDRESS', '10.0.0.1/24')
|
||||||
wg_port = int(os.environ.get('WG_PORT', '51820'))
|
wg_port = int(os.environ.get('WG_PORT', '51820'))
|
||||||
# Prefer existing config ip_range over env var so `make setup` is safe to re-run
|
ip_range = os.environ.get('CELL_IP_RANGE') or _read_existing_ip_range() or '172.20.0.0/16'
|
||||||
ip_range = os.environ.get('CELL_IP_RANGE') or _read_existing_ip_range() or '172.20.0.0/16'
|
|
||||||
|
|
||||||
print('--- Personal Internet Cell: Setup ---')
|
print('--- Personal Internet Cell: Setup ---')
|
||||||
print(f' cell={cell_name} domain={domain} vpn={vpn_address} port={wg_port}')
|
print(f' cell={cell_name} mode={domain_mode} domain={domain_name or "(lan)"} vpn={vpn_address} port={wg_port}')
|
||||||
print()
|
print()
|
||||||
|
|
||||||
for d in REQUIRED_DIRS:
|
for d in REQUIRED_DIRS:
|
||||||
@@ -372,12 +414,14 @@ def main():
|
|||||||
ensure_caddy_ca_cert()
|
ensure_caddy_ca_cert()
|
||||||
priv, _pub = generate_wg_keys()
|
priv, _pub = generate_wg_keys()
|
||||||
write_wg0_conf(priv, vpn_address, wg_port)
|
write_wg0_conf(priv, vpn_address, wg_port)
|
||||||
write_cell_config(cell_name, domain, wg_port)
|
write_cell_config(cell_name, domain, wg_port, domain_mode, domain_name)
|
||||||
write_compose_env(ip_range)
|
write_compose_env(ip_range)
|
||||||
write_caddy_config(ip_range, cell_name, domain)
|
write_caddy_config(ip_range, cell_name, domain)
|
||||||
ensure_session_secret()
|
ensure_session_secret()
|
||||||
bootstrap_admin_password()
|
bootstrap_admin_password()
|
||||||
register_with_ddns(cell_name)
|
|
||||||
|
if domain_mode == 'pic_ngo':
|
||||||
|
register_with_ddns(cell_name)
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print('--- Setup complete! Run: make start ---')
|
print('--- Setup complete! Run: make start ---')
|
||||||
|
|||||||
Reference in New Issue
Block a user