New packages: - secubox-app-backup: Unified backup for LXC containers, UCI config, services - luci-app-backup: KISS dashboard with container list and backup history - secubox-app-mailserver: Custom Postfix+Dovecot in LXC with mesh backup Enhanced dnsctl with: - generate: Auto-create subdomain A records - suggest: Name suggestions by category - mail-setup: MX, SPF, DMARC record creation - dkim-add: DKIM TXT record management Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
598 lines
16 KiB
Bash
598 lines
16 KiB
Bash
#!/bin/sh
|
|
# SecuBox DNS Provider Controller
|
|
# Programmatic DNS record management via provider APIs
|
|
|
|
VERSION="1.0.0"
|
|
CONFIG="dns-provider"
|
|
ADAPTER_DIR="/usr/lib/secubox/dns"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${GREEN}[DNS]${NC} $1"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
|
|
uci_get() { uci -q get ${CONFIG}.$1; }
|
|
|
|
# ============================================================================
|
|
# Provider Loading
|
|
# ============================================================================
|
|
|
|
load_provider() {
|
|
local provider=$(uci_get main.provider)
|
|
local zone=$(uci_get main.zone)
|
|
|
|
if [ -z "$provider" ]; then
|
|
error "No DNS provider configured. Set with: uci set dns-provider.main.provider='ovh'"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "$zone" ]; then
|
|
error "No DNS zone configured. Set with: uci set dns-provider.main.zone='example.com'"
|
|
exit 1
|
|
fi
|
|
|
|
local adapter="${ADAPTER_DIR}/${provider}.sh"
|
|
if [ ! -f "$adapter" ]; then
|
|
error "Unknown provider: $provider (no adapter at $adapter)"
|
|
exit 1
|
|
fi
|
|
|
|
. "$adapter"
|
|
}
|
|
|
|
get_zone() {
|
|
uci_get main.zone
|
|
}
|
|
|
|
# ============================================================================
|
|
# Commands
|
|
# ============================================================================
|
|
|
|
cmd_list() {
|
|
load_provider
|
|
local zone=$(get_zone)
|
|
log "Listing records for $zone..."
|
|
dns_list "$zone"
|
|
}
|
|
|
|
cmd_add() {
|
|
local type="$1" subdomain="$2" target="$3" ttl="${4:-3600}"
|
|
|
|
if [ -z "$type" ] || [ -z "$subdomain" ] || [ -z "$target" ]; then
|
|
echo "Usage: dnsctl add <TYPE> <subdomain> <target> [ttl]"
|
|
echo " TYPE: A, AAAA, CNAME, TXT, MX, SRV"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " dnsctl add A myservice 1.2.3.4"
|
|
echo " dnsctl add CNAME www mycdn.net"
|
|
echo " dnsctl add TXT _acme-challenge 'validation-token'"
|
|
return 1
|
|
fi
|
|
|
|
load_provider
|
|
local zone=$(get_zone)
|
|
log "Adding $type record: ${subdomain}.${zone} → $target (TTL: $ttl)"
|
|
dns_add "$zone" "$type" "$subdomain" "$target" "$ttl"
|
|
echo ""
|
|
log "Record created. Verify with: dnsctl verify ${subdomain}.${zone}"
|
|
}
|
|
|
|
cmd_rm() {
|
|
local type="$1" subdomain="$2"
|
|
|
|
if [ -z "$type" ] || [ -z "$subdomain" ]; then
|
|
echo "Usage: dnsctl rm <TYPE> <subdomain>"
|
|
echo " dnsctl rm A myservice"
|
|
return 1
|
|
fi
|
|
|
|
load_provider
|
|
local zone=$(get_zone)
|
|
log "Removing $type record for ${subdomain}.${zone}..."
|
|
dns_rm "$zone" "$type" "$subdomain"
|
|
log "Record removed."
|
|
}
|
|
|
|
cmd_sync() {
|
|
load_provider
|
|
local zone=$(get_zone)
|
|
log "Syncing local vhosts to DNS..."
|
|
|
|
# Read HAProxy vhosts
|
|
local idx=0
|
|
local synced=0
|
|
while uci -q get haproxy.@vhost[$idx] >/dev/null 2>&1; do
|
|
local domain=$(uci -q get haproxy.@vhost[$idx].domain)
|
|
local enabled=$(uci -q get haproxy.@vhost[$idx].enabled)
|
|
|
|
if [ "$enabled" = "1" ] && [ -n "$domain" ]; then
|
|
# Check if domain is within our zone
|
|
if echo "$domain" | grep -q "\.${zone}$"; then
|
|
local subdomain=$(echo "$domain" | sed "s/\.${zone}$//")
|
|
|
|
# Get public IP
|
|
local public_ip=$(curl -s --connect-timeout 5 https://ipv4.icanhazip.com 2>/dev/null | tr -d '\n')
|
|
if [ -n "$public_ip" ]; then
|
|
log " $subdomain → $public_ip"
|
|
dns_add "$zone" "A" "$subdomain" "$public_ip" 3600
|
|
synced=$((synced + 1))
|
|
fi
|
|
fi
|
|
fi
|
|
idx=$((idx + 1))
|
|
done
|
|
|
|
log "Synced $synced record(s)."
|
|
}
|
|
|
|
cmd_verify() {
|
|
local fqdn="$1"
|
|
|
|
if [ -z "$fqdn" ]; then
|
|
echo "Usage: dnsctl verify <fqdn>"
|
|
return 1
|
|
fi
|
|
|
|
log "Checking DNS propagation for $fqdn..."
|
|
|
|
# Check with multiple resolvers
|
|
local resolvers="1.1.1.1 8.8.8.8 9.9.9.9"
|
|
local resolved=0
|
|
local failed=0
|
|
|
|
for resolver in $resolvers; do
|
|
local result=$(nslookup "$fqdn" "$resolver" 2>/dev/null | grep "Address:" | tail -1 | awk '{print $2}')
|
|
if [ -n "$result" ] && [ "$result" != "$resolver" ]; then
|
|
echo -e " ${GREEN}$resolver${NC} → $result"
|
|
resolved=$((resolved + 1))
|
|
else
|
|
echo -e " ${RED}$resolver${NC} → not resolved"
|
|
failed=$((failed + 1))
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
if [ "$failed" -eq 0 ]; then
|
|
log "Fully propagated ($resolved/$((resolved + failed)) resolvers)"
|
|
elif [ "$resolved" -gt 0 ]; then
|
|
warn "Partially propagated ($resolved/$((resolved + failed)) resolvers)"
|
|
else
|
|
error "Not propagated yet. DNS changes may take up to 24 hours."
|
|
fi
|
|
}
|
|
|
|
cmd_test() {
|
|
load_provider
|
|
local provider=$(uci_get main.provider)
|
|
log "Testing $provider credentials..."
|
|
local result=$(dns_test_credentials)
|
|
if [ "$result" = "ok" ]; then
|
|
log "Credentials valid."
|
|
else
|
|
error "Credential test failed: $result"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
cmd_status() {
|
|
local provider=$(uci_get main.provider)
|
|
local zone=$(uci_get main.zone)
|
|
local enabled=$(uci_get main.enabled)
|
|
|
|
echo ""
|
|
echo "========================================"
|
|
echo " DNS Provider Status v$VERSION"
|
|
echo "========================================"
|
|
echo ""
|
|
echo " Enabled: $([ "$enabled" = "1" ] && echo -e "${GREEN}Yes${NC}" || echo -e "${RED}No${NC}")"
|
|
echo " Provider: ${provider:-not set}"
|
|
echo " Zone: ${zone:-not set}"
|
|
|
|
if [ -n "$provider" ]; then
|
|
echo ""
|
|
echo "Provider Config ($provider):"
|
|
case "$provider" in
|
|
ovh)
|
|
local endpoint=$(uci_get ovh.endpoint)
|
|
local app_key=$(uci_get ovh.app_key)
|
|
echo " Endpoint: ${endpoint:-ovh-eu}"
|
|
echo " App Key: ${app_key:+configured}${app_key:-NOT SET}"
|
|
echo " App Secret: $([ -n "$(uci_get ovh.app_secret)" ] && echo "configured" || echo "NOT SET")"
|
|
echo " Consumer Key:$([ -n "$(uci_get ovh.consumer_key)" ] && echo " configured" || echo " NOT SET")"
|
|
;;
|
|
gandi)
|
|
echo " API Key: $([ -n "$(uci_get gandi.api_key)" ] && echo "configured" || echo "NOT SET")"
|
|
;;
|
|
cloudflare)
|
|
echo " API Token: $([ -n "$(uci_get cloudflare.api_token)" ] && echo "configured" || echo "NOT SET")"
|
|
echo " Zone ID: $([ -n "$(uci_get cloudflare.zone_id)" ] && echo "configured" || echo "NOT SET")"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
echo ""
|
|
}
|
|
|
|
# ============================================================================
|
|
# DynDNS Commands
|
|
# ============================================================================
|
|
|
|
cmd_dyndns() {
|
|
local subdomain="${1:-@}"
|
|
local ttl="${2:-300}"
|
|
|
|
load_provider
|
|
local zone=$(get_zone)
|
|
local provider=$(uci_get main.provider)
|
|
|
|
log "DynDNS update for ${subdomain}.${zone}..."
|
|
|
|
# Check if provider has dyndns support
|
|
if ! type dns_dyndns >/dev/null 2>&1; then
|
|
error "Provider $provider does not support DynDNS (dns_dyndns function missing)"
|
|
return 1
|
|
fi
|
|
|
|
local result=$(dns_dyndns "$zone" "$subdomain" "$ttl")
|
|
echo "$result"
|
|
|
|
if echo "$result" | grep -q '"status":"updated"'; then
|
|
local new_ip=$(echo "$result" | jsonfilter -e '@.new_ip' 2>/dev/null)
|
|
log "Updated ${subdomain}.${zone} → $new_ip"
|
|
elif echo "$result" | grep -q '"status":"unchanged"'; then
|
|
local ip=$(echo "$result" | jsonfilter -e '@.ip' 2>/dev/null)
|
|
log "IP unchanged: $ip"
|
|
else
|
|
error "DynDNS update failed"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
cmd_get() {
|
|
local type="$1" subdomain="$2"
|
|
|
|
if [ -z "$type" ]; then
|
|
echo "Usage: dnsctl get <TYPE> [subdomain]"
|
|
echo " dnsctl get A @ # Get root A record"
|
|
echo " dnsctl get A www # Get www A record"
|
|
return 1
|
|
fi
|
|
|
|
[ -z "$subdomain" ] && subdomain="@"
|
|
|
|
load_provider
|
|
local zone=$(get_zone)
|
|
|
|
# Check if provider has get support
|
|
if type dns_get >/dev/null 2>&1; then
|
|
dns_get "$zone" "$type" "$subdomain"
|
|
else
|
|
# Fallback to list and filter
|
|
dns_list "$zone" | jsonfilter -e "@[*]" 2>/dev/null | \
|
|
grep -E "\"rrset_name\":\"$subdomain\"" | \
|
|
grep -E "\"rrset_type\":\"$type\""
|
|
fi
|
|
}
|
|
|
|
cmd_update() {
|
|
local type="$1" subdomain="$2" target="$3" ttl="${4:-3600}"
|
|
|
|
if [ -z "$type" ] || [ -z "$subdomain" ] || [ -z "$target" ]; then
|
|
echo "Usage: dnsctl update <TYPE> <subdomain> <target> [ttl]"
|
|
echo " Updates existing record (use 'add' for new records)"
|
|
return 1
|
|
fi
|
|
|
|
load_provider
|
|
local zone=$(get_zone)
|
|
|
|
# Check if provider has update support
|
|
if type dns_update >/dev/null 2>&1; then
|
|
log "Updating $type record: ${subdomain}.${zone} → $target"
|
|
dns_update "$zone" "$type" "$subdomain" "$target" "$ttl"
|
|
else
|
|
# Fallback: remove and add
|
|
warn "Provider lacks update, using rm+add"
|
|
dns_rm "$zone" "$type" "$subdomain"
|
|
dns_add "$zone" "$type" "$subdomain" "$target" "$ttl"
|
|
fi
|
|
}
|
|
|
|
cmd_domains() {
|
|
load_provider
|
|
local provider=$(uci_get main.provider)
|
|
|
|
if type dns_domains >/dev/null 2>&1; then
|
|
log "Domains in $provider account:"
|
|
dns_domains
|
|
else
|
|
error "Provider $provider does not support domain listing"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# Subdomain Generator
|
|
# ============================================================================
|
|
|
|
cmd_generate() {
|
|
local service="$1"
|
|
local prefix="${2:-}"
|
|
|
|
if [ -z "$service" ]; then
|
|
echo "Usage: dnsctl generate <service> [prefix]"
|
|
echo " Auto-generates unique subdomain and creates A record"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " dnsctl generate gitea # Creates gitea.zone.tld"
|
|
echo " dnsctl generate blog dev # Creates dev-blog.zone.tld"
|
|
echo " dnsctl generate api prod # Creates prod-api.zone.tld"
|
|
return 1
|
|
fi
|
|
|
|
load_provider
|
|
local zone=$(get_zone)
|
|
local public_ip=$(curl -s --connect-timeout 5 https://ipv4.icanhazip.com 2>/dev/null | tr -d '\n')
|
|
|
|
[ -z "$public_ip" ] && { error "Cannot detect public IP"; return 1; }
|
|
|
|
# Build subdomain name
|
|
local subdomain
|
|
if [ -n "$prefix" ]; then
|
|
subdomain="${prefix}-${service}"
|
|
else
|
|
subdomain="$service"
|
|
fi
|
|
|
|
# Check if subdomain exists
|
|
local existing=$(nslookup "${subdomain}.${zone}" 8.8.8.8 2>/dev/null | grep "Address:" | tail -1)
|
|
if echo "$existing" | grep -q "[0-9]"; then
|
|
warn "Subdomain ${subdomain}.${zone} already exists"
|
|
# Try with numeric suffix
|
|
local i=2
|
|
while [ $i -lt 10 ]; do
|
|
local try_sub="${subdomain}${i}"
|
|
local try_check=$(nslookup "${try_sub}.${zone}" 8.8.8.8 2>/dev/null | grep "Address:" | tail -1)
|
|
if ! echo "$try_check" | grep -q "[0-9]"; then
|
|
subdomain="$try_sub"
|
|
break
|
|
fi
|
|
i=$((i + 1))
|
|
done
|
|
fi
|
|
|
|
log "Generating subdomain: ${subdomain}.${zone}"
|
|
dns_add "$zone" "A" "$subdomain" "$public_ip" 3600
|
|
|
|
echo ""
|
|
echo "Created: ${subdomain}.${zone} → $public_ip"
|
|
echo ""
|
|
log "Verify with: dnsctl verify ${subdomain}.${zone}"
|
|
}
|
|
|
|
cmd_suggest() {
|
|
local category="${1:-web}"
|
|
|
|
echo ""
|
|
echo "Subdomain suggestions for category: $category"
|
|
echo ""
|
|
|
|
case "$category" in
|
|
web)
|
|
echo " www, blog, shop, app, portal, admin, api"
|
|
;;
|
|
mail)
|
|
echo " mail, smtp, imap, pop, webmail, mx, mta"
|
|
;;
|
|
dev)
|
|
echo " git, dev, staging, test, ci, cd, build"
|
|
;;
|
|
media)
|
|
echo " media, cdn, stream, video, music, files"
|
|
;;
|
|
iot)
|
|
echo " mqtt, home, sensor, hub, iot, zwave, zigbee"
|
|
;;
|
|
security)
|
|
echo " vpn, tor, proxy, guard, auth, sso"
|
|
;;
|
|
*)
|
|
echo " Custom: use 'dnsctl generate <name>'"
|
|
;;
|
|
esac
|
|
echo ""
|
|
}
|
|
|
|
# ============================================================================
|
|
# Mail DNS Records (MX, SPF, DKIM, DMARC)
|
|
# ============================================================================
|
|
|
|
cmd_mail_setup() {
|
|
local mail_host="${1:-mail}"
|
|
local priority="${2:-10}"
|
|
|
|
load_provider
|
|
local zone=$(get_zone)
|
|
local public_ip=$(curl -s --connect-timeout 5 https://ipv4.icanhazip.com 2>/dev/null | tr -d '\n')
|
|
|
|
[ -z "$public_ip" ] && { error "Cannot detect public IP"; return 1; }
|
|
|
|
log "Setting up mail DNS records for $zone..."
|
|
|
|
# Create mail A record
|
|
log " Creating A record: ${mail_host}.${zone} → $public_ip"
|
|
dns_add "$zone" "A" "$mail_host" "$public_ip" 3600
|
|
|
|
# Create MX record
|
|
log " Creating MX record: ${zone} → ${mail_host}.${zone} (priority $priority)"
|
|
dns_add "$zone" "MX" "@" "${priority} ${mail_host}.${zone}." 3600
|
|
|
|
# Create SPF record
|
|
local spf="v=spf1 mx a:${mail_host}.${zone} ~all"
|
|
log " Creating SPF record: $spf"
|
|
dns_add "$zone" "TXT" "@" "$spf" 3600
|
|
|
|
# Create DMARC record (relaxed policy for start)
|
|
local dmarc="v=DMARC1; p=none; rua=mailto:postmaster@${zone}"
|
|
log " Creating DMARC record: $dmarc"
|
|
dns_add "$zone" "TXT" "_dmarc" "$dmarc" 3600
|
|
|
|
echo ""
|
|
log "Mail DNS setup complete!"
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo " 1. Configure your mail server at ${mail_host}.${zone}"
|
|
echo " 2. Generate DKIM keys and add TXT record:"
|
|
echo " dnsctl add TXT mail._domainkey '<dkim-public-key>'"
|
|
echo " 3. Verify MX: dig MX ${zone}"
|
|
echo " 4. Test SPF: dig TXT ${zone}"
|
|
echo ""
|
|
}
|
|
|
|
cmd_dkim_add() {
|
|
local selector="${1:-mail}"
|
|
local public_key="$2"
|
|
|
|
if [ -z "$public_key" ]; then
|
|
echo "Usage: dnsctl dkim-add [selector] <public_key>"
|
|
echo " selector defaults to 'mail'"
|
|
echo ""
|
|
echo "Generate DKIM key pair:"
|
|
echo " openssl genrsa -out dkim.private 2048"
|
|
echo " openssl rsa -in dkim.private -pubout -out dkim.public"
|
|
echo " # Use content between -----BEGIN/END PUBLIC KEY----- (no newlines)"
|
|
return 1
|
|
fi
|
|
|
|
load_provider
|
|
local zone=$(get_zone)
|
|
|
|
# Format DKIM record
|
|
local dkim="v=DKIM1; k=rsa; p=${public_key}"
|
|
log "Adding DKIM record: ${selector}._domainkey.${zone}"
|
|
dns_add "$zone" "TXT" "${selector}._domainkey" "$dkim" 3600
|
|
|
|
log "DKIM record added. Verify with: dig TXT ${selector}._domainkey.${zone}"
|
|
}
|
|
|
|
# ============================================================================
|
|
# ACME DNS-01 Helper
|
|
# ============================================================================
|
|
|
|
cmd_acme_dns01() {
|
|
local domain="$1"
|
|
|
|
if [ -z "$domain" ]; then
|
|
echo "Usage: dnsctl acme-dns01 <domain>"
|
|
echo "Issues certificate via DNS-01 challenge using configured provider."
|
|
return 1
|
|
fi
|
|
|
|
load_provider
|
|
local provider=$(uci_get main.provider)
|
|
|
|
log "Issuing certificate for $domain via DNS-01 ($provider)..."
|
|
|
|
case "$provider" in
|
|
ovh)
|
|
export OVH_END_POINT=$(uci_get ovh.endpoint)
|
|
export OVH_APPLICATION_KEY=$(uci_get ovh.app_key)
|
|
export OVH_APPLICATION_SECRET=$(uci_get ovh.app_secret)
|
|
export OVH_CONSUMER_KEY=$(uci_get ovh.consumer_key)
|
|
acme.sh --issue -d "$domain" --dns dns_ovh
|
|
;;
|
|
gandi)
|
|
export GANDI_LIVEDNS_KEY=$(uci_get gandi.api_key)
|
|
acme.sh --issue -d "$domain" --dns dns_gandi_livedns
|
|
;;
|
|
cloudflare)
|
|
export CF_Token=$(uci_get cloudflare.api_token)
|
|
export CF_Zone_ID=$(uci_get cloudflare.zone_id)
|
|
acme.sh --issue -d "$domain" --dns dns_cf
|
|
;;
|
|
*)
|
|
error "Unsupported provider for ACME DNS-01: $provider"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ============================================================================
|
|
# Main
|
|
# ============================================================================
|
|
|
|
show_help() {
|
|
cat << EOF
|
|
SecuBox DNS Provider Control v$VERSION
|
|
|
|
Usage: dnsctl <command> [options]
|
|
|
|
Commands:
|
|
list List all DNS records in zone
|
|
add <TYPE> <sub> <target> Create DNS record (A, AAAA, CNAME, TXT, MX)
|
|
update <TYPE> <sub> <target> Update existing DNS record
|
|
get <TYPE> [subdomain] Get specific record value
|
|
rm <TYPE> <subdomain> Remove DNS record
|
|
sync Sync HAProxy vhosts → DNS A records
|
|
verify <fqdn> Check DNS propagation across resolvers
|
|
test Verify provider API credentials
|
|
status Show provider configuration status
|
|
domains List all domains in provider account
|
|
|
|
DynDNS:
|
|
dyndns [subdomain] [ttl] Update A record with current WAN IP
|
|
|
|
Generator:
|
|
generate <service> [prefix] Auto-generate subdomain with A record
|
|
suggest [category] Show subdomain name suggestions
|
|
|
|
Mail:
|
|
mail-setup [host] [priority] Set up MX, SPF, DMARC records
|
|
dkim-add [selector] <pubkey> Add DKIM TXT record
|
|
|
|
SSL:
|
|
acme-dns01 <domain> Issue SSL cert via DNS-01 challenge
|
|
|
|
Examples:
|
|
dnsctl add A gitea 1.2.3.4
|
|
dnsctl update A gitea 5.6.7.8
|
|
dnsctl get A www
|
|
dnsctl dyndns @ 300 # Update root A record
|
|
dnsctl dyndns api 600 # Update api.zone with WAN IP
|
|
dnsctl rm A gitea
|
|
dnsctl sync
|
|
dnsctl verify gitea.example.com
|
|
dnsctl acme-dns01 '*.example.com'
|
|
|
|
EOF
|
|
}
|
|
|
|
case "${1:-}" in
|
|
list) shift; cmd_list "$@" ;;
|
|
add) shift; cmd_add "$@" ;;
|
|
update) shift; cmd_update "$@" ;;
|
|
get) shift; cmd_get "$@" ;;
|
|
rm|remove) shift; cmd_rm "$@" ;;
|
|
sync) shift; cmd_sync "$@" ;;
|
|
verify) shift; cmd_verify "$@" ;;
|
|
test) shift; cmd_test "$@" ;;
|
|
status) shift; cmd_status "$@" ;;
|
|
domains) shift; cmd_domains "$@" ;;
|
|
dyndns) shift; cmd_dyndns "$@" ;;
|
|
generate) shift; cmd_generate "$@" ;;
|
|
suggest) shift; cmd_suggest "$@" ;;
|
|
mail-setup) shift; cmd_mail_setup "$@" ;;
|
|
dkim-add) shift; cmd_dkim_add "$@" ;;
|
|
acme-dns01) shift; cmd_acme_dns01 "$@" ;;
|
|
help|--help|-h|'') show_help ;;
|
|
*) error "Unknown command: $1"; show_help >&2; exit 1 ;;
|
|
esac
|
|
|
|
exit 0
|