cd ~кейси/server-office.md

кейс 04LIVE · ПРАЦЮЄ У КЛІЄНТА

19 робочих місць під контролем з телефона власника

У власника офісу вже був простий бот, який умів увімкнути й вимкнути сервер. За всім іншим він дзвонив адміну. Я зробив йому Telegram-бота з панеллю Mini App. Тепер він сам бачить, хто працює, закриває вхід, додає людей, перезапускає сервер і отримує попередження. З телефона, без віддаленого робочого стола.

Термін
від заглушки до версії 1.15.0
Стек
PowerShell 5.1 · Telegram Bot API · Telegram Mini App · Cloudflare Tunnel · Hyper-V · PowerShell Direct · RDS · WTS API · WireGuard · PBKDF2 · HMAC-SHA256
Виконавець
Forge Logic
19робочих місць керуються з телефона
0відкритих вхідних портів на сервері
20оновлень, у кожного бекап і скрипт відкату
75 мсна список працівників (було понад 12 с)
3ролі: власник, адмін, менеджер
15 хвживуть у панелі пароль і архів нового працівника
19 робочих місць під контролем з телефона власника1:00
Головний екран: скільки людей зараз на сервері
Стан сервера: процесор, пам'ять, диски, ліцензії, бекап
Керування: перезапуск із відліком

Що боліло

Бот у власника був, але простий: увімкнути, вимкнути, подивитись стан. Решта йшла через адміна. Хто зараз на сервері, як закрити комусь вхід, як додати новенького, як перезапустити. Власник хотів робити це сам і з телефона, без віддаленого робочого стола й консолі.

Що хотів замовник

  • Усе з телефона, і так, щоб розібралась людина без технічної освіти.
  • Кілька людей з різними правами. Сенс у тому, щоб власник адміністрував сам, а не дзвонив адміну.
  • Живлення, блокування і нові працівники тільки з PIN.
  • Блок не має вбивати чужу роботу: сеанс зберігається, людина продовжить з того ж місця.
  • «Видалити» працівника означає відключити. Дані лишаються, доступ можна повернути.

Обмеження

  • Старий бот працює до кінця тестів, і я його не чіпаю.
  • Облікову програму офісу теж не чіпаю. Бот може тільки читати її стан.
  • Жодного стеження за екранами і жодних скриншотів. Це була принципова вимога.
  • Фізичного доступу до офісу не було, все віддалено.
  • Кожна зміна приходить готовим скриптом PowerShell: з бекапом, окремим скриптом відкату й перевіркою синтаксису ще до поставки.

Рішення

Бот живе на тому ж хості, де крутиться віртуальна машина офісу, і керує нею зсередини. Назовні він тільки сам стукає: у Telegram і в тунель. Жодного порту ззовні не відкрито.

Назовні бот лише сам з’єднується — з Telegram і з тунелем. Жоден порт за межу не відкритий.

Телефон власника → тунель → бот на хості Hyper-V → робочий сервер і VPN працівників.
  • PowerShell 5.1, один процес. Він і так є в кожному Windows Server, ставити нічого не треба. Слухає Telegram і віддає панель одночасно, а довгі дії відправляє у фон, щоб бот не зависав.
  • Mini App. По суті вебсторінка, що відкривається всередині Telegram як застосунок. У чаті лишились тільки короткі сповіщення й кнопки «Дозволити / Відхилити».
  • Тунель Cloudflare. Сервер сам тягне з’єднання назовні, і панель отримує HTTPS-адресу. Слухає вона при цьому тільки локальну адресу хоста.
  • Кожен запит із панелі несе криптографічну печатку Telegram (HMAC-SHA256). Без токена бота її не підробиш, тож чужий запит відсікається.
  • PIN зберігається не сам, а його хеш PBKDF2. З хешу PIN не відновити, а PBKDF2 спеціально повільний, щоб перебір втратив сенс. П’ять помилок поспіль, і блок.
  • До віртуалки бот ходить через PowerShell Direct. Це канал між хостом Hyper-V і його віртуальною машиною, який узагалі не йде через мережу. Заходить бот під службовим записом, якому звичайний вхід заборонено. Пароль зашифрований DPAPI, вбудованим шифруванням Windows із прив’язкою до машини.
  • Хто підключений до сервера і скільки часу, бот дізнається з WTS API, вбудованого інтерфейсу Windows. Звідти ж статуси працівників.
  • WireGuard. Через нього підключаються працівники. Бот додає й вимикає ключі на льоту, чужі підключення при цьому не рвуться.
  • Дані бота лежать у JSON-файлах, поруч журнал дій. Окрема база даних під десяток користувачів бота була б зайвою деталлю, яку теж довелось би обслуговувати.
  • Задача планувальника піднімає бота разом із системою і перевіряє кожні 5 хвилин. Другу копію запустити не дасть.

