mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-06 16:58:18 +00:00
Compare commits
3 Commits
fix/upgrad
...
Xe/more-do
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c67b4c16a6 | ||
|
|
039da9aa1c | ||
|
|
81ead403c2 |
@@ -1,12 +1,12 @@
|
||||
services:
|
||||
playwright:
|
||||
image: mcr.microsoft.com/playwright:v1.56.0-noble
|
||||
image: mcr.microsoft.com/playwright:v1.52.0-noble
|
||||
init: true
|
||||
network_mode: service:workspace
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- npx -y playwright@1.56.0 run-server --port 9001 --host 0.0.0.0
|
||||
- npx -y playwright@1.52.0 run-server --port 9001 --host 0.0.0.0
|
||||
|
||||
valkey:
|
||||
image: valkey/valkey:8
|
||||
|
||||
1
.github/actions/spelling/allow.txt
vendored
1
.github/actions/spelling/allow.txt
vendored
@@ -8,4 +8,3 @@ msgbox
|
||||
xeact
|
||||
ABee
|
||||
tencent
|
||||
maintnotifications
|
||||
4
.github/workflows/asset-verification.yml
vendored
4
.github/workflows/asset-verification.yml
vendored
@@ -22,11 +22,11 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
|
||||
6
.github/workflows/docker-pr.yml
vendored
6
.github/workflows/docker-pr.yml
vendored
@@ -26,11 +26,11 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
|
||||
|
||||
6
.github/workflows/docker.yml
vendored
6
.github/workflows/docker.yml
vendored
@@ -36,11 +36,11 @@ jobs:
|
||||
run: |
|
||||
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
with:
|
||||
images: ${{ env.IMAGE }}
|
||||
|
||||
|
||||
2
.github/workflows/docs-deploy.yml
vendored
2
.github/workflows/docs-deploy.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
with:
|
||||
images: ghcr.io/techarohq/anubis/docs
|
||||
tags: |
|
||||
|
||||
2
.github/workflows/docs-test.yml
vendored
2
.github/workflows/docs-test.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
with:
|
||||
images: ghcr.io/techarohq/anubis/docs
|
||||
tags: |
|
||||
|
||||
10
.github/workflows/go.yml
vendored
10
.github/workflows/go.yml
vendored
@@ -24,11 +24,11 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-1.56.0
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: install node deps
|
||||
run: |
|
||||
@@ -46,8 +46,8 @@ jobs:
|
||||
|
||||
- name: install playwright browsers
|
||||
run: |
|
||||
npx playwright@1.56.0 install --with-deps
|
||||
npx playwright@1.56.0 run-server --port 9001 &
|
||||
npx --no-install playwright@1.52.0 install --with-deps
|
||||
npx --no-install playwright@1.52.0 run-server --port 9001 &
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
4
.github/workflows/package-builds-stable.yml
vendored
4
.github/workflows/package-builds-stable.yml
vendored
@@ -25,11 +25,11 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
|
||||
@@ -26,11 +26,11 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
|
||||
@@ -51,10 +51,3 @@
|
||||
all:
|
||||
- path.startsWith("/v2/")
|
||||
- userAgent.contains("containerd/")
|
||||
|
||||
- name: allow-renovate
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- path.startsWith("/v2/")
|
||||
- userAgent.contains("Renovate/")
|
||||
|
||||
@@ -3,6 +3,6 @@ package data
|
||||
import "embed"
|
||||
|
||||
var (
|
||||
//go:embed botPolicies.yaml all:apps all:bots all:clients all:common all:crawlers all:meta all:services
|
||||
//go:embed botPolicies.yaml all:apps all:bots all:clients all:common all:crawlers all:meta
|
||||
BotPolicies embed.FS
|
||||
)
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestBotPoliciesEmbed ensures all YAML files in the directory tree
|
||||
// are accessible in the embedded BotPolicies filesystem.
|
||||
func TestBotPoliciesEmbed(t *testing.T) {
|
||||
yamlFiles, err := filepath.Glob("./**/*.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to glob YAML files: %v", err)
|
||||
}
|
||||
|
||||
if len(yamlFiles) == 0 {
|
||||
t.Fatal("No YAML files found in directory tree")
|
||||
}
|
||||
|
||||
t.Logf("Found %d YAML files to verify", len(yamlFiles))
|
||||
|
||||
for _, filePath := range yamlFiles {
|
||||
embeddedPath := strings.TrimPrefix(filePath, "./")
|
||||
|
||||
t.Run(embeddedPath, func(t *testing.T) {
|
||||
content, err := BotPolicies.ReadFile(embeddedPath)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to read %s from embedded filesystem: %v", embeddedPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(content) == 0 {
|
||||
t.Errorf("File %s exists in embedded filesystem but is empty", embeddedPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM docker.io/library/node:lts AS build
|
||||
FROM docker.io/library/node AS build
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
@@ -13,14 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
<!-- This changes the project to: -->
|
||||
|
||||
- Expose WEIGHT rule matches as Prometheus metrics.
|
||||
- Allow more OCI registry clients [based on feedback](https://github.com/TecharoHQ/anubis/pull/1253#issuecomment-3506744184).
|
||||
- Expose services directory in the embedded `(data)` filesystem.
|
||||
- Add Ukrainian locale ([#1044](https://github.com/TecharoHQ/anubis/pull/1044)).
|
||||
- Allow Renovate as an OCI registry client.
|
||||
- Properly handle 4in6 addresses so that IP matching works with those addresses.
|
||||
- Add support to simple Valkey/Redis cluster mode
|
||||
- Bump the Playwright dev dependency to 1.56.0
|
||||
|
||||
## v1.23.1: Lyse Hext - Echo 1
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ const (
|
||||
actionChallenge action = "CHALLENGE"
|
||||
|
||||
placeholderIP = "fd11:5ee:bad:c0de::"
|
||||
playwrightVersion = "1.56.0"
|
||||
playwrightVersion = "1.52.0"
|
||||
)
|
||||
|
||||
type action string
|
||||
|
||||
@@ -576,7 +576,6 @@ func (s *Server) check(r *http.Request, lg *slog.Logger) (policy.CheckResult, *p
|
||||
return cr("bot/"+b.Name, b.Action, weight), &b, nil
|
||||
case config.RuleWeigh:
|
||||
lg.Debug("adjusting weight", "name", b.Name, "delta", b.Weight.Adjust)
|
||||
policy.Applications.WithLabelValues("bot/"+b.Name, "WEIGH").Add(1)
|
||||
weight += b.Weight.Adjust
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"pt-BR",
|
||||
"ru",
|
||||
"tr",
|
||||
"uk",
|
||||
"vi",
|
||||
"zh-CN",
|
||||
"zh-TW",
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
{
|
||||
"loading": "Завантаження...",
|
||||
"why_am_i_seeing": "Чому я це бачу?",
|
||||
"protected_by": "Захищено засобами",
|
||||
"protected_from": "за авторством",
|
||||
"made_with": "Зроблено з ❤️ у 🇨🇦",
|
||||
"mascot_design": "Дизайн персонажа від",
|
||||
"ai_companies_explanation": "Ви це бачите, оскільки адміністрація сайту налаштувала Anubis, щоб захистити сервер від тиску ШІ-компаній, які агресивно сканують вебсайти. Їхня діяльність спричиняє перебої в роботі вебсайтів, що робить матеріали недоступними для всіх.",
|
||||
"anubis_compromise": "Anubis — це компроміс. Anubis втілює схему доказу виконаної роботи подібно до Hashcash — засобу боротьби зі спамом. По ідеї, додаткове навантаження не обтяжує справжню людину, котра робить небагато запитів, а от масове сканування таким чином стає суттєво дорожчим.",
|
||||
"hack_purpose": "Це тимчасове рішення, котре дозволяє приділити більше часу розпізнанню й виокремленню автоматизованих браузерів (наприклад, за тим, як вони промальовують шрифти), щоб сторінку перевірки доказу виконаної роботи не доводилося показувати ймовірно справжнім користувачам.",
|
||||
"simplified_explanation": "Це засіб боротьби з ботами й зловмисними запитами, подібний до капчі. Проте замість того, щоб просити вас щось зробити, він пропонує вашому браузеру розв'язати обчислювальне завдання. Ця концепція називається <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">доказом виконаної роботи</a>. Завдання обчислюється кілька секунд, після чого вам надається доступ до сайту. Дякуємо за розуміння й терплячість.",
|
||||
"jshelter_note": "Зауважте, Anubis потребує сучасного JavaScript-функціоналу, котрий може бути недоступним при використанні розширень на зразок JShelter. Будь ласка, вимкніть JShelter чи інші подібні розширення для цього домену.",
|
||||
"version_info": "Цей вебсайт застосовує Anubis версії",
|
||||
"try_again": "Повторіть спробу",
|
||||
"go_home": "Перейдіть на головну сторінку",
|
||||
"contact_webmaster": "або, якщо ви певні в помилковості блокування, сконтактуйте з адміністрацією за адресою",
|
||||
"connection_security": "Зачекайте хвилинку, поки ми перевіримо безпеку вашого з'єднання.",
|
||||
"javascript_required": "На жаль, вам потрібно ввімкнути JavaScript, щоб пройти цю перевірку. Це необхідно, оскільки ШІ-компанії нехтують суспільним договором, завдяки якому можливо утримувати вебсайти. Робота над рішенням без використання JS триває.",
|
||||
"benchmark_requires_js": "Щоб запустити тестування продуктивності, ввімкніть JavaScript.",
|
||||
"difficulty": "Складність:",
|
||||
"algorithm": "Алгоритм:",
|
||||
"compare": "Порівняти:",
|
||||
"time": "Час",
|
||||
"iters": "Ітерації",
|
||||
"time_a": "Час A",
|
||||
"iters_a": "Ітерації A",
|
||||
"time_b": "Час B",
|
||||
"iters_b": "Ітерації B",
|
||||
"static_check_endpoint": "Це просто сторінка перевірки для вашого зворотного проксі.",
|
||||
"authorization_required": "Необхідно авторизуватися",
|
||||
"cookies_disabled": "У вашому браузері вимкнено кукі. Anubis використовує кукі, щоб упевнитись, що ви дійсно людина. Це законний інтерес. Будь ласка, ввімкніть кукі для цього домену",
|
||||
"access_denied": "Доступ заборонено: код помилки",
|
||||
"dronebl_entry": "DroneBL містить пункт",
|
||||
"see_dronebl_lookup": "див.",
|
||||
"internal_server_error": "Внутрішня помилка сервера: адміністрація хибно налаштувала Anubis. Будь ласка, сконтактуйте з адміністрацією й попросіть глянути логи довкола",
|
||||
"invalid_redirect": "Хибне переспрямування",
|
||||
"redirect_not_parseable": "Не вдається розпізнати URL-адресу переспрямування",
|
||||
"redirect_domain_not_allowed": "Заборонений домен переспрямування",
|
||||
"missing_required_forwarded_headers": "Бракує обов'язкових заголовків X-Forwarded-*",
|
||||
"failed_to_sign_jwt": "не вдається підписати JWT",
|
||||
"invalid_invocation": "Хибний виклик MakeChallenge",
|
||||
"client_error_browser": "Помилка клієнта: переконайтесь, що використовуєте браузер актуальної версії, й повторіть спробу.",
|
||||
"oh_noes": "Йой!",
|
||||
"benchmarking_anubis": "Тестування продуктивності Anubis!",
|
||||
"you_are_not_a_bot": "Ви не бот!",
|
||||
"making_sure_not_bot": "Перевірка, чи ви не бот!",
|
||||
"celphase": "CELPHASE",
|
||||
"js_web_crypto_error": "Ваш браузер не надає web.crypto. Ви певні, що дивитесь це через захищений контекст?",
|
||||
"js_web_workers_error": "Ваш браузер не підтримує Web Workers (Anubis використовує їх, щоб ваш браузер не зависав на час перевірки). Можливо, у вас встановлено розширення на зразок JShelter?",
|
||||
"js_cookies_error": "Ваш браузер не зберігає кукі. Anubis записує підписаний токен до кукі, щоб занотувати, що клієнт пройшов перевірку. Будь ласка, ввімкніть збереження кукі для цього домену. Назви кукі, які записує Anubis, можуть змінюватися без попередження. Назви й значення кукі не є частиною публічного API.",
|
||||
"js_context_not_secure": "Ваш контекст незахищений!",
|
||||
"js_context_not_secure_msg": "Спробуйте з'єднатися через HTTPS або попросіть адміністрацію налаштувати HTTPS. Докладніше — в <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
|
||||
"js_calculating": "Обчислення...",
|
||||
"js_missing_feature": "Бракує функціоналу",
|
||||
"js_challenge_error": "Помилка перевірки!",
|
||||
"js_challenge_error_msg": "Не вдалося визначити алгоритм перевірки. Спробуйте оновити сторінку.",
|
||||
"js_calculating_difficulty": "Обчислення...<br/>Складність:",
|
||||
"js_speed": "Швидкість:",
|
||||
"js_verification_longer": "Перевірка триває довше, ніж очікувалося. Будь ласка, не оновлюйте сторінку.",
|
||||
"js_success": "Успіх!",
|
||||
"js_done_took": "Готово! Знадобилося",
|
||||
"js_iterations": "ітерацій",
|
||||
"js_finished_reading": "Читання завершено, продовжити →",
|
||||
"js_calculation_error": "Помилка обчислення!",
|
||||
"js_calculation_error_msg": "Не вдалося обчислити перевірку:"
|
||||
}
|
||||
@@ -27,7 +27,6 @@ func TestLocalizationService(t *testing.T) {
|
||||
"pt-BR": "Carregando...",
|
||||
"tr": "Yükleniyor...",
|
||||
"ru": "Загрузка...",
|
||||
"uk": "Завантаження...",
|
||||
"vi": "Đang nạp...",
|
||||
"zh-CN": "加载中...",
|
||||
"zh-TW": "載入中...",
|
||||
|
||||
@@ -51,11 +51,6 @@ func (rac *RemoteAddrChecker) Check(r *http.Request) (bool, error) {
|
||||
return false, fmt.Errorf("%w: %s is not an IP address: %w", ErrMisconfiguration, host, err)
|
||||
}
|
||||
|
||||
// Convert IPv4-mapped IPv6 addresses to IPv4
|
||||
if addr.Is6() && addr.Is4In6() {
|
||||
addr = addr.Unmap()
|
||||
}
|
||||
|
||||
return rac.prefixTable.Contains(addr), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -21,20 +21,6 @@ func TestRemoteAddrChecker(t *testing.T) {
|
||||
ok: true,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "match_ipv4_in_ipv6",
|
||||
cidrs: []string{"0.0.0.0/0"},
|
||||
ip: "::ffff:1.1.1.1",
|
||||
ok: true,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "match_ipv4_in_ipv6_hex",
|
||||
cidrs: []string{"0.0.0.0/0"},
|
||||
ip: "::ffff:101:101",
|
||||
ok: true,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "match_ipv6",
|
||||
cidrs: []string{"::/0"},
|
||||
|
||||
@@ -5,98 +5,80 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/TecharoHQ/anubis/lib/store"
|
||||
valkey "github.com/redis/go-redis/v9"
|
||||
"github.com/redis/go-redis/v9/maintnotifications"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoURL = errors.New("valkey.Config: no URL defined")
|
||||
ErrBadURL = errors.New("valkey.Config: URL is invalid")
|
||||
)
|
||||
|
||||
func init() {
|
||||
store.Register("valkey", Factory{})
|
||||
}
|
||||
|
||||
// Errors kept as-is so other code/tests still pass.
|
||||
var (
|
||||
ErrNoURL = errors.New("valkey.Config: no URL defined")
|
||||
ErrBadURL = errors.New("valkey.Config: URL is invalid")
|
||||
)
|
||||
type Factory struct{}
|
||||
|
||||
// Config is what Anubis unmarshals from the "parameters" JSON.
|
||||
type Config struct {
|
||||
URL string `json:"url"`
|
||||
Cluster bool `json:"cluster,omitempty"`
|
||||
}
|
||||
func (Factory) Build(ctx context.Context, data json.RawMessage) (store.Interface, error) {
|
||||
var config Config
|
||||
|
||||
func (c Config) Valid() error {
|
||||
if c.URL == "" {
|
||||
return ErrNoURL
|
||||
if err := json.Unmarshal([]byte(data), &config); err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", store.ErrBadConfig, err)
|
||||
}
|
||||
|
||||
// Just validate that it's a valid Redis URL.
|
||||
if _, err := valkey.ParseURL(c.URL); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrBadURL, err)
|
||||
if err := config.Valid(); err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", store.ErrBadConfig, err)
|
||||
}
|
||||
|
||||
opts, err := valkey.ParseURL(config.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", store.ErrBadConfig, err)
|
||||
}
|
||||
|
||||
rdb := valkey.NewClient(opts)
|
||||
|
||||
if _, err := rdb.Ping(ctx).Result(); err != nil {
|
||||
return nil, fmt.Errorf("can't ping valkey instance: %w", err)
|
||||
}
|
||||
|
||||
return &Store{
|
||||
rdb: rdb,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (Factory) Valid(data json.RawMessage) error {
|
||||
var config Config
|
||||
if err := json.Unmarshal([]byte(data), &config); err != nil {
|
||||
return fmt.Errorf("%w: %w", store.ErrBadConfig, err)
|
||||
}
|
||||
|
||||
if err := config.Valid(); err != nil {
|
||||
return fmt.Errorf("%w: %w", store.ErrBadConfig, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// redisClient is satisfied by *valkey.Client and *valkey.ClusterClient.
|
||||
type redisClient interface {
|
||||
Get(ctx context.Context, key string) *valkey.StringCmd
|
||||
Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *valkey.StatusCmd
|
||||
Del(ctx context.Context, keys ...string) *valkey.IntCmd
|
||||
Ping(ctx context.Context) *valkey.StatusCmd
|
||||
type Config struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type Factory struct{}
|
||||
func (c Config) Valid() error {
|
||||
var errs []error
|
||||
|
||||
func (Factory) Valid(data json.RawMessage) error {
|
||||
var cfg Config
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
return err
|
||||
if c.URL == "" {
|
||||
errs = append(errs, ErrNoURL)
|
||||
}
|
||||
return cfg.Valid()
|
||||
}
|
||||
|
||||
func (Factory) Build(ctx context.Context, data json.RawMessage) (store.Interface, error) {
|
||||
var cfg Config
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := cfg.Valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts, err := valkey.ParseURL(cfg.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("valkey.Factory: %w", err)
|
||||
}
|
||||
|
||||
var client redisClient
|
||||
|
||||
if cfg.Cluster {
|
||||
// Cluster mode: use the parsed Addr as the seed node.
|
||||
clusterOpts := &valkey.ClusterOptions{
|
||||
Addrs: []string{opts.Addr},
|
||||
// Explicitly disable maintenance notifications
|
||||
// This prevents the client from sending CLIENT MAINT_NOTIFICATIONS ON
|
||||
MaintNotificationsConfig: &maintnotifications.Config{
|
||||
Mode: maintnotifications.ModeDisabled,
|
||||
},
|
||||
}
|
||||
client = valkey.NewClusterClient(clusterOpts)
|
||||
} else {
|
||||
opts.MaintNotificationsConfig = &maintnotifications.Config{
|
||||
Mode: maintnotifications.ModeDisabled,
|
||||
}
|
||||
client = valkey.NewClient(opts)
|
||||
}
|
||||
|
||||
// Optional but nice: fail fast if the cluster/single node is unreachable.
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
return nil, fmt.Errorf("valkey.Factory: ping failed: %w", err)
|
||||
}
|
||||
|
||||
return &Store{client: client}, nil
|
||||
|
||||
if _, err := valkey.ParseURL(c.URL); err != nil {
|
||||
errs = append(errs, ErrBadURL)
|
||||
}
|
||||
|
||||
if len(errs) != 0 {
|
||||
return fmt.Errorf("valkey.Config: invalid config: %w", errors.Join(errs...))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,46 +2,52 @@ package valkey
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/TecharoHQ/anubis/lib/store"
|
||||
valkey "github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// Store implements store.Interface on top of Redis/Valkey.
|
||||
type Store struct {
|
||||
client redisClient
|
||||
}
|
||||
|
||||
var _ store.Interface = (*Store)(nil)
|
||||
|
||||
func (s *Store) Get(ctx context.Context, key string) ([]byte, error) {
|
||||
cmd := s.client.Get(ctx, key)
|
||||
if err := cmd.Err(); err != nil {
|
||||
if err == valkey.Nil {
|
||||
return nil, store.ErrNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return cmd.Bytes()
|
||||
}
|
||||
|
||||
func (s *Store) Set(ctx context.Context, key string, value []byte, expiry time.Duration) error {
|
||||
return s.client.Set(ctx, key, value, expiry).Err()
|
||||
rdb *valkey.Client
|
||||
}
|
||||
|
||||
func (s *Store) Delete(ctx context.Context, key string) error {
|
||||
res := s.client.Del(ctx, key)
|
||||
if err := res.Err(); err != nil {
|
||||
return err
|
||||
n, err := s.rdb.Del(ctx, key).Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't delete from valkey: %w", err)
|
||||
}
|
||||
if n, _ := res.Result(); n == 0 {
|
||||
return store.ErrNotFound
|
||||
|
||||
switch n {
|
||||
case 0:
|
||||
return fmt.Errorf("%w: %d key(s) deleted", store.ErrNotFound, n)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) Get(ctx context.Context, key string) ([]byte, error) {
|
||||
result, err := s.rdb.Get(ctx, key).Result()
|
||||
if err != nil {
|
||||
if valkey.HasErrorPrefix(err, "redis: nil") {
|
||||
return nil, fmt.Errorf("%w: %w", store.ErrNotFound, err)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("can't fetch from valkey: %w", err)
|
||||
}
|
||||
|
||||
return []byte(result), nil
|
||||
}
|
||||
|
||||
func (s *Store) Set(ctx context.Context, key string, value []byte, expiry time.Duration) error {
|
||||
if _, err := s.rdb.Set(ctx, key, string(value), expiry).Result(); err != nil {
|
||||
return fmt.Errorf("can't set %q in valkey: %w", key, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsPersistent tells Anubis this backend is “real” storage, not in-memory.
|
||||
func (s *Store) IsPersistent() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -16,7 +16,7 @@
|
||||
"cssnano": "^7.1.2",
|
||||
"cssnano-preset-advanced": "^7.0.10",
|
||||
"esbuild": "^0.25.12",
|
||||
"playwright": "^1.56.0",
|
||||
"playwright": "^1.52.0",
|
||||
"postcss-cli": "^11.0.1",
|
||||
"postcss-import": "^16.1.1",
|
||||
"postcss-import-url": "^7.2.0",
|
||||
@@ -1603,13 +1603,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.56.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz",
|
||||
"integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==",
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz",
|
||||
"integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.56.1"
|
||||
"playwright-core": "1.52.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -1622,9 +1622,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.56.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz",
|
||||
"integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==",
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz",
|
||||
"integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"cssnano": "^7.1.2",
|
||||
"cssnano-preset-advanced": "^7.0.10",
|
||||
"esbuild": "^0.25.12",
|
||||
"playwright": "^1.56.0",
|
||||
"playwright": "^1.52.0",
|
||||
"postcss-cli": "^11.0.1",
|
||||
"postcss-import": "^16.1.1",
|
||||
"postcss-import-url": "^7.2.0",
|
||||
@@ -31,4 +31,4 @@
|
||||
"@aws-crypto/sha256-js": "^5.2.0",
|
||||
"preact": "^10.27.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
0
run/openrc/anubis.initd
Executable file → Normal file
0
run/openrc/anubis.initd
Executable file → Normal file
Reference in New Issue
Block a user