From 672467a69942fd4fea13a375655dbb736126f542 Mon Sep 17 00:00:00 2001 From: protokey Date: Tue, 13 Oct 2026 13:22:41 +0400 Subject: [PATCH] feat: MyPointVPN landing on mypointvpn.sokolab.xyz and post about Serverus Landings get per-project themes (params.theme, params.fonts) and optional chat/start/note/engine sections, so they don't look like clones. Co-Authored-By: Claude Opus 5.5 --- assets/css/landing-mypointvpn.css | 124 +++++++++++++++++++ content/blog/mypointvpn-serverus/index.en.md | 83 +++++++++++++ content/blog/mypointvpn-serverus/index.md | 83 +++++++++++++ content/projects/mypointvpn/index.en.md | 91 ++++++++++++++ content/projects/mypointvpn/index.md | 91 ++++++++++++++ layouts/projects/single.html | 70 ++++++++++- 6 files changed, 538 insertions(+), 4 deletions(-) create mode 100644 assets/css/landing-mypointvpn.css create mode 100644 content/blog/mypointvpn-serverus/index.en.md create mode 100644 content/blog/mypointvpn-serverus/index.md create mode 100644 content/projects/mypointvpn/index.en.md create mode 100644 content/projects/mypointvpn/index.md diff --git a/assets/css/landing-mypointvpn.css b/assets/css/landing-mypointvpn.css new file mode 100644 index 0000000..2a343b9 --- /dev/null +++ b/assets/css/landing-mypointvpn.css @@ -0,0 +1,124 @@ +/* MyPoint VPN theme: light paper, emerald accent, split hero with a chat, dark "under the hood" block. */ + +.theme-mypointvpn { + --bg: #f6f4ee; + --bg-alt: #eeebe2; + --surface: #fffdf8; + --border: #ddd8cb; + --text: #16201c; + --muted: #5c6862; + --accent: #0d8a5f; + --accent-strong: #0a6e4c; + --lime: #c8f169; + --ink: #0f1a16; + --radius: 1.1rem; + font-family: 'Manrope', system-ui, sans-serif; +} +.theme-mypointvpn h1, +.theme-mypointvpn h2 { font-family: 'Unbounded', 'Manrope', sans-serif; font-weight: 700; letter-spacing: -.01em; } +.theme-mypointvpn h3 { font-weight: 800; } +.theme-mypointvpn code { background: rgba(13, 138, 95, .1); padding: .05em .35em; border-radius: .35em; font-size: .9em; } + +/* nav */ +.theme-mypointvpn .nav { background: rgba(246, 244, 238, .85); } +.theme-mypointvpn .nav__logo { font-family: 'Unbounded', sans-serif; font-weight: 700; } +.theme-mypointvpn .nav__logo::before { + content: ""; display: inline-block; width: .6rem; height: .6rem; margin-right: .5rem; + border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 .25rem rgba(13, 138, 95, .2); + vertical-align: middle; +} + +/* hero: text left, chat right */ +.theme-mypointvpn .hero { + text-align: left; + background: + radial-gradient(40rem 26rem at 85% 20%, rgba(200, 241, 105, .45), transparent 70%), + var(--bg); +} +.theme-mypointvpn .hero--split .wrap { + display: grid; grid-template-columns: 1.15fr 1fr; gap: 3rem; align-items: center; +} +.theme-mypointvpn .hero__title { margin-inline: 0; max-width: 16ch; font-size: clamp(2rem, 5vw, 3.1rem); line-height: 1.12; } +.theme-mypointvpn .hero__lead { margin-inline: 0; } +.theme-mypointvpn .hero__cta { justify-content: flex-start; } +.theme-mypointvpn .badge { border-color: var(--accent); color: var(--accent-strong); background: var(--surface); } +.theme-mypointvpn .btn { background: var(--surface); border-color: var(--border); color: var(--text); border-radius: 999px; } +.theme-mypointvpn .btn:hover { background: #fff; } +.theme-mypointvpn .btn--primary { background: var(--ink); border-color: var(--ink); color: var(--lime); } +.theme-mypointvpn .btn--primary:hover { background: var(--accent-strong); color: #fff; } +@media (max-width: 820px) { + .theme-mypointvpn .hero--split .wrap { grid-template-columns: 1fr; gap: 2.5rem; } +} + +.chat { + display: flex; flex-direction: column; gap: .55rem; + padding: 1.1rem; border-radius: 1.4rem; + background: var(--ink); color: #e9f1ec; + box-shadow: 0 1.5rem 3rem -1.5rem rgba(15, 26, 22, .5); + transform: rotate(1.2deg); +} +.chat__title { margin: 0 0 .3rem; color: #8fa39a; font-size: .8rem; text-align: center; } +.chat__msg { + margin: 0; max-width: 85%; padding: .55rem .85rem; + border-radius: 1rem; font-size: .92rem; line-height: 1.45; +} +.chat__msg--bot { align-self: flex-start; background: #1d2b25; border-bottom-left-radius: .3rem; } +.chat__msg--me { align-self: flex-end; background: var(--lime); color: var(--ink); border-bottom-right-radius: .3rem; font-weight: 600; } +.theme-mypointvpn .chat code { background: none; padding: 0; font-size: .95em; } +@media (max-width: 820px) { .chat { transform: none; } } + +/* features */ +.theme-mypointvpn .section--alt { background: var(--bg-alt); } +.theme-mypointvpn .card { background: var(--surface); box-shadow: 0 1px 0 var(--border); } +.theme-mypointvpn .card h3 { color: var(--text); } +.theme-mypointvpn .card code { background: rgba(13, 138, 95, .1); } +.theme-mypointvpn .card--link:hover { border-color: var(--accent); } +.theme-mypointvpn .card--link h3 { color: var(--accent-strong); } + +/* get started: big numbered steps */ +.start { + list-style: none; margin: 2rem 0 0; padding: 0; counter-reset: s; + display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: 1rem; +} +.start li { counter-increment: s; position: relative; padding: 4.2rem 1.25rem 1.25rem; border-top: 3px solid var(--ink); } +.start li::before { + content: counter(s, decimal-leading-zero); position: absolute; top: .9rem; left: 1.25rem; + font: 700 2.2rem/1 'Unbounded', sans-serif; color: var(--accent); +} +.start p { margin: .5rem 0 0; color: var(--muted); } + +/* note */ +#note { padding-top: 0; } +.note { + padding: 1.5rem 1.75rem; border-radius: var(--radius); + background: var(--lime); color: var(--ink); +} +.note h2 { font-size: 1.25rem; } +.note p { margin: .6rem 0 0; max-width: 52rem; } +.theme-mypointvpn .note code { background: rgba(15, 26, 22, .1); } + +/* under the hood */ +.engine { background: var(--ink); color: #e9f1ec; } +.engine .section__lead { color: #a9bab2; } +.engine code { background: rgba(200, 241, 105, .12) !important; color: var(--lime); } +.engine__kicker { + margin: 0 0 .5rem; color: var(--lime); + font: 600 .8rem/1 'JetBrains Mono', monospace; text-transform: uppercase; letter-spacing: .12em; +} +.engine__list { + list-style: none; margin: 2rem 0 0; padding: 0; + display: grid; grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr)); gap: .75rem 2rem; +} +.engine__list li { position: relative; padding-left: 1.4rem; color: #d3ddd8; } +.engine__list li::before { content: "›"; position: absolute; left: 0; color: var(--lime); font-weight: 800; } +.engine__callout { + margin-top: 2.5rem; padding: 1.25rem 1.5rem; + border: 1px solid rgba(200, 241, 105, .35); border-radius: var(--radius); + background: rgba(200, 241, 105, .06); +} +.engine__callout h3 { color: var(--lime); font-size: 1.1rem; } +.engine__callout p { margin: .5rem 0 0; color: #d3ddd8; } + +/* community + footer */ +.theme-mypointvpn .grid--4 { grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); } +.theme-mypointvpn .footer { background: var(--bg-alt); } diff --git a/content/blog/mypointvpn-serverus/index.en.md b/content/blog/mypointvpn-serverus/index.en.md new file mode 100644 index 0000000..60b688a --- /dev/null +++ b/content/blog/mypointvpn-serverus/index.en.md @@ -0,0 +1,83 @@ +--- +title: "MyPointVPN: how a VPN bot learned to manage servers" +date: 2026-10-13T12:00:00+04:00 +draft: false +tags: + - telegram + - vpn + - server + - ansible + - docker +summary: "It started as a portfolio bot that installs 3x-ui. Along the way I split out Serverus, a package that manages the whole server. Now the bot lives on a server it manages itself" +--- + +On September 23 I made the first commit to a portfolio project: a Telegram bot that installs and configures the 3x-ui panel by itself, so a person gets their own VPN without any manual fiddling. Three weeks and about 40 commits later, the VPN turned out not to be the most interesting part. + +## How it looks + +You rent a server with Debian 12 or 13 from any hosting provider, get an IP and a root password, and send the bot one line: `ip:password`. The bot moves ssh to a random port with key-only login, turns on a firewall, updates the system and from then on manages the server itself. + +You can take the server back at any time with the "Return" button. The bot removes its access, rolls back ssh and the firewall and sends you a new root password. Your data on the server stays. + +## VPN + +This is what it all started for. The bot installs 3x-ui and sets up: + +- a VLESS + XHTTP + Reality connection on port 443, with the Reality target picked by the bot +- an optional camouflage server: clients connect to it, it forwards traffic to the panel, and the panel's address is never exposed +- a domain with a Let's Encrypt certificate for the panel and the subscription, and without a domain a short-lived IP certificate that renews automatically +- an outbound proxy: your own or Cloudflare WARP +- encrypted DNS on the server: DNS-over-TLS, with DNS-over-HTTPS as a fallback if TLS gets blocked + +The bot issues and revokes client keys and hands out subscription links for v2rayN, Hiddify, Happ and v2rayNG. Keys have no limits: no traffic cap, no expiry, no device limit. For every key you can see the traffic and when the client was last online. + +Once a day the bot checks the panel, Xray and the subscription certificate and tells you if something broke or got fixed. + +## Why no Cloudflare + +At first the bot could work through Cloudflare, and I had to drop it. Cloudflare terminates TLS on its side and opens a new connection to the server. Reality lives right inside the client's TLS handshake, and the server has to see it untouched. Reality can't work behind Cloudflare, so I removed the support. + +## Where the real work is + +When I looked at the code, almost all of the bot's work came down to one thing: take a bare Debian, lock it down and then manage it by key. Roughly what I described in [initial Debian server setup](/en/blog/zero-setup-debian-server/), just without a human. 3x-ui is only one of the tasks on top of that. + +So I moved the server setup code into a separate package, `serverus`. It's an ansible-based library with no Django and no Telegram and a clear programmatic API. The Telegram part also became a separate package. The Django app stayed as the glue with the business logic. + +## Second scenario: a docker server + +With that foundation in place, the next scenario comes together quickly. I built a docker server: + +- the bot installs Docker, Caddy and automatic system updates, and Caddy issues certificates for domains +- an app is a folder with `docker-compose.yaml`, `.env` and `caddy.conf`, sent to the bot as an archive or file by file +- before applying, the bot shows a diff and per-file warnings, and warns you if a domain doesn't point to the server +- while applying, it checks the compose file, busy ports and the Caddy config, and if something is wrong it leaves the app untouched +- only the changed services are recreated +- the "Roll back" button restores the previous version of the files +- there are start, stop, restart and logs per service, login to a private docker registry and guest access to volumes for uploading data + +The example in the repo is a self-hosted RustDesk server. + +## One codebase, different bots + +What the bot can do is decided by the interface selected in the admin panel: "MyPoint VPN", "Serverus" or both. It switches on the fly, without a restart. That's how one codebase makes different bots. + +## The bot hosts itself + +My favorite part. Production MyPointVPN is deployed as a Serverus app on a docker server managed by this very bot. To the bot, its own new version is just another app, like RustDesk. + +## Stack + +Python, Django, PostgreSQL, RabbitMQ + dramatiq (background jobs with a supervisor), ansible, a self-hosted Telegram Bot API server, Docker, Caddy, 3x-ui v3.8.5. + +## Try it and talk about it + +The project has its own page: **[mypointvpn.sokolab.xyz](https://mypointvpn.sokolab.xyz/en/)**. + +- [The bot](https://t.me/barbarisiska_bot) +- [Code](https://git.sokolab.xyz/s0k0l/mypointvpn), with a wiki: deployment, data migration and how a Serverus app is structured +- [Issues in Redmine](https://redmine.sokolab.xyz/projects/mypointvpn) +- [Forum for questions and ideas](https://redmine.sokolab.xyz/boards/2/topics/3), with a guest login, no sign-up needed + +## What's next + +Serverus will most likely become a separate project. I'll write about it in a separate post, and it will get its own landing page. To be continued. diff --git a/content/blog/mypointvpn-serverus/index.md b/content/blog/mypointvpn-serverus/index.md new file mode 100644 index 0000000..b69cf6d --- /dev/null +++ b/content/blog/mypointvpn-serverus/index.md @@ -0,0 +1,83 @@ +--- +title: "MyPointVPN: как бот для VPN научился управлять серверами" +date: 2026-10-13T12:00:00+04:00 +draft: false +tags: + - telegram + - vpn + - server + - ansible + - docker +summary: "Начинал с бота для портфолио, который ставит 3x-ui. По дороге выделил пакет Serverus, который управляет сервером целиком. Теперь бот живет на сервере, которым сам же и управляет" +--- + +23 сентября я сделал первый коммит в проект для портфолио: Телеграм-бот, который сам ставит и настраивает панель 3x-ui, чтобы у человека был свой VPN без ручной возни. Прошло три недели и около 40 коммитов, и VPN оказался не самой интересной частью. + +## Как это выглядит + +Вы арендуете сервер с Debian 12 или 13 у любого хостера, получаете IP и пароль root и присылаете боту одну строку `ip:пароль`. Бот переводит ssh на случайный порт со входом только по ключу, включает фаервол, обновляет систему и дальше управляет сервером сам. + +Сервер можно забрать в любой момент кнопкой «Вернуть». Бот уберет свои доступы, откатит ssh и фаервол и пришлет новый пароль root. Данные на сервере останутся. + +## VPN + +Ради этого все и затевалось. Бот ставит 3x-ui и настраивает: + +- подключение VLESS + XHTTP + Reality на 443 порту, цель для Reality бот подбирает сам +- по желанию сервер маскировки: клиенты подключаются к нему, он пересылает трафик на панель, и адрес панели нигде не светится +- домен с сертификатом Let's Encrypt для панели и подписки, а без домена короткий сертификат на IP, который продлевается автоматически +- прокси на выходе: свой или Cloudflare WARP +- шифрованный DNS на сервере: DNS-over-TLS, а если его режут, запасной DNS-over-HTTPS + +Ключи клиентов бот выпускает и отзывает сам и выдает ссылки на подписку для v2rayN, Hiddify, Happ и v2rayNG. Ограничений на ключи нет: ни по трафику, ни по сроку, ни по устройствам. По каждому ключу видно трафик и когда клиент был онлайн. + +Раз в сутки бот проверяет панель, Xray и сертификат подписки и пишет, если что-то сломалось или починилось. + +## Почему без Cloudflare + +Сначала бот умел работать через Cloudflare, и от этого пришлось отказаться. Cloudflare сам завершает TLS у себя и открывает до сервера новое соединение. А Reality живет как раз внутри TLS-рукопожатия клиента, и сервер должен увидеть его как есть. За Cloudflare Reality работать не может, так что поддержку я убрал. + +## Где на самом деле работа + +Когда я посмотрел на код, оказалось, что почти вся работа бота сводится к одному: взять голый Debian, закрыть его и дальше управлять им по ключу. Примерно то, что я описывал в [первичной настройке сервера](/blog/zero-setup-debian-server/), только без человека. 3x-ui лишь одна из задач поверх этого. + +Поэтому код настройки серверов я выделил в отдельный пакет `serverus`. Это библиотека на ansible без Django и без Telegram с понятным программным API. Телеграм-часть тоже стала отдельным пакетом. Django-приложение осталось связующим звеном с бизнес-логикой. + +## Второй сценарий: docker-сервер + +Когда такая основа есть, следующий сценарий собирается быстро. Я сделал docker-сервер: + +- бот ставит Docker, Caddy и автообновления системы, сертификаты для доменов выпускает Caddy +- приложение это папка с `docker-compose.yaml`, `.env` и `caddy.conf`, ее можно прислать боту архивом или по файлам +- перед применением бот показывает разницу и предупреждения по файлам, а если домен не указывает на сервер, тоже предупредит +- при применении бот проверяет compose, занятость портов и конфиг Caddy, и если что-то не так, приложение не трогает +- пересоздаются только изменившиеся сервисы +- кнопка «откатить» возвращает предыдущую версию файлов +- есть старт, стоп, рестарт и логи по сервисам, логин в приватный docker registry и гостевой доступ к volumes, чтобы залить данные + +В примере к проекту лежит свой сервер RustDesk. + +## Один код, разные боты + +Что умеет бот, решает интерфейс, выбранный в админке: «MyPoint VPN», «Serverus» или оба сразу. Переключается на ходу, без перезапуска. Так из одного кода получаются разные боты. + +## Бот держит сам себя + +Моя любимая часть. Прод MyPointVPN развернут как приложение Serverus на docker-сервере, которым управляет этот же бот. Новая версия бота для него такое же приложение, как RustDesk. + +## Стек + +Python, Django, PostgreSQL, RabbitMQ + dramatiq (фоновые задачи с супервизором), ansible, собственный сервер Telegram Bot API, Docker, Caddy, 3x-ui v3.8.5. + +## Попробовать и обсудить + +У проекта есть своя страница: **[mypointvpn.sokolab.xyz](https://mypointvpn.sokolab.xyz/)**. + +- [Бот](https://t.me/barbarisiska_bot) +- [Код](https://git.sokolab.xyz/s0k0l/mypointvpn), там же вики: деплой, перенос данных и как устроено приложение для Serverus +- [Задачи в Redmine](https://redmine.sokolab.xyz/projects/mypointvpn) +- [Форум для вопросов и идей](https://redmine.sokolab.xyz/boards/2/topics/3), там же гостевой вход, регистрироваться не обязательно + +## Что дальше + +Serverus, скорее всего, станет отдельным проектом. Расскажу о нем отдельной записью, и у него будет свой лендинг. Продолжение следует. diff --git a/content/projects/mypointvpn/index.en.md b/content/projects/mypointvpn/index.en.md new file mode 100644 index 0000000..d3ba0f6 --- /dev/null +++ b/content/projects/mypointvpn/index.en.md @@ -0,0 +1,91 @@ +--- +title: "MyPoint VPN" +tagline: "Your own VPN on your own server in one message" +description: "Rent a server, send the Telegram bot its IP and password, and it locks the server down, installs 3x-ui and sets up VLESS + Reality. Client keys and subscriptions come from the bot too." +domain: "https://mypointvpn.sokolab.xyz/" +badge: "Telegram bot · Debian 12 and 13" +theme: mypointvpn +fonts: "family=Unbounded:wght@500;700&family=Manrope:wght@400;600;800&family=JetBrains+Mono:wght@400;600" +post: "/blog/mypointvpn-serverus" +postLabel: "How a VPN bot learned to manage servers" +build: + list: never +sitemap: + disable: true + +nav: + - { label: "Features", href: "#features" } + - { label: "Get started", href: "#start" } + - { label: "Serverus", href: "#engine" } + - { label: "Community", href: "#community" } + +cta: + - { label: "Open the bot", href: "https://t.me/barbarisiska_bot", primary: true } + - { label: "Get started", href: "#start" } + +chat: + title: "What it looks like in the bot" + messages: + - { from: "me", text: "`203.0.113.7:root-password`" } + - { from: "bot", text: "ssh moved to a random port, key-only login. Firewall on, system updated." } + - { from: "bot", text: "3x-ui installed: VLESS + XHTTP + Reality on port 443." } + - { from: "me", text: "Issue a key" } + - { from: "bot", text: "Done. Here is the subscription link for your client." } + +features: + title: "What you get" + items: + - title: "VLESS + XHTTP + Reality" + text: "A connection on port 443. The bot picks the Reality target itself." + - title: "Camouflage server" + text: "Optional. Clients connect to it, it forwards traffic to the panel, and the panel's address is never exposed." + - title: "Certificates" + text: "A domain with Let's Encrypt for the panel and the subscription. Without a domain the bot issues a short-lived IP certificate and renews it itself." + - title: "Keys without limits" + text: "No traffic cap, no expiry, no device limit. Subscriptions for v2rayN, Hiddify, Happ and v2rayNG. For every key you see the traffic and when the client was last online." + - title: "Proxy and DNS" + text: "Outbound through your own proxy or Cloudflare WARP. DNS on the server is encrypted: DNS-over-TLS, with DNS-over-HTTPS as a fallback if TLS gets blocked." + - title: "Health checks" + text: "Once a day the bot checks the panel, Xray and the subscription certificate and tells you if something broke or got fixed." + +start: + title: "Get started" + steps: + - title: "Rent a server" + text: "Any hosting provider, Debian 12 or 13. All you need is the IP and the root password." + - title: "Send the bot `ip:password`" + text: "The bot moves ssh to a random port with key-only login, turns on a firewall, updates the system and installs 3x-ui." + - title: "Issue a key" + text: "Add the subscription link to your client, and the VPN works." + +note: + title: "The server stays yours" + text: "The \"Return\" button removes the bot's access, rolls back ssh and the firewall and sends you a new root password. Your data on the server stays. And the bot deliberately doesn't work through Cloudflare: Cloudflare terminates TLS itself, and Reality can't work behind it." + +engine: + kicker: "Under the hood" + title: "Serverus" + lead: "Almost all of the bot's work comes down to one thing: take a bare Debian, lock it down and manage it by key. That's the job of `serverus`, an ansible-based package inside the project. The VPN is just one task on top of it, the second one is a docker server." + items: + - "Docker, Caddy and automatic system updates, with Caddy issuing certificates for domains" + - "An app is a folder with `docker-compose.yaml`, `.env` and `caddy.conf`, sent as an archive or file by file" + - "A diff and per-file warnings before applying, including when a domain doesn't point to the server" + - "Checks of the compose file, ports and Caddy config. On error the app is left untouched, and only changed services are recreated" + - "Rollback to the previous version of the files, start, stop, restart and logs per service" + - "Login to a private docker registry and guest access to volumes for uploading data" + callout: + title: "The bot hosts itself" + text: "Production MyPoint VPN runs as a Serverus app on a docker server managed by this very bot. What the bot can do is set by the interface in the admin panel: \"MyPoint VPN\", \"Serverus\" or both." + +community: + title: "Community" + lead: "Questions, ideas and bugs go to Redmine. No sign-up needed: the guest login is in the [forum topic](https://redmine.sokolab.xyz/boards/2/topics/3)." + links: + - { title: "Bot", text: "Start using it in Telegram.", href: "https://t.me/barbarisiska_bot" } + - { title: "Code", text: "Sources and wiki: deployment, data migration, Serverus apps.", href: "https://git.sokolab.xyz/s0k0l/mypointvpn" } + - { title: "Issues", text: "Bugs and wishes in Redmine.", href: "https://redmine.sokolab.xyz/projects/mypointvpn" } + - { title: "Forum", text: "Questions and ideas, plus the guest login.", href: "https://redmine.sokolab.xyz/boards/2/topics/3" } + - { title: "Blog post", text: "How a VPN bot learned to manage servers.", href: "https://sokolab.xyz/en/blog/mypointvpn-serverus/" } + +footer: "Use a VPN within the laws of your country." +--- diff --git a/content/projects/mypointvpn/index.md b/content/projects/mypointvpn/index.md new file mode 100644 index 0000000..bb167ce --- /dev/null +++ b/content/projects/mypointvpn/index.md @@ -0,0 +1,91 @@ +--- +title: "MyPoint VPN" +tagline: "Свой VPN на своем сервере за одно сообщение" +description: "Арендуйте сервер, пришлите Телеграм-боту IP и пароль, и он сам закроет сервер, поставит 3x-ui и настроит VLESS + Reality. Ключи и подписки для клиентов тоже выдает бот." +domain: "https://mypointvpn.sokolab.xyz/" +badge: "Телеграм-бот · Debian 12 и 13" +theme: mypointvpn +fonts: "family=Unbounded:wght@500;700&family=Manrope:wght@400;600;800&family=JetBrains+Mono:wght@400;600" +post: "/blog/mypointvpn-serverus" +postLabel: "Как бот для VPN научился управлять серверами" +build: + list: never +sitemap: + disable: true + +nav: + - { label: "Возможности", href: "#features" } + - { label: "Как начать", href: "#start" } + - { label: "Serverus", href: "#engine" } + - { label: "Сообщество", href: "#community" } + +cta: + - { label: "Открыть бота", href: "https://t.me/barbarisiska_bot", primary: true } + - { label: "Как начать", href: "#start" } + +chat: + title: "Так это выглядит в боте" + messages: + - { from: "me", text: "`203.0.113.7:пароль-root`" } + - { from: "bot", text: "ssh перенесен на случайный порт, вход только по ключу. Фаервол включен, система обновлена." } + - { from: "bot", text: "3x-ui установлена: VLESS + XHTTP + Reality на 443 порту." } + - { from: "me", text: "Выпустить ключ" } + - { from: "bot", text: "Готово. Вот ссылка на подписку для клиента." } + +features: + title: "Что вы получаете" + items: + - title: "VLESS + XHTTP + Reality" + text: "Подключение на 443 порту. Цель для Reality бот подбирает сам." + - title: "Сервер маскировки" + text: "По желанию. Клиенты подключаются к нему, он пересылает трафик на панель, и адрес панели нигде не светится." + - title: "Сертификаты" + text: "Домен с Let's Encrypt для панели и подписки. Без домена бот выпустит короткий сертификат на IP и будет продлевать его сам." + - title: "Ключи без ограничений" + text: "Ни по трафику, ни по сроку, ни по устройствам. Подписки для v2rayN, Hiddify, Happ и v2rayNG. По каждому ключу видно трафик и когда клиент был онлайн." + - title: "Прокси и DNS" + text: "На выходе свой прокси или Cloudflare WARP. DNS на сервере шифруется: DNS-over-TLS, а если его режут, запасной DNS-over-HTTPS." + - title: "Присмотр" + text: "Раз в сутки бот проверяет панель, Xray и сертификат подписки и пишет, если что-то сломалось или починилось." + +start: + title: "Как начать" + steps: + - title: "Арендуйте сервер" + text: "Любой хостер, Debian 12 или 13. Нужны только IP и пароль root." + - title: "Пришлите боту `ip:пароль`" + text: "Бот переведет ssh на случайный порт со входом по ключу, включит фаервол, обновит систему и поставит 3x-ui." + - title: "Выпустите ключ" + text: "Добавьте ссылку на подписку в клиент, и VPN работает." + +note: + title: "Сервер остается вашим" + text: "Кнопка «Вернуть» убирает доступы бота, откатывает ssh и фаервол и присылает новый пароль root. Данные на сервере остаются. А через Cloudflare бот не работает специально: Cloudflare сам завершает TLS, и Reality за ним работать не может." + +engine: + kicker: "Под капотом" + title: "Serverus" + lead: "Почти вся работа бота сводится к одному: взять голый Debian, закрыть его и управлять им по ключу. Этим занимается `serverus`, пакет на ansible внутри проекта. VPN лишь одна из задач поверх него, вторая это docker-сервер." + items: + - "Docker, Caddy и автообновления системы, сертификаты для доменов выпускает Caddy" + - "Приложение это папка с `docker-compose.yaml`, `.env` и `caddy.conf`, архивом или по файлам" + - "Перед применением разница и предупреждения по файлам, в том числе если домен не указывает на сервер" + - "Проверка compose, портов и конфига Caddy. При ошибке приложение не трогается, пересоздаются только изменившиеся сервисы" + - "Откат на предыдущую версию файлов, старт, стоп, рестарт и логи по сервисам" + - "Логин в приватный docker registry и гостевой доступ к volumes для заливки данных" + callout: + title: "Бот держит сам себя" + text: "Прод MyPoint VPN развернут как приложение Serverus на docker-сервере, которым управляет этот же бот. Что умеет бот, решает интерфейс в админке: «MyPoint VPN», «Serverus» или оба сразу." + +community: + title: "Сообщество" + lead: "Вопросы, идеи и баги принимаются в Redmine. Регистрироваться не обязательно: гостевой вход описан в [теме на форуме](https://redmine.sokolab.xyz/boards/2/topics/3)." + links: + - { title: "Бот", text: "Начать пользоваться в Телеграме.", href: "https://t.me/barbarisiska_bot" } + - { title: "Код", text: "Исходники и вики: деплой, перенос данных, приложения Serverus.", href: "https://git.sokolab.xyz/s0k0l/mypointvpn" } + - { title: "Задачи", text: "Баги и пожелания в Redmine.", href: "https://redmine.sokolab.xyz/projects/mypointvpn" } + - { title: "Форум", text: "Вопросы и идеи, там же гостевой вход.", href: "https://redmine.sokolab.xyz/boards/2/topics/3" } + - { title: "Запись в блоге", text: "Как бот для VPN научился управлять серверами.", href: "https://sokolab.xyz/blog/mypointvpn-serverus/" } + +footer: "Используйте VPN в рамках законов вашей страны." +--- diff --git a/layouts/projects/single.html b/layouts/projects/single.html index 646d872..a7fa195 100644 --- a/layouts/projects/single.html +++ b/layouts/projects/single.html @@ -6,7 +6,13 @@ */ -}} {{- $p := .Params -}} {{- $home := cond (eq .Language.Lang hugo.Sites.Default.Language.Lang) "/" (printf "/%s/" .Language.Lang) -}} -{{- $css := resources.Get "css/landing.css" | minify | fingerprint -}} +{{- $css := resources.Get "css/landing.css" -}} +{{- /* params.theme adds css/landing-.css on top of the base styles */ -}} +{{- with $p.theme }}{{ with resources.Get (printf "css/landing-%s.css" .) }} + {{- $css = slice $css . | resources.Concat (printf "css/landing-%s.bundle.css" $p.theme) }} +{{- end }}{{ end -}} +{{- $css = $css | minify | fingerprint -}} +{{- $fonts := $p.fonts | default "family=Inter:wght@400;600;800&family=JetBrains+Mono:wght@400;600" -}} @@ -26,10 +32,10 @@ - + - +
-
+
+
{{ with $p.badge }}

{{ . }}

{{ end }}

{{ $p.tagline }}

{{ .Description }}

{{ range $p.cta }}{{ .label }}{{ end }}
+
+ {{ with $p.chat }} +
+

{{ .title }}

+ {{ range .messages }} +

{{ .text | markdownify }}

+ {{ end }} +
+ {{ end }}
@@ -103,6 +119,52 @@
{{ end }} + {{ with $p.start }} +
+
+

{{ .title }}

+
    + {{ range .steps }} +
  1. +

    {{ .title | markdownify }}

    +

    {{ .text | markdownify }}

    +
  2. + {{ end }} +
+
+
+ {{ end }} + + {{ with $p.note }} +
+
+ +
+
+ {{ end }} + + {{ with $p.engine }} +
+
+ {{ with .kicker }}

{{ . }}

{{ end }} +

{{ .title }}

+

{{ .lead | markdownify }}

+
    + {{ range .items }}
  • {{ . | markdownify }}
  • {{ end }} +
+ {{ with .callout }} +
+

{{ .title }}

+

{{ .text | markdownify }}

+
+ {{ end }} +
+
+ {{ end }} + {{ with $p.limits }}