Перейти до вмісту
Внутрішній устрій та нюанси роботи

Внутрішній устрій та нюанси роботи

Ця сторінка збирає операційні деталі, які не вписуються природно в інші сторінки - як саме працює пошук конфіг-файлу, що саме робить (і чого не робить) кожна команда життєвого циклу акаунта в signal2sip-gendb, і власні внутрішні таймери демона. Для адмінів, які хочуть точно знати, що відбувається під капотом, а не лише як почати.

Пошук конфіг-файлу

І signal2sip-daemon, і signal2sip-gendb шукають свій конфіг-файл однаково, у такому порядку:

  1. Явний шлях, переданий першим аргументом.
  2. /etc/signal2sip/signal2sip.conf, якщо він існує.
  3. ./signal2sip.conf (відносно поточної робочої директорії) - запасний варіант для dev-checkout’у, не для реального встановленого розгортання.

Кореневий CA-сертифікат Signal, який пінить кожен нативний TLS-клієнт у цьому проєкті, шукається за тим самим принципом: /etc/signal2sip/certs/signal-root-ca.pem, якщо він є, інакше ./certs/signal-root-ca.pem.

Що де живе: файл [global] проти бази даних на акаунт

У конфіг-файлі завжди лише одна секція [global] - невелика кількість загальних для процесу налаштувань. Все інше живе в SQLCipher-базі даних, на яку вказує сам [global] (db_path):

Ключ [global]За замовчуваннямЩо контролює
db_path / db_key(обов’язково)Єдиний файл бази даних SQLCipher, у якому живуть дані всіх акаунтів, і пароль до нього.
sip_reg_watchdog_sec60Скільки часу SIP-акаунт може лишатися незареєстрованим, перш ніж демон сам форсує нову спробу реєстрації (власний авто-retry PJSIP покриває не всі коди помилок, наприклад не 403).
resolved_contact_ttl_sec86400 (1 доба)Скільки часу кешований результат резолву e164→ACI/PNI (з реального запиту Contact Discovery) вважається дійсним, перш ніж резолвити наново.
storage_sync_interval_sec43200 (12 год)Як часто пов’язаний (linked) акаунт (той, що має справжній доступ до синхронізації контактів Signal) заново забирає список контактів із StorageService Signal.
config_poll_interval_sec30Як часто демон перечитує конфігурацію кожного акаунта з бази даних як запасний варіант, незалежно від SIGHUP (див. нижче).
on_account_error_cmd(порожньо = вимкнено)Опційна shell-команда, яка запускається, коли акаунт натрапляє на відому проблему, яку неможливо вирішити без дії адміністратора (сьогодні: Signal відхиляє облікові дані цього пристрою), або відновлюється після неї - див. PIN Signal і Registration Lock, чому це може статися навіть без проблеми на боці SIP.

Все інше - sip_host, sip_extension, sip_password, sip_transport, sip_srtp, sip_bridge_destination/sip_bridge_did, прапорець enabled, і решта - живе в таблиці account бази даних, по одному рядку на акаунт, і редагується через signal2sip-gendb <name> config set/get/list або enable/disable, ніколи вручну через файл.

Один неправильно налаштований акаунт не блокує інші

Кожен акаунт піднімається незалежно, як при старті, так і при кожному живому перезавантаженні - помилка в шляху sip_tls_ca_file, недоступна АТС чи будь-яка інша проблема на рівні одного акаунта заважає лише цьому акаунту, і ніколи жодному іншому:

  • Уся послідовність запуску одного акаунта виконується всередині одного try/catch - при будь-якій помилці демон пише account setup failed: ... - skipping this account, continuing with the rest і переходить до наступного. Акаунт узагалі без налаштованого SIP (тільки Signal) абсолютно не постраждає, якщо в іншого акаунта зламаний транк до АТС.
  • SIP/АТС-сторона має власний додатковий рівень захисту: неправильний sip_tls_ca_file (шлях до файлу, якого не існує або який неможливо прочитати) заваляє лише SIP-транспорт цього одного акаунта (у логах - TLS transport setup failed: ...) - його Signal-сторона, якщо вже підключена, продовжує працювати незалежно від цього.

Це не допоможе лише з ресурсом, який усі акаунти використовують однаково - наприклад, неправильний шлях до кореневого CA-сертифіката Signal (налаштування секції [global], не окремого акаунта) зламає всі акаунти однаково, бо тут просто немає різниці “хороший акаунт / поганий акаунт”, яку можна було б провести. Це інший тип збою, ніж помилка в одному акаунті, і сама лише ізоляція на рівні акаунта тут не допоможе.

Як зміна конфігурації насправді доходить до запущеного демона