Історія розробки

Від заглушки до версії 1.15.0 бот ріс невеликими оновленнями. Кожне чіпало тільки файли нового бота і мало свій скрипт відкату.

ops(stub)#00

Заглушка поруч зі старим ботом

Розробляти довелось на живому сервері, і робочий бот при цьому зачіпати було не можна. Тому окремий тестовий бот, окрема папка, окрема задача. Старий бот я тільки читаю. Сторож і захист від другої копії стояли з першого дня.

sec(auth)#01

Меню, PIN і ролі

Перша версія жила в чаті: меню, PIN із хешем, видача доступу, журнал дій. Якщо PIN забули, його скидає одноразовий код, який видно тільки на самому сервері.

feat(mini-app)#02

Mini App замість кнопок у чаті

Меню на кнопках у чаті виглядало дешево, це було видно одразу. Переїхав на панель Mini App за погодженим прототипом, зі світлою й темною темою. Спочатку на тимчасовому тунелі з випадковою адресою, потім на постійній.

feat(v1.1)#03

Хто і скільки працює

Щоб бачити, хто і скільки сидить на сервері, у віртуалці з’явився службовий запис без права входу, пароль зашифрований. На виході список з іменами й статусами.

feat(v1.2)#04

Блок входу без втрати роботи

Власнику треба було закривати вхід так, щоб нічия робота не згоріла. Блокується один працівник або всі: на 15 хвилин, годину, добу чи до розпорядження. Сеанс від’єднується, але зберігається, а блок знімається сам.

feat(v1.3)#05

Робочий час

Хто заходив уночі чи в неділю, ніхто не бачив. У панелі з’явився графік роботи. Вхід поза ним приходить власнику запитом «Дозволити / Відхилити». Відмова з PIN від’єднує людину і блокує до ранку.

fix(ui)#06

Екран не влазив у телефон

Головний екран і вибір терміну блоку не вміщались у телефон. Стиснув інтерфейс, тепер обходиться без прокрутки.

feat(v1.4)#07

Новий працівник однією кнопкою

Одна кнопка створює обліковий запис, VPN-ключ і підписаний ярлик підключення (підпис прибирає попередження Windows). Не більше 5 на добу. «Видалити» насправді вимикає вхід і VPN, профіль лишається.

fix(bug)#08

«Не вдалося додати працівника»

Перший же тест упав саме з цим. PowerShell не розрізняє регістр змінних, тож мої $n і $N виявились однією змінною, і номер нового користувача перезаписувався. Перейменував. А щоб таке не повторилось, перед кожною поставкою код тепер автоматично перевіряється на такі збіги.

feat(v1.6)#09

Власник адмініструє сам

Власник захотів адмініструвати сам, включно з живленням. Власник і адмін отримали повні права, живлення під PIN. З’явилась роль «менеджер»: працівники, блоки, графік. Заодно переверстав рядок фільтрів, який доводилось гортати вбік.

feat(v1.7)#10

Одноразові запрошення

Замовник хотів додавати людей прямо з адресної книги. Не вийде: за правилами Telegram бот не може написати першим тому, хто його не запускав. Зробив одноразові посилання з роллю, дійсні 24 години. У базі лежить лише їх хеш, а активне запрошення можна скасувати.

feat(v1.8)#11

Обслуговування без дзвінків адміну

