Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/xray-config-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Xray config (Ansible + xray -test)

on:
push:
branches: [main, master]
pull_request:
branches: [main, master]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Ansible
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq ansible unzip curl openssl

- name: Install Xray (for -test)
run: |
curl -fsSL -o /tmp/xray.zip "https://github.com/XTLS/Xray-core/releases/download/v26.2.6/Xray-linux-64.zip"
sudo unzip -q -o /tmp/xray.zip xray -d /usr/local/bin
sudo chmod +x /usr/local/bin/xray

- name: Run tests
working-directory: ${{ github.workspace }}
run: |
chmod +x tests/run.sh tests/scripts/gen-reality-keys.sh
SKIP_XRAY_TEST=0 ./tests/run.sh
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ roles/hosts.yml
roles/xray/defaults/secrets.yml
roles/xray/defaults/vic_secret.yml

# Generated by tests/run.sh
tests/.cache/
tests/.output/
tests/fixtures/test_secrets.yml

93 changes: 93 additions & 0 deletions docker/test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Docker: тест подписок и Xray-клиента

Стек для проверки **HTTP-подписки** (base64 → JSON или share links) и **Xray в Docker** (клиент с SOCKS).

## Быстрый тест (mock подписка + клиент)

```bash
cd docker/test
docker compose up -d --build
```

- Мок подписки: `http://127.0.0.1:18088/sub` — отдаёт base64 от `mock-sub/sample-client-config.json` (минимальный JSON: SOCKS 1080 → direct).
- Клиент Xray: SOCKS на хосте **`127.0.0.1:11080`** (контейнер `xray-client` подтягивает ту же подписку и запускает Xray).

Проверка подписки (ручной прогон скрипта в одноразовом контейнере):

```bash
docker compose run --rm -v "$(pwd)/scripts:/scripts:ro" alpine:3.20 \
sh -c "apk add --no-cache curl jq bash >/dev/null && chmod +x /scripts/test-subscription.sh && /scripts/test-subscription.sh http://subscription-mock/sub"
```

Проверка SOCKS:

```bash
curl -x socks5h://127.0.0.1:11080 -sI --max-time 15 https://example.com
```

Остановка:

```bash
docker compose down
```

## Панель 3x-ui (Xray + БД + подписки в UI)

Отдельный compose — веб-панель, встроенный Xray и SQLite:

```bash
cd docker/test
docker compose -f docker-compose.3x-ui.yml up -d
```

