Debian 13 LXC - Headscale VPN

mimic·2026년 9월 3일

개요

시놀로지 디스크에 대용량 업로드를 여러 원격지에서 하다보니 원격지의 접속 IP들을 화이트리스트 처리하더라도 PC 리부팅 시 IP가 변경될 때마다 새로 기재해야 하는 번거로움 등이 발생하였습니다.
이에 네트워크 드라이브(SMB)로 연결하게 할 VPN 대역을 구축하기로 하였습니다.

VPN용 서브도메인 생성한 뒤 Headscale 용 LXC를 구축해서 NginxProxyGuard로 도메인에 LXC를 연결하겠습니다.

서브 도메인 내역

그룹 별로 다른 도메인을 부여했기 때문에 도메인 A, B의 서브 도메인이 두 개 있는 상황입니다.

1번 도메인 (내부인용) ─┐
                      → 같은 공유기 공인 IP → NPG → 172.30.1.103:8080 (Headscale)
2번 도메인 (외부인용) ─┘

위 구조로 연결할 예정이니 서로 다른 도메인이더라도 A 레코드 값은 같은 공유기 IP를 바라보고 있습니다.
다만, Headscale 정책 상 config 파일 안에 선언하는 server_url은 한 개의 도메인만 사용할 수 있으니 가능한 외부에 공개되어도 괜찮은 도메인을 메인으로 지정하거나 vpn 전용 도메인을 생성하는 것을 권유드립니다.


Headscale LXC 구축

LXC 생성 옵션

Headscale 설치

패키지 업데이트 및 기본 패키지 설치

apt update -y && apt upgrade -y
apt install -y curl gnupg wget net-tools

Headscale 설치

cd /usr/local/src

# 다운로드
HEADSCALE_VERSION="0.29.3"
wget --output-document=headscale.deb \
  "https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_amd64.deb"

# 설치
apt install ./headscale.deb

# 확인
headscale version
===
headscale version v0.29.3
===

systemctl status headscale

Headscale config.yaml 설정

기본 config 확인

cat /etc/headscale/config.yaml | head -20
===
---
# headscale will look for a configuration file named `config.yaml` (or `config.json`) in the following order:
#
# - `/etc/headscale`
# - `~/.headscale`
# - current working directory

# The url clients will connect to.
# Typically this will be a domain like:
#
# https://myheadscale.example.com:443
#
server_url: http://127.0.0.1:8080

# Address to listen to / bind to on the server
#
# For production:
# listen_addr: 0.0.0.0:8080
listen_addr: 127.0.0.1:8080
===

config.yaml 수정

# 백업
cp -arp /etc/headscale/config.yaml /etc/headscale/config.yaml_ori

# 수정
/etc/headscale/config.yaml_ori
===
# server_url: http://127.0.0.1:8080
server_url: https://$Domain

# listen_addr: 127.0.0.1:8080
listen_addr: 0.0.0.0:8080

tls_cert_path: ""
tls_key_path: ""
===

# 검증
headscale configtest

검증 시 INFO 출력만 되는 건 상관 없습니다. 문제 없을 시 서비스를 재시작합니다.

# 서비스 재시작
systemctl restart headscale
systemctl status headscale

Local Address 값 변경이 잘 되었는지 확인합니다.

netstat -nltp | head -n 1 && netstat -nltp | grep 8080


NginxProxyGuard 연결

프록시 호스트 정보 생성

연결 테스트

Headscale 서버 자체는 백엔드(API) 기능만 제공하며 시각적인 웹 화면이 포함되어 있지 않습니다.

아무것도 출력되지 않고 /web 같은 특정 경로로 접근 시 404 not found가 발생합니다.

SSL 인증 적용

조금의 시간 이후 SSL 인증을 적용해줍니다. Let's Encrypt로 새로 생성해주면 됩니다.

보안 설정 변경

