Using the ZT2Hosts Script
The zt2hosts
script is a tool designed to interact with the ZTNET API. It retrieves a list of authorized network members and converts them to a format compatible with the hosts(5)
file, which is used for local hostname resolution on most Unix-like systems.
What the Script Does
The script performs the following functions:
Contacting ZTNET API: It makes requests to the ZTNET API to fetch information about network members.
Parsing Network Member Data: The script processes the JSON data returned by the API, focusing on authorized members.
Formatting for Hosts File: It converts the network member data into a format suitable for inclusion in a
hosts
file, providing both IPv4 and IPv6 addresses.
Prerequisites
Before using the script, ensure you have:
curl
andjq
installed (for API requests and JSON processing)
sudo apt install curl jq
How to Use the Script
- Set the API Token: The script requires a ZTNET API token that can be created in the ZTNET settings. Set this token in an environment variable named
ZTNET_API_TOKEN
.
export ZTNET_API_TOKEN='your-api-token'
- Set the API ADDRESS: The script requires a ZTNET API token that can be created in the ZTNET settings. Set this token in an environment variable named
API_ADDRESS
.
export API_ADDRESS='domain or ip address of your ztnet server'
# example 1: export API_ADDRESS='https://ztnet.network'
# example 2: export API_ADDRESS='http://localhost:3000'
Obtain the script.
#!/bin/bash
# zt2hosts: Contact ZTNET API, and convert a list of authorized network members to hosts(5) format
set -eo pipefail
## -----------------------------------------------------------------------------
# Function to check if the zone name is valid
is_valid_zone() {
if [[ $1 =~ ^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$ ]]; then
return 0
else
return 1
fi
}
[[ -z "$ZTNET_API_TOKEN" ]] && \
>&2 echo "ERROR: must set ZTNET_API_TOKEN!" && \
exit 1
[ "$1" = "" ] && \
>&2 echo "ERROR: must provide at least one network ID!" && \
exit 1
## -----------------------------------------------------------------------------
API_ADDRESS=${API_ADDRESS:-"http://localhost:3000"}
API_URL="${API_ADDRESS}/api/v1"
AUTH_HEADER="x-ztnet-auth: ${ZTNET_API_TOKEN}"
## -----------------------------------------------------------------------------
get_network_info() { curl -sH "${AUTH_HEADER}" "$API_URL/network/${1}/"; }
get_network_members() { curl -sH "${AUTH_HEADER}" "${API_URL}/network/${1}/member/"; }
print_ipv6_id() {
printf "%s:%s:%s" \
$(echo "$1" | cut -c1-2) \
$(echo "$1" | cut -c3-6) \
$(echo "$1" | cut -c7-10)
}
print_rfc4193() {
printf "fd%s:%s:%s:%s:%s99:93%s" \
$(echo "$2" | cut -c1-2) \
$(echo "$2" | cut -c3-6) \
$(echo "$2" | cut -c7-10) \
$(echo "$2" | cut -c11-14) \
$(echo "$2" | cut -c15-16) \
$(print_ipv6_id "$1")
}
print_6plane() {
local TOP=${2:0:8}
local BOT=${2:9:16}
local hashed=$(printf '%x\n' "$(( 0x$TOP ^ 0x$BOT ))")
printf "fc%s:%s:%s%s:0000:0000:0001" \
$(echo "$hashed" | cut -c1-2) \
$(echo "$hashed" | cut -c3-6) \
$(echo "$hashed" | cut -c7-8) \
$(print_ipv6_id "$1")
}
## -----------------------------------------------------------------------------
ipv4_lines=("127.0.0.1 localhost")
ipv6_lines=("::1 localhost ip6-localhost ip6-loopback")
for NETWORK in $@; do
mapfile -td \: FIELDS < <(printf "%s\0" "$NETWORK")
DNSNAME="${FIELDS[0]}"
NETWORK="${FIELDS[1]}"
# Check if the zone name is valid
if [ -n "$DNSNAME" ] && ! is_valid_zone "$DNSNAME"; then
>&2 echo "ERROR: Invalid domain name '$DNSNAME'"
exit 1
fi
netmembers=$(get_network_members "$NETWORK")
netinfo=$(get_network_info "$NETWORK")
# check if "error" is in the response
if [[ "$netinfo" == *"error"* ]]; then
>&2 echo "ERROR GET Network: $netinfo"
exit 1
fi
# check if "error" is in the response
if [[ "$netmembers" == *"error"* ]]; then
>&2 echo "ERROR GET Network Members: $netmembers"
exit 1
fi
joined=$(echo "$netmembers" | \
jq '.[] | select(.authorized == true) |
{ name: (.name | gsub(" "; "_")), id: .id, ips: .ipAssignments }')
v6conf=$(echo "$netinfo" | jq -c '.v6AssignMode')
sixplane=$(echo "$v6conf" | jq -r '.["6plane"]')
rfc4193=$(echo "$v6conf" | jq -r '.rfc4193')
for entry in $(echo "$joined" | jq -c '.'); do
nodeid=$(echo "$entry" | jq -r '.id')
nodename=$(echo "$entry" | jq -r '.name')
for ipv4_address in $(echo "$entry" | jq -r '.ips[]'); do
line=$(printf "%s\t%s\t%s" \
"$ipv4_address" \
"$nodename.$DNSNAME" \
"$nodeid.$DNSNAME")
ipv4_lines+=("$line")
done
done
for entry in $(echo "$joined" | jq -c '.'); do
nodeid=$(echo "$entry" | jq -r '.id')
if [ "$rfc4193" = "true" ]; then
line=$(printf "%s\t%s.%s\t%s" \
$(print_rfc4193 "$nodeid" "$NETWORK") \
$(echo "$entry" | jq -r '.name') \
"$DNSNAME" \
"$nodeid.$DNSNAME")
ipv6_lines+=("$line")
fi
if [ "$sixplane" = "true" ]; then
line=$(printf "%s\t%s.%s\t%s" \
$(print_6plane "$nodeid" "$NETWORK") \
$(echo "$entry" | jq -r '.name') \
"$DNSNAME" \
"$nodeid.$DNSNAME")
ipv6_lines+=("$line")
fi
done
done
## -----------------------------------------------------------------------------
(
for x in "${ipv4_lines[@]}"; do printf "%s\n" "$x"; done
for x in "${ipv6_lines[@]}"; do printf "%s\n" "$x"; done
) | column -t
Make it executable
chmod +x zt2hosts.sh
Run the script
./zt2hosts.sh <ZONE>:<NETWORK_ID>
Example output
./zt2hosts.sh ztnet.network:8056c2e21c000001
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
172.25.25.31 awesome_user.ztnet.network efcc1b0000.ztnet.network
fd17:d395:d8cb:43a8:1899:93ef:cc1b:0947 awesome_user.ztnet.network efcc1b0000.ztnet.network
fc1c:903d:c0ef:cc1b:0947:0000:0000:0001 awesome_user.ztnet.network efcc1b0000.ztnet.network
Backup your hosts file
sudo cp /etc/hosts /etc/hosts.bak
Add the output to your hosts file
./zt2hosts.sh ztnet.network:8056c2e21c000001 | sudo tee -i /etc/hosts
Test it out
ping awesome_user.ztnet.network