Откройте в браузере **`http://127.0.0.1:2053/`** (если порт занят — смотрите логи контейнера и документацию [3x-ui](https://github.com/MHSanaei/3x-ui)).

1. Войдите (часто `admin` / `admin` — **сразу смените пароль**).
2. Создайте inbound (VLESS и т.д.).
3. Скопируйте **ссылку подписки** для клиента.

### Почему `import_sub` / `premature_eof`?

Типичные причины:

- URL подписки открывается не полностью (обрыв по таймауту, TLS, прокси).
- Подписка не base64 или не тот формат (ожидается то, что отдаёт панель).
- Блокировка или редирект без тела ответа.

Проверьте с хоста:

```bash
curl -vS --max-time 60 'https://YOUR_PANEL/sub/YOUR_TOKEN'
```

Должен прийти **текст** (часто одна строка base64). Декод:

```bash
curl -fsS 'URL' | base64 -d | head -c 200
```

### Клиент в Docker и подписка 3x-ui

Образ `xray-client` умеет брать **только JSON** из base64 (как в моке). У **3x-ui** подписка часто — **набор `vless://...` строк**, а не готовый `config.json`; такой формат нужно импортировать в приложении (v2rayN, Nekobox и т.д.) или собрать конфиг вручную.

Для проверки только «подписка отдаётся и декодится» используйте `test-subscription.sh` с URL от панели.

## Перегенерация мока `sub.b64`

После правки `mock-sub/sample-client-config.json`:

```bash
./scripts/generate-mock-subscription.sh
```

## Переменные

| Переменная | Описание |
|-------------------|----------|
| `SUBSCRIPTION_URL` | URL для `xray-client` (по умолчанию `http://subscription-mock/sub`) |

Пример с внешней подпиской (если отдаёт **JSON в base64**):

```bash
SUBSCRIPTION_URL='https://example.com/sub/xxx' docker compose up -d --build xray-client
```
26 changes: 26 additions & 0 deletions docker/test/docker-compose.3x-ui.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 3x-ui: Xray + SQLite + web panel (subscription links in UI).
# Panel default path/port depend on image version — check container logs after start.
#
# cd docker/test && docker compose -f docker-compose.3x-ui.yml up -d
# # Open http://127.0.0.1:2053/ (or see README), login admin/admin, change password,
# # add inbound, copy subscription URL for clients.

services:
x-ui:
image: ghcr.io/mhsanaei/3x-ui:latest
container_name: raven-3x-ui
restart: unless-stopped
environment:
- XRAY_VMESS_AEAD_FORCED=false
volumes:
- xui_db:/etc/x-ui
- xui_cert:/root/cert
ports:
- "2053:2053"
# Map additional ports for Xray inbounds after you create them in the panel
# - "443:443"
# - "8443:8443"

volumes:
xui_db:
xui_cert:
26 changes: 26 additions & 0 deletions docker/test/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Docker test stack: mock subscription HTTP + subscription checker + optional Xray client.
# Usage:
# cd docker/test && docker compose up -d --build
# docker compose run --rm subtest ./scripts/test-subscription.sh http://subscription-mock/sub
# curl -x socks5h://127.0.0.1:11080 -sI https://example.com # after xray-client up

services:
subscription-mock:
image: nginx:alpine
container_name: raven-subscription-mock
volumes:
- ./mock-sub/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./mock-sub:/usr/share/nginx/html:ro
ports:
- "18088:80"

xray-client:
build: ./xray-client
container_name: raven-xray-client
environment:
# JSON base64 subscription (same mock as /sub): works with entrypoint.
SUBSCRIPTION_URL: ${SUBSCRIPTION_URL:-http://subscription-mock/sub}
ports:
- "11080:1080"
depends_on:
- subscription-mock
3 changes: 3 additions & 0 deletions docker/test/mock-sub/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Subscription mock</title></head>
<body><p>Mock subscription server. Use <code>/sub</code> for base64 payload.</p></body></html>
15 changes: 15 additions & 0 deletions docker/test/mock-sub/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
default_type text/plain;
location / {
try_files $uri $uri/ =404;
}
# GET /sub — body = base64 (one line), same idea as panel subscription URL
location = /sub {
default_type text/plain;
add_header Cache-Control "no-store";
alias /usr/share/nginx/html/sub.b64;
}
}
19 changes: 19 additions & 0 deletions docker/test/mock-sub/sample-client-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"log": { "loglevel": "warning" },
"inbounds": [
{
"tag": "socks-in",
"port": 1080,
"listen": "0.0.0.0",
"protocol": "socks",
"settings": { "udp": true }
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
}
]
}
1 change: 1 addition & 0 deletions docker/test/mock-sub/sub.b64
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ewogICJsb2ciOiB7ICJsb2dsZXZlbCI6ICJ3YXJuaW5nIiB9LAogICJpbmJvdW5kcyI6IFsKICAgIHsKICAgICAgInRhZyI6ICJzb2Nrcy1pbiIsCiAgICAgICJwb3J0IjogMTA4MCwKICAgICAgImxpc3RlbiI6ICIwLjAuMC4wIiwKICAgICAgInByb3RvY29sIjogInNvY2tzIiwKICAgICAgInNldHRpbmdzIjogeyAidWRwIjogdHJ1ZSB9CiAgICB9CiAgXSwKICAib3V0Ym91bmRzIjogWwogICAgewogICAgICAidGFnIjogImRpcmVjdCIsCiAgICAgICJwcm90b2NvbCI6ICJmcmVlZG9tIiwKICAgICAgInNldHRpbmdzIjoge30KICAgIH0KICBdCn0K
14 changes: 14 additions & 0 deletions docker/test/scripts/generate-mock-subscription.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Generates mock-sub/sub.b64 from a minimal Xray JSON (for nginx /sub endpoint).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
JSON="$ROOT/mock-sub/sample-client-config.json"
OUT="$ROOT/mock-sub/sub.b64"

if [[ ! -f "$JSON" ]]; then
echo "Missing $JSON"
exit 1
fi

base64 -w0 "$JSON" > "$OUT" 2>/dev/null || base64 "$JSON" | tr -d '\n' > "$OUT"
echo "Wrote $OUT ($(wc -c < "$OUT") bytes base64)"
43 changes: 43 additions & 0 deletions docker/test/scripts/test-subscription.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# Test subscription URL: HTTP 200, body decodes from base64, optional JSON or vless links.
set -eo pipefail

SUB_URL="${1:-http://subscription-mock:80/sub}"
echo "==> Fetching: $SUB_URL"

RAW="$(curl -fsS --connect-timeout 10 --max-time 60 "$SUB_URL")" || {
echo "ERROR: curl failed (TLS/firewall/DNS?)"
exit 1
}

echo "==> Body length: ${#RAW} bytes"

# Trim whitespace
TRIM="$(echo "$RAW" | tr -d '\r\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"

decode_b64() {
echo "$1" | base64 -d 2>/dev/null || echo "$1" | base64 --decode 2>/dev/null
}

DECODED="$(decode_b64 "$TRIM" 2>/dev/null || true)"
if [[ -z "$DECODED" || "$DECODED" == "$TRIM" ]]; then
echo "WARN: Treating body as plain text (not base64 or decode failed)"
DECODED="$TRIM"
fi

if echo "$DECODED" | jq -e . >/dev/null 2>&1; then
echo "==> Valid JSON (jq ok)"
echo "$DECODED" | jq -c '{has_outbounds: (.outbounds != null), has_inbounds: (.inbounds != null)}' 2>/dev/null || true
exit 0
fi

if echo "$DECODED" | grep -qE '^vless://|^vmess://|^trojan://|^ss://'; then
N="$(echo "$DECODED" | grep -cE '^vless://|^vmess://|^trojan://|^ss://' || true)"
echo "==> Subscription contains $N share link(s) (vless/vmess/trojan/ss)"
exit 0
fi

echo "ERROR: Decoded payload is neither JSON nor known share links"
echo "---- first 200 chars ----"
echo "${DECODED:0:200}"
exit 1
17 changes: 17 additions & 0 deletions docker/test/xray-client/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Minimal Xray client (SOCKS). Config mounted or generated from subscription URL.
FROM alpine:3.20

RUN apk add --no-cache ca-certificates curl jq unzip wget

ARG XRAY_VERSION=26.2.6
RUN wget -q -O /tmp/xray.zip "https://github.com/XTLS/Xray-core/releases/download/v${XRAY_VERSION}/Xray-linux-64.zip" \
&& unzip -q /tmp/xray.zip -d /usr/local/bin xray \
&& chmod +x /usr/local/bin/xray \
&& rm /tmp/xray.zip

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

EXPOSE 1080

ENTRYPOINT ["/entrypoint.sh"]
19 changes: 19 additions & 0 deletions docker/test/xray-client/config.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"log": { "loglevel": "warning" },
"inbounds": [
{
"tag": "socks-in",
"port": 1080,
"listen": "0.0.0.0",
"protocol": "socks",
"settings": { "udp": true }
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
}
]
}
38 changes: 38 additions & 0 deletions docker/test/xray-client/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/sh
set -eu