외부 기기 특히 모바일 디바이스에서 원활하게 연결되려면 아래 두 가지 사항을 체크해야 합니다.

① 봇 필터 예외 추가

로그 확인 시 Go-http-client/ 2.0으로 초기 진입하고 이후 마무리 연결에서 Go-http-client/ 1.1을 사용하였습니다. 둘 다 에이전트 예외 설정을 해주어야 합니다.

Go-http-client/2.0
Go-http-client/1.1

② fail2ban 조건 완화
maxretry = 10
findtime = 10m


WEB UI 설정

WEB UI는 기본적인 headscale-ui와 headplane이 있습니다. 더 최근에 개발되어 다양한 기능을 제공하는 headplane으로 진행하겠습니다.

API Key 생성

명령어로 WEB에서 연결한 API Key를 생성합니다. 아래 명령어 입력 시 단 한 번만 출력되기 때문에 반드시 별도로 복사해두어야 합니다.

headscale apikeys create --expiration 3650d

headplane은 엄격한 32글자의 문자열을 cookie_secret으로 요구합니다. 실제로 23글자를 넣었다가 에러가 발생하였으니 꼭 아래와 같이 그냥 길이에 맞는 난수를 생성해서 사용하시는 게 좋습니다.

openssl rand -hex 16
===
문자열 복사
===

docker 설치

github에서 확인 시 docker 배포 방식을 사용합니다.

curl -fsSL https://get.docker.com | sh
systemctl enable docker --now
mkdir -p /opt/headplane

만일 여러 위치에 컨테이너를 두는 방식이 싫다면 NginxProxyGuard 안에 컨테이너로 생성하고 웹만 npg LXC에서, 실제 VPN은 headscale LXC에서 운영하는 방법도 가능합니다.

headplane config.yaml 생성

cd /opt/headplane
vi config.yaml
===
# Headplane 설정 파일
server:
  host: 0.0.0.0
  port: 3000
  cookie_secret: "여기에_생성된_정확히_32글자_문자열_입력"

headscale:
  url: http://172.30.1.103:8080
===

web ui docker 컨테이너 생성

cd /opt/headplane
vi docker-compose.yaml
===
version: "3"
services:
  headplane:
    image: ghcr.io/tale/headplane:latest
    container_name: headplane
    restart: always
    ports:
      - "3000:3000"
    volumes:
      - /var/lib/headscale:/var/lib/headscale # Headscale 데이터 경로 (읽기/쓰기)
      - /etc/headscale:/etc/headscale         # Headscale 설정 경로
      - ./config.yaml:/etc/headplane/config.yaml  # [추가] 설정 파일 마운트
    environment:
      - PORT=3000
      - COOKIE_SECRET=$여기에_생성된_정확히_32글자_문자열_입력
      - HEADSCALE_BACKEND_URL=http://127.0.0.1:8080
      - MAGIC_DNS_ENABLED=true
===
docker compose up -d

NginxProxyGuard 프록시 호스트 연결

프록시 호스트 도메인을 http://$IP:3000으로 연결되도록 설정합니다.

브라우저 접근 시 초기 접속의 경우 https://$domain/admin 이렇게 admin 경로로 들어가야 아래와 같이 초기 설정 사이트가 출력됩니다.

API Key로 연결

Key 입력 후 성공하면 아래와 같이 출력됩니다.


VPN 계정 생성

① 계정 생성 bash script

# 필요 패키지 설치
apt install -y jq

# 스크립트 생성
mkdir -p /root/bin/
vi /root/bin/
===
#!/usr/bin/env bash
#
# headscale VPN 계정 생성 자동화 스크립트
#   - 아이디/비밀번호를 입력받아 headscale 사용자 + pre-auth key 를 만들고 결과를 출력합니다.
#   - 비밀번호는 입력할 때 화면에 보이도록 되어 있습니다.
#
# 사용법:  chmod +x create-vpn-account.sh  &&  ./create-vpn-account.sh
#
set -euo pipefail

