Compare commits

...

6 commits

Author SHA1 Message Date
ef6abdea15 Merge pull request 'docs: DE443 home-server inbound and scoped acceptance' (#22) from codex/vps-de-home-inbound-20261023 into main
Some checks are pending
Bash syntax / syntax (push) Waiting to run
2026-10-23 10:51:20 +00:00
ochenstarik-ui
7329c1164f docs: record DE443 home inbound and scoped acceptance
All checks were successful
Bash syntax / syntax (push) Successful in 17s
Bash syntax / syntax (pull_request) Successful in 17s
2026-10-23 17:22:39 +07:00
6f699607b0 Merge pull request 'docs: RU/DE VPS setup and verified direct VPN acceptance' (#21) from codex/vps-ru-de-setup-20261023 into main
All checks were successful
Bash syntax / syntax (push) Successful in 17s
2026-10-23 10:00:34 +00:00
9097be0fbc Merge pull request 'Handle socket-activated SSH runtime and require UFW boot readiness' (#20) from codex/sshd-runtime-dir-20261023 into main
Some checks failed
Bash syntax / syntax (push) Has been cancelled
2026-10-23 10:00:31 +00:00
ochenstarik-ui
e5b06793ec Reject masked UFW boot service before changing firewall rules
All checks were successful
Bash syntax / syntax (push) Successful in 18s
Bash syntax / syntax (pull_request) Successful in 17s
2026-10-23 15:26:10 +07:00
ochenstarik-ui
9e8f024b89 fix: prepare sshd runtime directory before validation
All checks were successful
Bash syntax / syntax (push) Successful in 18s
Bash syntax / syntax (pull_request) Successful in 19s
2026-10-23 14:31:21 +07:00
6 changed files with 152 additions and 2 deletions

View file

@ -59,3 +59,9 @@ jobs:
- name: Test initial backup policy and upload retry
run: bash tests/test-backup-initial-retry.sh
- name: Test step 3 socket activation runtime directory
run: bash tests/test-step3-runtime-directory.sh
- name: Test step 2 UFW boot readiness
run: bash tests/test-step2-ufw-boot.sh

View file

@ -270,3 +270,59 @@ Panel DB (без path/credentials):
- Публичная панель, публичные подписки, Telegram-ноды, AI-агенты и WARP не включались. Новых дополнительных мониторов/автоматизаций нет; прежний тик30 не возобновлялся.
- Для послеустановочного восстановления оставлены согласованные SQLite checkpoints и локальные initial/daily snapshots с проверенными checksum. Локальная копия не заменяет отдельное внешнее хранилище; offsite не включался.
- Этот PR содержит только данный обезличенный отчёт. Code PR20 отдельный, автор не вливает. Прежний release gate для новых PR/документов без CI не обходится.
## Дополнение 23.10.2026 — DE443 для домашней машины
Основание: актуальная заменяющая версия `kagent/agents/claude/done/2026-10-23-fixes/CODEX-vps-de-close-443.md`. Закрытие443 отменено. Эта работа — отдельный report-only PR поверх main `6f699607b079a57ea19765b19f781ef012d7de18`, где PR20 и PR21 уже merged внешним участником. Итоговый main CI18/task7390 SUCCESS; промежуточный CI17/task7389 cancelled, зелёным не объявляется.
SSH использован **только к de-vpn** с ранее разрешённым операторским ключом и pinned known_hosts. RU/FI/DK по SSH не посещались, их конфигурации не менялись. Системный прокси/bypass этой машины не менялся; ключ владельца/исходные доступы не копировались и не читались дополнительно.
Через API приватной панели DE добавлен отдельный inbound `home-server-de`: VLESS+Reality TCP443, клиент `home-server`, Vision, самостоятельные private/public key и shortId, отличные от связки24443. Camouflage `www.microsoft.com:443`: TLS1.3 и проверка цепочки с DE PASS до создания. Ссылка записана **только** в `/home/ochenstarik/srgfd/home_server_de_link.txt` (0600), без вывода URI/UUID/ключей.
UFW443/tcp уже открыт для всех; правило и запись443 в managed ports оставлены. UFW user.rules и managed-ports файл побайтно не изменились. Профиль `codex-ru-de-link`24443, его разрешение только с RU_IP, outbounds и routing DE не менялись: сравнены API-настройки и native existing-inbound до/после. Новый вход добавлен горячим API без смены PID Xray и без ручного рестарта.
Перед изменением сохранены согласованная SQLite backup, native-config и UFW/managed файлы в закрытом root checkpoint `codex-before-de-home-*`. После — новая согласованная SQLite backup с `integrity_check=ok`, обе записи443/24443 на месте, и snapshot собранного панелью конфига (`codex-after-de-home-*`). API `server/getConfigJson` содержит оба inbound. Последний записанный native config.json на диске при hot-add ещё не содержит443; он не переписывался вручную. Рабочий443 подтверждён слушателем и реальными запросами, сохранение — БД и собранным конфигом; перезапуск DE ради этого теста не выполнялся.
| Проверка | Результат и предел |
| --- | --- |
| Новый home-server, временный Xray на DE → публичный DE443 | PASS: api.ipify.org и icanhazip.com, curl0, фактический egress DE_IP |
| Новый home-server, прямой клиент с домашней машины | Не выполнялся: DE_IP отсутствует в локальном bypass4. Самостоятельное добавление прямо запрещено этим заданием; проверка передана в отдельный `CODEX-home-xray-via-ru.md` |
| Прежняя связка RU→DE, обычный временный клиент на этой машине | PASS: api.ipify.org egress DE_IP, российский API Яндекса egress RU_IP, оба curl0. Только клиентский трафик по прежней ссылке, без SSH/изменений RU |
| Сохранность24443 | API/native profile, routing/outbounds и firewall неизменны; слушатель24443 активен |
Временные клиенты работали на свободных loopback SOCKS-портах, proxy env удалены; SSH data forwarding не использовался. Self-test DE не подменяет приёмку внешнего домашнего пути. На домашнем системном прокси новая ссылка не установлена — это задача другого поручения при владельце.
### DE: фактический вывод после добавления (доступы отредактированы)
`ufw status verbose`:
```text
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
<SSH_PORT>/tcp ALLOW IN Anywhere
443/tcp ALLOW IN Anywhere
24443/tcp ALLOW IN <RU_IP> # RU-DE Reality upstream
```
`ss -ltnpH` (расширенный ss -ltn, для проверки процесса):
```text
LISTEN 0 4096 127.0.0.1:62789 0.0.0.0:* users:(("xray-linux-amd6",pid=1023,fd=3))
LISTEN 0 4096 0.0.0.0:<SSH_PORT> 0.0.0.0:* users:(("sshd",pid=792,fd=3),("systemd",pid=1,fd=153))
LISTEN 0 4096 127.0.0.1:<PANEL_PORT> 0.0.0.0:* users:(("x-ui",pid=788,fd=10))
LISTEN 0 4096 127.0.0.1:2096 0.0.0.0:* users:(("x-ui",pid=788,fd=18))
LISTEN 0 4096 127.0.0.54:53 0.0.0.0:* users:(("systemd-resolve",pid=746,fd=17))
LISTEN 0 4096 127.0.0.1:11111 0.0.0.0:* users:(("xray-linux-amd6",pid=1023,fd=7))
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=746,fd=15))
LISTEN 0 4096 [::]:<SSH_PORT> [::]:* users:(("sshd",pid=792,fd=4),("systemd",pid=1,fd=154))
LISTEN 0 4096 *:443 *:* users:(("xray-linux-amd6",pid=1023,fd=9))
LISTEN 0 4096 *:24443 *:* users:(("xray-linux-amd6",pid=1023,fd=6))
```
Панель/подписки по-прежнему loopback. Новый report-only PR не содержит кода, конфигураций с ключами или самой ссылки; автоматический CI и действующий release-review gate сохраняются. Самослияние не выполняется.

View file

@ -17,6 +17,20 @@ require_command() {
command -v "$1" >/dev/null 2>&1 || die "Не найдена команда: $1"
}
ensure_ufw_boot_service() {
local state
state="$(systemctl is-enabled ufw.service 2>/dev/null || true)"
case "$state" in
masked|masked-runtime)
die "ufw.service замаскирована: сначала проверьте причину маски и включите службу вручную. Правила не изменены."
;;
esac
systemctl enable ufw.service \
|| die "Не удалось включить автозапуск ufw.service; правила не изменены"
systemctl is-enabled --quiet ufw.service \
|| die "Автозапуск ufw.service не подтверждён; правила не изменены"
}
is_valid_port() {
local port="$1"
[[ "$port" =~ ^[0-9]{1,5}$ ]] || return 1
@ -403,9 +417,10 @@ if [[ "$ACTION" == install ]]; then
apt-get install -y \
sudo ufw fail2ban curl ca-certificates openssl openssh-server logrotate iproute2
for command_name in ip sed sysctl ufw; do
for command_name in ip sed sysctl ufw systemctl; do
require_command "$command_name"
done
ensure_ufw_boot_service
install -d -m 700 -o root -g root "$CONFIG_DIR"
load_managed_rules
PREVIOUS_MANAGED_RULES=("${MANAGED_RULES[@]}")
@ -415,9 +430,10 @@ if [[ "$ACTION" == install ]]; then
save_configuration
log "SSH-порт ${SSH_PORT} сохранён для этапа 3"
else
for command_name in ip sed sysctl ufw; do
for command_name in ip sed sysctl ufw systemctl; do
require_command "$command_name"
done
ensure_ufw_boot_service
install -d -m 700 -o root -g root "$CONFIG_DIR"
SSH_PORT="$(read_saved_value "$SSH_PORT_CONFIG" SSH_PORT)"
is_valid_port "$SSH_PORT" || SSH_PORT="$DEFAULT_SSH_PORT"

View file

@ -73,6 +73,12 @@ is_valid_port() {
(( 10#$port >= 1 && 10#$port <= 65535 ))
}
prepare_sshd_runtime_directory() {
# Socket-activated Ubuntu can have no active ssh.service after an upgrade.
# Its RuntimeDirectory then does not exist, but standalone sshd -t needs it.
install -d -o root -g root -m 755 /run/sshd
}
read_ip_mode() {
local mode=both
if [[ -e "$IP_FAMILY_CONFIG" ]]; then
@ -517,6 +523,7 @@ UsePAM yes
EOF
chmod 644 "$SSHD_DROPIN"
prepare_sshd_runtime_directory
sshd -t || die "sshd syntax validation failed"
# Capture the complete output once. With pipefail enabled, piping `sshd -T`
# into an awk program that exits after the first match can terminate sshd with

View file

@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -Eeuo pipefail
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
source <(awk '/^\[\[ "\$EUID"/{exit} {print}' ochenstarik-server-2.sh)
test_dir="$(mktemp -d)"
trap 'rm -rf -- "$test_dir"' EXIT
systemctl() {
printf '%s\n' "$*" >> "$test_dir/calls"
case "$*" in
'is-enabled ufw.service') cat "$test_dir/state"; return 1 ;;
'enable ufw.service')
[[ "$scenario" != enable-failed ]] || return 1
[[ "$scenario" == verify-failed ]] || printf enabled > "$test_dir/state"
;;
'is-enabled --quiet ufw.service') [[ "$(cat "$test_dir/state")" == enabled ]] ;;
*) return 99 ;;
esac
}
for state in masked masked-runtime; do
printf '%s' "$state" > "$test_dir/state"
: > "$test_dir/calls"
scenario=masked
if (ensure_ufw_boot_service) > "$test_dir/output" 2>&1; then exit 1; fi
! grep -q '^enable ' "$test_dir/calls"
[[ "$(cat "$test_dir/state")" == "$state" ]]
done
for scenario in disabled enabled enable-failed verify-failed; do
printf '%s' "$scenario" > "$test_dir/state"
: > "$test_dir/calls"
if [[ "$scenario" == *-failed ]]; then
if (ensure_ufw_boot_service) > "$test_dir/output" 2>&1; then exit 1; fi
else
ensure_ufw_boot_service
[[ "$(cat "$test_dir/state")" == enabled ]]
grep -qx 'is-enabled --quiet ufw.service' "$test_dir/calls"
fi
done
printf 'Step 2 UFW boot readiness tests passed.\n'

View file

@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -Eeuo pipefail
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
source <(awk '/^\[\[ "\$EUID"/{exit} {print}' ochenstarik-server-user-3.sh)
test_dir="$(mktemp -d)"
trap 'rm -rf -- "$test_dir"' EXIT
# Relocate just the privileged filesystem boundary; use the real install
# binary to check first creation, idempotency and repair of unsafe modes.
install() {
[[ "$*" == '-d -o root -g root -m 755 /run/sshd' ]]
/usr/bin/install -d -m 755 "$test_dir/sshd"
}
prepare_sshd_runtime_directory
[[ -d "$test_dir/sshd" && "$(stat -c %a "$test_dir/sshd")" == 755 ]]
chmod 777 "$test_dir/sshd"
prepare_sshd_runtime_directory
[[ "$(stat -c %a "$test_dir/sshd")" == 755 ]]
# The runtime prerequisite must run before standalone configuration validation.
prepare_line="$(grep -n '^prepare_sshd_runtime_directory$' ochenstarik-server-user-3.sh | cut -d: -f1)"
validate_line="$(grep -n '^sshd -t ' ochenstarik-server-user-3.sh | cut -d: -f1)"
((prepare_line < validate_line))
printf 'Step 3 socket-activation runtime directory tests passed.\n'