Satont.
301 subscribers
321 photos
52 videos
1 file
202 links
Download Telegram
Тем, кто держит свой сайт — рекомендую поставить его за Cloudflare, и озаботиться фильтрациями.

Сейчас всякие AI боты активно скрапят интернет, тем самым они могут создавать вам непредвиденную нагрузку.
Как от этого защититься:
- Паркуем домен на cloudflare.
- Во вкладке`DNS` указываем нужные нам записи A, AAAA, CNAME и так далее. Подробнее как это сделать — в интернете погуглите.
- После этого открываем вкладку Security -> Bots, и там включаем Block AI Bots, а так же Bot Fight Mode, если хотите всякие другие странные автоматические скраперы заблочить.

После этого на вкладке Security -> Events можете наблюдать (см. скриншот), как всякие Claude, GPT и т.д пытаются безуспешно считать ваш сайт.
Ну и всякие скрипты, ищущие уязвимости по типу /admin/login.php тоже отпадут.
👍161
А я для вас сегодня с приколами под го.

Знаете zod? Ну такая библеотечка с декларативным описанием схемы.

Есть отличная альтернатива под го: https://zog.dev/getting-started

Usage валидации очень прост:


import (
z "github.com/Oudwins/zog"
)


type User struct {
Name string `zog:"firstname"` // tag is optional. If not set zog will check for "name" field in the input data
Age int
}

var userSchema = z.Struct(z.Schema{
// its very important that schema keys like "name" match the struct field name NOT the input data
"name": z.String().Min(3, z.Message("Override default message")).Max(10),
"age": z.Int().GT(18).Required(z.Message("is required")),
})


func main() {
u := User{
Name: "Zog",
Age: 1,
}
errsMap := schema.Validate(&u)
if errsMap != nil {
// handle errors -> see Errors section
}
}


Или же err := userSchema.Parse(zhttp.Request(r), &user), что ещё больше сокращает, сначала спарсив реквест, потом прокинув в валидацию.

Посмотрите, поиграйтесь, это многим лучше go-validate, который очень пополярный и до жути не удобный.
🔥5
Я к вам с небольшим performance дайджестом по поводу Redis.

У меня на сервере Redis стабильно 100% потреблял, и вот что я понял:

Существует команда KEYS, чтобы получить ключи из редиса по определённому паттерну. Например мне в твире нужно тянуть ключи из редиса на сообщение в чате, чтобы вытащить все смайлики канала, коих может быть больше одной тысячи. Нужно это для модерации кол-ва смайликов, tts (не читать смайлки), и для того чтобы записывать статистику использования смайликов.

Но знаете ли вы, что KEYS это антипаттерн? KEYS читает весь ваш кейспейс, то есть как я понимаю проверяет все ключи. Если у вас их сотни тысяч, миллионы = получаете выстрел в ногу по CPU нагрузке, так как это большая CPU bound таска.

Что же делать? — Использовать SCAN!

Со сканом всё хорошо, в голанге вы можете использовать конструкцию вида
iter := rdb.Scan(ctx, 0, "prefix:*", 0).Iterator()
for iter.Next(ctx) {
fmt.Println("keys", iter.Val())
}
if err := iter.Err(); err != nil {
panic(err)
}


И это будет работать хорошо. С малюсеньким НО — это плохо работает в dragonflydb (форк редиса), и скорее всего плохо работает во всех форках редиса.
Тут я конкретного решения подсказать не могу, но у себя я просто применил немного другой алгоритм: вместо скана всех ключей, я делаю strings.Fields(text) по входной строке, и по каждой части просто делаю redis.Exists команду, которая на порядок быстрее.

                       emotes := make(map[string]int)
splittedMsg := strings.Fields(msg.Message.Text)
for _, part := range splittedMsg {
// do not make redis requests if emote already present in map
if emote, ok := emotes[part]; ok {
emotes[part] = emote + 1
continue
}

if exists, _ := c.redis.Exists(
ctx,
fmt.Sprintf("emotes:channel:%s:%s", msg.BroadcasterUserId, part),
).Result(); exists == 1 {
emotes[part] += 1
continue
}

if exists, _ := c.redis.Exists(
ctx,
fmt.Sprintf("emotes:global:%s", part),
).Result(); exists == 1 {
emotes[part] += 1
continue
}
}


Да, такой workaround потенциально может породить 250 запросов к редису, потому как на твиче ограничение в 500 символов, и если написать сообщение по одной букве с пробелами, у нас получится 250 частей. Но это всё ещё лучше SCAN и уж тем более KEYS.

Будте аккуратны с использованием таких вещей.

Так же рекоммендую вам использовать пайплайны на мутирующие операции, которые помогут вам избежать ненужных network-trip операций. То есть одним запросом вы можете сделать N SET, N EXPIRE и т.д операций.

А что скажите вы, есть ли у вас какие-то performance фишки?

P.S
Пришёл я к этому всему с помощью pprof гошки, проанализировав flame graph моего процесса. Не забывайте профилировать ваши приложения (по надобности).
👍101🥴1🦄1
Переписал Twir на Bun вместо Node

Bun это альтернативный js рантайм, который имеет кучу built-in апишек, написаных на языке zig. Апишки эти работают на порядок быстрее, чем в ноде, и их просто больше.
Для примера, из встроенного и написанного на zig есть:
- S3 client
- PostgreSQL client
- SQLite client
- zx альтернатива - $, чтобы удобнее было писать кроссплатформенные скриптики на js
- brypt/hash/argon2/sha/md
- Прикольный парсер семвера semver.satisfies("1.0.0", "^1.0.0");
- Парсеры цветов в hex, rgb, css, hsl
- Встроенный Glob, конечно же супер быстрый

Мне особенно подошло Single File Executable, когда вы можете скомпилить ваше приложение в бинарь вместе с рантаймом и зависимости. Я так деплою теперь свои приложения через докер, считай тоже самое, что Go.
Вот пример внимание, ПОЛНОГО докерфайла для bun бинаря:
FROM gcr.io/distroless/cc AS base  
COPY apps/eval/.out/twir-eval /app/twir-eval
ENV NODE_ENV=production
CMD ["/app/twir-eval"]

Да, тут я немного читерю, потому что установка зависимостей и собственно сборка бинарного файла у меня происходит в CI, но всё же.
Вот кстати команда для билда бинарного файла: bun build --compile --minify --sourcemap src/index.ts --outfile=.out/twir-eval

Пакетный менеджер довольно таки быстрый, хоть пока и не особо богатый. Но мои потребности монорепозитория он закрыл. Патчинг есть, воркспейсы есть, lifecycle хуки есть, пока большего не надо мне, но это не все его фичи. Почитаете сами, если интересно будет.

Теперь я не использую pre-транспайлинг кода вообще, то есть мне не нужен pre-build для например каких-то шейреных либ, ведь TypeScript код можно импортировать и запускать из коробки, имея адекватный watch. То есть я запускаю приложение, которые зависит от библеотечки, меняю код в библеоетчке - и приложение перезапускается само.
Фронтэнд бандлеры ведут себя идентичным образом, потому даже фронтэнд библеотеки мне не нужно прибилдить.

Я боялся, что у меня nuxt не заведётся, но... Знаете же, что nuxt под собой юзает nitro для большинства серверных магий? Так вот, у этого nitro есть поддержка bun прямо из коробки!
Просто воткнул в nuxt.config.ts данный конфиг и полетели!
nitro: {  
preset: 'bun'
}

Что с Vite, что с Nitro - никаких проблем не наблюдается.

bun --env-file=../../.env src/index.ts
Так же прикольно, что есть встроенный dotenv, но это уже мелочи. У ноды тоже уже есть вроде как.

В общем и целом мне зашло, продолжу использовать и ждать новых крутых фич.
А то всякие node медленно развиваются из-за того, что боятся ломать обратные совместимости. Тут такой проблемы нет, потому что не с чем совмещать. :)
Хотя они очень усиленно работают над совместимостью с нодой, у меня мои приложения завелись без изменений. GRPC, vm, knex, redis — работают.
👍9🔥31😎1
Satont. pinned «Переписал Twir на Bun вместо Node Bun это альтернативный js рантайм, который имеет кучу built-in апишек, написаных на языке zig. Апишки эти работают на порядок быстрее, чем в ноде, и их просто больше. Для примера, из встроенного и написанного на zig есть:…»
Планирую расширять ресурсы твира, и перекинуть всё на hetzner. Напоминаю, сейчас меня хостят бесплатно, один сервак всего. Докинуть ресурсов не могу. А ресурсы порой кончаются, например сейчас в пике может 12гб оперативки потребляться из 16ти. В какой-то скорый момент они кончатся.

