v1.2.6: 10 ИТ-сайтов-заглушек вместо авторских + умолчание протоколов REALITY и AWG-3.1

This commit is contained in:
2026-10-12 20:30:14 +03:00
parent 1fdc4945f1
commit 445c066791
4 changed files with 466 additions and 11 deletions
+8
View File
@@ -5,6 +5,14 @@
## v1.2.6: вопросы при установке
* Набор по умолчанию — **REALITY + AmneziaWG-3.1** (самый совместимый минимум); ответ «все»
ставит полный набор автора. Умолчание задаётся в одном месте (`DEFAULT_PROTOS`).
* **Сайт-заглушка переделан на ИТ-тематику:** десять правдоподобных сайтов (хостинг, DevOps,
мониторинг, разработка, аудит, ИТ-поддержка, колокейшн, заметки про Linux, бэкапы, документация)
вместо авторских «кофеен и пекарен». Ставится случайный, конкретный — `kit local site 7`,
список — `kit local site --list`. Команда обновляет и файл для nginx, и копию страницы внутри
подключения Hysteria2 (`kit net masq on`): если менять файл вручную, 443/tcp и UDP-порт начнут
отвечать разными страницами — это заметно снаружи.
* Обёртка спрашивает, **какие протоколы ставить** (нумерованный список, `1,3,11` или «все»,
Enter — набор автора): невыбранные протоколы не создаются вообще — ни подключения в панели,
ни записей в nginx, ни ссылок в подписке. Список и умолчание взяты из самого установщика
+5 -2
View File
@@ -9,7 +9,7 @@
1. Если нужен свой домен для маскировки — заранее направьте его A-запись на IP сервера и освободите
порт 80 (иначе Let's Encrypt не выпустит сертификат). Без домена этот шаг пропускается.
2. Одна команда на сервере (от root) — **дальше скрипт спросит сам**: какие протоколы ставить
(нумерованный список: `1,3,11` или «все», Enter — набор автора), какую маскировку и домен,
(Enter — REALITY + AmneziaWG-3.1, «все» — набор автора), какую маскировку и домен,
закрывать ли ответ на ping, включать ли автообновления, менять ли порт SSH (Enter — оставить
текущий, число — перевести на него), менять ли пароль root:
@@ -41,7 +41,8 @@ bash <(curl -fsSL …/scripts/3x-ui-local.sh) --dry-run --domain vpn.example.com
| Флаг | Что делает |
|---|---|
| `--protocols список` | какие протоколы ставить: `all`, `minimal` или через запятую — `reality,reality2,hy2,xhttp,ws,trojan,vmess,ss,tuic,wg,awg,awg3,mtproto`. Невыбранные не создаются вообще. Без флага — вопрос |
| `--protocols список` | какие протоколы ставить: `all`, `minimal` или через запятую — `reality,reality2,hy2,xhttp,ws,trojan,vmess,ss,tuic,wg,awg,awg3,mtproto`. Невыбранные не создаются вообще. Без флага — вопрос, по умолчанию предлагаются **REALITY + AmneziaWG-3.1** (ответ «все» — набор автора) |
| `--site N\|random\|off` | сайт-заглушка: конкретный из нашего набора (10 ИТ-сайтов), случайный (по умолчанию) или не трогать авторскую страницу (то же — `--no-site`) |
| `--yes` | не задавать вопросы (ping закрывается, автообновления включаются, порт SSH и пароль не трогаются) |
| `--harden` | закрыть ответ на ping и включить автообновления без вопросов |
| `--domain домен` | свой домен для маскировки: сайт и REALITY маскируются под него (A-запись на сервер, свободный 80) |
@@ -88,6 +89,8 @@ kit local hardening # сводка по защите сервер
| Команда | Что делает |
|---|---|
| `kit local site` | поставить случайный сайт-заглушку из нашего набора (10 ИТ-сайтов) и синхронизировать ответ Hysteria2 |
| `kit local site --list` / `kit local site 7` / `--show` | список сайтов с номерами / поставить конкретный / какой стоит сейчас |
| `kit local ssh 2222` | добавить порт SSH (прежние продолжают работать; порт сначала открывается в ufw, конфиг проверяется через `sshd -t`) |
| `kit local ssh --status` | какие порты SSH слушаются сейчас |
| `kit local ssh --close-old 2222` | закрыть прежние порты после того, как новый проверен из второго окна |
+46 -9
View File
@@ -9,10 +9,12 @@
# а можно ничего не передавать — тогда он задаст вопросы сам (в том числе про домен);
# 4) спрашивает, какие протоколы ставить: невыбранные не создаются вообще — ни подключения в панели,
# ни записей в nginx, ни ссылок в подписке (за это отвечает флаг автора --protocols);
# 5) спрашивает и делает то, чего у автора нет: закрыть ответ на ping, включить обновления
# 5) ставит сайт-заглушку: случайный правдоподобный ИТ-сайт из нашего набора (10 штук;
# --site N — конкретный, --no-site — не трогать авторскую страницу);
# 6) спрашивает и делает то, чего у автора нет: закрыть ответ на ping, включить обновления
# безопасности, перевести SSH на другой порт, сменить пароль root (поведение из StressOzz/vps-setup,
# но безопаснее);
# 6) в конце: kit fix (время и безопасные починки), kit check, kit local hardening.
# 7) в конце: kit fix (время и безопасные починки), kit check, kit local hardening.
#
# Запуск:
# # одна команда, дальше — вопросы:
@@ -73,7 +75,9 @@ usage() {
--no-harden не трогать ping и автообновления
--ssh-port N перевести SSH на порт N (прежний закроется после проверки входа)
--yes ни о чём не спрашивать (для автоматизации)
--no-time не запускать kit fix (время и безопасные починки)
--site N|random|off сайт-заглушка: конкретный из нашего набора, случайный (по умолчанию) или
не трогать авторскую страницу (то же: --no-site)
--no-time не запускать kit fix (время и безопасные починки)
--no-check не запускать в конце kit check
--verify-only только проверить релиз (подпись и SHA256) и выйти
--dry-run показать план и ничего не делать
@@ -84,6 +88,7 @@ EOF
# ------------------------------------------------------------------ разбор флагов
INSTALLER_ARGS=(); harden=auto; ssh_port=""; fix_time=yes; do_check=yes; dry=no; verify_only=no; assume_yes=no
site=random # заглушка: случайный сайт из нашего набора (off — не трогать)
while (($#)); do
case $1 in
--harden) harden=yes ;;
@@ -91,6 +96,9 @@ while (($#)); do
--ssh-port) ssh_port=${2:-}; shift || die "--ssh-port: нужен номер порта" ;;
--ssh-port=*) ssh_port=${1#*=} ;;
--yes | -y) assume_yes=yes ;;
--site) site=${2:-random}; shift || die "--site: нужен номер сайта (1..10), random или off" ;;
--site=*) site=${1#*=} ;;
--no-site) site=off ;;
--no-time) fix_time=no ;;
--no-check) do_check=no ;;
--verify-only) verify_only=yes ;;
@@ -102,6 +110,11 @@ while (($#)); do
done
[[ -z $ssh_port ]] || { [[ $ssh_port =~ ^[0-9]{1,5}$ ]] && ((10#$ssh_port >= 1024 && 10#$ssh_port <= 65535)); } \
|| die "--ssh-port: нужно число 1024–65535, например 2222"
if [[ $site != off && $site != random ]]; then
# 10 — число сайтов в наборе (scripts/kit-local.sh, KL_SITE_TITLES); добавите сайт — поднимите границу
[[ $site =~ ^[0-9]{1,2}$ ]] && ((10#$site >= 1 && 10#$site <= 10)) \
|| die "--site: random, off или номер 1..10 (список: kit local site --list)"
fi
# Если --protocols задан флагом — не спрашиваем и не дублируем его.
proto_flag=""
@@ -124,11 +137,16 @@ if [[ $dry == yes ]]; then
elif [[ $harden == yes ]]; then pre "защита: закрыть ping и включить автообновления (--harden)"
else pre "защита: пропускаю (--no-harden)"; fi
if [[ -n $proto_flag ]]; then pre "протоколы: ${proto_flag} (задано флагом)"
elif interactive; then pre "протоколы: спрошу (Enter — всё, кроме WireGuard и REALITY-2)"
else pre "протоколы: всё, кроме WireGuard и REALITY-2"; fi
elif interactive; then pre "протоколы: спрошу (Enter — REALITY + AmneziaWG-3.1, «все» — набор автора)"
else pre "протоколы: REALITY + AmneziaWG-3.1 (без терминала вопроса нет)"; fi
if [[ -n $ssh_port ]]; then pre "порт SSH: перевожу на $ssh_port (прежний — после проверки входа)"
elif interactive; then pre "порт SSH: спрошу (Enter — оставить текущий)"; fi
if interactive; then pre "пароль root: спрошу (по умолчанию — не менять)"; fi
case $site in
off) pre "заглушка: оставляю авторскую страницу (--no-site)" ;;
random) pre "заглушка: случайный ИТ-сайт из нашего набора (10 штук)" ;;
*) pre "заглушка: сайт №$site из нашего набора (список: kit local site --list)" ;;
esac
[[ $fix_time == yes ]] && pre "после: kit fix (время, безопасные починки)"
[[ $do_check == yes ]] && pre "проверка: kit check + kit local hardening"
echo
@@ -197,7 +215,10 @@ PROTO_HINT=(
"AmneziaWG-3.1 AmneziaWG с защитой заголовков: AmneziaVPN"
"MTProto прокси для Telegram"
)
DEFAULT_PROTOS=(reality hy2 xhttp ws trojan vmess ss tuic awg awg3 mtproto)
# Наш набор по умолчанию: VLESS REALITY и AmneziaWG-3.1 — самый совместимый минимум.
# Набор автора (флаг --protocols all) доступен ответом «все».
AUTHOR_PROTOS=(reality hy2 xhttp ws trojan vmess ss tuic awg awg3 mtproto)
DEFAULT_PROTOS=(reality awg3)
proto_menu() {
local i n mark
@@ -207,7 +228,8 @@ proto_menu() {
[[ " ${DEFAULT_PROTOS[*]} " == *" ${ALL_PROTOS[i]} "* ]] && mark="*"
printf ' %s %2d. %s\n' "$mark" "$n" "${PROTO_HINT[i]}" >&2
done
printf ' %s\n' "${D}[* — набор по умолчанию (всё, кроме WireGuard и REALITY-2). Невыбранные протоколы не создаются вообще.]${N}" >&2
printf ' %s\n' "${D}[* — по умолчанию: REALITY + AmneziaWG-3.1. «все» — набор автора (всё, кроме WireGuard).${N}" >&2
printf ' %s\n' "${D}[Невыбранные протоколы не создаются вообще: ни подключения, ни записей в nginx, ни ссылок в подписке.]${N}" >&2
}
proto_parse() { # ответ → выбранные имена через запятую; код 1, если не разобрал
@@ -215,7 +237,7 @@ proto_parse() { # ответ → выбранные имена через зап
ans=${ans//[[:space:]]/,} # «1 3 11» и «1,3,11» — одно и то же
[[ -n $ans ]] || { printf '%s' "$(IFS=,; echo "${DEFAULT_PROTOS[*]}")"; return 0; }
case $ans in
all | все | всё) printf '%s' "$(IFS=,; echo "${DEFAULT_PROTOS[*]}")"; return 0 ;;
all | все | всё) printf '%s' "$(IFS=,; echo "${AUTHOR_PROTOS[*]}")"; return 0 ;;
minimal | минимум) printf '%s' "reality"; return 0 ;;
esac
local IFS=','
@@ -238,7 +260,7 @@ ask_protocols() { # печатает итоговый список через з
interactive || { printf '%s' "$(IFS=,; echo "${DEFAULT_PROTOS[*]}")"; return 0; }
proto_menu
for i in 1 2 3; do
printf '\n%sНомера через запятую (например 1,3,11) или «все»; Enter — набор по умолчанию: %s' "$B" "$N" >&2
printf '\n%sНомера через запятую (например 1,12) или «все»; Enter — REALITY + AmneziaWG-3.1: %s' "$B" "$N" >&2
ans=""
read -r -t 300 ans <"$ASK_SRC" 2>/dev/null || true
if picked=$(proto_parse "$ans"); then printf '%s' "$picked"; return 0; fi
@@ -281,6 +303,21 @@ ask_yes() { # "вопрос" y|n — по умолчанию
[[ $ans =~ ^(y|yes|д|да)$ ]]
}
# Сайт-заглушка: у автора это случайные «кофейни и пекарни», у нас — правдоподобный ИТ-сайт.
# Меняем не файл вручную, а командой форка: она же обновляет копию страницы в ответе Hysteria2,
# иначе 443/tcp и UDP-порт начнут отвечать разными страницами (это видно снаружи).
case $site in
off) say "заглушку не трогаю (--no-site): остаётся страница автора" ;;
random)
say "ставлю сайт-заглушку: случайный ИТ-сайт из нашего набора"
kit local site || warn "не удалось поставить заглушку — позже: kit local site"
;;
*)
say "ставлю сайт-заглушку №$site"
kit local site "$site" || warn "не удалось поставить заглушку — позже: kit local site $site"
;;
esac
if [[ $fix_time == yes ]]; then
say "kit fix: синхронизация времени и безопасные починки (авторская команда, ничего необратимого)"
kit fix || warn "kit fix завершился с замечаниями — посмотрите вывод"
+407
View File
@@ -74,6 +74,12 @@ kit_local_help() {
kit local masking состояние маскировки (у REALITY должен быть свой домен)
kit local hardening сводка по защите сервера и что ещё можно включить
Заглушка (сайт на 443 и ответ Hysteria2 — одна и та же страница)
kit local site поставить случайный сайт из нашего набора (10 штук)
kit local site --list список сайтов с номерами
kit local site 7 поставить конкретный
kit local site --show какой сайт стоит сейчас
Защита сервера (поведение взято из StressOzz/vps-setup, сделано безопаснее)
kit local ssh 2222 добавить порт SSH (прежние продолжают работать)
kit local ssh --status какие порты SSH слушаются сейчас
@@ -452,6 +458,406 @@ EOF
return 0
}
# ------------------------------------------------------------------ заглушка: ИТ-сайты (наши)
# Десять правдоподобных ИТ-сайтов: хостинг, DevOps, мониторинг, разработка, безопасность,
# поддержка, сети и колокейшн, заметки про Linux, бэкапы, документация проекта.
# Ставится случайный (kit local site), конкретный — по номеру (kit local site 7).
# Страница отдаётся nginx на 443 при неизвестном SNI и синхронизируется в ответ Hysteria2
# (kit net masq on) — поэтому менять её нужно этой командой, иначе 443/tcp и UDP-порт начнут
# отвечать разными страницами (это заметно снаружи).
KL_SITE_FILE=/var/www/kit/index.html
KL_SITE_TITLES=(
"Nordhost — VPS and cloud hosting"
"Circuit Nine — DevOps consulting"
"Cortex Monitor — server monitoring"
"Bluefin Software — custom development"
"Vantage Security — audits and penetration tests"
"Keystone IT — managed systems support"
"Meridian Networks — colocation and transit"
"Lumen Notes — Linux and networking"
"Evercore Backup — backup and recovery"
"Halyard Docs — project documentation"
)
KL_SITE_TOPICS=(
"VPS-хостинг: тарифы, регионы, платформа"
"DevOps-консалтинг: CI/CD, Kubernetes, наблюдаемость"
"мониторинг серверов: метрики, алерты, статус-страница"
"разработка ПО на заказ: портфолио, стек, процесс"
"аудит и пентест: этапы, отчёт, повторная проверка"
"ИТ-поддержка: серверы, сети, рабочие места"
"колокейшн и транзит: стойки, каналы, BGP"
"заметки про Linux, nginx и сети"
"бэкапы и восстановление: политика, проверки, тесты"
"документация проекта: установка, конфигурация, справка"
)
KL_SITE_CSS=':root{--fg:#1b1f24;--mu:#5b6670;--bd:#e3e6ea;--ac:#1f6feb;--bg:#fff}
*{box-sizing:border-box}
body{margin:0;font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;color:var(--fg);background:var(--bg)}
.wrap{max-width:980px;margin:0 auto;padding:0 20px}
header{border-bottom:1px solid var(--bd)}
header .wrap{display:flex;align-items:center;justify-content:space-between;height:64px}
.logo{font-weight:700;text-decoration:none;color:var(--fg)}
nav a{color:var(--mu);text-decoration:none;margin-left:18px;font-size:15px}
.hero{padding:72px 0 40px}
.hero h1{font-size:40px;line-height:1.15;margin:0 0 16px;letter-spacing:-.02em}
.hero p{font-size:18px;color:var(--mu);max-width:640px;margin:0 0 28px}
.btn{display:inline-block;background:var(--ac);color:#fff;text-decoration:none;padding:12px 22px;border-radius:6px;font-weight:600}
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:20px;padding:8px 0 44px}
.card{border:1px solid var(--bd);border-radius:8px;padding:22px}
.card h3{margin:0 0 8px;font-size:18px}
.card p{margin:0;color:var(--mu)}
.prose{padding:0 0 56px;max-width:780px}
.prose h2{font-size:22px;margin:34px 0 10px}
.prose p,.prose li{color:var(--mu)}
code{background:#f4f6f8;padding:2px 5px;border-radius:4px;font-size:14px}
footer{border-top:1px solid var(--bd);padding:26px 0;color:var(--mu);font-size:14px}
table{border-collapse:collapse;width:100%}
th,td{border-bottom:1px solid var(--bd);text-align:left;padding:10px 8px;font-size:15px}
ul{padding-left:20px}
@media(max-width:600px){.hero{padding:44px 0 28px}.hero h1{font-size:29px}}'
kl_site_open() { # заголовок описание бренд «навигация как HTML»
cat <<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>$1</title>
<meta name="description" content="$2">
<meta name="generator" content="static">
<style>$KL_SITE_CSS</style>
</head>
<body>
<header><div class="wrap"><a class="logo" href="/">$3</a><nav>$4</nav></div></header>
HTML
}
kl_site_close() { # бренд
cat <<HTML
<footer><div class="wrap"><p>© $(date +%Y) $1 · <a href="#">Imprint</a> · <a href="#">Privacy</a> · <a href="#">Status</a></p></div></footer>
</body>
</html>
HTML
}
kl_site_html() { # номер (с нуля) → страница на stdout
case $1 in
0) # хостинг
kl_site_open "Nordhost — VPS and cloud hosting" "Virtual servers on NVMe storage in three European regions. Hourly billing, snapshots, private networks and a full API." "Nordhost" '<a href="#plans">Plans</a><a href="#network">Network</a><a href="#docs">Docs</a>'
cat <<'HTML'
<main>
<section class="hero"><div class="wrap">
<h1>Virtual servers that stay out of the way</h1>
<p>KVM instances in Frankfurt, Amsterdam and Zurich. Hourly billing, snapshots, private networking, and an API for everything the control panel can do.</p>
<a class="btn" href="#plans">See plans</a>
</div></section>
<section id="plans" class="wrap cards">
<div class="card"><h3>Starter — €4/mo</h3><p>1 vCPU, 2 GB RAM, 40 GB NVMe, 20 TB traffic.</p></div>
<div class="card"><h3>Standard — €9/mo</h3><p>2 vCPU, 4 GB RAM, 80 GB NVMe, 40 TB traffic.</p></div>
<div class="card"><h3>Compute — €18/mo</h3><p>4 vCPU, 8 GB RAM, 160 GB NVMe, 100 TB traffic.</p></div>
</section>
<section id="network" class="wrap prose">
<h2>Network</h2>
<p>Every region is connected with redundant 10 GbE uplinks. Traffic inside a region is free, between regions it is billed at cost. IPv6 is included, IPv4 on request.</p>
<h2>Platform</h2>
<ul>
<li>Snapshots, weekly backups and one-click rebuilds</li>
<li>Private networks, floating IPs and firewalls</li>
<li>cloud-init, Terraform provider and REST API</li>
<li>Two-factor sign-in and audit log</li>
</ul>
</section>
</main>
HTML
kl_site_close "Nordhost" ;;
1) # DevOps-консалтинг
kl_site_open "Circuit Nine — DevOps consulting" "We help small teams ship safely: CI/CD, Kubernetes, infrastructure as code and observability." "Circuit Nine" '<a href="#work">Work</a><a href="#approach">Approach</a><a href="#about">About</a>'
cat <<'HTML'
<main>
<section class="hero"><div class="wrap">
<h1>Ship on Fridays without holding your breath</h1>
<p>We set up pipelines, infrastructure and monitoring so that releasing is a boring, repeatable event — and so that on-call does not ruin anyone’s week.</p>
<a class="btn" href="#work">What we do</a>
</div></section>
<section id="work" class="wrap cards">
<div class="card"><h3>CI/CD</h3><p>Build pipelines, artefact registries, staging environments and one-command rollbacks.</p></div>
<div class="card"><h3>Infrastructure</h3><p>Terraform and Ansible for servers and cloud, with reviews and tests in the pipeline.</p></div>
<div class="card"><h3>Observability</h3><p>Metrics, logs and traces wired together, with alerts that people actually trust.</p></div>
</section>
<section id="approach" class="wrap prose">
<h2>How we work</h2>
<p>A two-week discovery, then small changes with a review after each one. Everything lands in your repository and your accounts — no private tooling, no lock-in.</p>
<table>
<tr><th>Week</th><th>What happens</th></tr>
<tr><td>1–2</td><td>Audit of the current setup, a written report with priorities</td></tr>
<tr><td>3–6</td><td>Pipelines and infrastructure, pairing with your engineers</td></tr>
<tr><td>7–8</td><td>Monitoring, runbooks and a handover session</td></tr>
</table>
</section>
</main>
HTML
kl_site_close "Circuit Nine" ;;
2) # мониторинг
kl_site_open "Cortex Monitor — server monitoring" "Uptime, resource and certificate monitoring with alerting to Telegram, e-mail and webhooks." "Cortex Monitor" '<a href="#checks">Checks</a><a href="#alerts">Alerts</a><a href="#pricing">Pricing</a>'
cat <<'HTML'
<main>
<section class="hero"><div class="wrap">
<h1>Know about the outage before your users do</h1>
<p>HTTP, TCP, ICMP and TLS checks from six locations, host metrics through a lightweight agent, and alerts that reach you in seconds.</p>
<a class="btn" href="#checks">See checks</a>
</div></section>
<section id="checks" class="wrap cards">
<div class="card"><h3>Availability</h3><p>HTTP and TCP checks with keyword matching, redirects and status codes.</p></div>
<div class="card"><h3>Resources</h3><p>CPU, memory, disk and network of every host on one page.</p></div>
<div class="card"><h3>Certificates</h3><p>Expiry warnings 30, 14 and 3 days ahead, with a list of what needs renewing.</p></div>
</section>
<section id="alerts" class="wrap prose">
<h2>Alerting</h2>
<p>Alerts are grouped so that a single broken router does not produce forty messages. Quiet hours, maintenance windows and escalation to a second contact are configurable per check.</p>
<h2>Maybe you do not need us yet</h2>
<p>If you run one server, an uptime check and a cron job with certificate renewal will do. Come back when there are more of them than you can remember.</p>
</section>
</main>
HTML
kl_site_close "Cortex Monitor" ;;
3) # разработка ПО
kl_site_open "Bluefin Software — custom development" "A small team building web services, integrations and internal tools for companies that grew out of spreadsheets." "Bluefin Software" '<a href="#services">Services</a><a href="#stack">Stack</a><a href="#contact">Contact</a>'
cat <<'HTML'
<main>
<section class="hero"><div class="wrap">
<h1>Software for the part of the business nobody sells</h1>
<p>Integrations, back-office tools and web services. We take a process that lives in three spreadsheets and one person’s head, and give it a proper home.</p>
<a class="btn" href="#services">Services</a>
</div></section>
<section id="services" class="wrap cards">
<div class="card"><h3>Web services</h3><p>APIs and admin panels in Python or Go, deployed to your infrastructure.</p></div>
<div class="card"><h3>Integrations</h3><p>Accounting, warehouses, CRM and everything that has an API or a database.</p></div>
<div class="card"><h3>Rescue work</h3><p>Taking over an unfinished project: audit, plan, tidy up, finish.</p></div>
</section>
<section id="stack" class="wrap prose">
<h2>Stack</h2>
<p>Python (Django, FastAPI), PostgreSQL, Redis, Docker, and whatever the customer already runs. We keep deployments simple enough that a new colleague can understand them in a day.</p>
<h2>Team</h2>
<p>Four engineers and a part-time designer. We take on three projects at a time so that nobody is queued behind somebody else’s deadline.</p>
</section>
</main>
HTML
kl_site_close "Bluefin Software" ;;
4) # безопасность
kl_site_open "Vantage Security — audits and penetration tests" "External and internal penetration tests, configuration reviews and help with fixing what we find." "Vantage Security" '<a href="#services">Services</a><a href="#process">Process</a><a href="#contact">Contact</a>'
cat <<'HTML'
<main>
<section class="hero"><div class="wrap">
<h1>Find the problems before somebody else does</h1>
<p>Penetration tests and configuration reviews for web services, networks and cloud accounts — with a report your engineers can act on and a retest when the fixes are in.</p>
<a class="btn" href="#services">Services</a>
</div></section>
<section id="services" class="wrap cards">
<div class="card"><h3>External test</h3><p>Everything reachable from the internet: services, TLS, DNS, mail.</p></div>
<div class="card"><h3>Internal test</h3><p>What an attacker does after getting a foothold in an office or a VPN.</p></div>
<div class="card"><h3>Review</h3><p>Settings, backups, access rights and monitoring — usually the cheapest findings.</p></div>
</section>
<section id="process" class="wrap prose">
<h2>Process</h2>
<p>Scope, rules of engagement and a test window are agreed in writing first. We work against a staging copy where possible, and we never touch production data beyond what the scope allows.</p>
<h2>Report</h2>
<p>Findings are ranked by what they let an attacker do, with reproduction steps and concrete fixes. A retest of the fixed issues is included in the price.</p>
</section>
</main>
HTML
kl_site_close "Vantage Security" ;;
5) # ИТ-поддержка
kl_site_open "Keystone IT — managed systems support" "We look after servers, networks, backups and workstations for offices of ten to two hundred people." "Keystone IT" '<a href="#services">Services</a><a href="#support">Support</a><a href="#contact">Contact</a>'
cat <<'HTML'
<main>
<section class="hero"><div class="wrap">
<h1>An IT department, without hiring one</h1>
<p>Monitoring, updates, backups and a real person on the phone. Contracts start at a fixed monthly fee, and you can leave with thirty days’ notice.</p>
<a class="btn" href="#services">What is included</a>
</div></section>
<section id="services" class="wrap cards">
<div class="card"><h3>Servers</h3><p>Updates, monitoring, disk space and certificate renewal handled for you.</p></div>
<div class="card"><h3>Backups</h3><p>Nightly copies, offsite storage and a restore test twice a year.</p></div>
<div class="card"><h3>Workplaces</h3><p>Accounts, laptops, printers and the printer driver that never works.</p></div>
</section>
<section id="support" class="wrap prose">
<h2>Support</h2>
<p>Weekdays from 8 to 19, with an on-call line for outages. Response time for a full outage is one hour, for ordinary requests one working day.</p>
<p>We keep a written inventory of your systems, so that a new engineer — ours or yours — can take over without a two-week interview with the person who built it.</p>
</section>
</main>
HTML
kl_site_close "Keystone IT" ;;
6) # сети и колокейшн
kl_site_open "Meridian Networks — colocation and transit" "Rack space, cross-connects and IP transit in two carrier-neutral facilities." "Meridian Networks" '<a href="#colo">Colocation</a><a href="#transit">Transit</a><a href="#contact">Contact</a>'
cat <<'HTML'
<main>
<section class="hero"><div class="wrap">
<h1>Racks, cross-connects and transit</h1>
<p>Quarter, half and full racks in Frankfurt and Amsterdam, with remote hands, redundant power and a choice of transit providers.</p>
<a class="btn" href="#colo">Colocation</a>
</div></section>
<section id="colo" class="wrap cards">
<div class="card"><h3>Quarter rack</h3><p>10 U, 2 kW, 1 Gbps port and one IPv4 prefix.</p></div>
<div class="card"><h3>Half rack</h3><p>22 U, 4 kW and a redundant 10 Gbps pair.</p></div>
<div class="card"><h3>Private cage</h3><p>From eight racks, with your own access control and monitoring.</p></div>
</section>
<section id="transit" class="wrap prose">
<h2>Transit and peering</h2>
<p>We announce your prefix over two upstreams and the local internet exchange. BGP communities for traffic engineering, RPKI filtering and a looking glass are available to customers.</p>
<table>
<tr><th>Port</th><th>Committed rate</th></tr>
<tr><td>1 Gbps</td><td>from 50 Mbps</td></tr>
<tr><td>10 Gbps</td><td>from 1 Gbps</td></tr>
<tr><td>2×10 Gbps</td><td>from 2 Gbps, redundant</td></tr>
</table>
</section>
</main>
HTML
kl_site_close "Meridian Networks" ;;
7) # блог
kl_site_open "Lumen Notes — Linux and networking" "Short notes about servers, nginx, storage and networks — written while working, mostly for myself." "Lumen Notes" '<a href="#posts">Posts</a><a href="#about">About</a>'
cat <<'HTML'
<main>
<section class="hero"><div class="wrap">
<h1>Notes from a server room</h1>
<p>Things I worked out once and do not want to work out again: nginx, systemd, ZFS, routing and the odd shell trick.</p>
<a class="btn" href="#posts">Read the posts</a>
</div></section>
<section id="posts" class="wrap prose">
<h2>Replacing a certificate without dropping connections</h2>
<p>Reload, not restart. <code>systemctl reload nginx</code> keeps the listeners open and swaps the configuration, so long downloads and websockets survive. A restart does not.</p>
<h2>Finding what filled the disk</h2>
<p><code>du -xh --max-depth=1 / | sort -h</code> first, then follow the biggest branch. Journals under <code>/var/log/journal</code> are a common answer; <code>journalctl --vacuum-size=200M</code> trims them.</p>
<h2>Checking that a port is really listening</h2>
<p><code>ss -ltnp</code> shows TCP listeners and the process behind each one. If something answers on 443 but is not in the list, look for a proxy in front of it.</p>
<h2>Backup is not a copy</h2>
<p>A copy on the same disk protects against nothing but a fat finger. The test that matters is a restore, done on purpose, on a machine that is not the original.</p>
</section>
</main>
HTML
kl_site_close "Lumen Notes" ;;
8) # бэкапы
kl_site_open "Evercore Backup — backup and recovery" "Offsite backups with a documented restore procedure and a restore test every quarter." "Evercore Backup" '<a href="#policy">Policy</a><a href="#restore">Restore</a><a href="#contact">Contact</a>'
cat <<'HTML'
<main>
<section class="hero"><div class="wrap">
<h1>A backup you have never restored is a rumour</h1>
<p>We copy your servers and databases to a separate location, keep versions, and prove once a quarter that a restore actually works.</p>
<a class="btn" href="#policy">How it works</a>
</div></section>
<section id="policy" class="wrap cards">
<div class="card"><h3>Nightly copies</h3><p>Files and databases, encrypted before they leave the server.</p></div>
<div class="card"><h3>Versions</h3><p>Hourly for a day, daily for a month, monthly for a year.</p></div>
<div class="card"><h3>Restore test</h3><p>A quarterly restore into a clean machine, with a short report.</p></div>
</section>
<section id="restore" class="wrap prose">
<h2>Recovery</h2>
<p>A lost file takes minutes: we restore the version you pick. A lost server takes an hour or two: a prepared image plus your latest copy, booted and checked before it goes back into service.</p>
<p>Encryption keys are kept by you as well as by us, so that neither side alone can read the copies.</p>
</section>
</main>
HTML
kl_site_close "Evercore Backup" ;;
9) # документация проекта
kl_site_open "Halyard Docs — project documentation" "Installation, configuration and reference for a self-hosted service: everything in one place." "Halyard Docs" '<a href="#install">Install</a><a href="#config">Config</a><a href="#reference">Reference</a>'
cat <<'HTML'
<main>
<section class="hero"><div class="wrap">
<h1>Documentation</h1>
<p>Installation, configuration and the reference pages. Written for the current release; older versions keep their own section.</p>
<a class="btn" href="#install">Start here</a>
</div></section>
<section id="install" class="wrap prose">
<h2>Installation</h2>
<p>Download the archive, unpack it into <code>/opt/halyard</code> and run the setup script. The script asks three questions and writes a configuration file next to the binary.</p>
<h2>Configuration</h2>
<p>Settings live in <code>/etc/halyard/config.toml</code>. Every option has a default, so a minimal file is often enough. Changes are applied with <code>halyard reload</code> — no restart, no dropped connections.</p>
</section>
<section id="reference" class="wrap prose">
<h2>Reference</h2>
<table>
<tr><th>Command</th><th>What it does</th></tr>
<tr><td><code>halyard status</code></td><td>Shows the running version and the state of each component</td></tr>
<tr><td><code>halyard check</code></td><td>Validates the configuration and the listening ports</td></tr>
<tr><td><code>halyard logs</code></td><td>Follows the log with readable timestamps</td></tr>
</table>
<p>Questions and patches are welcome through the issue tracker; the address is on the contact page.</p>
</section>
</main>
HTML
kl_site_close "Halyard Docs" ;;
*) kl_die "нет такого сайта: $1" ;;
esac
}
kl_site_current() { # какой из наших сайтов стоит сейчас (пусто — не наш)
local i t
[[ -s $KL_SITE_FILE ]] || return 0
for i in "${!KL_SITE_TITLES[@]}"; do
t=${KL_SITE_TITLES[i]}
if grep -qF "<title>$t</title>" "$KL_SITE_FILE" 2>/dev/null; then
printf '%s' "$((i + 1)). $t"
return 0
fi
done
return 0
}
kit_local_site() {
kl_args "$@"
local arg=${KL_REST[0]:-} n cur
case $arg in
--list | list)
kl_say "сайты-заглушки в наборе (ставится случайный)"
local i
for i in "${!KL_SITE_TITLES[@]}"; do
printf ' %2d. %-22s %s\n' "$((i + 1))" "${KL_SITE_TITLES[i]%% — *}" "${KL_SITE_TOPICS[i]}"
done
printf ' %s\n' "поставить конкретный: kit local site 7"
return 0 ;;
--show | show | status)
cur=$(kl_site_current)
if [[ -n $cur ]]; then kl_ok "сейчас стоит наш сайт: $cur"
elif [[ -s $KL_SITE_FILE ]]; then kl_ok "сейчас стоит страница автора (случайная из его наборов)"
else kl_warn "файла $KL_SITE_FILE нет"; fi
return 0 ;;
"" | random | --random) n=$((RANDOM % ${#KL_SITE_TITLES[@]})) ;;
*)
[[ $arg =~ ^[0-9]{1,2}$ ]] && ((10#$arg >= 1 && 10#$arg <= ${#KL_SITE_TITLES[@]})) \
|| { kl_die "kit local site [номер 1..${#KL_SITE_TITLES[@]}|--list|--show]"; return 1; }
n=$((10#$arg - 1)) ;;
esac
if kl_dry; then
kl_say "заглушка: ${KL_SITE_TITLES[n]} (${KL_SITE_TOPICS[n]})"
printf ' (dry-run) записал бы %s и синхронизировал бы ответ Hysteria2 (kit net masq on)\n' "$KL_SITE_FILE"
return 0
fi
kl_need_root || return 1
kl_say "заглушка: ${KL_SITE_TITLES[n]} (${KL_SITE_TOPICS[n]})"
install -d -m 755 /var/www/kit
[[ -f $KL_SITE_FILE ]] && kl_backup "$KL_SITE_FILE"
if ! kl_site_html "$n" >"$KL_SITE_FILE.new"; then
rm -f "$KL_SITE_FILE.new"; kl_die "не удалось собрать страницу"; return 1
fi
chmod 644 "$KL_SITE_FILE.new" && mv "$KL_SITE_FILE.new" "$KL_SITE_FILE" \
|| { rm -f "$KL_SITE_FILE.new"; kl_die "не удалось заменить $KL_SITE_FILE"; return 1; }
kl_ok "страница обновлена: $KL_SITE_FILE ($(wc -c <"$KL_SITE_FILE") байт)"
# Порт 443 отдаёт файл через nginx (перезагрузка не нужна). UDP-порт Hysteria2 держит копию
# страницы внутри подключения — её надо обновить, иначе ответы разойдутся.
if kl_have kit; then
if kit net masq on >/dev/null 2>&1; then
kl_ok "Hysteria2 отвечает той же страницей"
else
kl_warn "Hysteria2 не синхронизирован (может, его нет в этой установке) — проверьте: kit net masq on"
fi
fi
kl_ok "посмотреть снаружи: curl -k https://<IP-или-домен>/ ; состояние: kit local site --show, kit check"
}
kit_local_cmd() {
case ${1:-help} in
help | -h | --help) kit_local_help ;;
@@ -464,6 +870,7 @@ kit_local_cmd() {
passwd | pass) shift; kit_local_passwd "$@" ;;
upgrades | auto) shift; kit_local_upgrades "$@" ;;
hardening | hard) shift; kit_local_hardening "$@" ;;
site | stub | stub-site) shift; kit_local_site "$@" ;;
*)
printf 'Неизвестная команда форка: %s\n' "$1" >&2
kit_local_help >&2