CONFIG="${XRAY_CONFIG:-/etc/xray/config.json}"

if [ -n "${SUBSCRIPTION_URL:-}" ]; then
echo "==> Fetching subscription: $SUBSCRIPTION_URL"
RAW="$(curl -fsS --connect-timeout 15 --max-time 120 "$SUBSCRIPTION_URL")"
TRIM="$(echo "$RAW" | tr -d '\r\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
DECODED="$(printf '%s' "$TRIM" | base64 -d 2>/dev/null || true)"
if [ -z "$DECODED" ]; then
echo "ERROR: failed to base64-decode subscription body"
exit 1
fi
if echo "$DECODED" | jq -e . >/dev/null 2>&1; then
echo "$DECODED" > /tmp/config-from-sub.json
CONFIG=/tmp/config-from-sub.json
echo "==> Using JSON config from subscription ($(wc -c < "$CONFIG") bytes)"
else
echo "ERROR: Subscription is not a JSON Xray config (3x-ui often returns vless:// links)."
echo "Use a client app to import, or mount a static config.json."
echo "---- first 120 chars ----"
printf '%s' "$DECODED" | head -c 120
echo
exit 1
fi
fi

if [ ! -f "$CONFIG" ]; then
echo "ERROR: No config at $CONFIG (set SUBSCRIPTION_URL for JSON base64 or mount config)"
exit 1
fi

echo "==> xray -test"
/usr/local/bin/xray -test -c "$CONFIG"

echo "==> xray run"
exec /usr/local/bin/xray run -c "$CONFIG"
Loading
Loading