Два незалежні механізми, спрацьовує той, що першим:

  • SIGHUP - gendb config set/enable/disable надсилають запущеному демону SIGHUP на основі “найкращого зусилля” (читають його pidfile, перевіряють, що /proc/<pid>/exe дійсно веде до справжнього бінарника signal2sip-daemon, перш ніж сигналити - застарілий або перевикористаний PID мовчки пропускається, сигнал не надсилається). Майже миттєво, коли спрацьовує.
  • Запасний опитувальний цикл - незалежно від того, чи дійшов SIGHUP до демона (демон на той момент не запущений, застарілий pidfile, проблема з правами), демон сам перечитує [global] плюс конфігурацію кожного увімкненого акаунта кожні config_poll_interval_sec (за замовчуванням 30с). Це реальна гарантія; SIGHUP - лише швидкий шлях.

Обидва шляхи запускають абсолютно ту саму логіку перезавантаження: порівняти щойно завантажену конфігурацію з тим, що зараз працює - акаунти, яких більше немає (вимкнені або видалені), демонтуються, нові або щойно увімкнені - піднімаються, а вже запущений акаунт, чий config_version змінився (автоматично збільшується кожним config set), демонтується і збирається наново з новими налаштуваннями. Акаунт, якого зміна не стосується, продовжує працювати без жодних змін - жодні дзвінки чи реєстрація інших акаунтів не зачіпаються.

Головний цикл демона: незалежні таймери, а не один спільний тік

Навмисно розділені, кожен зі своєї причини:

  • Опитування конфігурації (config_poll_interval_sec, за замовчуванням 30с) - див. вище.
  • Вотчдог SIP-реєстрації (sip_reg_watchdog_sec, за замовчуванням 60с) - на кожен акаунт, форсує нову спробу реєстрації, якщо акаунт незареєстрований довше цього часу, покриваючи коди помилок, які власний авто-retry PJSIP не охоплює. Див. Двосторонній зв’язок про окремий вотчдог, що керує станом SIP залежно від з’єднання Signal і співпрацює з цим.
  • Кеш резолву контактів (resolved_contact_ttl_sec, за замовчуванням 1 доба) - контролює, як довго кешований результат Contact Discovery для цілі вихідного дзвінка використовується повторно, перш ніж запит повториться. Навмисно тривалий, бо реальні запити CDS обмежені по частоті на акаунт.
  • Пересинхронізація контактів через StorageService (storage_sync_interval_sec, за замовчуванням 12 год) - на кожен пов’язаний (linked) акаунт, заново забирає реальний список контактів із серверів Signal. Виконується синхронно в тому самому циклі, що й усе інше (у головному циклі цього демона нічого не асинхронне) - повільний мережевий round-trip тут може затримати інші перевірки циклу максимум приблизно на 90с, але лише раз на акаунт за інтервал, а не на кожному тіку.

Життєвий цикл акаунта в signal2sip-gendb, точно

Точний ефект кожної команди життєвого циклу - що змінюється локально (у базі даних), а що на реальних серверах Signal, і чи це оборотно:

КомандаОборотно?Локальна база данихСервери Signal
register --e164 <e164> sms|voice-створює рядок акаунтапочинає справжню сесію реєстрації з підтвердженням SMS/голосом (Flow A)
verify <code>-заповнює справжню ідентичність акаунта (ACI/PNI/ключі)завершує реєстрацію
link-створює рядок акаунталінкує як вторинний пристрій через QR-код (Flow B)
deactivate (псевдонім: unregister)Так (reactivate)не зачіпаєтьсяперемикає fetchesMessages=false - відправники не можуть достукатися до цього номера, більше нічого не змінюється
reactivate-не зачіпаєтьсяперемикає fetchesMessages=true назад
enable / disableТак (одне іншим)перемикає лише прапорець enabled у рядкуне зачіпається - вимкнений акаунт демон просто не завантажує
unlinkНіповністю стирає рядок цього акаунтане зачіпається - реальний акаунт Signal (і його номер) абсолютно не постраждав
delete-accountНістирається, але лише при підтвердженій успішній відповіді - див. нижчеDELETE /v1/accounts/me - знищує акаунт і кожен пов’язаний пристрій, звільняє номер для будь-чиєї нової реєстрації

