Commit 006049c1 authored by Vitaly Lipatov's avatar Vitaly Lipatov

Add Claude Code skills, docs and update CLAUDE.md

- Add 7 skills: dns, pve, mail, eterban, vpn, network, sip - Add reference docs: dhash.ru, epm, nfs - Remove redundant agents (dns-manager, pve-manager) - Move SPICE docs from vz/pve-spice-connect.md into pve skill - Add skills table to CLAUDE.md - Update .gitignore to track only .claude/skills/ and .claude/docs/ Co-Authored-By: 's avatarClaude Opus 4.6 (1M context) <noreply@anthropic.com>
parent 92266c14
---
name: dhash.ru (IPFS)
description: CT 366 на spacer, IPFS gateway и нода, 91.232.225.49
type: reference
---
# dhash.ru — IPFS
- **PVE**: CT 366, нода spacer, unprivileged
- **IPv4**: 91.232.225.49
- **IPv6**: 2a03:5a00:c:20::49/64 (включён 2026-03-11)
- **Доступ**: `ssh ipfs@dhash` (для ipfs-команд), `ssh dhash` (lav)
- **Gateway**: https://dhash.ru/ipfs/
- **API**: /ip4/91.232.225.49/tcp/5001
- **Сервис**: `serv ipfsd start/stop/status`
- **Хранилище**: mp0 ipfs:subvol-366-disk-0 (1511G), /var/local/ipfs
- **Peer ID**: 12D3KooWLDKZGAgD5v4gvgtXbCwxPV9aeRnhQyigSMW9gfKdY4xC
- **Peering**: 12× Cloudflare + Pinata; Bootstrap: 4× Protocol Labs + 1 legacy
## Удалённый доступ к IPFS
```bash
ipfs --api=/ip4/91.232.225.49/tcp/5001 get QmHASH -o output
curl https://dhash.ru/ipfs/QmHASH -o output
```
## Диагностика
```bash
ssh ipfs@dhash "ipfs swarm peers | wc -l"
ssh ipfs@dhash "ipfs swarm resources | head"
ssh ipfs@dhash "ipfs stats bw"
```
## Известные проблемы
- FD exhaustion: "unable to fully refresh routing table" — увеличить MaxFileDescriptors
- Ежедневный рестарт в 04:00 (ipfsd-restart.timer)
- Bottleneck в IPFS gateway, не в сети (localhost медленнее remote)
## Changelog
(пока нет записей)
---
name: epm и serv (управление пакетами и сервисами)
description: Команды epm/serv для ALT Linux — установка, поиск, управление сервисами
type: reference
---
# epm — управление пакетами
- `epmqp PATTERN` — поиск установленных пакетов
- `epm ql PACKAGE` — список файлов пакета
- `epm show PACKAGE` — информация о пакете
- `epm search PATTERN` — поиск в репозитории (ищет и по epm play тоже)
- `epm req FILE.rpm` — зависимости rpm-файла
- `epm play APPNAME` — установить приложение
- `epm rl` — вывод репозиториев
- `epmi TASK` — установить все пакеты из задачи girar
- `epm install TASK/PACKAGE` — конкретный пакет из задачи girar
- `epm install 'URL/package*.rpm'` — установить по URL (wildcard)
- `epm ei` — обновить сам epm
- `epm release-upgrade --force` ��� обновление ALT до следующей версии
- `epm -y` — неинтерактивная установка
- sudo перед epm НЕ нужен — он сам повысит привилегии
Обновление epm из Korinf (для p11 и стабильных веток):
```
epm install 'https://updates.etersoft.ru/pub/Korinf/x86_64/ALTLinux/p11/eepm*.rpm'
```
При проблемах с epm — писать в backlog на исполнителя epm, а не подбирать альтернативы.
# serv — управление сервисами
- `serv NAME` — статус сервиса
- `serv NAME start|stop|restart` — управление
- `serv NAME enable|disable` — автозапуск
- `serv` — список всех сервисов
---
name: NFS (aspetos, spacer)
description: NFS-серверы — /home, /srv на aspetos, /var/ftp на spacer, manage-gids, FS-Cache
type: reference
---
# NFS
## Серверы
- **aspetos** (homeserver, srvserver) — /home, /srv
- **spacer** (ftpserver) — /var/ftp/pub, /var/ftp/pvt, /var/ftp/tmp
## Проблема 16 групп
NFS v3 AUTH_SYS передаёт максимум 16 групп. Решение — `--manage-gids` на сервере:
```bash
mkdir -p /etc/systemd/system/nfs-mountd.service.d
cat > /etc/systemd/system/nfs-mountd.service.d/manage-gids.conf << "EOF"
[Service]
ExecStart=
ExecStart=/usr/sbin/rpc.mountd --manage-gids
EOF
systemctl daemon-reload
systemctl restart nfs-mountd nfs-server
```
Настроено на: spacer
## После изменения групп в AD
NFS-сервер кэширует старое членство:
```bash
ssh root@spacer "systemctl restart nfs-mountd"
```
## Добавление в группу через samba-tool
При конфликте имён (пользователь и компьютер):
```bash
samba-tool group addmembers ГРУППА ПОЛЬЗОВАТЕЛЬ --object-types=user
```
## FS-Cache (локальный кэш NFS)
Ускоряет повторное чтение (до 12x):
```bash
epm install cachefilesd
# /etc/cachefilesd.conf: brun 50%, bcull 45%, bstop 40%
serv cachefilesd on && serv cachefilesd start
umount /srv && mount -o fsc /srv # remount не работает!
# Добавить fsc в /etc/fstab
```
Настроено на: white (/srv)
## Changelog
(пока нет записей)
---
name: dns
description: "DNS management view, add, modify, delete DNS records on ns1 (external) and dhcp (internal). TRIGGER when: DNS records, zone files, domain configuration, dig, BIND"
user-invocable: true
allowed-tools: Bash(ssh* dig*)
argument-hint: "[check|add|change|delete] domain [record] [value]"
context: fork
---
Execute DNS management task: $ARGUMENTS
If no arguments provided, show help with usage examples.
## DNS Servers
| Server | Role | Access | Zone files |
|---|---|---|---|
| ns1.etersoft.ru | External DNS (BIND) | `ssh -p 32 root@ns1.etersoft.ru` | `/var/lib/bind/zone/` |
| dhcp | Internal DNS | `ssh root@dhcp` | check with `named-checkconf -p` |
### ns1 Structure
- Config dir: `/etc/bind/` (`eterhost.conf` — hosting zones, `ns4.etersoft.ru.conf` — slave zones)
- Zone files: `/var/lib/bind/zone/`
- Git repo: `/var/lib/bind/` (commit all changes)
- Reverse zone: `/var/lib/bind/zone/225.232.91.in-addr.arpa` (free IPs, PTR records)
- Slave sync: `/etc/bind/z.sh` — ns4 (hetzner), ns2 (beget), ns3 (vdska)
## Workflow for Record Changes
### 1. Check current state
```bash
dig @ns1.etersoft.ru DOMAIN TYPE +short
ssh -p 32 root@ns1.etersoft.ru "grep -n 'PATTERN' /var/lib/bind/zone/DOMAIN"
```
### 2. Edit zone file
- ALWAYS increment serial (format: YYYYMMDDNN)
### 3. Validate
```bash
ssh -p 32 root@ns1.etersoft.ru "cd /var/lib/bind/zone && named-checkzone DOMAIN DOMAIN"
```
Must output "OK". NEVER reload if validation fails.
### 4. Reload
```bash
ssh -p 32 root@ns1.etersoft.ru "rndc reload"
```
### 5. Verify propagation (MANDATORY)
```bash
dig @ns1.etersoft.ru RECORD TYPE +short
dig @8.8.8.8 RECORD TYPE +short
dig @1.1.1.1 RECORD TYPE +short
```
### 6. Git commit
```bash
ssh -p 32 root@ns1.etersoft.ru "cd /var/lib/bind && git add zone/ZONEFILE && git commit -m 'DOMAIN: description'"
```
## Report Format (for Telegram)
```
Выполнено для {domain} на {ns-host}:
- Serial: old → new
Изменения:
- {record}: old_value → new_value
Проверка распространения:
- ns1.etersoft.ru: value ✅
- Google DNS (8.8.8.8): value ✅
- Cloudflare DNS (1.1.1.1): value ✅
Git commit: hash
```
## Common Record Types
| Type | Example | Notes |
|---|---|---|
| A | `subdomain A 1.2.3.4` | IPv4 |
| CNAME | `alias CNAME target` | Without trailing dot = same zone |
| MX | `@ MX 10 mail` | Priority + target |
| TXT | `@ TXT "v=spf1 ..."` | Quoted |
| PTR | `32 PTR host.domain.ru.` | In reverse zone |
## Rules
- ALWAYS check → edit → validate → reload → verify → commit
- When adding new IP — also add PTR in reverse zone
- For finding free IPs — check reverse zone
- Ask confirmation before deleting records
- Use `rndc reload` (not `rndc reload ZONE` — multiple views)
- DNS egw containers: delegated subzone `egw.etersoft.ru` on ns1, NOT in eterhost.ru
## Operations
- `/dns check azbyka.ru` — show all records
- `/dns check bot.azbyka.ru` — check specific record
- `/dns add test.azbyka.ru A 87.228.124.32` — add A record
- `/dns change bot.azbyka.ru CNAME a04` — change CNAME
- `/dns delete old.azbyka.ru` — delete (with confirmation)
- `/dns add 32.225.232.91 PTR host.domain.ru.` — add PTR
---
name: eterban
description: "IP ban management check, ban, unban IPs, test ban page. TRIGGER when: IP blocking, eterban, fail2ban, banned users, ban page testing"
user-invocable: true
allowed-tools: Bash(ssh* curl*)
argument-hint: "[search|ban|unban|test|count] [IP]"
context: fork
---
Execute eterban task: $ARGUMENTS
## Access
- priv.etersoft.ru (= router 91.232.225.1): `ssh -p32 priv.etersoft.ru` (lav, then sudo)
## Commands
```bash
sudo eterban search IP # check if IP is banned
sudo eterban list # list all banned IPs
sudo eterban count # count banned
sudo eterban ban IP # ban IP
sudo eterban unban IP # unban IP
```
## Ban pages
- External (banned IP → our sites): http://91.232.225.67/ (port 80/81)
- Internal (our users → banned IP): port 82, `eterban-internal.service` (int2.py, ThreadingHTTPServer)
## NAT rules on priv (iptables)
- `eterban_1 src` → DNAT all traffic to 91.232.225.67 (ban page)
- `eterban_1 dst` → DNAT ports 80,443 to 91.232.225.67:82 (internal page)
## Testing procedure
**NEVER ban our own servers (hetzner, etc.)!**
1. Use external host: `ssh -A root@beget.ogw.eterhost.ru "ssh lav@a03.azbyka.ru ..."`
2. Ban a03 (87.228.29.78): `sudo eterban ban 87.228.29.78`
3. From a03 via beget: `curl -s http://etersoft.ru/` → should show ban page
4. **ALWAYS unban after test**: `sudo eterban unban 87.228.29.78`
## int2.py (internal ban page)
- Path: `/var/www/html/eterban/int2.py`
- Repo: `~/Projects/git/eterban` (ban-internal-server/data/www/int2.py)
- Uses SO_ORIGINAL_DST to show which IP user tried to access
- ThreadingHTTPServer with 5s timeout (fixed from single-threaded hang)
## Logs
`/var/log/eterban/eterban.log` — ban/unban history with source server
---
name: mail
description: "Mail server management Cyrus IMAP, Postfix, SASL, user accounts, autoconfig. TRIGGER when: mail delivery issues, IMAP errors, creating mail accounts, checking mail logs, autoconfig/autodiscover"
user-invocable: true
allowed-tools: Bash(ssh* dig* curl*)
argument-hint: "[check|logs|account|autoconfig] [domain|user]"
context: fork
---
Execute mail management task: $ARGUMENTS
## Server access
- mail.etersoft.ru: `ssh -p32 root@mail.etersoft.ru` (CT 120 on border)
- Roundcube (10.20.30.66): `ssh root@10.20.30.66`
- Test stand (10.20.30.246): `ssh root@10.20.30.246` (CT 943)
## Key configs
- Cyrus IMAP: `/etc/imapd.conf`
- SASL SQL: `/etc/sasl2/{Cyrus,smtpd,saslpasswd}.conf`
- Postfix: `/etc/postfix/main.cf`, `mydestination`, `virtual-etersoft-regexp`
- Autoconfig XML: `/var/www/autoconfig/` on mail.etersoft.ru
- Roundcube: `/etc/roundcube/config.inc.php` on 10.20.30.66
## CRITICAL: defaultdomain Hoffice.etersoft.ru
**NEVER change this!** The `H` prefix is intentional. When defaultdomain matches login domain, Cyrus strips domain from userid → SASL gets username without @domain → auth breaks. `Hoffice` doesn't match any real domain → full userid preserved.
## Architecture
- Domains etersoft.ru/com/org/net: regexp rewriting → @office.etersoft.ru
- Symlink: `/var/spool/imap/domain/e/etersoft.ru → .../o/office.etersoft.ru`
- SASL SQL with OR: accepts both `user@etersoft.ru` and `user@office.etersoft.ru`
- `reject_unverified_recipient`: Postfix checks Cyrus LMTP before accepting mail
- amavis on as.office.etersoft.ru (10.20.30.210)
## MySQL
```
mysql --skip-ssl -h mysql.auth.dmz.etersoft.ru -u mail -p'VaQLNsXnyp3KVK4e' mail
```
Tables: `accountuser` (auth), `virtual` (aliases), `domain` (domains)
## Cyrus autocreate
`autocreate_inbox_folders: Drafts|Junk|Sent|Trash` (separator is `|`, NOT space!)
## Logs
- Mail: `/var/log/mail/all`, `/var/log/maillog`
- Roundcube: `/var/log/roundcube/errors.log` (on 10.20.30.66)
- Cyrus: syslog → `/var/log/messages` (via journalctl)
## Operations
- **Check logs**: `grep 'status=sent\|status=bounced\|reject' /var/log/mail/all | tail -20`
- **Check delivery**: `echo -e 'EHLO t\r\nMAIL FROM:<t@t.ru>\r\nRCPT TO:<user@domain>\r\nQUIT' | nc localhost 25`
- **List mailboxes**: `/usr/lib/cyrus/ctl_mboxlist -d | grep DOMAIN`
- **Reconstruct**: `/usr/lib/cyrus/reconstruct -r -f 'user.NAME@DOMAIN'`
- **Fix folder**: mkdir + chown cyrus:cyrus + reconstruct
## rspamd (as.office.etersoft.ru)
- Milter: `10.20.30.210:11332`, web UI: `10.20.30.210:11334` (simsimopen)
- Parallel with amavis, headers only (no reject/greylist)
- DKIM signing disabled (OpenDKIM does it on mail)
- `milter_protocol = 6` in Postfix (not 2!)
- Greylist module must be disabled via `enabled=false`, not just `greylist=null` in actions
- Postfix milter chain: OpenDKIM (127.0.0.1:8891) + rspamd (as:11332)
---
name: network
description: "Network infrastructure routing, tunnels, gateways, firewall, NAT. TRIGGER when: route-update, igw/egw/ogw, tunnel problems, site blocked, gateway health, iptables, traffic routing"
user-invocable: true
allowed-tools: Bash(ssh* curl* dig* ping* traceroute*)
argument-hint: "[check|routes|health|block] [domain|IP|gateway]"
context: fork
---
Execute network task: $ARGUMENTS
## Key hosts
| Host | IP | Role | Access |
|---|---|---|---|
| igw | 91.232.225.13 | Default gateway, route distributor | `ssh root@igw.etersoft.ru` |
| egw | 91.232.225.14 | Tunnel traffic distributor | `ssh root@egw.etersoft.ru` |
| priv | 91.232.225.1 | Border router, BIRD2, eterban | `ssh -p32 priv.etersoft.ru` (lav+sudo) |
| server | 192.168.8.1 | Wi-Fi gateway (192.168.8.0/24) | `ssh root@server` |
| hetzner | 135.181.95.108 | VDS Germany, tunnels, containers | `ssh -p32 root@hetzner.egw.eterhost.ru` |
| vdska | - | VDS, tunnels | `ssh root@vdska.egw.eterhost.ru` |
| beget | 217.12.37.55 | VDS, tunnels | `ssh root@beget.ogw.eterhost.ru` |
## Route web UI
- http://igw.etersoft.ru/ — manage bypass/direct/geo lists
- API: `/api/check` (POST, domain), `/api/list`, `/api/add`, `/api/remove`
- Check site blocking: `curl -s -X POST http://igw.etersoft.ru/api/check -H 'Content-Type: application/json' -d '{"domain":"example.com"}'`
## route-update.sh
- Location: `/root/etersoft-admin-essential/router/route-update.sh` on igw
- Watches `/root/egw-route/`, `/root/antifilter/` via inotifywait
- Config: `routes.d/GROUP/{gateway,*.list}`, `routes6.d/` for IPv6
- `web-bypass.list` must be **symlink** in `routes.d/egw/``/home/routeweb/route-web-api/web-bypass.list`
## Tunnel gateways (egw)
amneziawg.hetzner (.116), cloak.ovpn.hetzner (.132), gre.hetzner (.122), gre.vdska (.127), ikev2.hetzner (.120), ikev2.vdska (.131), openconnect.hetzner (.112), ovpn.hetzner (.118), ovpn.vdska (.128), warp (.134), xray.hetzner (.125)
## Tunnel gateways (ogw — no VDS)
bydpi (.129), gre.beget (.124), ikev2.beget (.130), nfqws.ogw (.126)
## Firewall notes
- server: `iptables -L FORWARD -n -v` (use -v to see interface binding!)
- Rule with `out=inet` does NOT match internal traffic
- priv: NEVER use `iptables-restore` — breaks podman/docker rules
- priv eterban rules: see `/eterban` skill
## ISP notes
- petrosvyaz filters UDP:500/4500 (IKE) on ether3
- nfqws transparent doesn't work through petrosvyaz DPI, but tpws SOCKS (port 987) works
- beget/sprintbox VDS don't support IPv6
## telemt MTProxy (chat.eterfund.ru)
- 3 nodes: main (91.232.225.3), div (divserver→GRE→vdska), beget (→GRE→hetzner)
- `bind_addresses` in `[[upstreams]]` — clean way to set outgoing IP
- Metrics: `/usr/local/bin/telemt-metrics.sh` → InfluxDB `gateways.telemt`
- DNS round-robin: `dns/chat-dns.sh on|off|status`
## Cloak+OpenVPN (CT 695, .132)
- Cloak tunnel through hetzner, SOCKS5 on port 1080
- Access: `ssh root@91.232.225.132`
## Route Export API (igw)
- `/api/export/resolved` — all resolved routes as CIDR
- `/api/export/resolved?format=mikrotik` — MikroTik format
- `/api/export/resolved?group=GROUP` — filter by group
- `/api/metrics` — list sizes, route counts
---
name: pve
description: PVE cluster management — list, start, stop, clone, console, destroy VMs/CTs
user-invocable: true
allowed-tools: Bash(pve/* ssh*)
argument-hint: "[list|start|stop|clone|console|destroy] [args...]"
context: fork
---
Execute PVE management task: $ARGUMENTS
If no arguments provided, run `pve/pve-list.sh --running` to show current cluster state.
Use the pve/ scripts from the project root directory. The scripts are:
- `pve/pve-list.sh` — list VMs/CTs with filters
- `pve/pve-start.sh VMID` — start VM/CT
- `pve/pve-stop.sh VMID [--force]` — stop VM/CT
- `pve/pve-clone.sh VMID [--name NAME] [--pool POOL]` — clone
- `pve/pve-console.sh VMID` — SPICE console
- `pve/pve-destroy.sh VMID` — delete with confirmation
Examples:
- `/pve list --running --node gefest`
- `/pve list --user lav`
- `/pve start 426`
- `/pve stop 426`
- `/pve clone 228 --name lav-test-p11`
- `/pve console 228`
## SPICE console
### Access
- PVE host gefest: `ssh root@gefest`
- PVE host border: `ssh root@border`
### Connect from CLI
```bash
ssh lav@lav 'DATA=$(ssh root@gefest "pvesh create /nodes/gefest/qemu/VMID/spiceproxy --proxy gefest.office.etersoft.ru --output-format=json")
cat > ~/tmp/claude/vm.vv << EOF
[virt-viewer]
password=$(echo "$DATA" | jq -r ".password")
host=$(echo "$DATA" | jq -r ".host")
toggle-fullscreen=Shift+F11
secure-attention=Ctrl+Alt+Ins
release-cursor=Ctrl+Alt+R
title=$(echo "$DATA" | jq -r ".title")
delete-this-file=0
host-subject=$(echo "$DATA" | jq -r ".\"host-subject\"")
ca=$(echo "$DATA" | jq -r ".ca")
proxy=http://gefest:3128
tls-port=$(echo "$DATA" | jq -r ".\"tls-port\"")
type=spice
EOF
DISPLAY=:0.0 remote-viewer ~/tmp/claude/vm.vv'
```
Run with `run_in_background: true` (don't use `&`).
### Critical notes
- `proxy=http://gefest:3128` — short hostname only! FQDN doesn't work
- `host-subject` — mandatory for TLS
- password/host are one-time — generate right before connecting
- Use `remote-viewer`, NOT `virt-viewer`
### VM rules
- Don't work in other people's VMs without asking
- Shut down VMs when not in use
- Delete VMs when no longer needed
- Don't use hibernation (may hang forever)
- Use snapshots for rollback capability
- Naming: `USER-BUGNUMBER-OS` (e.g. lav-18725-ALTp11)
- Testing pool, linked clone for testing, full clone for templates
---
name: sip
description: "SIP/VoIP telephony FreeSWITCH on sip.etersoft.ru, phones, sipnet, dialplan. TRIGGER when: SIP, phone, FreeSWITCH, telephony, call, dialplan, sipnet, gigaset"
user-invocable: true
allowed-tools: Bash(ssh*)
argument-hint: "[status|logs|user] [phone|number]"
context: fork
---
Execute SIP/telephony task: $ARGUMENTS
## Access
- `ssh -p32 lav@sip.etersoft.ru` (then sudo)
- FreeSWITCH 1.10.12, configs: `/etc/freeswitch/`
- Logs: `/var/log/freeswitch/freeswitch.log`
- Diagnostics: `ngrep`, `sngrep` (installed)
- Console: `sudo fs_cli`
## Phones
| ID | Ext | Type |
|---|---|---|
| gigaset | 20 | Gigaset A510 IP, UDP, 91.232.225.2:5060 |
| grandstream | 21 | Grandstream, UDP, 91.232.225.2:9237 |
| lav | 502 | Softphone, TLS |
Users configured in `/etc/freeswitch/directory/default.xml`:
```xml
<user id="USERNAME" number-alias="EXT"><params><param name="password" value="PASS"/></params></user>
```
## SIPnet provider
- Gateway: sipnet.ru (212.53.40.40, realm etc.tario.ru)
- Login: 0042160601
- Profile: sipnet (port 5080)
- **Codecs**: PCMU, PCMA, G729 only. **G722 NOT supported** (488 Not Acceptable)
## Dialplan
- Internal calls (2-4 digits): `absolute_codec_string=PCMU,PCMA,G722`
- Outgoing via sipnet: `absolute_codec_string=PCMU,PCMA,G729` (NO G722!)
- Number formats: 00 (operator), 7 digits (SPb→7812+), +78-812 (SPb), +78-495/499 (Moscow), 810 (intl), +7/8 (Russia)
- Incoming from sipnet: ring gigaset 25s → IVR "enter extension" → transfer
## Key lessons
- `nolocal:absolute_codec_string=` forces codecs for outgoing leg (ignores inbound negotiation)
- `inbound-late-negotiation=true` + `start_dtmf_generate` can cause codec mismatch on bridge
- Sofia internal profile: TLS on 5061, `rtp-secure-media=optional`
---
name: vpn
description: "VPN user management create, check, revoke VPN access. TRIGGER when: VPN access, create VPN user, openvpn certificate, muvpn"
user-invocable: true
allowed-tools: Bash(ssh*)
argument-hint: "[create|check|list] [email] [server]"
context: fork
---
Execute VPN management task: $ARGUMENTS
## VPN servers
| Server | Access | Clients |
|---|---|---|
| vpn.office | `ssh vpn.office` (lav, not root) | Office VPN |
| vpn.eterfund.ru | `ssh -p32 root@vpn.eterfund.ru` | External VPN |
| evpn.eterfund.ru | `ssh -p32 root@evpn.eterfund.ru` | External VPN |
## Create VPN user
```bash
cd /root/vpn && EASYRSA_BATCH=1 ./muvpn.sh --create email@domain
```
- `EASYRSA_BATCH=1` required — easyrsa asks for interactive "yes" confirmation
- "No Easy-RSA 'vars' configuration file exists!" warning is normal
- Key is mailed to the user automatically
## Check existing user
```bash
grep -i USERNAME /etc/openvpn/pki/index.txt
```
Format: `V <date> <serial> unknown /CN=email@domain`
## Revoke user
```bash
cd /root/vpn && ./muvpn.sh --revoke email@domain
```
## Remove CA key password (if needed)
```bash
openssl rsa -in /etc/openvpn/pki/private/ca.key -out /etc/openvpn/pki/private/ca.key
```
......@@ -2,3 +2,10 @@ web/config
vz/azbyka/base.task
dns/whois-cache/*
router/.state/
# Claude Code: only track skills and docs
.claude/*
!.claude/skills/
!.claude/skills/**
!.claude/docs/
!.claude/docs/**
.playwright-mcp/
# Подключение к VM Proxmox через SPICE из CLI
## Обзор
Для подключения к VM на Proxmox (gefest) через SPICE из командной строки нужно:
1. Получить одноразовые credentials через API
2. Сформировать .vv файл в правильном формате
3. Запустить remote-viewer с этим файлом
## 1. Получение данных для подключения
```bash
ssh root@gefest "pvesh create /nodes/gefest/qemu/VMID/spiceproxy --proxy gefest.office.etersoft.ru --output-format=json"
```
Возвращает JSON:
```json
{
"password": "одноразовый_пароль",
"host": "pvespiceproxy:TICKET:VMID:NODE::HASH",
"tls-port": 61007,
"ca": "-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\\n",
"title": "VM VMID - имя",
"host-subject": "OU=PVE Cluster Node,O=Proxmox Virtual Environment,CN=gefest.office.etersoft.ru",
"proxy": "http://gefest.office.etersoft.ru:3128"
}
```
## 2. Формат .vv файла
Порядок полей соответствует тому, что генерирует веб-интерфейс Proxmox:
```ini
[virt-viewer]
password=be3bcd8c390050b6e5ffa9b1385a154b1152c36b
host=pvespiceproxy:69505df5:228:gefest::e80b5a4bdde6e651a940b5c5b037c3aba9972401
toggle-fullscreen=Shift+F11
secure-attention=Ctrl+Alt+Ins
release-cursor=Ctrl+Alt+R
title=VM 228 - lav-altp11-gnome
delete-this-file=1
host-subject=OU=PVE Cluster Node,O=Proxmox Virtual Environment,CN=gefest.office.etersoft.ru
ca=-----BEGIN CERTIFICATE-----\nMIIF...\n-----END CERTIFICATE-----\n
proxy=http://gefest:3128
tls-port=61007
type=spice
```
## 3. Критически важные моменты
### proxy — короткое имя хоста!
```
proxy=http://gefest:3128 # ПРАВИЛЬНО
proxy=http://gefest.office.etersoft.ru:3128 # НЕ РАБОТАЕТ
```
### host-subject — обязателен
Без этого поля TLS соединение не установится.
### password и host — одноразовые
Генерировать непосредственно перед подключением. Повторно использовать нельзя.
### Клиент — remote-viewer
```bash
remote-viewer файл.vv # ПРАВИЛЬНО
virt-viewer файл.vv # Пытается подключиться к libvirt, не работает
```
## 4. Полный скрипт для Claude Code
```bash
ssh lav@lav 'DATA=$(ssh root@gefest "pvesh create /nodes/gefest/qemu/VMID/spiceproxy --proxy gefest.office.etersoft.ru --output-format=json")
cat > ~/tmp/claude/vm.vv << EOF
[virt-viewer]
password=$(echo "$DATA" | jq -r ".password")
host=$(echo "$DATA" | jq -r ".host")
toggle-fullscreen=Shift+F11
secure-attention=Ctrl+Alt+Ins
release-cursor=Ctrl+Alt+R
title=$(echo "$DATA" | jq -r ".title")
delete-this-file=0
host-subject=$(echo "$DATA" | jq -r ".\"host-subject\"")
ca=$(echo "$DATA" | jq -r ".ca")
proxy=http://gefest:3128
tls-port=$(echo "$DATA" | jq -r ".\"tls-port\"")
type=spice
EOF
DISPLAY=:0.0 remote-viewer ~/tmp/claude/vm.vv'
```
Заменить:
- `VMID` — номер виртуальной машины
- `lav@lav` — пользователь и хост с графической сессией
## 5. Запуск из Claude Code
### Найти DISPLAY пользователя
```bash
ssh lav@HOST "for pid in \$(pgrep -u lav); do d=\$(grep -z '^DISPLAY=' /proc/\$pid/environ 2>/dev/null | tr '\0' '\n'); [ -n \"\$d\" ] && echo \"\$d\" && break; done"
```
Обычно возвращает `:0.0`
### Запуск команды
- Использовать параметр `run_in_background: true` в Bash tool
- НЕ использовать `&` в конце команды
## 6. Отладка
### Проверить доступность прокси
```bash
nc -zv gefest 3128
```
### Проверить что VM запущена
```bash
ssh root@gefest "qm status VMID"
```
### Посмотреть ошибки remote-viewer
Вывод сохраняется в файл задачи при использовании `run_in_background: true`
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment