diff --git a/FORK-CHANGELOG.md b/FORK-CHANGELOG.md
index 92d2914..2b07b43 100644
--- a/FORK-CHANGELOG.md
+++ b/FORK-CHANGELOG.md
@@ -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, ни ссылок в подписке. Список и умолчание взяты из самого установщика
diff --git a/FORK.md b/FORK.md
index 06075f4..26d3c6f 100644
--- a/FORK.md
+++ b/FORK.md
@@ -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` | закрыть прежние порты после того, как новый проверен из второго окна |
diff --git a/scripts/3x-ui-local.sh b/scripts/3x-ui-local.sh
index edb8946..4773bc1 100644
--- a/scripts/3x-ui-local.sh
+++ b/scripts/3x-ui-local.sh
@@ -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 завершился с замечаниями — посмотрите вывод"
diff --git a/scripts/kit-local.sh b/scripts/kit-local.sh
index 0542de4..b08d501 100644
--- a/scripts/kit-local.sh
+++ b/scripts/kit-local.sh
@@ -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 <
+
+
+
+
+$1
+
+
+
+
+
+
+HTML
+}
+
+kl_site_close() { # бренд
+ cat <
+
+
+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" 'PlansNetworkDocs'
+ cat <<'HTML'
+
+
+
Virtual servers that stay out of the way
+
KVM instances in Frankfurt, Amsterdam and Zurich. Hourly billing, snapshots, private networking, and an API for everything the control panel can do.
+
See plans
+
+
+Starter — €4/mo
1 vCPU, 2 GB RAM, 40 GB NVMe, 20 TB traffic.
+Standard — €9/mo
2 vCPU, 4 GB RAM, 80 GB NVMe, 40 TB traffic.
+Compute — €18/mo
4 vCPU, 8 GB RAM, 160 GB NVMe, 100 TB traffic.
+
+
+Network
+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.
+Platform
+
+- Snapshots, weekly backups and one-click rebuilds
+- Private networks, floating IPs and firewalls
+- cloud-init, Terraform provider and REST API
+- Two-factor sign-in and audit log
+
+
+
+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" 'WorkApproachAbout'
+ cat <<'HTML'
+
+
+
Ship on Fridays without holding your breath
+
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.
+
What we do
+
+
+CI/CD
Build pipelines, artefact registries, staging environments and one-command rollbacks.
+Infrastructure
Terraform and Ansible for servers and cloud, with reviews and tests in the pipeline.
+Observability
Metrics, logs and traces wired together, with alerts that people actually trust.
+
+
+How we work
+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.
+
+| Week | What happens |
+| 1–2 | Audit of the current setup, a written report with priorities |
+| 3–6 | Pipelines and infrastructure, pairing with your engineers |
+| 7–8 | Monitoring, runbooks and a handover session |
+
+
+
+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" 'ChecksAlertsPricing'
+ cat <<'HTML'
+
+
+
Know about the outage before your users do
+
HTTP, TCP, ICMP and TLS checks from six locations, host metrics through a lightweight agent, and alerts that reach you in seconds.
+
See checks
+
+
+Availability
HTTP and TCP checks with keyword matching, redirects and status codes.
+Resources
CPU, memory, disk and network of every host on one page.
+Certificates
Expiry warnings 30, 14 and 3 days ahead, with a list of what needs renewing.
+
+
+Alerting
+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.
+Maybe you do not need us yet
+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.
+
+
+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" 'ServicesStackContact'
+ cat <<'HTML'
+
+
+
Software for the part of the business nobody sells
+
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.
+
Services
+
+
+Web services
APIs and admin panels in Python or Go, deployed to your infrastructure.
+Integrations
Accounting, warehouses, CRM and everything that has an API or a database.
+Rescue work
Taking over an unfinished project: audit, plan, tidy up, finish.
+
+
+Stack
+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.
+Team
+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.
+
+
+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" 'ServicesProcessContact'
+ cat <<'HTML'
+
+
+
Find the problems before somebody else does
+
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.
+
Services
+
+
+External test
Everything reachable from the internet: services, TLS, DNS, mail.
+Internal test
What an attacker does after getting a foothold in an office or a VPN.
+Review
Settings, backups, access rights and monitoring — usually the cheapest findings.
+
+
+Process
+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.
+Report
+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.
+
+
+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" 'ServicesSupportContact'
+ cat <<'HTML'
+
+
+
An IT department, without hiring one
+
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.
+
What is included
+
+
+Servers
Updates, monitoring, disk space and certificate renewal handled for you.
+Backups
Nightly copies, offsite storage and a restore test twice a year.
+Workplaces
Accounts, laptops, printers and the printer driver that never works.
+
+
+Support
+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.
+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.
+
+
+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" 'ColocationTransitContact'
+ cat <<'HTML'
+
+
+
Racks, cross-connects and transit
+
Quarter, half and full racks in Frankfurt and Amsterdam, with remote hands, redundant power and a choice of transit providers.
+
Colocation
+
+
+Quarter rack
10 U, 2 kW, 1 Gbps port and one IPv4 prefix.
+Half rack
22 U, 4 kW and a redundant 10 Gbps pair.
+Private cage
From eight racks, with your own access control and monitoring.
+
+
+Transit and peering
+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.
+
+| Port | Committed rate |
+| 1 Gbps | from 50 Mbps |
+| 10 Gbps | from 1 Gbps |
+| 2×10 Gbps | from 2 Gbps, redundant |
+
+
+
+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" 'PostsAbout'
+ cat <<'HTML'
+
+
+
Notes from a server room
+
Things I worked out once and do not want to work out again: nginx, systemd, ZFS, routing and the odd shell trick.
+
Read the posts
+
+
+Replacing a certificate without dropping connections
+Reload, not restart. systemctl reload nginx keeps the listeners open and swaps the configuration, so long downloads and websockets survive. A restart does not.
+Finding what filled the disk
+du -xh --max-depth=1 / | sort -h first, then follow the biggest branch. Journals under /var/log/journal are a common answer; journalctl --vacuum-size=200M trims them.
+Checking that a port is really listening
+ss -ltnp 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.
+Backup is not a copy
+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.
+
+
+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" 'PolicyRestoreContact'
+ cat <<'HTML'
+
+
+
A backup you have never restored is a rumour
+
We copy your servers and databases to a separate location, keep versions, and prove once a quarter that a restore actually works.
+
How it works
+
+
+Nightly copies
Files and databases, encrypted before they leave the server.
+Versions
Hourly for a day, daily for a month, monthly for a year.
+Restore test
A quarterly restore into a clean machine, with a short report.
+
+
+Recovery
+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.
+Encryption keys are kept by you as well as by us, so that neither side alone can read the copies.
+
+
+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" 'InstallConfigReference'
+ cat <<'HTML'
+
+
+
Documentation
+
Installation, configuration and the reference pages. Written for the current release; older versions keep their own section.
+
Start here
+
+
+Installation
+Download the archive, unpack it into /opt/halyard and run the setup script. The script asks three questions and writes a configuration file next to the binary.
+Configuration
+Settings live in /etc/halyard/config.toml. Every option has a default, so a minimal file is often enough. Changes are applied with halyard reload — no restart, no dropped connections.
+
+
+Reference
+
+| Command | What it does |
+halyard status | Shows the running version and the state of each component |
+halyard check | Validates the configuration and the listening ports |
+halyard logs | Follows the log with readable timestamps |
+
+Questions and patches are welcome through the issue tracker; the address is on the contact page.
+
+
+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 "$t" "$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:/// ; состояние: 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