DevOps 31.10.2026 г.

Собствена алтернатива на GitHub Gists — self-hosted

GitHub Gists и OpenGist все нещо не ми бяха ок — затова си написах gistly: self-hosted снипети с Next.js, SQLite и една Docker команда.

Началната страница на gistly в тъмна тема — списък със снипети, търсене и облак от тагове
self-hosted nextjs docker sqlite opensource

От доста време работя с GitHub Gists и OpenGist и все нещо не ми бе ок.

Едното: в интерфейса никъде няма български. Другото: GitHub Gists са завързани за GitHub — не можеш да ги пуснеш на свой сървър, няма тагове, няма статистики, няма как да кажеш „този снипет е за моя сървър, на моя домейн, по моите правила”. OpenGist честно казано е страхотен и вдъхнови части от този проект, но все исках друг вкус: модерен дашборд, командна палитра, моят език в интерфейса — и настройка толкова проста, че цялото нещо е един SQLite файл и една Docker команда.

Затова направих това, което всички правим в такива моменти — написах си собствен. Запознайте се с gistly — self-hosted приложение за споделяне на снипети с отворен код.

Идеята накратко

Поставяш код, получаваш линк, споделяш го. Всичко живее на твоя сървър, в един-единствен SQLite файл. Като GitHub Gists, само че:

  • 🎨 Оцветяване на синтаксиса със Shiki и официалните GitHub Light & Dark теми — 40+ езика, номера на редовете, превключваем soft-wrap
  • 📝 Markdown преглед — .md файловете се рендерят с таблици, task списъци и оцветени код блокове (GFM)
  • 🔐 Single-admin режим — само ти (с парола) създаваш и редактираш; всички останали просто отварят линкове
  • 👁 Три нива на видимост — Публичен (вижда се в списъка), Скрит (само по линк), Частен (линк + токен)
  • 🔎 Търсене по цял текст + тагове (#typescript, #sql…) с кликаем облак от тагове
  • 📊 Дашборд „Статистика” (Insights) — heatmap на активността в стил GitHub, разпределение по езици и тагове, най-гледаният и последният gist
  • ⌨️ Командна палитра — Ctrl+K от всякъде, търсиш гистове и пускаш команди
  • 📄 Пагинация „зареди още” с живи броячи
  • 🌗 Тъмна и светла тема и напълно двуезичен интерфейс EN / BG — да, това беше точка едно 😄
  • 🐳 Docker от първия кадър — multi-stage образ, постоянен volume за SQLite, ./deploy.sh и си готов

Дашбордът „Статистика” и командната палитра са двете неща, които наистина ми липсваха другаде. Heatmap-ът е графиката с приносите от GitHub, но за снипетите ти — 26 седмици, по една клетка за всеки ден, виолетово вместо зелено. Палитрата е моделът от VS Code и Linear: натискаш Ctrl+K, пишеш три букви и скачаш директно в гиста.

Дашбордът „Статистика" на gistly

Командната палитра — търсене с Ctrl+K

Стекът

Скучен нарочно, модерен там, където има значение:

  • Next.js (App Router) + TypeScript + Tailwind CSS 4 + shadcn/ui
  • Prisma + SQLite — без отделен сървър за база данни. Целият „клъстер” е един файл, който изнасяш с docker cp и готов бекъп
  • Shiki за оцветяване на кода (същият двигател като в VS Code, с истинските GitHub теми)
  • Framer Motion, next-themes и Sonner за финалния щрих
  • Docker multi-stage build с Bun — финалният образ върви с Next.js standalone и прилага Prisma схемата при стартиране, така че прясно пуснат контейнер просто работи

Деплойване: 4 команди, сериозно

git clone https://github.com/fedya-dev/gistly.git
cd gistly
echo "ADMIN_PASSWORD=change-me" > .env
echo "AUTH_SECRET=$(openssl rand -hex 32)" >> .env
./deploy.sh

Това е — приложението е на порт 3000, данните оцеляват при всеки rebuild в volume-а gistly_data, а отпред може да застане всеки reverse proxy (Caddy ти дава HTTPS автоматично):

gist.example.com {
    reverse_proxy localhost:3000
}

Редакторът за нов gist в светла тема

Два бъга, които искам да запомня

Докато го пусках в продукшън, научих няколко неща, струващи си споделянето.

1. navigator.clipboard не съществува на чист HTTP. Бутонът „Копирай линка” работеше на localhost и умираше с TypeError на http://<server-ip>:3000 — Clipboard API съществува само в secure context (HTTPS или localhost). Поправката е класическият fallback, който работи навсякъде:

if (window.isSecureContext && navigator.clipboard) {
  await navigator.clipboard.writeText(text)
} else {
  // legacy път: скрит <textarea> + document.execCommand('copy')
}

Пусни и двата пътя и бутонът работи както преди TLS-а, така и след него.

2. BuildKit Bake е строг със network: host. Моят docker-compose.yml строи на host мрежата (някои VPS-и имат счупен Docker bridge DNS — класическите EAI_AGAIN зависвания по време на bun install). По-новите версии на Compose (2.36+) строят през BuildKit Bake и отказват с additional privileges requested: pass "--allow=network.host". Обходният път се превърна в малък deploy.sh:

docker buildx build --allow=network.host --network=host -t gistly:latest .
docker compose up -d

Ако Docker мрежата ти е здрава, обикновеното docker compose up -d --build също върши работа.

Страницата на отделен gist

Какво стои под капака на статистиката

Insights endpoint-ът е един паралелен Promise.all — броячки, groupBy за таговете/езиците/видимостта и една raw SQLite заявка, която брои редовете код, без изобщо да зарежда съдържанието на файловете в Node:

db.$queryRaw`
  SELECT SUM(LENGTH(content) - LENGTH(REPLACE(content, CHAR(10), '')) + 1) AS lines
  FROM File
  WHERE gistId IN (SELECT id FROM Gist ${visibilityFragment})
`

Heatmap-ът на активността е просто 182 дневни „кофи”, подравнени към неделите — изчислението е евтино и се рендерира мигновено на клиента.

Какво следва

В списъка са: скъсяване на raw URL адресите, гистове с автоматично изтичане, опционален multi-user режим и OG изображения, които рендерират първите редове код за красиви линк прегледи. Проектът е с MIT лиценз и е достатъчно малък, за да го прочетеш на един дъх — PR-и и порции критика в issues са добре дошли.

Ако искаш да го видиш на живо, работи тук: gistly.fedia.eu — а целият изходен код, README-ът и ръководството за персонализация (къде се сменят хедъра, футърът, цветовете и текстовете) са в репото:

👉 github.com/fedya-dev/gistly

Мобилният изглед на gistly

Благодаря, че прочете! Ако и ти държиш снипетите си на собствен сървър, кажи какво ти липсва при теб — събирам идеи в issues.