Unix: add dns static ip blog

This commit is contained in:
Akemi Izuko 2024-04-21 21:30:02 -06:00
parent 7ec3d37186
commit aa9c63256d
Signed by: akemi
GPG key ID: 8DE0764E1809E9FC

View file

@ -24,25 +24,25 @@ easily get *inward* access to any port.
The easiest method, and probably recommended for most users, is using Software
Driven WAN (SDWAN). This is similar to a VPN, but unlike Wireguard, ZeroTier One
doesn't [[1]] require a centralized server.
doesn't<sup>1</sup> require a centralized server.
Advantages:
- Free version is good enough for most small groups
- Very simple setup
- Very fast through UDP hole-punching
- Very low latency through UDP hole-punching
- Free (for up to 25 clients)
Drawbacks:
- Limit of 25 clients (on the [free
version](https://www.zerotier.com/pricing/))
- Requires installing separate software on all clients
- Sometimes fails to connect for up to an hour... very hard to debug when it
happens
- Can only be used by clients on the VPN. For example, a public webserver won't
be able to use this
- Limit of 25 clients (on the [free
version](https://www.zerotier.com/pricing/))
To use ZeroTier:
1. Sign up for an account at [zerotier.com](https://www.zerotier.com/)
2. Under "Networks" create a network and give it a name. Ensure access control
2. Under "Networks" create a network and give it a name. Ensure Access Control
is private.
3. Install ZeroTier One on all clients you'd like to connect. You can add more
later.
@ -168,3 +168,163 @@ ssh -p 8022 emiliko@172.27.10.10
```
This will be forwarded to port 22 on the server!
### Reverse Proxy without a Static IP
If you're planning to use a home computer, you'll quickly find that most
internet service providers do not offer static IPs for consumer plans. Luckily,
there's a very simple way around this: Domain Name Servers (DNS).
You will need a domain to achieve this. Domains should not cost more than
$20/year. Here I'll use the domain `example.com` as an example.
The idea is that a domain will point to a specific IP, but this IP is determined
through a lookup to the DNS. This means that if we change the IP the DNS has
every time our computer's IP changes, we'll appear to have a static IP!
First, put your nameservers on a good DNS provider. I use
[Cloudflare](https://pages.cloudflare.com/), it's free and fast. You'll need to
find the DNS page. The URL will look something like:
```
https://dash.cloudflare.com/<long-project-id>/example.com/dns/records
```
There, you'll want to add an "A Record". The name will be the subdomain. So if
my computer is called `mycomputer` and that's in the name field, it'll be
accessible at `mycomputer.example.com`.
Now you need to identify your IP address. This is your PUBLIC IP address, not
your LOCAL IP address. One easy way to do this is `curl -q
https://ifconfig.me/ip`.
Make sure "Proxy Status" is OFF. Proxying the connection appears to make this
whole idea break down very quickly, so don't.
With that "A Record" set, try `host mycomputer.example.com` to see when the DNS
updates. This can take up to 4 hour, but usually takes under a minute in
practice. With this, you should be able to access your computer using the
domain! Of course, make sure your router's ports are forwarding to your
computer.
We now need to make your computer update Cloudflare's DNS, whenever the IP
changes. I use the script below to do this. Fill in the `HOST`, `DOMAIN`,
`TOKEN`, `ZONE_ID`. The `TOKEN` is your Cloudflare application token:
```bash
#!/usr/bin/env bash
declare wan_ip_record wan_ip cf_records host_record cf_host_ip cf_rec_id
declare -r HOST='mycomputer'
declare -r DOMAIN='example.com'
declare -r TOKEN='...'
declare -r ZONE_ID='...'
#╔─────────────────────────────────────────────────────────────────────────────╗
#│ Gετ WΛN IP |
#╚─────────────────────────────────────────────────────────────────────────────╝
if ! wan_ip_record="$(curl ifconfig.me)"; then
echo "Hosts timed out" >&2
exit 1
fi
wan_ip="$wan_ip_record"
#wan_ip="$(echo "$wan_ip_record" | tail -n1 | awk '{ split($0, a, " "); print a[4] }')"
#╔─────────────────────────────────────────────────────────────────────────────╗
#│ Gετ Λ rεcδrd δη Clδμdflαrε |
#╚─────────────────────────────────────────────────────────────────────────────╝
if ! cf_records="$(curl -s --request GET \
--url https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $TOKEN")"
then
echo "Failed to retrive cloudflare zone records" >&2
exit 1
fi
for i in {0..4000}; do # Assuming 4000 is enough
record="$(echo "$cf_records" | jq --raw-output ".result[${i}].name")"
if [[ "$record" == "${HOST}.${DOMAIN}" ]]; then
host_record="$(echo "$cf_records" | jq -r ".result[${i}]")"
break
elif [[ "$record" == null ]]; then
echo "No record found for ${HOST}.${DOMAIN}" >&2
exit 1
fi
done
#╔─────────────────────────────────────────────────────────────────────────────╗
#│ Sετ Λ rεcδrd τδ cμrrεητ WΛN |
#╚─────────────────────────────────────────────────────────────────────────────╝
cf_host_ip="$(echo "$host_record" | jq -r '.content')"
cf_rec_id="$(echo "$host_record" | jq -r '.id')"
if [[ -z "$cf_host_ip" || "$cf_host_ip" == null ]]; then
echo "Failed to find content of A record for ${HOST}.${DOMAIN}" >&2
exit 1
elif [[ -z "$cf_rec_id" || "$cf_rec_id" == null ]]; then
echo "Failed to find A record ID for ${HOST}.${DOMAIN}" >&2
exit 1
fi
if [[ "$cf_host_ip" == "$wan_ip" ]]; then
echo "Cloudflare is up to date @ $(date)" >&2
else
echo "Updating Cloudflare's A record from $cf_host_ip to $wan_ip" >&2
patch_response="$(curl -s --request PATCH \
--url "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${cf_rec_id}" \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $TOKEN" \
--data '{
"comment": "'"${HOST} @ $(date)"'",
"content": "'"$wan_ip"'",
"name": "'"${HOST}.${DOMAIN}"'",
"proxied": false,
"ttl": 1
}')"
if [[ "$(echo "$patch_response" | jq -r '.success')" == true ]]; then
echo "Update to $wan_ip succeeded @ $(date)" >&2
else
echo "Failed to update A record. DUMP:"
echo "$patch_response"
exit 1
fi
fi
```
Now we need a systemd-timer to run this script. I run it once every 15 minutes.
Please refer to the [systemd-timers]() blog for more information, but breifly I
use:
```ini
[Unit]
Wants=update_a_record.timer
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/set_a_records.sh
```
Timer:
```ini
[Unit]
Requires=update_a_record.service
[Timer]
Unit=update_a_record.service
OnCalendar=*-*-* *:00,15,30,45:00
RandomizedDelaySec=15min
[Install]
WantedBy=timers.target
```
Then start it with `systemctl enable update_a_record.service`. The name of the
service will be different based on what you called the files.