# ==================== 설정 (환경에 맞게 수정) ====================
# headscale 를 실행하는 방법. LXC 안에서 직접 실행하면 아래 그대로 두세요.
HEADSCALE_CMD=(headscale)
# 예) 호스트에서 LXC 로 실행:   HEADSCALE_CMD=(lxc exec <컨테이너명> -- headscale)
# 예) Docker 컨테이너로 실행:   HEADSCALE_CMD=(docker exec headscale headscale)

# 클라이언트 접속 안내 문구에만 쓰는 headscale 주소
HEADSCALE_URL="https://headscale.example.com"

# pre-auth key 옵션
KEY_EXPIRATION="24h"   # 키 유효기간 (예: 1h, 24h, 7d)
KEY_REUSABLE="false"   # 여러 기기에서 재사용 허용: true / false

# 생성 기록을 남길 파일 (평문 저장 → 권한 600). 남기지 않으려면 "" 로 두세요.
RECORD_FILE="${HOME}/headscale-accounts.log"
# ================================================================

# ponytail: headscale JSON 출력을 안전하게 파싱하기 위해 jq 사용.
# (사람이 읽는 표 형식은 버전마다 열 구성이 달라 grep/awk 파싱이 불안정함)
command -v jq >/dev/null 2>&1 || {
  echo "오류: 'jq' 가 필요합니다.  설치: sudo apt-get install -y jq" >&2
  exit 1
}
# headscale(또는 lxc/docker) 실행 파일 확인
command -v "${HEADSCALE_CMD[0]}" >/dev/null 2>&1 || {
  echo "오류: '${HEADSCALE_CMD[0]}' 명령을 찾을 수 없습니다. 스크립트 상단 HEADSCALE_CMD 를 확인하세요." >&2
  exit 1
}

# 사용자명으로 headscale 사용자 ID(숫자)를 찾음. 없으면 빈 문자열.
find_user_id() {
  "${HEADSCALE_CMD[@]}" users list --output json 2>/dev/null \
    | jq -r --arg u "$USERNAME" \
        '(if type=="object" then (.users // []) else . end)
         | map(select(.name==$u)) | .[0].id // empty' 2>/dev/null || true
}

# ---------- 입력 ----------
echo "================================"
echo "  Headscale VPN 계정 생성"
echo "================================"
read -rp "아이디(username): " USERNAME
# 비밀번호를 화면에 보이게 입력받음 (숨기려면 -s 추가: read -rsp)
read -rp "비밀번호(password): " PASSWORD

# ---------- 입력 검증 ----------
# (read 는 앞뒤 공백을 이미 제거함)
if [[ -z "$USERNAME" ]]; then
  echo "오류: 아이디가 비어 있습니다." >&2
  exit 1
fi
if [[ "$USERNAME" == *@ ]]; then
  # headscale 제약: 사용자명은 '@' 로 끝날 수 없음
  echo "오류: 아이디는 '@' 로 끝날 수 없습니다." >&2
  exit 1
fi

# ---------- 사용자 조회 / 생성 ----------
echo
echo "[1/3] 사용자 확인/생성: $USERNAME"
USER_ID="$(find_user_id)"
if [[ -n "$USER_ID" ]]; then
  echo "  → 이미 존재하는 사용자입니다 (ID: $USER_ID). 기존 사용자를 사용합니다."
else
  "${HEADSCALE_CMD[@]}" users create "$USERNAME" >/dev/null
  USER_ID="$(find_user_id)"
  [[ -n "$USER_ID" ]] || { echo "오류: 사용자 생성 후 ID 를 찾지 못했습니다." >&2; exit 1; }
  echo "  → 생성 완료 (ID: $USER_ID)"
fi

# ---------- pre-auth key 발급 ----------
echo "[2/3] pre-auth key 발급 (유효기간: $KEY_EXPIRATION, 재사용: $KEY_REUSABLE)"
key_args=(preauthkeys create --user "$USER_ID" --expiration "$KEY_EXPIRATION" --output json)
[[ "$KEY_REUSABLE" == "true" ]] && key_args+=(--reusable)