Буду брать серваки на hetzner, горизонтальное масштабирование, в целом быстрее cpu, и вот это всё.
Буду признателен вам за подписки, или донаты копеечкой.
Если хотите чтобы я вас в ответ тоже отблагодарил, можете в лс писать. Могу предоставить vpn, какие-то фишки в твире, которые вы хотите (в силу возможностей). Жопу не покажу.

https://boosty.to/yakui
USDT TRC20: TVTagN51Wtykm8nXVthTJXStbNAAgYCmgp
USDC Polygon PoS: 0xbdde3a059458468371b613d113c15a97499950fb
Ethereum ERC20: 0xbdde3a059458468371b613d113c15a97499950fb
Bybit UID: 424720268
CARD RUB: 2202 2061 3958 1651
9👍1🔥1
Satont. pinned «Планирую расширять ресурсы твира, и перекинуть всё на hetzner. Напоминаю, сейчас меня хостят бесплатно, один сервак всего. Докинуть ресурсов не могу. А ресурсы порой кончаются, например сейчас в пике может 12гб оперативки потребляться из 16ти. В какой-то скорый…»
Satont.
Планирую расширять ресурсы твира, и перекинуть всё на hetzner. Напоминаю, сейчас меня хостят бесплатно, один сервак всего. Докинуть ресурсов не могу. А ресурсы порой кончаются, например сейчас в пике может 12гб оперативки потребляться из 16ти. В какой-то скорый…
Перенёс.

Взял 3 сервера, 2 под сервисы, 1 под бдшки, чтобы они отдельно крутились.

Процы на hetzner сразу видно лучше, вот avg load с сервера с базами: 0.09, 0.10, 0.09. Я так понимаю это потому что они быстрее и лучше справляются с поставленными вещами, плюсом не надо делить процессорное время уже с самими сервисами.

Поставил traefik, который за cloudflared тоннелем. Что это значит?
Вы запускаете агент cloudflared, создаёте докер сеть под него, и потом прикрепляете эту докер сеть к вашему сервису.
services:
cloudflared:
image: cloudflare/cloudflared
command: tunnel run
networks:
- cloudflared
environment:
- TUNNEL_TOKEN=${TUNNEL_TOKEN}


networks:
cloudflared:
external: true


И второй файлик:
services:
image: nginx
networks:
- cloudflared

networks:
cloudflared:
external: true


Запустили, и можно через панельку cloudflare открыть nginx по домену в сеть! (см. скриншот №1)

Там поддерживается как http, так и ssh, tcp. Можно почти любой сервис так открыть за доменом через эти тонели.

Ещё из апгрейда инфры я сделал zero-access до своих панелек. Например adminer закрыл, и логиниться могу только я туда. Не нужно настраивать traefik мидлвары, кастомные плагины, парится об env всяких. Просто настроил и работает. Можно даже сделать доступ за warp (впн клиент такой), если надо. Чем меньше env, тем лучше.

Брал кстати сервера в америке, и твич стал сильно быстрее отвечать за счёт этого. Но у этого минус есть - фронт у европейцев будет на 150мс медленее грузить.
🔥5👍3🏆1
Фига чё гитлаб придумали.

Если вы потеряли своё 2fa приложение, и у вас привязан ssh ключ — то вы можете перегенерить рекавери коды использовав ssh [email protected] 2fa_recovery_codes.
👍6🤯6
Ребят, если зарегаетесь по моей рефке на хетзнере — получите 20 евро на баланс клауда.
А когда потратите свои закинутые 10 евро — их получу я.
Потому, если кто задумывался и имеет возможность — дерзайте.

https://hetzner.cloud/?ref=CLdub3cH5y9p

П.С россияне не могут.
🗿8
Добавил в twir автоопределение играющей музыки на стриме, без необходимости подключать сторонний сервис по типу spotify, last.fm. Бот просто получает отрезочек стрима, отправляет его на определение.
Пока использую только shazam reverse-engineered api, потом подключу acrcloud, который уже будет платным для меня.
Но acrcloud имеет более лучшее определение, потому сделать придётся. Сейчас много мисов в определении, но уже хоть что-то, и бесплатно для меня и вас.
🔥15👍42🆒1
This media is not supported in your browser
VIEW IN TELEGRAM
🗿72💅2
Какой вариант вам нравится больше?
Думаю может взять старый дизайн, и осовременить его чутка, а конкретно взять блок features с нового.

👍https://twir.app
❤️https://web.archive.org/web/20230721100427/https://twir.app/
👍297
8
Может кому полезно будет: https://github.com/orval-labs/orval

