О чем эта статья?
Эта секция написана уже после статьи, чтобы читатель посмотрел, а надо ли ему что-то отсюда или нет, но это забавное приключение (напоминаю, что статьи в форме (б|в)лога, как всегда.
Что будет ниже:
Поиск open source решения для общения голосом, шаринга экрана, включения видео и чатов в режиме peer-to-peer, без лишних бекендов
Запуск этого решения в открытую в github pages
Заворачивание этого решения на приватный сервер
Простенькое закрытие доступа туды через http basic authentication
Заключение с описанием некоторых замечаний и потенцевальных возможностей
Зачем?
В продолжении к первой публикации про старт инди-дев-(б|в)лога, в котором поделился первыми шагами по созданию кроссплатформенного решения, я понял, что мне негде его обсуждать, а значит нужен добротный, как гнедой конь, решение по коммуникации.
Звонки в raidcall, skype, teams, mattermost, google chat, slack, jabber и телеге звучат безумно, особенно, когда нужно быстро подключиться, пошарить экран, а самое главное — не думать о том, что шаришь кому-то телеметрию за проприетарное 3rd-party.
Когда ведешь лекции, особенно бесит, что дискорд блочит screen sharing, когда там юзеров >25, а в гугл мит есть лимит по времени.
Разумеется, читатель имеет право предъявить за проприетарные ОС, браузеры и прочие ПО, либо, что мне просто лень платить, но скорее просто хочется поискать велосипедов, да и поделиться с вами, что накопал и начал юзать.
Как и говорилось ранее, я попытался придумать оправданий для велосипедов, едем-те.
Гуглим какой-нибудь open source
Спустя полчаса поисков, находим какое-то более менее звучащее решение
Надо пойти посмотреть демку - https://chitchatter.im/
Выглядит более чем достаточно, переходя по кнопкам находим, что есть: mardkown-based чат, видео, аудио, шаринг экрана и файлов
Тестим и понимаем, что работает вроде как норм, осталось разобраться что под капотом.
Находим упоминания rtc, а самое главное trystero
import { joinRoom as trysteroJoinRoom, Room, BaseRoomConfig } from 'trystero'
export const joinRoom: typeof trysteroJoinRoom = (
_config: BaseRoomConfig,
_roomId: string
) => {
const room: Room = {
makeAction: () => [() => Promise.resolve([]), () => {}, () => {}],
ping: () => Promise.resolve(0),
leave: () => {},
getPeers: () => ({}),
addStream: () => [Promise.resolve()],
removeStream: () => {},
addTrack: () => [Promise.resolve()],
removeTrack: () => {},
replaceTrack: () => [Promise.resolve()],
onPeerJoin: () => {},
onPeerLeave: () => {},
onPeerStream: () => {},
onPeerTrack: () => {},
}
return room
}
export const selfId = ''
Копаем про этого зверя и находим репу trystero, а с ним и вебсайт, обещающий сделать любое решение мультиплеером — https://oxism.com/trystero/
Читаем и понимаем, что это какая-то шляпо про
Trystero can connect peers via 🌊 BitTorrent, 🐦 Nostr, 📡 MQTT, 🪐 IPFS, and 🔥 Firebase.
Посидел, покурил, не понял, думаю комментаторы лучше объяснят, почему для https://en.wikipedia.org/wiki/Session_Description_Protocol нужен торрент, жду объяснений в комментариях. Учитывая, что все обещают зашифрованный трафик, вроде как звучит норм.
Решение найдено, теперь внедряем
Форк
Просто делаем форк основного репозитория отседа
И идем его настраивать:
Вырубаем ненужные странички (Wikis, Issues, Projects), и идем сразу же конфигурировать все необходимое из секции self-hosted
Включаем удаление веток, оставляем только Rebase flow (почему? пишите нормальные коммиты, не храните PRs дольше 1-2 дней, и будет вам счастье)
Судя по README, нужно сделать две вещи:
Поменять homepage в package.json, чтобы приложение резолвило к себе статику
Указать DEPLOY_KEY (как же бесит постоянна вакханалия вокруг обслуживания ключей в очередном github action
Начну со второго, так как сначала надо проверить, что оно запускается само
Локально генерим ключики
ssh-keygen -t rsa -b 4096 -C "$(git config user.email)" -f gh-pages -N ""
Топаем сюды — settings → deploy keys → add new
Пишем в Title: DEPLOY_KEY
Пишем в Key: значение сгнерированного публичного ключа: cat gh-pages.pub
Ставим галочку возле Allow write access
Потом идем в — secrets → actions → new repository secret, и делаем тоже самое, только вставляя приватный ключ: cat gh-pages
Судя по action-у, оно будет тригерриться только при пуше в main и в github pages, а значит надо создать main ветку и сделать её главной
Топаем в ветки и создаем main
Затем топаем в настройки репозитория и ставим main главной веткой
Дальше пытаемся понять почему не запустились actions и находим, что надо было разрешить actions у форкнутой репы
Соглашаемся и триггерим action для деплоя в gh-pages
Примерно полторы минуты, оно прошло
Справа Code-странице репозитория появляется инфо про github pages
Кликаем туда и находим ссылку на инстанс по клику на иконку возле 1 minute ago
Работать оно работает, вот только статику не отдаёт, но это не важно, так как статика просто не там лежит
Оно ищет под https://the-homeless-god.github.io/assets/index-OD1TD8_t.js
А вот часть github pages оно пропустило в рамках homepage
В любом случае, давайте настроим, чтобы лишний раз убедиться, что всё работает, а вдруг и кому-то достаточно только этого.
Так что 1 раз пушим в мастер редактирование в package.json поля homepage на ссылку на наш github page
Коммитим и ждем как пройдет пайплайн
Помогло? Нет.
Идем в vite.config.js и указываем ему base
Запустилось, потыкался, ок
Пойду открою им PR в README хоть, пусть про base напишут
А теперь мы хотим это тащить на приватный сервер
А почему? А зачем? Оно же итак работает из-под github pages, но вот что важно — а я не хочу чтобы туда кто-то имел доступ
Берите тачку где хотите, мне нравятся ruvds, digitalocean и из самых бюджетных — это justhost.
Берите бубунту (осуждаю, но для статьи на хабре пойдет).
На этой самой бубунте:
Создайте отдельного юзера и папку под нашу статику, ну и права отдайте
adduser ga mkdir -m 755 /var/www/chat chown ga:ga /var/www/chat su -- ga mkdir /home/ga/.ssh/ ssh-keygen -t rsa -b 4096 -C "deployer" -f deployer -N ""
И пишем публичный ключик в
authorized_keys
Поставьте nginx и настройте конфиг смотреть на статику
# устанавливаем nginx apt install nginx # говорим firewall чтоб разрешал ваш nginx, ssh и http/https ufw allow ssh ufw enable ufw allow http ufw allow https ufw allow 'Nginx HTTP'
И редачим конфиг: vim /etc/nginx/nginx.conf
server { access_log /var/log/nginx/access.log; add_header X-Xss-Protection "1; mode=block" always; add_header X-Frame-Options SAMEORIGIN; add_header X-Content-Type-Options nosniff; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; root /var/www/chat; index index.html; location / { try_files $uri $uri/ /index.html; } }
В настройках репозитория добавляем три новых ключа:
SERVER_HOST
- адрес нашего сервакаSERVER_USERNAME
- имя юзераSERVER_SSH_KEY
- содержимое приватного ключа
И дописываем deploy-логику
# тут мы собс-на просто смотрим, что сервер жив и радуемся
- name: agent - get server status
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: neofetch && df -H / && free -m
# тут мы ставим на github action ssh ключ, чтобы можно было пользоваться всякими командами а-ля rsync, scp и прочее
- name: agent - install ssh key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.SERVER_SSH_KEY }}
known_hosts: unnecessary
# тут мы его устанавливаем
- name: agent - install ssh
run: ssh-keyscan -p 22 -H ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts
# подчищаем за собой директорию на сервере
- name: agent - cleanup static folder
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: rm -rf /var/www/chat/**
# деплоим статику на сервер
- name: agent - deploy static
run: ls -la ./dist && rsync -avz -r -e "ssh -p 22" ./dist/ ${{ secrets.SERVER_USERNAME }}@${{ secrets.SERVER_HOST }}:/var/www/chat/
# смотрим, что статика действительно там
- name: agent - show deployed dir
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: ls -la /var/www/chat/
Коммитим это всё и ждём
Прячем за basic http authentication
Подробнее тут
# Ставим либу
apt install apache2-utils
# Генерим пользователя + пароль
htpasswd -c /etc/apache2/.htpasswd ga
# Смотрим, что всё хорошо
cat /etc/apache2/.htpasswd
Дальше в nginx.conf пишем чтобы доступов не было
auth_basic "Administrator’s Area";
auth_basic_user_file /etc/apache2/.htpasswd;
Получится что-то вроде
Перезапускаем Nginx + дописываем себе генерацию серта откуда-нибудь, будь-то Let’s Encrypt
systemctl nginx restart
И теперь когда вы перейдете по ip своего сервера будет окошко с запросом логина и пароля
Остаётся только убирать налету информацию про ip адрес в homepage в package.json и удалить содержимое vite.config.ts про base
Получится примерно так:
Мы просто удаляем строку с помощью sed
, а для подмены содержимого homepage
поля используем встроенные методы и пересобираем
Заключение
Теперь у вас как и у меня есть свой спрятанный на сервере клиент для коммуникации без проприетарного бугага.
Может быть вы что-то накоммуниздите здесь полезного для себя, а может что-то напишите тут на улучшение.
Допустим вы решили коммерциализировать это решение, но оно по лицензии к вам не подходит?
Вы можете написать адаптер который вклинивается в этот код с вашими дописками хоть из-под другого npm пакета, хоть из-под git submodule.
Из ограничений у этого клиента — 256 участников в вашей комнате
Я бы написал как это всё дело скрыть под VPN, но мне лень писать статью дольше чем сейчас, так как просто сидел в дискорде и хотел показать коллегам другие способы коммуницировать и шарить экран, а поэтому это тянет для другой статьи организации демо-энва только для режима разработчиков.
Среди прочего, упустил немногое про бубунту, просто потому что ну не люблю я её, используйте BSD.
А в остальном — ничего нигде не хранится, всё шифруется, да и всё бесплатно, если не нужно приватить доступ к статике. И можно вести лекции не боясь, что качество просядет и общаться с коллегами используя максимальный стриминг.
Спасибо за внимание, надеюсь следующая статейка выйдет раньше чем разница между этой и предыдущей.
Have a fun!