KEY_JSON="$("${HEADSCALE_CMD[@]}" "${key_args[@]}" 2>/dev/null || true)"
AUTHKEY="$(printf '%s' "$KEY_JSON" | jq -r '.key // .preAuthKey.key // empty' 2>/dev/null || true)"
if [[ -z "$AUTHKEY" ]]; then
  echo "오류: pre-auth key 발급에 실패했습니다. 아래 명령을 직접 실행해 오류를 확인하세요:" >&2
  echo "  ${HEADSCALE_CMD[*]} ${key_args[*]}" >&2
  exit 1
fi
echo "  → 발급 완료"

# ---------- 기록 저장 ----------
if [[ -n "$RECORD_FILE" ]]; then
  echo "[3/3] 기록 저장: $RECORD_FILE"
  umask 077
  touch "$RECORD_FILE"; chmod 600 "$RECORD_FILE"
  printf '%s | user=%s | id=%s | password=%s | authkey=%s\n' \
    "$(date '+%Y-%m-%d %H:%M:%S')" "$USERNAME" "$USER_ID" "$PASSWORD" "$AUTHKEY" >> "$RECORD_FILE"
else
  echo "[3/3] 기록 저장 생략"
fi

# ---------- 결과 출력 ----------
cat <<EOF

================================
  생성 결과
================================
아이디         : $USERNAME
사용자 ID      : $USER_ID
비밀번호       : $PASSWORD
pre-auth key  : $AUTHKEY
유효기간       : $KEY_EXPIRATION (재사용: $KEY_REUSABLE)

클라이언트에서 접속:
  tailscale up --login-server $HEADSCALE_URL --authkey $AUTHKEY
================================
EOF

② WEB에서 계정 생성

계정 생성

[Users → Add user]

사용할 Key 생성


원격지 Windows(11 Pro) ↔ Xpenology SMB 연결

Tailscale 설치

https://tailscale.com/download/windows

다운로드한 프로그램을 설치만 하고 로그인이나 기타 과정은 어차피 CMD 명령어로 할 것이므로 필요 없습니다.

Headscale LXC와 연결

Windows 프로그램을 설치 후에 CMD 명령어로 접근해야 인증 서버를 자신의 것으로 바꿀 수 있습니다.

tailscale up --login-server=https://$domain


연결은 잘 되었습니다.

그럼 이제, SMB로 연결해보겠습니다.

Snology 용 Key 생성

시놀로지에도 headscale VPN 연결을 하기 위해 api key와 user를 생성해야 합니다. 재사용할 수 있도록 했습니다.

synology sshd 켜고 모듈 로드

modprobe tun
ls -al /dev/net/tun

Snology DSM에서 tailscale 설치

윈도우 클라이언트 연결과 방식은 동일합니다. 설치 후 명령어로 개인 서버를 지정하여 실행합니다. 다만 패키지 센터는 구형 버전이 있어 설치 후에도 제대로 연결이 되지 않습니다. 따라서 수동 설치해줍니다.

DSM 버전 확인

[제어판 → 정보 센터 → DSM 버전]

tailscale SPK 설치파일 다운로드

https://pkgs.tailscale.com/stable/#spks

작업중인 PC에 받아서 불러오면 됩니다.

tailscale update
# 연결 명령어 실행

WEB에서 확인 시 연결이 잘 되었습니다.

SMB 연결

[내 PC → 점 세 개 클릭 → 네트워크 드라이브 추가]

계정 정보 입력

연결 완료


모바일 Android(Galaxy Z Fold 7) Headscale VPN 연결

Tailscale 설치

https://play.google.com/store/apps/details?id=com.tailscale.ipn

Headscale LXC와 연결