Либа из openapi спеки генерит вам tanstack квери и мутации. Сам не пользовал, но выглядит интересно.
🔥6
Чутка поработал над безопасностью исполняемого пользовательского кода в Twir.

В Twir есть фича, называется scripts variable, позволяющая пользователям писать код на javascript, для расширения функционала. К примеру сходить на какое-то стороннее апи, распарсить ответ старндартными средствами js.

Конечно, это порождает дыры в безопасности, хоть это и вынесено в отдельный сервис, который занимается выполонением, но всё же, можно:
- забить эти сервисы оперативкой
- сильно забить CPU, сделав какой-то while (true) {}
- сбежать из сандбокса node:vm, и получить доступ к вайловой системе контейнера, с целью абуза, или с целью последующего взлома, попыток сбежать из докера
- повесить сервис каким-то долго выплняющимся скриптом

Половину из этих проблем я пытался решить с помощью vm2 пакета. В нём есть таймауты, вроде ограничения по RAM. Но работало там это через раз, например while (true) {} не таймаутился.

Что я решил делать с этим?

А давайте я буду запускать контейнер при каждом запросе на выполнение скрипта!

Было решено написать небольшое апи на Go, которое будет заниматься оркестрацией контейнеров (почти).
На каждый запрос на выполнение скрипта, я спавню контейнер через docker api, задавая ему лимиты по cpu, ram, времени жизни. Ограничиваю контейнеру системые capabilities, доступ к вайловой системе, а именно делаю её read-only. Пользовательский скрипт я чучуть оборачиваю:
wrapperContent := `
import { readFileSync } from 'fs';
import vm from 'node:vm'
import _ from 'lodash';

const consoleRegex = /console\.(log|debug|info|warn|error|table|trace|group|groupEnd|time|timeEnd)\s*\([^;]*\);?/g;

try {
const code = readFileSync('/code/user_code.mjs', 'utf8').replace(consoleRegex, '');
const result = await eval('(async () => { ' + code + ' })()');
console.log(JSON.stringify({ result: result.toString() }));
} catch (e) {
console.log(JSON.stringify({ error: e.message }));
}

Так я добавляю в контекст выполнения lodash, для более удобной работы с некоторыми вещами для пользователей.
Скрипт выполняется, и через докеровское апи я читаю результат выполнения из консоли.

Почему именно так? Сделано так, чтобы упростить написание скрипта для пользователя, условно чтобы он в скрипте мог сделать return 1, и без каких-либо дополнительных вещей.

Вчера ещё бился с проблемой, что пользователь можно легко узнать IP сервера, где это всё выполняется каким нибудь запросом на сайт, например https://ifconfig.me/ip. Плясал вокруг cloudflare, думал пойти вообще в сторону workers, а не докера, но в итоге получилось довольно интересно.
Оказывается, докер контейнеру можно задать network_mode другого контейнера. Что это нам даёт? Мы можем поднять рядом vpn, в лице которого я использую wrap, и пустить весь трафик контейнера через него: network_mode: "service:cloudflare-warp".
networkMode := container.NetworkMode(network.DefaultNetwork)
if os.Getenv("APP_ENV") == "production" {
networkMode = container.NetworkMode("container:executron-warp")
}

pidsLimit := int64(100)
hostConfig := &container.HostConfig{
Mounts: containerCtx.mounts,
Resources: container.Resources{
Memory: 128 * 1024 * 1024, // 128 MB
NanoCPUs: 1000000000, // 1 CPU
PidsLimit: &pidsLimit, // Limit to 100 PIDs
},
NetworkMode: networkMode,
ReadonlyRootfs: true,
AutoRemove: true,
SecurityOpt: []string{"no-new-privileges"},
CapDrop: []string{"ALL"}, // Drop all capabilities
//Runtime: "runsc",
}


На всякий случай ещё сделал, чтобы сервис не мог запустить более чем N контейнеров одновременно, через некую очередь ожидания на исполнение через атомики:
for c.runedContainers.Load() >= c.maxRunedContainers {
time.Sleep(100 * time.Millisecond)
}

c.runedContainers.Add(1)
defer c.runedContainers.Add(-1)


Всё просто, и вроде как (я надеюсь) безопасно. Я ещё пробовал дополнительный слой безопасности в виде https://gvisor.dev, но к сожалению это добавляет неплохую такую задержку при исполнении.

Буду рад любому фидбеку.

https://github.com/twirapp/ExecuTron
14🏆6🔥4
Channel photo updated
🤡6😁5🌚2