Дві речі варто підкреслити явно:

  • deactivate - безпечна, оборотна команда - суто серверна зміна прапорця, локальні дані ніколи не зачіпаються, і reactivate повністю це скасовує. unlink і delete-account - ось команди, які дійсно видаляють локальні дані. Стара назва unregister досі працює як псевдонім.
  • Відповідь сервера на delete-account може бути справді неоднозначною. Власний протокол Signal документує, що цей ендпоінт іноді завершується через закриття WebSocket (код 4401) замість звичайної HTTP-відповіді - signal2sip ще не розшифровує цей код закриття, тож з’єднання, що закрилося до отримання будь-якої відповіді, невідрізнюване від звичайного мережевого збою. У такому неоднозначному випадку локальні дані навмисно залишаються недоторканими, замість того щоб вгадувати - перевірте вручну (наприклад, спробуйте зареєструвати той самий e164 наново десь-інде), перш ніж самостійно запускати unlink, щоб очистити локальний рядок.

Сигналізація адміну про проблеми з акаунтом

На відміну від звичайного падіння SIP-реєстрації (яку демон просто сам ретраїть, суто з пам’яті), деякі збої на боці Signal неможливо відновити без людини: сьогодні це означає спрацювання AuthSocket::isDeauthorized() (акаунт відв’язали/видалили десь-інде, або - коли з’явиться підтримка Registration Lock, див. PIN Signal і Registration Lock - спробу захоплення коректно відхилено через відсутність правильного PIN, що також заморожує облікові дані справжнього власника як побічний ефект). Демон припиняє ретраїти саме цей акаунт і потребує signal2sip-gendb <name> link (або unlink, а потім link) з наступним рестартом.

Три незалежні, взаємодоповнювальні способи, якими це доходить до адміністратора:

  1. account.last_error/last_error_at - записується в базу даних у момент, коли це стається, і автоматично очищується наступного разу, коли акаунт успішно підключається (завжди після рестарту, оскільки демон припиняє ретраїти в межах процесу). Видно через signal2sip-gendb list (показується як суфікс ERROR (since ...)) і в списку акаунтів TUI (червоний статус “problem”) - обидва раніше не бачили цього, оскільки воно існувало лише в пам’яті запущеного демона.

  2. Рядок логу, видимий через journalctl -p err - власне повідомлення демона в stderr позначене префіксом рівня syslog systemd <3> (LOG_ERR), тож його можна грепати/використовувати для алертів через journald без жодного додаткового налаштування (запакований systemd-юніт вже надсилає stderr у журнал).

  3. on_account_error_cmd (див. таблицю конфігурації вище) - опційний shell-хук, щоб підключити це до будь-якого реального каналу сповіщень (пошта, бот Telegram/ntfy.sh, вебхук Slack - signal2sip свідомо не обирає його за вас). Викликається як /bin/sh -c '<ваша команда>' sh <ім'я-акаунта> <e164> <тип-помилки> - ці три значення приходять як $1/$2/$3 всередині вашої команди, наприклад:

    on_account_error_cmd=curl -s -d "signal2sip: $1 ($2) is $3" ntfy.sh/your-topic

    <тип-помилки> наразі - deauthorized або recovered. Fire-and- forget - демон ніколи не чекає на неї і не перевіряє код завершення, тож повільний або зависаючий скрипт не може загальмувати обробку жодного іншого акаунта.

Перевірки прав та привілеїв при старті

signal2sip-daemon, signal2sip-gendb і signal2sip-tui всі відмовляються стартувати замість того, щоб працювати з більшим доступом, ніж їм реально потрібно:

  • Під root - явна, гучна помилка, а не тихе скидання привілеїв. Жодному з них root не потрібен: лише вихідні з’єднання, жодних привілейованих портів, немає причин довіряти процесу, що працює зі справжніми ключами Signal, більшим доступом, ніж решті системи.
  • Конфіг-файл належить іншому користувачу, або права не рівно 0600 - він містить пароль до бази даних SQLCipher у відкритому вигляді, тож будь-що слабше за доступ лише власника на читання/запис відхиляється одразу, з точним рецептом виправлення (chmod 600 <шлях>) прямо в тексті помилки.
  • Файл бази даних належить іншому користувачу - та сама перевірка власника, без конкретної вимоги до прав доступу (він і так зашифрований, тож файл із шифротекстом, доступний для читання групі,
    • цілком підтримуваний вибір для деяких розгортань; тут має значення лише власник).

Обидві перевірки виконуються до того, як вміст файлу взагалі читається, і працюють однаково незалежно від того, чи файл вже існував, чи щойно створюється вперше (свіжий gendb register/link на новому розгортанні одразу пише власний бутстрапнутий конфіг з правами 0600).

Перевірено наживо: протестовано на реальному розгортанні - виділений непривілейований користувач signal2sip запускає демон через власний systemd-юніт, і кожна комбінація неправильного власника/прав (конфіг, що належить root, 0644 замість 0600, база даних, що належить root) підтверджено відхиляється з конкретною, дієвою помилкою вище, перш ніж все запрацювало з правильним налаштуванням.