공식 홈페이지 설명은 아래 링크를 참고 바랍니다.

Tailscale 로컬 서버 연결 방법

연결 방법

[우측 상단 톱니바퀴 아이콘 클릭 → 점 세 개 클릭 → Use an auth key 클릭 → hskey-로 시작하는 키 값만 입력 → Add account 클릭]

연결이 되었습니다. 아직 ACL이나 접근권한이 설정되어 있지 않은 순정 상태이므로 모든 접근자가 노출됩니다.


연결 관리

CLI 연결 확인

headscale node list

IP 대역과 할당 방식 설정

vi /etc/headscale/config.yaml
===
prefixes:
  # 할당 대역
  v4: 100.64.0.0/10 
  # 할당 방식 (sequential = 순차 분배)
  allocation: sequential
===

할당 IP 변경

초기 연결 시 IP가 할당되면 해당 IP는 변경되지 않고 이후 연결 시에도 고정으로 할당됩니다.
다만, 순차 분배로 설정되어 있어 연결도 순서대로 해야 한다는 불편함이 있습니다. 이에 이미 연결된 IP를 변경해보겠습니다.
자체 기능으로는 구현되어 있지 않아 장비에 VPN 연결 후 순차 IP를 할당받은 다음 DB(sqlite)에서 할당 받은 IP를 변경하는 방법입니다.
보통은 sqlite로 되어 있습니다. 확인 방법은 설정 파일에서 찾아보는 것입니다.

DB 확인

vi /etc/headscale/config.yaml
===
# database: 로 검색
sqlite:
	path: /var/lib/headscale/db.sqlite
===

DB 구조 확인

# sqlite3 설치 명령어
# apt install -y sqlite3

sqlite3 /var/lib/headscale/db.sqlite ".tables"
===
api_keys           migrations         policies           users            
database_versions  nodes              pre_auth_keys 
===

조회가 잘 된다면 여기서 작업할 테이블은 nodes입니다. nodes 테이블의 구조만 다시 한 번 확인합니다.

sqlite3 /var/lib/headscale/db.sqlite "PRAGMA table_info(nodes);"
===
0|id|INTEGER|0||1
1|machine_key|TEXT|0||0
2|node_key|TEXT|0||0
3|disco_key|TEXT|0||0
4|endpoints|TEXT|0||0
5|host_info|TEXT|0||0
6|ipv4|TEXT|0||0
7|ipv6|TEXT|0||0
8|hostname|TEXT|0||0
9|given_name|varchar(63)|0||0
10|user_id|INTEGER|0||0
11|register_method|TEXT|0||0
12|tags|TEXT|0||0
13|auth_key_id|INTEGER|0||0
14|expiry|datetime|0||0
15|last_seen|datetime|0||0
16|approved_routes|TEXT|0||0
17|created_at|datetime|0||0
18|updated_at|datetime|0||0
19|deleted_at|datetime|0||0
===

내부 데이터 정보를 조회합니다.

 sqlite3 -header -column /var/lib/headscale/db.sqlite \
"SELECT id, hostname, given_name, ipv4, ipv6, deleted_at FROM nodes;"

고정 할당 IP 변경

  • 변경 예시
연결 디바이스 ID변경 전 할당 IP변경 후 할당 IP
3100.64.0.3100.64.0.30

먼저 변경할 IP인 100.64.0.30 사용 여부를 확인합니다.

# 변경할 IP 사용 여부 확인
sqlite3 -header -column /var/lib/headscale/db.sqlite "SELECT id, hostname, given_name, ipv4 FROM nodes WHERE ipv4 = '100.64.0.30' AND deleted_at IS NULL;"
# 결과 없어야 변경 가능

변경을 위해 VPN 서비스 데몬 종료 및 기존 DB를 백업합니다.

# VPN 서비스 종료
systemctl stop headscale

# DB 백업
cp -arp /var/lib/headscale/db.sqlite \
/var/lib/headscale/db.sqlite.bak.$(date +%Y%m%d_%H%M%S)