Лишалось кілька дій, за якими все одно дзвонили адміну. Додав зміну пароля, повідомлення всім на екран і перезапуск із попередженням за 1, 5, 10 чи 15 хвилин, який можна скасувати. І окремий екран «Стан сервера»: процесор, пам’ять, диски, бекап, попередження.

fix(perf)#12

Список працівників: з 12 с до 75 мс

Список працівників раптом перестав вкладатись у 12 секунд і падав за тайм-аутом. Винне було блокування Windows, яке перевірялось окремо для кожного користувача. Переписав на один запит для всіх, вийшло 75 мс. Першу перевірку стану відсунув на хвилину після старту, щоб не заважала.

ops(live)#13

Живий тест на справжньому сервері

Усе треба було перевірити на справжньому сервері й не зачепити людей. Через бота створили тестового працівника, на ньому прогнали зміну пароля й розсилку, потім видалили й прибрали за собою. Перезапуск теж пройшов вживу.

feat(v1.9)#14

Відлік до перезапуску

Замовник хотів бачити таймер до перезапуску, потім «перезапускається» і «онлайн». У панелі тепер живий відлік, а в чаті одне повідомлення, яке оновлюється, замість потоку нових. «Стан» і «Керування» переїхали на головний екран.

feat(v1.10)#15

Ліцензії й облікова програма

Ніхто не стежив, коли закінчуються ліцензії віддаленого доступу і чи працює облікова програма. Тепер є лічильник днів і попередження за 30, 14, 7, 3, 1 і 0 днів. Облікову програму бот лише читає і, поки вона стоїть, нагадує щогодини.

fix(ui)#16

Позначка стану вибивалась з рядка

На вузькому екрані позначка стану вилазила за рядок. Склав позначки в один стовпчик і вкоротив підписи.

sec(v1.11)#17

Секрети не в чат

Дві знахідки. Таймер PIN продовжував іти, поки PIN ще перевірявся. А паролі й архіви приходили в чат, де лежать вічно.

Таймер тепер замирає на час перевірки. Секрети живуть 15 хвилин у вкладці «Сповіщення» панелі, а в чат приходить тільки нагадування. Заодно вкладки отримали значки. Перевірено на живому сервері.

ops(release)#18

Заміна старого бота без простою

Новий бот мав замінити старий так, щоб офіс ні хвилини не лишився без керування. Пакет переносу передає токен, переносить доступи й вимикає старий бот, не видаляючи його. Якщо щось не так, відкат вмикає старий назад. Після цього бот передано власнику.

Дрібніші помилки по дорозі

  • return , $list у PowerShell ламав фільтрацію далі по конвеєру, і запрошення та лічильник сповіщень рахувались криво. Тепер результат спершу кладу в змінну.
  • Два стилі панелі мали однакову назву й зламали вигляд таймера. Перейменував.
  • Заборона пересилання в Telegram заблокувала б передачу архіву новому працівнику. Зняв її, захищає 15-хвилинний термін життя.
  • Для інтерфейсу зібрав локальний стенд: справжній код бота працює проти імітації Telegram і сервера. Верстку панелі перевіряв там, до поставки.

Що я виніс для себе

  • Нове ставлю поруч зі старим, а не замість. Тестовий бот дав працювати на живому сервері, і власник жодного дня не лишився без керування.
  • Малі оновлення з відкатом дешевші за одне велике. Баги з додаванням працівника й тайм-аутом списку пішли наступним же оновленням, бо було чітко видно, що саме зламалось.
  • Систему питаю одним запитом, а не в циклі. Різниця: тайм-аут на 12 секундах проти 75 мс.
  • Секрети в чат не відправляю. Чат пам’ятає все назавжди, а панель показує пароль 15 хвилин і забуває.
  • Локальний стенд швидший за живий сервер. Верстку й сценарії видно за секунди, а живий сервер лишається для фінальної перевірки.

Результат

Версія 1.15.0 працює на живому сервері, бот у власника. Вживу перевірено:

  • додавання, відключення й повернення працівника;
  • зміну пароля і розсилку на екрани;
  • перезапуск із відліком і стан сервера;
  • сповіщення і завантаження архіву нового працівника.

До бота є покрокова інструкція для власника.