Compare commits
2 Commits
f44e7e7f8f
...
feat/etap-
| Author | SHA1 | Date | |
|---|---|---|---|
| 887c39a676 | |||
| 5df142d47a |
31
ansible/group_vars/sol_rpc.yml
Normal file
31
ansible/group_vars/sol_rpc.yml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
solana_user: solana
|
||||||
|
solana_group: solana
|
||||||
|
solana_home: /var/lib/solana
|
||||||
|
|
||||||
|
solana_validator_bin: /opt/solana/bin/agave-validator
|
||||||
|
solana_rpc_service_name: solana-rpc
|
||||||
|
|
||||||
|
solana_identity_path: /var/lib/solana/identity.json
|
||||||
|
solana_ledger_dir: /var/lib/solana/ledger
|
||||||
|
solana_accounts_dir: /var/lib/solana/accounts
|
||||||
|
solana_log_dir: /var/log/solana
|
||||||
|
|
||||||
|
solana_rpc_bind_address: 10.10.0.2
|
||||||
|
solana_rpc_port: 8899
|
||||||
|
solana_rpc_pubsub_port: 8900
|
||||||
|
solana_dynamic_port_range: "8000-8020"
|
||||||
|
|
||||||
|
solana_entrypoints:
|
||||||
|
- entrypoint.mainnet-beta.solana.com:8001
|
||||||
|
solana_known_validators: []
|
||||||
|
|
||||||
|
solana_geyser_enabled: false
|
||||||
|
solana_geyser_plugin_config_path: /etc/solana/yellowstone-geyser.json
|
||||||
|
|
||||||
|
solana_rpc_extra_args:
|
||||||
|
- --full-rpc-api
|
||||||
|
- --limit-ledger-size
|
||||||
|
|
||||||
|
solana_rpc_manage_service: true
|
||||||
|
solana_rpc_enable_on_boot: true
|
||||||
|
solana_rpc_start_now: true
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
hosts: sol_rpc
|
hosts: sol_rpc
|
||||||
gather_facts: true
|
gather_facts: true
|
||||||
become: true
|
become: true
|
||||||
|
vars_files:
|
||||||
|
- ../group_vars/sol_rpc.yml
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Install operator packages (Debian/Ubuntu)
|
- name: Install operator packages (Debian/Ubuntu)
|
||||||
@@ -16,6 +18,32 @@
|
|||||||
update_cache: true
|
update_cache: true
|
||||||
when: ansible_facts.os_family == "Debian"
|
when: ansible_facts.os_family == "Debian"
|
||||||
|
|
||||||
|
- name: Install Solana host base packages (Debian/Ubuntu)
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name:
|
||||||
|
- chrony
|
||||||
|
- curl
|
||||||
|
- jq
|
||||||
|
- smartmontools
|
||||||
|
- nvme-cli
|
||||||
|
- prometheus-node-exporter
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
when: ansible_facts.os_family == "Debian"
|
||||||
|
|
||||||
|
- name: Ensure solana group exists
|
||||||
|
ansible.builtin.group:
|
||||||
|
name: "{{ solana_group }}"
|
||||||
|
system: true
|
||||||
|
|
||||||
|
- name: Ensure solana user exists
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ solana_user }}"
|
||||||
|
group: "{{ solana_group }}"
|
||||||
|
home: "{{ solana_home }}"
|
||||||
|
system: true
|
||||||
|
create_home: true
|
||||||
|
|
||||||
- name: Ensure root config directories exist
|
- name: Ensure root config directories exist
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "{{ item }}"
|
path: "{{ item }}"
|
||||||
@@ -29,6 +57,20 @@
|
|||||||
- /root/.config/nvim
|
- /root/.config/nvim
|
||||||
- /root/.config/nvim/lua
|
- /root/.config/nvim/lua
|
||||||
|
|
||||||
|
- name: Ensure Solana directories exist
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item.path }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ item.owner }}"
|
||||||
|
group: "{{ item.group }}"
|
||||||
|
mode: "{{ item.mode }}"
|
||||||
|
loop:
|
||||||
|
- { path: "/etc/solana", owner: "root", group: "root", mode: "0755" }
|
||||||
|
- { path: "{{ solana_home }}", owner: "{{ solana_user }}", group: "{{ solana_group }}", mode: "0750" }
|
||||||
|
- { path: "{{ solana_ledger_dir }}", owner: "{{ solana_user }}", group: "{{ solana_group }}", mode: "0750" }
|
||||||
|
- { path: "{{ solana_accounts_dir }}", owner: "{{ solana_user }}", group: "{{ solana_group }}", mode: "0750" }
|
||||||
|
- { path: "{{ solana_log_dir }}", owner: "{{ solana_user }}", group: "{{ solana_group }}", mode: "0750" }
|
||||||
|
|
||||||
- name: Deploy tmux config (Ctrl+a prefix)
|
- name: Deploy tmux config (Ctrl+a prefix)
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
src: ../files/operator-dotfiles/tmux.conf
|
src: ../files/operator-dotfiles/tmux.conf
|
||||||
@@ -64,6 +106,50 @@
|
|||||||
- { src: "lua/utils.lua", dest: "lua/utils.lua" }
|
- { src: "lua/utils.lua", dest: "lua/utils.lua" }
|
||||||
- { src: "lua/hazard3_dap.lua", dest: "lua/hazard3_dap.lua" }
|
- { src: "lua/hazard3_dap.lua", dest: "lua/hazard3_dap.lua" }
|
||||||
|
|
||||||
|
- name: Deploy solana-rpc systemd unit (runs as solana user)
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: ../templates/solana-rpc.service.j2
|
||||||
|
dest: /etc/systemd/system/{{ solana_rpc_service_name }}.service
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
register: solana_rpc_unit
|
||||||
|
|
||||||
|
- name: Reload systemd after unit change
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
daemon_reload: true
|
||||||
|
when: solana_rpc_unit.changed
|
||||||
|
|
||||||
|
- name: Check validator binary exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ solana_validator_bin }}"
|
||||||
|
register: solana_validator_bin_stat
|
||||||
|
|
||||||
|
- name: Check identity key exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ solana_identity_path }}"
|
||||||
|
register: solana_identity_stat
|
||||||
|
|
||||||
|
- name: Ensure solana-rpc service state when prerequisites exist
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "{{ solana_rpc_service_name }}"
|
||||||
|
enabled: "{{ solana_rpc_enable_on_boot | bool }}"
|
||||||
|
state: "{{ 'started' if (solana_rpc_start_now | bool) else 'stopped' }}"
|
||||||
|
when:
|
||||||
|
- solana_rpc_manage_service | bool
|
||||||
|
- solana_validator_bin_stat.stat.exists
|
||||||
|
- solana_identity_stat.stat.exists
|
||||||
|
|
||||||
|
- name: Report skipped solana-rpc start due to missing prerequisites
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "solana-rpc start skipped: missing prerequisites"
|
||||||
|
- "validator_bin={{ solana_validator_bin }} exists={{ solana_validator_bin_stat.stat.exists }}"
|
||||||
|
- "identity={{ solana_identity_path }} exists={{ solana_identity_stat.stat.exists }}"
|
||||||
|
when:
|
||||||
|
- solana_rpc_manage_service | bool
|
||||||
|
- not (solana_validator_bin_stat.stat.exists and solana_identity_stat.stat.exists)
|
||||||
|
|
||||||
- name: Validate Ansible transport
|
- name: Validate Ansible transport
|
||||||
ansible.builtin.ping:
|
ansible.builtin.ping:
|
||||||
|
|
||||||
|
|||||||
35
ansible/templates/solana-rpc.service.j2
Normal file
35
ansible/templates/solana-rpc.service.j2
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Solana RPC node (Agave)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User={{ solana_user }}
|
||||||
|
Group={{ solana_group }}
|
||||||
|
WorkingDirectory={{ solana_home }}
|
||||||
|
Environment=RUST_LOG=info
|
||||||
|
LimitNOFILE=1048576
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
TimeoutStopSec=120
|
||||||
|
ExecStart={{ solana_validator_bin }} \
|
||||||
|
--identity {{ solana_identity_path }} \
|
||||||
|
--ledger {{ solana_ledger_dir }} \
|
||||||
|
--accounts {{ solana_accounts_dir }} \
|
||||||
|
--rpc-bind-address {{ solana_rpc_bind_address }} \
|
||||||
|
--rpc-port {{ solana_rpc_port }} \
|
||||||
|
--rpc-pubsub-port {{ solana_rpc_pubsub_port }} \
|
||||||
|
--dynamic-port-range {{ solana_dynamic_port_range }}{% for ep in solana_entrypoints %} \
|
||||||
|
--entrypoint {{ ep }}{% endfor %}{% for kv in solana_known_validators %} \
|
||||||
|
--known-validator {{ kv }}{% endfor %}{% if solana_geyser_enabled | bool %} \
|
||||||
|
--geyser-plugin-config {{ solana_geyser_plugin_config_path }}{% endif %}{% for arg in solana_rpc_extra_args %} \
|
||||||
|
{{ arg }}{% endfor %} \
|
||||||
|
--log {{ solana_log_dir }}/validator.log
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=full
|
||||||
|
ReadWritePaths={{ solana_ledger_dir }} {{ solana_accounts_dir }} {{ solana_log_dir }} /var/lib/solana
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
22
doc/etap-005-solana-rpc-user-svc.md
Normal file
22
doc/etap-005-solana-rpc-user-svc.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Etap 005: Solana RPC jako usługa użytkownika `solana`
|
||||||
|
|
||||||
|
Cel etapu: przygotować i wdrożyć baseline pod `solana-rpc`, uruchamiany jako dedykowany użytkownik systemowy `solana` (nie `root`).
|
||||||
|
|
||||||
|
## Zakres
|
||||||
|
|
||||||
|
1. Rozszerzyć playbook o:
|
||||||
|
- pakiety bazowe dla hosta RPC,
|
||||||
|
- utworzenie użytkownika i katalogów `solana`,
|
||||||
|
- deployment unitu `systemd` `solana-rpc.service` z `User=solana`.
|
||||||
|
2. Dodać zmienne grupowe `sol_rpc` (ścieżki, porty, opcje startu).
|
||||||
|
3. Dodać bezpieczną logikę startu:
|
||||||
|
- unit jest wdrażany zawsze,
|
||||||
|
- start usługi tylko gdy istnieją wymagane artefakty (`agave-validator`, identity keypair).
|
||||||
|
4. Wdrożyć na `mevnode` i zweryfikować stan.
|
||||||
|
|
||||||
|
## Kryteria akceptacji
|
||||||
|
|
||||||
|
- `id solana` istnieje na `mevnode`.
|
||||||
|
- `/etc/systemd/system/solana-rpc.service` istnieje i zawiera `User=solana`.
|
||||||
|
- Playbook kończy się bez błędów.
|
||||||
|
- Jeśli binarka/identity nie istnieją, playbook raportuje to jawnie i nie wymusza startu.
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
# Step 001A: WireGuard tools (server/client) + instrukcje reset
|
|
||||||
|
|
||||||
Ten step dodaje dwa skrypty pomocnicze do zarzadzania WireGuard:
|
|
||||||
|
|
||||||
- server: `scripts/wireguard/wg_serverctl.py`
|
|
||||||
- client: `scripts/wireguard/wg_client.py`
|
|
||||||
|
|
||||||
Skrypty trzymaja stan na dysku, zgodnie z konwencja:
|
|
||||||
|
|
||||||
- server keys: `/etc/wireguard/server.key`, `/etc/wireguard/server.pub`
|
|
||||||
- clients: `/etc/wireguard/clients/<name>.key|.pub|.ip|.conf`
|
|
||||||
- generated server config: `/etc/wireguard/wg0.conf`
|
|
||||||
|
|
||||||
Wymagania: `wireguard-tools` (czyli `wg`, `wg-quick`) + `systemd`.
|
|
||||||
|
|
||||||
## Instalacja (server)
|
|
||||||
|
|
||||||
Na bare metalu:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y wireguard
|
|
||||||
```
|
|
||||||
|
|
||||||
Z repo (na hoście) zainstaluj skrypt jako root-only:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
install -m 700 scripts/wireguard/wg_serverctl.py /usr/local/sbin/wg_serverctl.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Instalacja (client)
|
|
||||||
|
|
||||||
Na kliencie (laptop/VPS):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y wireguard
|
|
||||||
install -m 700 scripts/wireguard/wg_client.py /usr/local/sbin/wg_client.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Typowy flow (server)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1) wygeneruj klucze serwera
|
|
||||||
wg_serverctl.py gen-server --force
|
|
||||||
|
|
||||||
# 2) wygeneruj klucze klienta (trzymane na serwerze w /etc/wireguard/clients)
|
|
||||||
wg_serverctl.py gen-client client1
|
|
||||||
|
|
||||||
# 3) dodaj klienta do puli, wygeneruj wg0.conf i zrestartuj wg-quick@wg0
|
|
||||||
wg_serverctl.py add client1
|
|
||||||
|
|
||||||
# 4) wyeksportuj client config (do przekazania na klienta)
|
|
||||||
wg_serverctl.py export client1
|
|
||||||
|
|
||||||
# 5) podglad
|
|
||||||
wg_serverctl.py list
|
|
||||||
wg_serverctl.py show client1
|
|
||||||
```
|
|
||||||
|
|
||||||
Plik konfiguracyjny klienta powstaje jako:
|
|
||||||
|
|
||||||
- `/etc/wireguard/clients/client1.conf`
|
|
||||||
|
|
||||||
## Typowy flow (client)
|
|
||||||
|
|
||||||
Skopiuj `client1.conf` na klienta, a potem:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wg_client.py install ./client1.conf
|
|
||||||
wg_client.py up
|
|
||||||
wg_client.py status
|
|
||||||
```
|
|
||||||
|
|
||||||
## Stop / disable / reset wg0 (Ubuntu 24.04)
|
|
||||||
|
|
||||||
Stop (zostaw config, tylko down):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemctl stop wg-quick@wg0
|
|
||||||
```
|
|
||||||
|
|
||||||
Disable autostart:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemctl disable wg-quick@wg0
|
|
||||||
```
|
|
||||||
|
|
||||||
Flush (pewny down + usuniecie interfejsu):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemctl stop wg-quick@wg0
|
|
||||||
ip link del wg0 2>/dev/null || true
|
|
||||||
ip a show wg0 2>/dev/null || echo "wg0 not present"
|
|
||||||
```
|
|
||||||
|
|
||||||
Hard reset (usuniecie kluczy + konfiguracji):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemctl stop wg-quick@wg0
|
|
||||||
systemctl disable wg-quick@wg0
|
|
||||||
ip link del wg0 2>/dev/null || true
|
|
||||||
|
|
||||||
rm -f /etc/wireguard/wg0.conf
|
|
||||||
rm -f /etc/wireguard/server.key /etc/wireguard/server.pub
|
|
||||||
rm -rf /etc/wireguard/clients
|
|
||||||
```
|
|
||||||
|
|
||||||
Reset peerow bez kasowania server key:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemctl stop wg-quick@wg0
|
|
||||||
rm -f /etc/wireguard/clients/*.pub /etc/wireguard/clients/*.ip
|
|
||||||
systemctl start wg-quick@wg0
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
# Step 001: WireGuard + UFW + SSH tylko po WG (Ubuntu 24.04, 1 NIC)
|
|
||||||
|
|
||||||
Cel: na świeżym Ubuntu 24.04 na bare metalu ustawić:
|
|
||||||
|
|
||||||
- **WireGuard** jako prywatny kanał administracyjny,
|
|
||||||
- **SSH dostępne tylko przez WireGuard**,
|
|
||||||
- przygotować grunt pod **Agave RPC dostępne tylko przez WireGuard** (8899/8900 prywatnie),
|
|
||||||
- zostawić porty klastra Agave na publicznym NIC, żeby node mógł się zsynchronizować.
|
|
||||||
|
|
||||||
Kolejność jest krytyczna: najpierw uruchom WireGuard i sprawdź, że działa, dopiero potem ograniczaj SSH/firewall.
|
|
||||||
|
|
||||||
## Założenia
|
|
||||||
|
|
||||||
- Host ma **1 publiczny interfejs** (np. `enp1s0`).
|
|
||||||
- WireGuard interfejs to `wg0`.
|
|
||||||
- Masz dostęp awaryjny do konsoli (IPMI/KVM) na wypadek pomyłki.
|
|
||||||
|
|
||||||
## Parametry (dostosuj)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export PUBLIC_IFACE="enp1s0"
|
|
||||||
export WG_IFACE="wg0"
|
|
||||||
|
|
||||||
export WG_SERVER_IP="10.66.66.1/24"
|
|
||||||
export WG_SERVER_IP_ONLY="10.66.66.1"
|
|
||||||
export WG_PORT="51820"
|
|
||||||
|
|
||||||
export WG_CLIENT_IP="10.66.66.2/32"
|
|
||||||
export WG_CLIENT_PUBKEY="<PASTE_CLIENT_PUBLIC_KEY>"
|
|
||||||
|
|
||||||
# Agave/validator ports
|
|
||||||
export RPC_PORT="8899"
|
|
||||||
export WS_PORT="8900"
|
|
||||||
export DYNAMIC_PORT_RANGE="8000:8020"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 1) WireGuard (nie ruszaj jeszcze SSH)
|
|
||||||
|
|
||||||
### Instalacja
|
|
||||||
|
|
||||||
```bash
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y wireguard
|
|
||||||
```
|
|
||||||
|
|
||||||
### Klucze serwera
|
|
||||||
|
|
||||||
```bash
|
|
||||||
umask 077
|
|
||||||
wg genkey | tee /etc/wireguard/server.key >/dev/null
|
|
||||||
cat /etc/wireguard/server.key | wg pubkey | tee /etc/wireguard/server.pub >/dev/null
|
|
||||||
cat /etc/wireguard/server.pub
|
|
||||||
```
|
|
||||||
|
|
||||||
### Konfiguracja `/etc/wireguard/wg0.conf`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cat >/etc/wireguard/${WG_IFACE}.conf <<EOF
|
|
||||||
[Interface]
|
|
||||||
Address = ${WG_SERVER_IP}
|
|
||||||
ListenPort = ${WG_PORT}
|
|
||||||
PrivateKey = $(cat /etc/wireguard/server.key)
|
|
||||||
|
|
||||||
[Peer]
|
|
||||||
PublicKey = ${WG_CLIENT_PUBKEY}
|
|
||||||
AllowedIPs = ${WG_CLIENT_IP}
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
Start:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemctl enable --now wg-quick@${WG_IFACE}
|
|
||||||
wg show
|
|
||||||
ip a show ${WG_IFACE}
|
|
||||||
```
|
|
||||||
|
|
||||||
Test z klienta (po zestawieniu tunelu): `ping 10.66.66.1`.
|
|
||||||
|
|
||||||
## 2) UFW: public tylko WG + porty klastra; prywatnie RPC/SSH po wg0
|
|
||||||
|
|
||||||
Instalacja:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
apt-get install -y ufw
|
|
||||||
```
|
|
||||||
|
|
||||||
Public: pozwól na WireGuard:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ufw allow ${WG_PORT}/udp
|
|
||||||
```
|
|
||||||
|
|
||||||
Public: pozwól na porty klastra Agave (żeby node mógł syncować).
|
|
||||||
|
|
||||||
Uwaga: Solana/Agave używa głównie **UDP** w tym zakresie; jeśli będziesz miał problemy z reachability, rozważ dopuszczenie też TCP w tym samym zakresie.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ufw allow in on "${PUBLIC_IFACE}" to any port ${DYNAMIC_PORT_RANGE}/udp
|
|
||||||
```
|
|
||||||
|
|
||||||
Prywatnie (wg0): RPC/WS:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ufw allow in on "${WG_IFACE}" to any port ${RPC_PORT} proto tcp
|
|
||||||
ufw allow in on "${WG_IFACE}" to any port ${WS_PORT} proto tcp
|
|
||||||
```
|
|
||||||
|
|
||||||
Na tym etapie NIE blokuj jeszcze publicznego SSH (żeby się nie zablokować).
|
|
||||||
|
|
||||||
Włącz UFW:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ufw enable
|
|
||||||
ufw status verbose
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3) SSH tylko po WireGuard (dopiero gdy WG działa)
|
|
||||||
|
|
||||||
### 3.1) UFW: pozwól na SSH po wg0
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ufw allow in on "${WG_IFACE}" to any port 22 proto tcp
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2) sshd: nasłuch tylko na WG IP
|
|
||||||
|
|
||||||
Edytuj `sshd_config`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sed -n '1,220p' /etc/ssh/sshd_config
|
|
||||||
```
|
|
||||||
|
|
||||||
Dodaj (lub ustaw) na końcu pliku:
|
|
||||||
|
|
||||||
```conf
|
|
||||||
PasswordAuthentication no
|
|
||||||
PermitRootLogin no
|
|
||||||
ListenAddress 10.66.66.1
|
|
||||||
```
|
|
||||||
|
|
||||||
Zanim zrestartujesz SSH:
|
|
||||||
|
|
||||||
- miej otwartą obecną sesję SSH,
|
|
||||||
- zestaw WG na kliencie,
|
|
||||||
- przygotuj drugi terminal do testu po `10.66.66.1`.
|
|
||||||
|
|
||||||
Restart:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemctl restart ssh
|
|
||||||
ss -lntp | grep -E ':22\\b' || true
|
|
||||||
```
|
|
||||||
|
|
||||||
Test z klienta po WG:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ssh <user>@10.66.66.1
|
|
||||||
```
|
|
||||||
|
|
||||||
Jeśli działa: zablokuj publiczny SSH:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ufw deny 22/tcp
|
|
||||||
ufw status verbose
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4) Kernel/sysctl baseline pod Agave (na tym etapie bez uruchamiania Agave)
|
|
||||||
|
|
||||||
Minimalne wartości, które już mamy w Ansible (i kilka dodatkowych bezpiecznych):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cat >/etc/sysctl.d/90-agave.conf <<'EOF'
|
|
||||||
fs.file-max = 2000000
|
|
||||||
vm.max_map_count = 1000000
|
|
||||||
net.core.somaxconn = 65535
|
|
||||||
net.core.netdev_max_backlog = 250000
|
|
||||||
net.core.rmem_max = 134217728
|
|
||||||
net.core.wmem_max = 134217728
|
|
||||||
EOF
|
|
||||||
|
|
||||||
sysctl --system
|
|
||||||
```
|
|
||||||
|
|
||||||
Limity `nofile`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cat >/etc/security/limits.d/90-solana.conf <<'EOF'
|
|
||||||
* soft nofile 1000000
|
|
||||||
* hard nofile 1000000
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5) Co dalej
|
|
||||||
|
|
||||||
- Instalacja i uruchomienie Agave: patrz `doc/etap-007-manual-baremetal-rpc-commands.md`.
|
|
||||||
- Narzedzia do generowania konfiguracji WireGuard (server/client) + reset wg0: patrz `doc/step-001-wireguard-tools.md`.
|
|
||||||
- Docelowo: bind RPC/WS do WG IP (zamiast `0.0.0.0`) i spójne, minimalne reguły firewall.
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
WG_DIR = Path("/etc/wireguard")
|
|
||||||
WG0 = WG_DIR / "wg0.conf"
|
|
||||||
|
|
||||||
|
|
||||||
def require_root():
|
|
||||||
if os.geteuid() != 0:
|
|
||||||
print("ERROR: run as root (sudo).", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def run(cmd, check=True, capture=False):
|
|
||||||
return subprocess.run(
|
|
||||||
cmd,
|
|
||||||
check=check,
|
|
||||||
text=True,
|
|
||||||
stdout=subprocess.PIPE if capture else None,
|
|
||||||
stderr=subprocess.PIPE if capture else None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def backup_if_exists(p: Path):
|
|
||||||
if p.exists():
|
|
||||||
ts = datetime.now().strftime("%Y-%m-%d_%H%M%S")
|
|
||||||
b = p.with_name(p.name + f".bak.{ts}")
|
|
||||||
shutil.copy2(p, b)
|
|
||||||
|
|
||||||
|
|
||||||
def cmd_install(src: str):
|
|
||||||
srcp = Path(src)
|
|
||||||
if not srcp.exists():
|
|
||||||
raise RuntimeError(f"Missing source config: {srcp}")
|
|
||||||
WG_DIR.mkdir(parents=True, exist_ok=True)
|
|
||||||
os.chmod(WG_DIR, 0o700)
|
|
||||||
backup_if_exists(WG0)
|
|
||||||
shutil.copy2(srcp, WG0)
|
|
||||||
os.chmod(WG0, 0o600)
|
|
||||||
os.chown(WG0, 0, 0)
|
|
||||||
print(f"OK: installed {WG0}")
|
|
||||||
|
|
||||||
|
|
||||||
def cmd_up():
|
|
||||||
run(["wg-quick", "up", "wg0"], check=True)
|
|
||||||
|
|
||||||
|
|
||||||
def cmd_down():
|
|
||||||
run(["wg-quick", "down", "wg0"], check=False)
|
|
||||||
|
|
||||||
|
|
||||||
def cmd_status():
|
|
||||||
run(["wg", "show"], check=False)
|
|
||||||
run(["ip", "a", "show", "wg0"], check=False)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
require_root()
|
|
||||||
p = argparse.ArgumentParser(prog="wg_client.py")
|
|
||||||
sub = p.add_subparsers(dest="cmd", required=True)
|
|
||||||
|
|
||||||
i = sub.add_parser("install")
|
|
||||||
i.add_argument("config", help="Path to clientX.conf from server export")
|
|
||||||
|
|
||||||
sub.add_parser("up")
|
|
||||||
sub.add_parser("down")
|
|
||||||
sub.add_parser("status")
|
|
||||||
|
|
||||||
args = p.parse_args()
|
|
||||||
|
|
||||||
try:
|
|
||||||
if args.cmd == "install":
|
|
||||||
cmd_install(args.config)
|
|
||||||
elif args.cmd == "up":
|
|
||||||
cmd_up()
|
|
||||||
elif args.cmd == "down":
|
|
||||||
cmd_down()
|
|
||||||
elif args.cmd == "status":
|
|
||||||
cmd_status()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ERROR: {e}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
||||||
@@ -1,419 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Optional, List
|
|
||||||
|
|
||||||
WG_KEY_RE = re.compile(r"^[A-Za-z0-9+/]{43}=$")
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Cfg:
|
|
||||||
wg_dir: Path = Path("/etc/wireguard")
|
|
||||||
clients_dir: Path = Path("/etc/wireguard/clients")
|
|
||||||
server_key: Path = Path("/etc/wireguard/server.key")
|
|
||||||
server_pub: Path = Path("/etc/wireguard/server.pub")
|
|
||||||
wg0_conf: Path = Path("/etc/wireguard/wg0.conf")
|
|
||||||
|
|
||||||
wg_addr: str = "10.66.66.1/24"
|
|
||||||
wg_port: int = 51820
|
|
||||||
keepalive: int = 25
|
|
||||||
|
|
||||||
client_net_prefix: str = "10.66.66."
|
|
||||||
client_ip_start: int = 2
|
|
||||||
client_ip_end: int = 254
|
|
||||||
|
|
||||||
|
|
||||||
def require_root():
|
|
||||||
if os.geteuid() != 0:
|
|
||||||
print("ERROR: run as root (sudo).", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def run(
|
|
||||||
cmd: List[str],
|
|
||||||
capture: bool = False,
|
|
||||||
check: bool = True,
|
|
||||||
input_text: Optional[str] = None,
|
|
||||||
) -> subprocess.CompletedProcess:
|
|
||||||
return subprocess.run(
|
|
||||||
cmd,
|
|
||||||
text=True,
|
|
||||||
input=input_text,
|
|
||||||
stdout=subprocess.PIPE if capture else None,
|
|
||||||
stderr=subprocess.PIPE if capture else None,
|
|
||||||
check=check,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_dirs(cfg: Cfg):
|
|
||||||
cfg.wg_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
cfg.clients_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
os.chmod(cfg.wg_dir, 0o700)
|
|
||||||
os.chmod(cfg.clients_dir, 0o700)
|
|
||||||
|
|
||||||
|
|
||||||
def read_first_nonempty_line(p: Path) -> str:
|
|
||||||
if not p.exists():
|
|
||||||
return ""
|
|
||||||
for line in p.read_text(encoding="utf-8", errors="ignore").splitlines():
|
|
||||||
line = line.strip().replace("\r", "")
|
|
||||||
if line:
|
|
||||||
return line
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def validate_key(k: str, what: str):
|
|
||||||
if not WG_KEY_RE.match(k):
|
|
||||||
raise ValueError(f"{what} has invalid WireGuard key format: {k!r}")
|
|
||||||
|
|
||||||
|
|
||||||
def chmod600_root(p: Path):
|
|
||||||
os.chmod(p, 0o600)
|
|
||||||
os.chown(p, 0, 0)
|
|
||||||
|
|
||||||
|
|
||||||
def backup_if_exists(p: Path):
|
|
||||||
if p.exists():
|
|
||||||
ts = datetime.now().strftime("%Y-%m-%d_%H%M%S")
|
|
||||||
b = p.with_name(p.name + f".bak.{ts}")
|
|
||||||
shutil.copy2(p, b)
|
|
||||||
|
|
||||||
|
|
||||||
def gen_priv_key_to(path: Path):
|
|
||||||
priv = run(["wg", "genkey"], capture=True).stdout.strip()
|
|
||||||
validate_key(priv, "generated private key")
|
|
||||||
path.write_text(priv + "\n", encoding="utf-8")
|
|
||||||
chmod600_root(path)
|
|
||||||
|
|
||||||
|
|
||||||
def pubkey_from_priv(priv: str) -> str:
|
|
||||||
validate_key(priv, "private key")
|
|
||||||
pub = run(["wg", "pubkey"], capture=True, input_text=priv + "\n").stdout.strip()
|
|
||||||
validate_key(pub, "generated public key")
|
|
||||||
return pub
|
|
||||||
|
|
||||||
|
|
||||||
def detect_public_ip() -> str:
|
|
||||||
# Works on Ubuntu/Debian in typical setups
|
|
||||||
out = run(
|
|
||||||
[
|
|
||||||
"bash",
|
|
||||||
"-lc",
|
|
||||||
"ip route get 1.1.1.1 | awk '{for (i=1;i<=NF;i++) if ($i==\"src\") {print $(i+1); exit}}'",
|
|
||||||
],
|
|
||||||
capture=True,
|
|
||||||
).stdout.strip()
|
|
||||||
if not out:
|
|
||||||
raise RuntimeError("Could not detect server public IP.")
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def restart_wg(unit: str = "wg-quick@wg0"):
|
|
||||||
run(["systemctl", "restart", unit], capture=False, check=True)
|
|
||||||
# optional status (non-fatal)
|
|
||||||
try:
|
|
||||||
run(["systemctl", "--no-pager", "-l", "status", unit], capture=False, check=False)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def list_clients(cfg: Cfg) -> List[str]:
|
|
||||||
pubs = sorted(cfg.clients_dir.glob("*.pub"))
|
|
||||||
return [p.stem for p in pubs]
|
|
||||||
|
|
||||||
|
|
||||||
def used_octets(cfg: Cfg) -> set:
|
|
||||||
s = set()
|
|
||||||
for f in cfg.clients_dir.glob("*.ip"):
|
|
||||||
v = read_first_nonempty_line(f)
|
|
||||||
if v.isdigit():
|
|
||||||
s.add(int(v))
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def alloc_octet(cfg: Cfg) -> int:
|
|
||||||
used = used_octets(cfg)
|
|
||||||
for i in range(cfg.client_ip_start, cfg.client_ip_end + 1):
|
|
||||||
if i not in used:
|
|
||||||
return i
|
|
||||||
raise RuntimeError("No free IPs left in pool.")
|
|
||||||
|
|
||||||
|
|
||||||
def gen_server(cfg: Cfg, force: bool):
|
|
||||||
ensure_dirs(cfg)
|
|
||||||
if not force and (cfg.server_key.exists() or cfg.server_pub.exists()):
|
|
||||||
raise RuntimeError("Server key already exists. Use --force to overwrite.")
|
|
||||||
gen_priv_key_to(cfg.server_key)
|
|
||||||
priv = read_first_nonempty_line(cfg.server_key)
|
|
||||||
pub = pubkey_from_priv(priv)
|
|
||||||
cfg.server_pub.write_text(pub + "\n", encoding="utf-8")
|
|
||||||
chmod600_root(cfg.server_pub)
|
|
||||||
print("OK: generated server keys:")
|
|
||||||
print(f" - {cfg.server_key} (PRIVATE)")
|
|
||||||
print(f" - {cfg.server_pub} (PUBLIC)")
|
|
||||||
print("Server public key:")
|
|
||||||
print(pub)
|
|
||||||
|
|
||||||
|
|
||||||
def gen_client(cfg: Cfg, name: str, force: bool):
|
|
||||||
ensure_dirs(cfg)
|
|
||||||
keyf = cfg.clients_dir / f"{name}.key"
|
|
||||||
pubf = cfg.clients_dir / f"{name}.pub"
|
|
||||||
if not force and (keyf.exists() or pubf.exists()):
|
|
||||||
raise RuntimeError(f"Client {name} exists. Use --force to overwrite.")
|
|
||||||
gen_priv_key_to(keyf)
|
|
||||||
priv = read_first_nonempty_line(keyf)
|
|
||||||
pub = pubkey_from_priv(priv)
|
|
||||||
pubf.write_text(pub + "\n", encoding="utf-8")
|
|
||||||
chmod600_root(pubf)
|
|
||||||
print("OK: generated client keys:")
|
|
||||||
print(f" - {keyf} (PRIVATE)")
|
|
||||||
print(f" - {pubf} (PUBLIC)")
|
|
||||||
print("Client public key:")
|
|
||||||
print(pub)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_wg0_conf(cfg: Cfg):
|
|
||||||
ensure_dirs(cfg)
|
|
||||||
if not cfg.server_key.exists():
|
|
||||||
raise RuntimeError("Missing server.key. Run gen-server first.")
|
|
||||||
server_priv = read_first_nonempty_line(cfg.server_key)
|
|
||||||
validate_key(server_priv, "server private key")
|
|
||||||
|
|
||||||
backup_if_exists(cfg.wg0_conf)
|
|
||||||
|
|
||||||
lines: List[str] = []
|
|
||||||
lines += [
|
|
||||||
"[Interface]",
|
|
||||||
f"Address = {cfg.wg_addr}",
|
|
||||||
f"ListenPort = {cfg.wg_port}",
|
|
||||||
f"PrivateKey = {server_priv}",
|
|
||||||
]
|
|
||||||
|
|
||||||
for name in sorted(list_clients(cfg)):
|
|
||||||
pubf = cfg.clients_dir / f"{name}.pub"
|
|
||||||
ipf = cfg.clients_dir / f"{name}.ip"
|
|
||||||
if not ipf.exists():
|
|
||||||
raise RuntimeError(f"Client {name} has no .ip file (run add {name}).")
|
|
||||||
pub = read_first_nonempty_line(pubf)
|
|
||||||
validate_key(pub, f"client {name} pubkey")
|
|
||||||
octet = read_first_nonempty_line(ipf)
|
|
||||||
if not octet.isdigit():
|
|
||||||
raise RuntimeError(f"Invalid ip octet in {ipf}: {octet!r}")
|
|
||||||
o = int(octet)
|
|
||||||
if o < 2 or o > 254:
|
|
||||||
raise RuntimeError(f"Invalid ip octet in {ipf}: {o}")
|
|
||||||
lines += [
|
|
||||||
"",
|
|
||||||
"[Peer]",
|
|
||||||
f"# {name}",
|
|
||||||
f"PublicKey = {pub}",
|
|
||||||
f"AllowedIPs = {cfg.client_net_prefix}{o}/32",
|
|
||||||
f"PersistentKeepalive = {cfg.keepalive}",
|
|
||||||
]
|
|
||||||
|
|
||||||
cfg.wg0_conf.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
||||||
chmod600_root(cfg.wg0_conf)
|
|
||||||
print(f"OK: generated {cfg.wg0_conf}")
|
|
||||||
|
|
||||||
|
|
||||||
def add_client(cfg: Cfg, name: str, pubkey: Optional[str], ip: Optional[str], no_restart: bool):
|
|
||||||
ensure_dirs(cfg)
|
|
||||||
pubf = cfg.clients_dir / f"{name}.pub"
|
|
||||||
ipf = cfg.clients_dir / f"{name}.ip"
|
|
||||||
|
|
||||||
# resolve pubkey
|
|
||||||
if pubkey:
|
|
||||||
pubkey = pubkey.strip()
|
|
||||||
validate_key(pubkey, "provided client public key")
|
|
||||||
pubf.write_text(pubkey + "\n", encoding="utf-8")
|
|
||||||
chmod600_root(pubf)
|
|
||||||
else:
|
|
||||||
if not pubf.exists():
|
|
||||||
raise RuntimeError(f"Missing {pubf}. Run gen-client {name} or provide --pubkey.")
|
|
||||||
pubkey = read_first_nonempty_line(pubf)
|
|
||||||
validate_key(pubkey, f"client {name} pubkey")
|
|
||||||
|
|
||||||
# resolve ip octet
|
|
||||||
if ip is None:
|
|
||||||
octet = alloc_octet(cfg)
|
|
||||||
else:
|
|
||||||
ip = ip.strip()
|
|
||||||
if ip.startswith(cfg.client_net_prefix):
|
|
||||||
ip = ip[len(cfg.client_net_prefix) :]
|
|
||||||
if not ip.isdigit():
|
|
||||||
raise RuntimeError("--ip must be an octet (2..254) or full IP like 10.66.66.X")
|
|
||||||
octet = int(ip)
|
|
||||||
if octet < 2 or octet > 254:
|
|
||||||
raise RuntimeError("--ip must be 2..254")
|
|
||||||
if octet in used_octets(cfg):
|
|
||||||
raise RuntimeError(f"IP octet {octet} already used")
|
|
||||||
|
|
||||||
ipf.write_text(f"{octet}\n", encoding="utf-8")
|
|
||||||
chmod600_root(ipf)
|
|
||||||
|
|
||||||
print(f"OK: added peer {name} -> {cfg.client_net_prefix}{octet}/32")
|
|
||||||
generate_wg0_conf(cfg)
|
|
||||||
if not no_restart:
|
|
||||||
restart_wg()
|
|
||||||
|
|
||||||
|
|
||||||
def del_client(cfg: Cfg, name: str, no_restart: bool):
|
|
||||||
ensure_dirs(cfg)
|
|
||||||
pubf = cfg.clients_dir / f"{name}.pub"
|
|
||||||
ipf = cfg.clients_dir / f"{name}.ip"
|
|
||||||
# keep .key on purpose
|
|
||||||
if pubf.exists():
|
|
||||||
pubf.unlink()
|
|
||||||
if ipf.exists():
|
|
||||||
ipf.unlink()
|
|
||||||
print(f"OK: removed peer {name} (pub/ip). Private key kept if existed.")
|
|
||||||
generate_wg0_conf(cfg)
|
|
||||||
if not no_restart:
|
|
||||||
restart_wg()
|
|
||||||
|
|
||||||
|
|
||||||
def export_client_conf(cfg: Cfg, name: str, endpoint: Optional[str]):
|
|
||||||
ensure_dirs(cfg)
|
|
||||||
keyf = cfg.clients_dir / f"{name}.key"
|
|
||||||
ipf = cfg.clients_dir / f"{name}.ip"
|
|
||||||
|
|
||||||
if not keyf.exists():
|
|
||||||
raise RuntimeError(f"Missing {keyf}. Run gen-client {name} on server or create key on client.")
|
|
||||||
if not ipf.exists():
|
|
||||||
raise RuntimeError(f"Missing {ipf}. Run add {name} first.")
|
|
||||||
if not cfg.server_pub.exists():
|
|
||||||
raise RuntimeError("Missing server.pub. Run gen-server first.")
|
|
||||||
|
|
||||||
client_priv = read_first_nonempty_line(keyf)
|
|
||||||
validate_key(client_priv, f"client {name} private key")
|
|
||||||
server_pub = read_first_nonempty_line(cfg.server_pub)
|
|
||||||
validate_key(server_pub, "server public key")
|
|
||||||
|
|
||||||
octet = int(read_first_nonempty_line(ipf))
|
|
||||||
if endpoint is None:
|
|
||||||
endpoint = f"{detect_public_ip()}:{cfg.wg_port}"
|
|
||||||
|
|
||||||
out = cfg.clients_dir / f"{name}.conf"
|
|
||||||
content = "\n".join(
|
|
||||||
[
|
|
||||||
"[Interface]",
|
|
||||||
f"Address = {cfg.client_net_prefix}{octet}/32",
|
|
||||||
f"PrivateKey = {client_priv}",
|
|
||||||
"",
|
|
||||||
"[Peer]",
|
|
||||||
f"PublicKey = {server_pub}",
|
|
||||||
f"Endpoint = {endpoint}",
|
|
||||||
# Default: only reach the server's WG IP via tunnel.
|
|
||||||
"AllowedIPs = 10.66.66.1/32",
|
|
||||||
f"PersistentKeepalive = {cfg.keepalive}",
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
out.write_text(content, encoding="utf-8")
|
|
||||||
chmod600_root(out)
|
|
||||||
print(f"OK: wrote {out}")
|
|
||||||
|
|
||||||
|
|
||||||
def cmd_list(cfg: Cfg):
|
|
||||||
ensure_dirs(cfg)
|
|
||||||
names = list_clients(cfg)
|
|
||||||
if not names:
|
|
||||||
print("(no clients)")
|
|
||||||
return
|
|
||||||
for n in names:
|
|
||||||
ipf = cfg.clients_dir / f"{n}.ip"
|
|
||||||
octet = read_first_nonempty_line(ipf) if ipf.exists() else "?"
|
|
||||||
print(f"{n}\t{cfg.client_net_prefix}{octet}/32")
|
|
||||||
|
|
||||||
|
|
||||||
def cmd_show(cfg: Cfg, name: str):
|
|
||||||
ensure_dirs(cfg)
|
|
||||||
pubf = cfg.clients_dir / f"{name}.pub"
|
|
||||||
ipf = cfg.clients_dir / f"{name}.ip"
|
|
||||||
keyf = cfg.clients_dir / f"{name}.key"
|
|
||||||
print(f"Name: {name}")
|
|
||||||
if ipf.exists():
|
|
||||||
print(f"IP: {cfg.client_net_prefix}{read_first_nonempty_line(ipf)}/32")
|
|
||||||
else:
|
|
||||||
print("IP: (not assigned)")
|
|
||||||
if pubf.exists():
|
|
||||||
print(f"PUB: {read_first_nonempty_line(pubf)}")
|
|
||||||
else:
|
|
||||||
print("PUB: (missing)")
|
|
||||||
print(f"KEY: {'exists' if keyf.exists() else 'missing'} ({keyf})")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
require_root()
|
|
||||||
cfg = Cfg()
|
|
||||||
p = argparse.ArgumentParser(prog="wg_serverctl.py")
|
|
||||||
sub = p.add_subparsers(dest="cmd", required=True)
|
|
||||||
|
|
||||||
s = sub.add_parser("gen-server")
|
|
||||||
s.add_argument("--force", action="store_true")
|
|
||||||
|
|
||||||
g = sub.add_parser("gen-client")
|
|
||||||
g.add_argument("name")
|
|
||||||
g.add_argument("--force", action="store_true")
|
|
||||||
|
|
||||||
a = sub.add_parser("add")
|
|
||||||
a.add_argument("name")
|
|
||||||
a.add_argument("--pubkey", help="Client public key (base64, ends with =)")
|
|
||||||
a.add_argument("--ip", help="IP octet (2..254) or full 10.66.66.X")
|
|
||||||
a.add_argument("--no-restart", action="store_true")
|
|
||||||
|
|
||||||
d = sub.add_parser("del")
|
|
||||||
d.add_argument("name")
|
|
||||||
d.add_argument("--no-restart", action="store_true")
|
|
||||||
|
|
||||||
ap = sub.add_parser("apply")
|
|
||||||
ap.add_argument("--no-restart", action="store_true")
|
|
||||||
|
|
||||||
sub.add_parser("list")
|
|
||||||
|
|
||||||
sh = sub.add_parser("show")
|
|
||||||
sh.add_argument("name")
|
|
||||||
|
|
||||||
ex = sub.add_parser("export")
|
|
||||||
ex.add_argument("name")
|
|
||||||
ex.add_argument("--endpoint", help="Override endpoint ip:port (default: auto-detect public ip)")
|
|
||||||
|
|
||||||
args = p.parse_args()
|
|
||||||
|
|
||||||
try:
|
|
||||||
if args.cmd == "gen-server":
|
|
||||||
gen_server(cfg, args.force)
|
|
||||||
elif args.cmd == "gen-client":
|
|
||||||
gen_client(cfg, args.name, args.force)
|
|
||||||
elif args.cmd == "add":
|
|
||||||
add_client(cfg, args.name, args.pubkey, args.ip, args.no_restart)
|
|
||||||
elif args.cmd == "del":
|
|
||||||
del_client(cfg, args.name, args.no_restart)
|
|
||||||
elif args.cmd == "apply":
|
|
||||||
generate_wg0_conf(cfg)
|
|
||||||
if not args.no_restart:
|
|
||||||
restart_wg()
|
|
||||||
elif args.cmd == "list":
|
|
||||||
cmd_list(cfg)
|
|
||||||
elif args.cmd == "show":
|
|
||||||
cmd_show(cfg, args.name)
|
|
||||||
elif args.cmd == "export":
|
|
||||||
export_client_conf(cfg, args.name, args.endpoint)
|
|
||||||
else:
|
|
||||||
p.print_help()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ERROR: {e}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user