이제 IP 정보를 변경합니다.

# 변경
sqlite3 /var/lib/headscale/db.sqlite \
"UPDATE nodes SET ipv4 = '100.64.0.30' WHERE id = 3;"

# 확인
sqlite3 -header -column /var/lib/headscale/db.sqlite "SELECT id, hostname, given_name, ipv4 FROM nodes WHERE ipv4 = '100.64.0.30' AND deleted_at IS NULL;"

# VPN 서비스 가동
systemctl start headscale

이제 위 과정을 간단하게 bash script로 처리할 수 있도록 제작합니다.

vi /root/bin/privIpChg.sh
#!/bin/bash

DB="/var/lib/headscale/db.sqlite"
SERVICE="headscale"
BACKUP="${DB}.bak.$(date +%Y%m%d_%H%M%S)"

# root 확인
if [ "$EUID" -ne 0 ]; then
    echo "ERROR: root 권한으로 실행하세요."
    exit 1
fi

# sqlite 확인
if ! command -v sqlite3 >/dev/null 2>&1; then
    echo "ERROR: sqlite3 명령어가 없습니다."
    exit 1
fi

# DB 확인
if [ ! -f "$DB" ]; then
    echo "ERROR: DB 파일이 없습니다: $DB"
    exit 1
fi

echo
echo "=============================================="
echo " Headscale Node IPv4 변경"
echo "=============================================="
echo

# 1. 등록 정보 출력
echo "[1] 현재 등록된 노드"
echo

sqlite3 -header -column "$DB" \
"SELECT id, hostname, given_name, ipv4, ipv6, deleted_at
 FROM nodes
 WHERE deleted_at IS NULL
 ORDER BY id;"

echo
echo "----------------------------------------------"

# 2. 변경할 노드 선택
read -rp "변경할 Node ID를 입력하세요: " NODE_ID

if ! sqlite3 "$DB" \
    "SELECT 1 FROM nodes WHERE id=$NODE_ID AND deleted_at IS NULL;" \
    | grep -q 1; then

    echo
    echo "ERROR: 존재하지 않는 Node ID입니다."
    exit 1
fi

echo
echo "[선택한 노드]"

sqlite3 -header -column "$DB" \
"SELECT id, hostname, given_name, ipv4, ipv6
 FROM nodes
 WHERE id=$NODE_ID;"

CURRENT_IP=$(sqlite3 "$DB" \
    "SELECT ipv4 FROM nodes WHERE id=$NODE_ID;")

echo
echo "현재 IPv4 : $CURRENT_IP"

# 3. 변경할 IP 입력
echo
read -rp "변경할 IPv4 주소를 입력하세요: " NEW_IP

# 기본적인 IPv4 형식 검사
if ! [[ "$NEW_IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
    echo
    echo "ERROR: 올바른 IPv4 형식이 아닙니다."
    exit 1
fi

# 각 옥텟 범위 검사
IFS='.' read -r O1 O2 O3 O4 <<< "$NEW_IP"

for OCTET in "$O1" "$O2" "$O3" "$O4"; do
    if [ "$OCTET" -gt 255 ]; then
        echo
        echo "ERROR: 올바른 IPv4 주소가 아닙니다."
        exit 1
    fi
done

# 현재 IP와 동일한 경우
if [ "$CURRENT_IP" = "$NEW_IP" ]; then
    echo
    echo "현재 IP와 동일합니다. 변경할 필요가 없습니다."
    exit 0
fi

# 4. 변경할 IP 사용 여부 조회
echo
echo "[4] IP 사용 여부 확인"
echo

USED_NODE=$(sqlite3 "$DB" \
    "SELECT id FROM nodes
     WHERE ipv4='$NEW_IP'
     AND deleted_at IS NULL
     AND id != $NODE_ID
     LIMIT 1;")

if [ -n "$USED_NODE" ]; then
    echo "ERROR: $NEW_IP 는 이미 사용 중입니다."
    echo
    sqlite3 -header -column "$DB" \
    "SELECT id, hostname, given_name, ipv4
     FROM nodes
     WHERE ipv4='$NEW_IP'
     AND deleted_at IS NULL;"
    exit 1
fi

echo "사용 가능: $NEW_IP"

# 변경 내용 확인
echo
echo "=============================================="
echo " 변경 예정 정보"
echo "=============================================="
echo "Node ID   : $NODE_ID"
echo "Hostname  : $(sqlite3 "$DB" "SELECT hostname FROM nodes WHERE id=$NODE_ID;")"
echo "현재 IPv4 : $CURRENT_IP"
echo "변경 IPv4 : $NEW_IP"
echo "=============================================="
echo

read -rp "위 내용으로 변경하시겠습니까? (y/N): " CONFIRM

if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
    echo "변경을 취소했습니다."
    exit 0
fi

# 5. DB 백업 및 VPN 서비스 종료
echo
echo "[5] DB 백업"

if ! cp -a "$DB" "$BACKUP"; then
    echo "ERROR: DB 백업 실패"
    exit 1
fi

echo "백업 완료:"
echo "$BACKUP"

echo
echo "Headscale 서비스 종료 중..."

if ! systemctl stop "$SERVICE"; then
    echo "ERROR: Headscale 서비스 종료 실패"
    exit 1
fi

echo "Headscale 서비스 종료 완료."

# 6. 변경
echo
echo "[6] IPv4 변경"

sqlite3 "$DB" <<EOF
BEGIN TRANSACTION;

UPDATE nodes
SET ipv4 = '$NEW_IP',
    updated_at = datetime('now')
WHERE id = $NODE_ID;

COMMIT;
EOF

if [ $? -ne 0 ]; then
    echo
    echo "ERROR: DB 변경 실패"
    echo "백업 파일: $BACKUP"

    systemctl start "$SERVICE"
    exit 1
fi

# 7. 변경 재확인
echo
echo "[7] DB 변경 결과 확인"
echo

sqlite3 -header -column "$DB" \
"SELECT id, hostname, given_name, ipv4, ipv6
 FROM nodes
 WHERE id=$NODE_ID;"

CHECK_IP=$(sqlite3 "$DB" \
    "SELECT ipv4 FROM nodes WHERE id=$NODE_ID;")

if [ "$CHECK_IP" != "$NEW_IP" ]; then
    echo
    echo "ERROR: IP 변경 확인 실패"
    echo "백업 파일: $BACKUP"

    systemctl start "$SERVICE"
    exit 1
fi

echo
echo "IP 변경 확인 완료: $CURRENT_IP -> $NEW_IP"

# 8. VPN 서비스 가동
echo
echo "[8] Headscale 서비스 시작"

if ! systemctl start "$SERVICE"; then
    echo
    echo "ERROR: Headscale 시작 실패"
    echo "백업 파일: $BACKUP"
    exit 1
fi

sleep 2

if ! systemctl is-active --quiet "$SERVICE"; then
    echo
    echo "ERROR: Headscale 서비스가 정상적으로 실행되지 않았습니다."
    echo "백업 파일: $BACKUP"
    systemctl status "$SERVICE" --no-pager
    exit 1
fi

echo "Headscale 서비스 정상 가동."

# 9. 최종 노드 연결 리스트
echo
echo "=============================================="
echo "[9] 최종 Headscale Node 목록"
echo "=============================================="
echo

headscale nodes list

echo
echo "=============================================="
echo " 완료"
echo "=============================================="
echo
echo "Node ID   : $NODE_ID"
echo "기존 IPv4 : $CURRENT_IP"
echo "변경 IPv4 : $NEW_IP"
echo "DB 백업   : $BACKUP"
echo
profile
Raiju Hantu Goryo Obake

0개의 댓글