meridian-vpn 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meridian_vpn-2.0.0/.gitignore +24 -0
- meridian_vpn-2.0.0/BACKLOG.md +64 -0
- meridian_vpn-2.0.0/CLAUDE.md +438 -0
- meridian_vpn-2.0.0/CONTRIBUTING.md +78 -0
- meridian_vpn-2.0.0/LICENSE +21 -0
- meridian_vpn-2.0.0/PKG-INFO +150 -0
- meridian_vpn-2.0.0/README.md +123 -0
- meridian_vpn-2.0.0/SECURITY.md +24 -0
- meridian_vpn-2.0.0/VERSION +1 -0
- meridian_vpn-2.0.0/install.sh +156 -0
- meridian_vpn-2.0.0/inventory.yml.example +23 -0
- meridian_vpn-2.0.0/pyproject.toml +60 -0
- meridian_vpn-2.0.0/setup.sh +75 -0
- meridian_vpn-2.0.0/src/meridian/__init__.py +12 -0
- meridian_vpn-2.0.0/src/meridian/__main__.py +5 -0
- meridian_vpn-2.0.0/src/meridian/ai.py +87 -0
- meridian_vpn-2.0.0/src/meridian/ansible.py +200 -0
- meridian_vpn-2.0.0/src/meridian/cli.py +241 -0
- meridian_vpn-2.0.0/src/meridian/commands/__init__.py +0 -0
- meridian_vpn-2.0.0/src/meridian/commands/check.py +250 -0
- meridian_vpn-2.0.0/src/meridian/commands/client.py +188 -0
- meridian_vpn-2.0.0/src/meridian/commands/diagnostics.py +199 -0
- meridian_vpn-2.0.0/src/meridian/commands/ping.py +208 -0
- meridian_vpn-2.0.0/src/meridian/commands/resolve.py +152 -0
- meridian_vpn-2.0.0/src/meridian/commands/scan.py +142 -0
- meridian_vpn-2.0.0/src/meridian/commands/server.py +79 -0
- meridian_vpn-2.0.0/src/meridian/commands/setup.py +205 -0
- meridian_vpn-2.0.0/src/meridian/commands/uninstall.py +83 -0
- meridian_vpn-2.0.0/src/meridian/config.py +41 -0
- meridian_vpn-2.0.0/src/meridian/console.py +73 -0
- meridian_vpn-2.0.0/src/meridian/credentials.py +86 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/.ansible-lint +17 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/ansible.cfg +24 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/group_vars/all.yml +116 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/group_vars/exit.yml +15 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/group_vars/relay.yml +11 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/inventory-chain.yml.example +30 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/playbook-chain.yml +168 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/playbook-client.yml +71 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/playbook-uninstall.yml +130 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/playbook.yml +87 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/requirements.yml +9 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/caddy/handlers/main.yml +5 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/caddy/tasks/main.yml +170 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/caddy/templates/Caddyfile.j2 +59 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/caddy/templates/connection-info.html.j2 +278 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/caddy/templates/update-stats.py.j2 +111 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/client_management/tasks/add_client.yml +250 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/client_management/tasks/list_clients.yml +42 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/client_management/tasks/main.yml +94 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/client_management/tasks/remove_client.yml +138 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/common/handlers/main.yml +5 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/common/tasks/firewall.yml +66 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/common/tasks/hardening.yml +19 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/common/tasks/main.yml +15 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/common/tasks/packages.yml +38 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/common/tasks/sysctl.yml +14 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/docker/tasks/main.yml +106 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/haproxy/handlers/main.yml +5 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/haproxy/tasks/main.yml +25 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/haproxy/templates/haproxy.cfg.j2 +52 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/output/tasks/main.yml +170 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/output/templates/connection-info.html.j2 +249 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/output/templates/connection-summary-client.txt.j2 +33 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/output/templates/connection-summary.txt.j2 +69 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/output_relay/tasks/main.yml +94 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/output_relay/templates/connection-info.html.j2 +196 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/output_relay/templates/connection-summary.txt.j2 +49 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/shared/tasks/check_qrencode.yml +18 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/shared/tasks/generate_client_output.yml +110 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/shared/tasks/load_credentials.yml +30 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/shared/tasks/resolve_ip.yml +30 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/shared/tasks/verify_xray.yml +31 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/xray/tasks/apply_panel_settings.yml +129 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/xray/tasks/configure_panel.yml +177 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/xray/tasks/configure_reality.yml +141 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/xray/tasks/configure_reality_xhttp.yml +127 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/xray/tasks/configure_wss.yml +91 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/xray/tasks/deploy.yml +97 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/xray/tasks/main.yml +25 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/xray/tasks/maintenance.yml +20 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/xray/templates/docker-compose.yml.j2 +21 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/xray_relay/tasks/configure_inbound.yml +86 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/xray_relay/tasks/configure_outbound.yml +159 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/xray_relay/tasks/configure_panel.yml +76 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/xray_relay/tasks/deploy.yml +73 -0
- meridian_vpn-2.0.0/src/meridian/playbooks/roles/xray_relay/tasks/main.yml +21 -0
- meridian_vpn-2.0.0/src/meridian/servers.py +98 -0
- meridian_vpn-2.0.0/src/meridian/ssh.py +191 -0
- meridian_vpn-2.0.0/src/meridian/update.py +138 -0
- meridian_vpn-2.0.0/tests/__init__.py +0 -0
- meridian_vpn-2.0.0/tests/conftest.py +66 -0
- meridian_vpn-2.0.0/tests/render_templates.py +174 -0
- meridian_vpn-2.0.0/tests/test_ansible.py +66 -0
- meridian_vpn-2.0.0/tests/test_cli.py +81 -0
- meridian_vpn-2.0.0/tests/test_credentials.py +103 -0
- meridian_vpn-2.0.0/tests/test_servers.py +115 -0
- meridian_vpn-2.0.0/tests/test_update.py +59 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Credentials contain all secrets — NEVER commit these
|
|
2
|
+
credentials/
|
|
3
|
+
/meridian/
|
|
4
|
+
vpn-credentials/
|
|
5
|
+
|
|
6
|
+
# Ansible
|
|
7
|
+
*.retry
|
|
8
|
+
|
|
9
|
+
# Python
|
|
10
|
+
*.pyc
|
|
11
|
+
__pycache__/
|
|
12
|
+
*.egg-info/
|
|
13
|
+
dist/
|
|
14
|
+
build/
|
|
15
|
+
venv/
|
|
16
|
+
.venv/
|
|
17
|
+
|
|
18
|
+
# OS
|
|
19
|
+
.DS_Store
|
|
20
|
+
.claude/
|
|
21
|
+
|
|
22
|
+
# Environment
|
|
23
|
+
.env
|
|
24
|
+
*.log
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Backlog
|
|
2
|
+
|
|
3
|
+
**Last updated:** 2026-03-20
|
|
4
|
+
**Version:** 2.0.0
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## P0 — Critical (done)
|
|
9
|
+
|
|
10
|
+
- [x] Replace `eval` with `printf -v` in `prompt()` to prevent code injection
|
|
11
|
+
- [x] Add SHA256 checksum verification to auto-update
|
|
12
|
+
- [x] Make shellcheck blocking in CI (was `|| true`, no-op)
|
|
13
|
+
- [x] Fix auto-update downgrade when running dev version (semver direction check)
|
|
14
|
+
- [x] Fix `eval` injection vector in `prompt()` function
|
|
15
|
+
- [x] **Rewrite CLI in Python** — migrated 1,727-line bash script to modular Python package (typer + rich + PyYAML)
|
|
16
|
+
|
|
17
|
+
## P1 — High (done)
|
|
18
|
+
|
|
19
|
+
- [x] Gate CD and Release workflows on CI success (`workflow_run` chain)
|
|
20
|
+
- [x] Add `body_format` policy check in CI (prevent form-urlencoded on inbound/client APIs)
|
|
21
|
+
- [x] Auto-discover templates in render test (was manual list, missed `update-stats.py.j2`)
|
|
22
|
+
- [x] Add connection-info app link sync check in CI
|
|
23
|
+
- [x] Add Xray health check after inbound creation (catch crash loops)
|
|
24
|
+
- [x] Fix CLI server install for non-root users (sudo mv fallback)
|
|
25
|
+
- [x] Suggest `meridian uninstall` + retry on inbound creation failures
|
|
26
|
+
- [x] Add `apt` fallback for Ansible installation (WSL compat)
|
|
27
|
+
- [x] Retry Ansible collection install up to 3 times (flaky networks)
|
|
28
|
+
- [x] Validate VERSION format in CI — `^\d+\.\d+\.\d+$`
|
|
29
|
+
- [x] Extract YAML parsing into structured credentials — `ServerCredentials` dataclass replaces 10+ `grep|awk|tr` instances
|
|
30
|
+
- [x] Standardize flag parsing — typer decorators replace 8 duplicate `while/case` blocks
|
|
31
|
+
|
|
32
|
+
## P1 — High (open)
|
|
33
|
+
|
|
34
|
+
- [ ] Pin ansible-lint via `gh_action_ref: "v25.5.0"` — `@main` is upstream-recommended but a breaking change could randomly fail CI
|
|
35
|
+
- [ ] Add `ansible.cfg` validation to CI — verify `jinja2_native = True` exists (critical for `body_format: json` integer typing)
|
|
36
|
+
- [ ] Add Docker integration test for 3x-ui API — spin up 3x-ui in CI, run login/create-inbound/list/verify JSON round-trip
|
|
37
|
+
- [ ] Configure PyPI trusted publisher — required for automated PyPI publishing from GitHub Actions
|
|
38
|
+
- [ ] Register `meridian-vpn` on PyPI — publish initial package to reserve name
|
|
39
|
+
|
|
40
|
+
## P2 — Medium (open)
|
|
41
|
+
|
|
42
|
+
- [ ] Add deployed playbook version to diagnostics — write VERSION to `/etc/meridian/version` during deploy, read in `meridian diagnostics` to catch version mismatch
|
|
43
|
+
- [ ] Consolidate 3 connection-info HTML templates into one — biggest drift risk; use `{% if domain_mode %}` / `{% if relay_mode %}` conditionals instead of 3 copies
|
|
44
|
+
- [ ] Add "broke after update" issue template — capture old version, new version, timing, auto-update vs manual
|
|
45
|
+
- [ ] Improve dry-run CI job — remove `|| echo` suppression, use `--tags` for local-compatible tasks, add domain mode and chain mode dry-runs
|
|
46
|
+
- [ ] Add playbook sync automation — script or Makefile to copy root playbooks into `src/meridian/playbooks/` and verify in CI
|
|
47
|
+
- [ ] Add mypy type checking to CI — strict mode on `src/meridian/`
|
|
48
|
+
|
|
49
|
+
## P3 — Low (open)
|
|
50
|
+
|
|
51
|
+
- [ ] Add VERSION semver validation in release workflow (not just CI)
|
|
52
|
+
- [ ] Replace `MockUndefined` in template tests with stricter undefined handling
|
|
53
|
+
- [ ] Add HTML validation for rendered connection-info templates
|
|
54
|
+
- [ ] Add docs/ drift detection in CI — verify `docs/` copies match source files
|
|
55
|
+
- [ ] Add lightweight opt-in telemetry — anonymous ping on setup success (version, OS, mode)
|
|
56
|
+
|
|
57
|
+
## Future — Architecture (open)
|
|
58
|
+
|
|
59
|
+
- [ ] Proactive IP block notification — scheduled reachability check with Telegram/webhook alerts
|
|
60
|
+
- [ ] Self-steal mode — Reality masquerades as your own domain
|
|
61
|
+
- [ ] Zero-to-VPN onboarding wizard on meridian.msu.rocks
|
|
62
|
+
- [ ] Password-protected connection info page for family sharing
|
|
63
|
+
- [ ] Key/credential rotation without full uninstall/reinstall
|
|
64
|
+
- [ ] Shell completion support — typer has built-in completion generation
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
## Project overview
|
|
4
|
+
|
|
5
|
+
Ansible automation for deploying censorship-resistant VLESS+Reality proxy servers. Supports standalone single-server mode and a two-server relay chain for IP whitelist bypass.
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
- **Standalone mode** (`playbook.yml`): Single server with VLESS+Reality. Optional domain mode adds HAProxy (SNI routing), Caddy (TLS), and VLESS+WSS (CDN fallback via Cloudflare).
|
|
10
|
+
- **Chain mode** (`playbook-chain.yml`): Exit node (Germany) + Relay node (Russia on whitelisted IP). User connects to relay via plain VLESS+TCP; relay forwards to exit via VLESS+Reality+XHTTP.
|
|
11
|
+
|
|
12
|
+
### Key design decisions
|
|
13
|
+
|
|
14
|
+
- HAProxy on port 443 does TCP-level SNI routing without TLS termination, so both Reality and Caddy can coexist on port 443.
|
|
15
|
+
- 3x-ui panel is managed entirely via its REST API (no manual web UI steps).
|
|
16
|
+
- Caddy handles TLS automatically — email is optional, not required.
|
|
17
|
+
- Caddy config uses import pattern: Meridian writes to `/etc/caddy/conf.d/meridian.caddy`, main Caddyfile just has `import /etc/caddy/conf.d/*.caddy`. User's own Caddyfile is never overwritten.
|
|
18
|
+
- Credentials are persisted locally in `~/meridian/<host>.yml` for idempotent re-runs. Credentials are saved BEFORE changing the panel password to prevent lockout on failure.
|
|
19
|
+
- Uninstall deletes credentials from both server and local machine to prevent stale state on reinstall.
|
|
20
|
+
- Docker installation is skipped if Docker is already running with containers.
|
|
21
|
+
- In no-domain mode, the panel binds to localhost only (SSH tunnel required).
|
|
22
|
+
- 3x-ui Docker image is pinned to a tested version (`threexui_version` in group_vars) to prevent API breakage.
|
|
23
|
+
- All tasks handling secrets use `no_log: true` to keep credentials out of terminal output.
|
|
24
|
+
|
|
25
|
+
## Project structure
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
pyproject.toml Python package config (hatchling build, PyPI as meridian-vpn)
|
|
29
|
+
VERSION Version source of truth (read by hatchling + importlib.metadata)
|
|
30
|
+
src/meridian/ Python CLI package
|
|
31
|
+
__init__.py Version from importlib.metadata
|
|
32
|
+
__main__.py python -m meridian support
|
|
33
|
+
cli.py Typer app, subcommand registration, global --server flag
|
|
34
|
+
config.py Paths, URLs, constants
|
|
35
|
+
console.py Rich terminal output: info/ok/warn/fail/prompt/confirm
|
|
36
|
+
credentials.py ServerCredentials dataclass, YAML load/save
|
|
37
|
+
servers.py ServerRegistry: list/find/add/remove
|
|
38
|
+
ssh.py ServerConnection: run, check_ssh, detect_local_mode
|
|
39
|
+
ansible.py Playbook execution, Ansible bootstrap, inventory generation
|
|
40
|
+
update.py Auto-update via PyPI (semver: auto-patch, prompt minor/major)
|
|
41
|
+
ai.py AI prompt building, clipboard, doc fetching
|
|
42
|
+
commands/ One module per subcommand
|
|
43
|
+
resolve.py Server resolution logic (shared by most commands)
|
|
44
|
+
setup.py Interactive wizard + playbook execution
|
|
45
|
+
client.py client add/list/remove (list=direct API, add/remove=ansible)
|
|
46
|
+
server.py server add/list/remove
|
|
47
|
+
check.py Pre-flight validation (SNI, ports, DNS, OS, disk, clock)
|
|
48
|
+
scan.py RealiTLScanner download + execution + SNI selection
|
|
49
|
+
ping.py Reachability test (TCP, TLS, clock drift)
|
|
50
|
+
diagnostics.py System info collection + redaction
|
|
51
|
+
uninstall.py Playbook execution + credential cleanup
|
|
52
|
+
playbooks/ Bundled Ansible playbooks (package_data)
|
|
53
|
+
playbook.yml Standalone mode
|
|
54
|
+
playbook-chain.yml Chain mode
|
|
55
|
+
playbook-client.yml Client management
|
|
56
|
+
playbook-uninstall.yml Clean removal
|
|
57
|
+
ansible.cfg Ansible configuration
|
|
58
|
+
requirements.yml Galaxy collections
|
|
59
|
+
group_vars/ Shared defaults
|
|
60
|
+
roles/ All Ansible roles
|
|
61
|
+
playbook.yml Standalone mode (repo root copy, used by CI)
|
|
62
|
+
playbook-chain.yml Chain mode
|
|
63
|
+
playbook-client.yml Client management
|
|
64
|
+
playbook-uninstall.yml Clean removal
|
|
65
|
+
group_vars/ Shared defaults (repo root copy)
|
|
66
|
+
roles/ All Ansible roles (repo root copy)
|
|
67
|
+
install.sh CLI installer (bootstraps uv, installs from PyPI, migrates old bash CLI)
|
|
68
|
+
setup.sh Compat shim → installs CLI + forwards args
|
|
69
|
+
tests/
|
|
70
|
+
test_cli.py CliRunner smoke tests
|
|
71
|
+
test_credentials.py Credential dataclass tests
|
|
72
|
+
test_servers.py Server registry tests
|
|
73
|
+
test_ansible.py Inventory generation + playbook bundling tests
|
|
74
|
+
test_update.py Version comparison + update throttle tests
|
|
75
|
+
render_templates.py Jinja2 template rendering test (with Ansible filter mocks)
|
|
76
|
+
conftest.py Shared fixtures
|
|
77
|
+
docs/index.html Website hosted on meridian.msu.rocks (GitHub Pages)
|
|
78
|
+
docs/install.sh Installer served from website (CD sync)
|
|
79
|
+
docs/setup.sh Compat shim served from website (CD sync)
|
|
80
|
+
docs/version Version file served from website (CD sync)
|
|
81
|
+
docs/ping.html Web-based ping tool (server reachability test)
|
|
82
|
+
docs/CNAME Custom domain for GitHub Pages
|
|
83
|
+
.github/workflows/ci.yml CI: ansible-lint, pytest, ruff, shellcheck, dry-run
|
|
84
|
+
.github/workflows/cd.yml CD: sync install.sh/setup.sh/version → docs/
|
|
85
|
+
.github/workflows/release.yml Release: git tag + GitHub Release + PyPI publish
|
|
86
|
+
.ansible-lint Ansible lint configuration
|
|
87
|
+
SECURITY.md Vulnerability reporting policy
|
|
88
|
+
CONTRIBUTING.md Development setup and PR guidelines
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Implicit dependencies & cross-file relationships
|
|
92
|
+
|
|
93
|
+
These are easy to break by editing one file without updating the others:
|
|
94
|
+
|
|
95
|
+
### CLI architecture
|
|
96
|
+
- `meridian` CLI is a Python package (`meridian-vpn` on PyPI), installed via `uv tool install` or `pipx install`
|
|
97
|
+
- Built with typer (CLI framework) + rich (terminal output) + PyYAML (credential management)
|
|
98
|
+
- Data directory: `~/.meridian/` (credentials, cache, servers, inventory)
|
|
99
|
+
- Playbooks bundled inside the Python package (`src/meridian/playbooks/`), accessed via `importlib.resources`
|
|
100
|
+
- Credentials cached locally in `~/.meridian/credentials/<IP>/` (per-server subdirectories)
|
|
101
|
+
- Server as source of truth: credentials stored at `/etc/meridian/` on the server (synced by playbook post_tasks)
|
|
102
|
+
- Server index: `~/.meridian/servers` (line-oriented: `host user name`, no spaces in names)
|
|
103
|
+
- Auto-update checks PyPI JSON API (throttled to 1x/60s); auto-patches via `uv tool upgrade` / `pipx upgrade`
|
|
104
|
+
- `VERSION` file at repo root is the single source of truth — read by hatchling at build time, by `importlib.metadata` at runtime
|
|
105
|
+
- `setup.sh` is a compat shim that installs the CLI and forwards args
|
|
106
|
+
- Ansible is NOT a Python dependency — installed lazily via `ensure_ansible()` (pipx → pip3 → apt cascade)
|
|
107
|
+
|
|
108
|
+
### meridian CLI ↔ playbooks
|
|
109
|
+
- CLI passes `-e server_public_ip=... -e credentials_dir=...` to ansible-playbook via `ansible.run_playbook()`
|
|
110
|
+
- All output templates use `{{ server_public_ip }}` instead of `{{ ansible_host }}` for user-facing URLs
|
|
111
|
+
- CLI writes `inventory.yml` to `~/.meridian/inventory.yml` (not inside package dir — may be read-only)
|
|
112
|
+
- CLI sets `ANSIBLE_CONFIG` env var pointing to bundled `ansible.cfg`, runs with `cwd=playbooks_dir`
|
|
113
|
+
- CLI adds `ansible_connection: local` when running on the target server itself
|
|
114
|
+
- CLI adds `ansible_become: true` for non-root users
|
|
115
|
+
- Playbook post_tasks sync `credentials_dir` to `/etc/meridian/` on the server (unless already local)
|
|
116
|
+
- Playbooks exist in two places: repo root (for CI/development) and `src/meridian/playbooks/` (bundled in package)
|
|
117
|
+
|
|
118
|
+
### meridian subcommands
|
|
119
|
+
- `meridian setup [IP] [--domain --sni --xhttp --name --user --yes]` — deploy server
|
|
120
|
+
- `meridian client add|list|remove NAME` — manage clients via `playbook-client.yml`
|
|
121
|
+
- `meridian server add|list|remove` — manage known servers
|
|
122
|
+
- `meridian check [IP] [--ai]` — pre-flight validation (SNI, ports, DNS, OS, disk, ASN)
|
|
123
|
+
- `meridian scan [IP]` — find optimal SNI targets via RealiTLScanner on server
|
|
124
|
+
- `meridian ping [IP]` — test proxy reachability from client device (no SSH needed)
|
|
125
|
+
- `meridian diagnostics [IP] [--ai]` — collect system info for bug reports
|
|
126
|
+
- `meridian uninstall [IP]` — remove proxy via `playbook-uninstall.yml`
|
|
127
|
+
- `meridian self-update` — update CLI via `uv tool upgrade` / `pipx upgrade`
|
|
128
|
+
- `meridian version` — show version
|
|
129
|
+
|
|
130
|
+
### docs/index.html ↔ meridian CLI
|
|
131
|
+
- Website command builder has tabbed interface generating `meridian` subcommands
|
|
132
|
+
- Install files served from `meridian.msu.rocks/` — synced by CD workflow: `install.sh`, `setup.sh`, `version`
|
|
133
|
+
- CLI itself distributed via PyPI (`meridian-vpn`), not served from website
|
|
134
|
+
- Website references the same app download links as the HTML templates in roles
|
|
135
|
+
- `docs/ping.html` — standalone web ping tool, uses `fetch()` timing to test server reachability from browser. Supports URL params (`?ip=...&domain=...&name=...`) for shareable pre-filled links. Stores server history in localStorage.
|
|
136
|
+
|
|
137
|
+
### Connection info HTML templates (3 copies)
|
|
138
|
+
- `roles/caddy/templates/connection-info.html.j2` — served on the server (domain mode)
|
|
139
|
+
- `roles/output/templates/connection-info.html.j2` — saved locally (standalone/exit)
|
|
140
|
+
- `roles/output_relay/templates/connection-info.html.j2` — saved locally (relay)
|
|
141
|
+
- All three have similar CSS/JS but different Jinja2 variables; app download links must be kept in sync across all three
|
|
142
|
+
|
|
143
|
+
### Credential flow
|
|
144
|
+
- Server is source of truth: `/etc/meridian/proxy.yml` on the server
|
|
145
|
+
- Local cache: `~/.meridian/credentials/<IP>/proxy.yml` per server
|
|
146
|
+
- `meridian` CLI passes `credentials_dir=$HOME/.meridian/credentials/$SERVER_IP` (remote) or `/etc/meridian` (local mode)
|
|
147
|
+
- `roles/xray/tasks/configure_panel.yml` saves to `{{ credentials_file }}` which is `{{ credentials_dir }}/{{ inventory_hostname }}.yml`
|
|
148
|
+
- `roles/xray/tasks/configure_panel.yml` also creates `{{ credentials_dir }}/{{ inventory_hostname }}-clients.yml` with the first client
|
|
149
|
+
- `playbook.yml` and `playbook-client.yml` post_tasks sync `credentials_dir` to `/etc/meridian/` on the server
|
|
150
|
+
- `playbook-chain.yml` relay play loads EXIT node credentials from `{{ credentials_dir }}/{{ exit_node }}.yml`
|
|
151
|
+
- Domain is saved to credentials file for detection on re-runs
|
|
152
|
+
- CLI reads saved credentials to find the server IP (for client/uninstall/diagnostics commands)
|
|
153
|
+
- CLI fetches credentials from `/etc/meridian/` via SSH when not found locally (handles cross-machine runs)
|
|
154
|
+
- `meridian server add IP` fetches credentials from server, caches locally
|
|
155
|
+
|
|
156
|
+
### Client management flow
|
|
157
|
+
- `playbook-client.yml` loads credentials from `proxy.yml`, reads `domain` field to detect domain mode
|
|
158
|
+
- Client names map to 3x-ui `email` fields: `reality-{name}`, `wss-{name}`, `xhttp-{name}` (e.g., `reality-alice`, `wss-alice`, `xhttp-alice`)
|
|
159
|
+
- The first client created during install uses `reality-{{ first_client_name | default('default') }}` — same naming convention
|
|
160
|
+
- Clients are tracked in `{{ credentials_dir }}/{{ inventory_hostname }}-clients.yml` with UUIDs and timestamps
|
|
161
|
+
- `roles/output/tasks/generate_client_output.yml` is shared between the `output` role and `client_management` role
|
|
162
|
+
- 3x-ui API: `addClient` adds to existing inbound (id in form body), `delClient/{uuid}` removes by client UUID (NOT email — email silently succeeds but doesn't delete)
|
|
163
|
+
- `--add-client`/`--remove-client` resolve server IP from saved credentials (same as `--uninstall`)
|
|
164
|
+
|
|
165
|
+
### Caddy config pattern
|
|
166
|
+
- Meridian writes to `/etc/caddy/conf.d/meridian.caddy` (not the main Caddyfile)
|
|
167
|
+
- Main Caddyfile gets a single `import /etc/caddy/conf.d/*.caddy` line added via `lineinfile`
|
|
168
|
+
- Uninstall removes only `/etc/caddy/conf.d/meridian.caddy`, not the user's Caddyfile
|
|
169
|
+
- `meridian setup` interactive wizard checks saved credentials for domain suggestion
|
|
170
|
+
|
|
171
|
+
### Port 443 pre-check allowlist
|
|
172
|
+
- `roles/xray/tasks/deploy.yml` checks port 443 and allows `3x-ui`, `xray`, `haproxy`, `caddy` — if a new service is added to the stack, add it here too
|
|
173
|
+
|
|
174
|
+
### Panel health check URL
|
|
175
|
+
- After first run, the panel root `/` returns 404 (webBasePath is set) — health check accepts 404 as "responsive"
|
|
176
|
+
- Post-restart health checks in `configure_panel.yml` use `/{{ panel_web_base_path }}/` (guaranteed set at that point)
|
|
177
|
+
- The panel needs a `docker restart` after changing `webBasePath` (setting doesn't apply live)
|
|
178
|
+
|
|
179
|
+
### Xray binary path
|
|
180
|
+
- Binary is at `/app/bin/xray-linux-*` inside the 3x-ui container (architecture-dependent)
|
|
181
|
+
- Discovered dynamically via `ls` glob — stored in `xray_cmd` fact
|
|
182
|
+
- Used in both `roles/xray/tasks/configure_panel.yml` and `roles/xray_relay/tasks/configure_panel.yml`
|
|
183
|
+
- x25519 output format: `PrivateKey:` and `Password:` (not `Private key:` / `Public key:` in newer Xray versions)
|
|
184
|
+
- Parsing uses regex with both old and new format patterns; assertion verifies keys were parsed
|
|
185
|
+
|
|
186
|
+
### Handler flush timing
|
|
187
|
+
- `roles/output/tasks/main.yml` calls `meta: flush_handlers` before the port verification check
|
|
188
|
+
- Without this, HAProxy/Caddy handlers haven't fired yet and port 443 shows as not listening
|
|
189
|
+
|
|
190
|
+
### Terminal output rendering
|
|
191
|
+
- QR codes and connection summary use `ansible.builtin.shell` with `printf`/`cat` instead of `debug msg:` — this is required because Ansible's debug module JSON-escapes ANSI codes, making QR codes unreadable
|
|
192
|
+
- The `ansible.cfg` must NOT have `result_format = yaml` for the same reason
|
|
193
|
+
|
|
194
|
+
### Feedback loop
|
|
195
|
+
- `fail()` in meridian CLI suggests `meridian diagnostics` and links to GitHub issues
|
|
196
|
+
- Success output mentions feedback URL
|
|
197
|
+
- Ansible connection summary includes feedback section
|
|
198
|
+
- Website has troubleshooting with `meridian check` and `meridian diagnostics` commands
|
|
199
|
+
- README has troubleshooting section
|
|
200
|
+
|
|
201
|
+
## Key API patterns
|
|
202
|
+
|
|
203
|
+
- 3x-ui login: `POST /login` (form-urlencoded) returns session cookie. Login MUST use form-urlencoded (not JSON).
|
|
204
|
+
- Add inbound: `POST /panel/api/inbounds/add` (JSON body with `body_format: json`). The `settings`, `streamSettings`, `sniffing` fields must be JSON **strings** (not nested objects). The Go struct uses `string` type for these fields. With `body_format: json` + `jinja2_native = True`, the `>-` YAML blocks containing mixed text+Jinja2 expressions remain strings (Jinja2 NativeEnvironment only returns native types for single-expression templates like `{{ x }}`, not mixed content).
|
|
205
|
+
- List inbounds: `GET /panel/api/inbounds/list` (check by remark before creating)
|
|
206
|
+
- **CRITICAL: Do NOT use `body_format: form-urlencoded` for inbound/client API calls.** Ansible's uri module silently corrupts inline JSON values in form-urlencoded bodies — the API returns `success: true` but stores only the first key name instead of the full JSON object. This was a production bug. Always use `body_format: json` for inbound operations.
|
|
207
|
+
- 3x-ui rejects duplicate ports — two inbounds cannot share the same port. XHTTP needs its own dedicated port separate from Reality TCP.
|
|
208
|
+
- Update settings: `POST /panel/setting/update` (JSON body)
|
|
209
|
+
- Update credentials: `POST /panel/setting/updateUser` (JSON body)
|
|
210
|
+
- Read settings: `POST /panel/setting/all`
|
|
211
|
+
- Xray template config: set `xrayTemplateConfig` field in `/panel/setting/update`
|
|
212
|
+
- Key generation: `docker exec 3x-ui sh -c '/app/bin/xray-linux-* x25519'` (parse PrivateKey/Password lines), `docker exec 3x-ui sh -c '/app/bin/xray-linux-* uuid'`
|
|
213
|
+
|
|
214
|
+
## Build and test
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# Install CLI in editable mode with dev dependencies
|
|
218
|
+
pip install -e ".[dev]"
|
|
219
|
+
|
|
220
|
+
# Run Python tests
|
|
221
|
+
pytest tests/ -v
|
|
222
|
+
|
|
223
|
+
# Run linter
|
|
224
|
+
ruff check src/ tests/
|
|
225
|
+
ruff format --check src/ tests/
|
|
226
|
+
|
|
227
|
+
# Run template rendering test
|
|
228
|
+
python3 tests/render_templates.py
|
|
229
|
+
|
|
230
|
+
# Install Ansible for playbook validation
|
|
231
|
+
pip install ansible
|
|
232
|
+
ansible-galaxy collection install -r requirements.yml
|
|
233
|
+
|
|
234
|
+
# Run standalone
|
|
235
|
+
ansible-playbook playbook.yml
|
|
236
|
+
|
|
237
|
+
# Run chain mode
|
|
238
|
+
cp inventory-chain.yml.example inventory-chain.yml
|
|
239
|
+
ansible-playbook -i inventory-chain.yml playbook-chain.yml
|
|
240
|
+
|
|
241
|
+
# The playbook verifies ports are listening at the end.
|
|
242
|
+
# To fully test, import the VLESS URL into v2rayNG and check connectivity.
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
## Conventions
|
|
247
|
+
|
|
248
|
+
- All tasks use FQCNs (e.g., `ansible.builtin.uri`, not `uri`)
|
|
249
|
+
- API responses are validated with `ansible.builtin.assert` checking `.json.success`
|
|
250
|
+
- Tasks handling secrets use `no_log: true`
|
|
251
|
+
- Conditional roles use `include_tasks` (not `import_tasks`) with `when`
|
|
252
|
+
- Port variables use deterministic random seeds (`inventory_hostname + suffix`) for idempotency
|
|
253
|
+
- QR codes generated with `qrencode` — must be installed on the local machine
|
|
254
|
+
- Cross-platform: `base64 | tr -d '\n'` instead of `base64 -w0` (macOS compat on localhost tasks; remote tasks can use `-w0` since remote is always Linux)
|
|
255
|
+
- XHTTP transport doesn't support `xtls-rprx-vision` flow (must be empty string)
|
|
256
|
+
- Docker compose errors include `rescue` blocks with container logs and common fix suggestions
|
|
257
|
+
- DNS resolution check for domain mode fails hard (override with `-e skip_dns_check=true`)
|
|
258
|
+
- Use `ansible_facts['distribution']` not `ansible_distribution` (deprecated in 2.24)
|
|
259
|
+
- Docker role removes conflicting `docker.io` / `containerd` / `runc` packages only when `docker-ce` is not already installed AND no containers are running
|
|
260
|
+
- `reality_dest` is derived from `reality_sni` (`{{ reality_sni }}:443`) — don't hardcode separately
|
|
261
|
+
- **SNI target selection**: Never recommend apple.com or icloud.com (Apple-owned ASN — mismatch with VPS hosting is instantly detectable). Good choices: www.microsoft.com, www.twitch.tv, dl.google.com, github.com (global CDN, shared infrastructure). Best: run `meridian scan` for same-network targets.
|
|
262
|
+
- **Always use context7 MCP to check up-to-date docs** before writing or modifying code that depends on external tools/libraries (Ansible, Docker, Caddy, GitHub Actions, shellcheck, etc.) — stale patterns and outdated common knowledge cause real deployment failures. Don't rely on training data for API syntax, CLI flags, or workflow configuration — verify against current docs first.
|
|
263
|
+
- **curl|bash stdin trap**: in `install.sh` and `setup.sh` (compat shim), any command that reads stdin MUST have `</dev/null` — the `meridian` CLI runs directly so this isn't needed there, but `</dev/null` on SSH commands is still good practice
|
|
264
|
+
- **Ansible debug vs shell for terminal output**: use `shell` with `printf`/`cat` for output containing ANSI codes (QR codes); `debug msg:` JSON-escapes them
|
|
265
|
+
- **pip3 install on modern Debian/Ubuntu**: must handle PEP 668 "externally managed environment" — try pipx, then `--user`, then `--break-system-packages`, then apt
|
|
266
|
+
- **pip user bin PATH**: after pip3 install --user, add `~/.local/bin` (Linux) and `~/Library/Python/*/bin` (macOS) to PATH
|
|
267
|
+
- **meridian interactive prompts**: `console.prompt()` reads from `/dev/tty` for pipe safety; detect public IPv4 with `curl -4` to avoid IPv6; suggest domain from saved credentials
|
|
268
|
+
- **console output functions**: `info()`, `ok()`, `warn()`, `fail()` use Rich markup — pass plain text, not ANSI codes. `fail()` raises `typer.Exit(1)` and is testable with CliRunner.
|
|
269
|
+
- **GitHub raw CDN caching**: raw.githubusercontent.com caches for ~60-120s; can't bust with query params or headers, just wait. Serving from meridian.msu.rocks avoids this.
|
|
270
|
+
- **HAProxy health checks**: do NOT use `check` on TLS backends (Caddy, Xray) — TCP probes fail on TLS-only ports, causing "backend has no server available" errors. These are local systemd services, not load-balanced pools.
|
|
271
|
+
- **docs/ sync**: `docs/install.sh`, `docs/setup.sh`, `docs/version` are synced by CD workflow. Manual edits to docs/ copies will be overwritten. CLI is no longer synced to docs/ (distributed via PyPI).
|
|
272
|
+
- **AI docs**: Source files in `docs/ai/` (`context.md`, `architecture.md`, `troubleshooting.md`). CD workflow concatenates them into `docs/ai/reference.md` — the single file the CLI fetches. Edit the source files, not `reference.md`.
|
|
273
|
+
- **`--ai` flag**: `meridian check --ai` and `meridian diagnostics --ai` bundle AI docs + command output into a clipboard-ready prompt for ChatGPT/Claude. Docs are cached at `~/.meridian/cache/ai-reference.md`, invalidated on version change.
|
|
274
|
+
- **VERSION consistency**: `VERSION` file is the single source of truth. Hatchling reads it at build time; Python code uses `importlib.metadata.version("meridian-vpn")` at runtime. CI validates VERSION format (`^\d+\.\d+\.\d+$`).
|
|
275
|
+
- **Ecosystem cross-promotion**: Always upsell/reference related Meridian tools where contextually appropriate. When adding error messages, output, templates, issue templates, or docs — promote the relevant tool for that context. Current tools to cross-promote:
|
|
276
|
+
- `meridian ping` / `meridian.msu.rocks/ping` — for connection issues, reachability testing
|
|
277
|
+
- `meridian check` — for pre-deployment server validation
|
|
278
|
+
- `meridian diagnostics` — for bug reports and server-side debugging
|
|
279
|
+
- `meridian.msu.rocks` — for docs, setup guides, command builder
|
|
280
|
+
- Connection info pages — for end-user onboarding and troubleshooting
|
|
281
|
+
- The pattern: error/failure messages → suggest ping first (network issue?), then diagnostics (server issue?), then GitHub issues (bug?)
|
|
282
|
+
- **Context-sensitive upsells**: don't blindly suggest every tool — only suggest the tool that helps for the specific failure mode. Example: if ping shows port 443 is blocked, suggesting `meridian diagnostics` doesn't help (it's a firewall issue). But if ping passes and VPN still fails, diagnostics is the right next step.
|
|
283
|
+
- **Pre-fill URLs with known data**: when server IP and domain are available in context (templates, CLI output), always generate pre-filled `meridian.msu.rocks/ping?ip=...&domain=...` URLs so users land on a ready-to-run test.
|
|
284
|
+
- **CLI installation**: installed via `uv tool install meridian-vpn` (preferred) or `pipx install meridian-vpn`. Entry point is `meridian`. Location depends on the tool (typically `~/.local/bin/`).
|
|
285
|
+
- **Auto-update**: checks PyPI JSON API for latest version. Auto-upgrades patches via `uv tool upgrade` / `pipx upgrade`, prompts for minor/major. Uses `os.execvp()` to re-exec after auto-patch.
|
|
286
|
+
- **Auto-update direction**: only updates when remote version is strictly newer (`packaging.version.Version` comparison). Running a local dev build with a higher version will NOT trigger a downgrade.
|
|
287
|
+
- **`jinja2_native = True` in ansible.cfg**: required for `body_format: json` to send native integer types (e.g., `port`). Safe with mixed text+expression templates (`settings: >-` blocks) because Jinja2 NativeEnvironment only returns native types for single-expression templates. Do NOT remove without an alternative solution for integer typing.
|
|
288
|
+
- **`client list` bypasses Ansible**: uses direct curl + native Python JSON parsing for instant results instead of running a full playbook. `client add` and `client remove` still use Ansible playbooks because they modify state and benefit from idempotency.
|
|
289
|
+
- **Playbook bundling**: playbooks exist in two places — repo root (for CI/dev) and `src/meridian/playbooks/` (bundled in package). When editing playbooks, update both copies. A sync script or CI check should enforce this.
|
|
290
|
+
- **Credential management**: `ServerCredentials` dataclass in `credentials.py` replaces all `grep|awk|tr` YAML parsing. Handles special characters, preserves unknown fields, type-safe access.
|
|
291
|
+
- **When the user says "remember"**: save the instruction to this CLAUDE.md file so it persists across sessions. Don't use auto-memory — CLAUDE.md is the canonical place for project conventions.
|
|
292
|
+
|
|
293
|
+
## Documentation surfaces & update checklist
|
|
294
|
+
|
|
295
|
+
When adding or changing a feature, update ALL relevant surfaces. The source of truth for each type of information is marked with ★.
|
|
296
|
+
|
|
297
|
+
### Sources of truth
|
|
298
|
+
|
|
299
|
+
| Information | Source of Truth | Propagated To |
|
|
300
|
+
|---|---|---|
|
|
301
|
+
| **CLI commands & flags** | ★ `src/meridian/cli.py` (typer commands) | README.md commands table, docs/index.html builder, docs/ai/context.md CLI section, CLAUDE.md subcommands |
|
|
302
|
+
| **Architecture & modes** | ★ `CLAUDE.md` architecture section | docs/ai/architecture.md, docs/ai/context.md, README.md "How it works" |
|
|
303
|
+
| **SNI recommendations** | ★ `group_vars/all.yml` comments | docs/ai/troubleshooting.md SNI section, roles/output/templates/connection-summary.txt.j2 |
|
|
304
|
+
| **Troubleshooting guidance** | ★ `docs/ai/troubleshooting.md` | docs/index.html troubleshooting section, connection_issue.yml template |
|
|
305
|
+
| **App download links** | ★ `docs/index.html` apps section | 3x connection-info.html.j2 templates, README.md client apps table |
|
|
306
|
+
| **Version** | ★ `VERSION` file | `importlib.metadata` at runtime (hatchling reads VERSION at build), docs/version (CD sync) |
|
|
307
|
+
| **Error/failure guidance** | ★ `meridian` script `fail()` function | docs/ai/troubleshooting.md decision tree |
|
|
308
|
+
|
|
309
|
+
### Surface update checklist
|
|
310
|
+
|
|
311
|
+
When adding a **new subcommand**:
|
|
312
|
+
- [ ] `src/meridian/commands/newcmd.py` — implement `run()` function
|
|
313
|
+
- [ ] `src/meridian/cli.py` — add typer command + import
|
|
314
|
+
- [ ] `tests/test_cli.py` — add help smoke test
|
|
315
|
+
- [ ] `README.md` — add to commands table
|
|
316
|
+
- [ ] `docs/index.html` — add tab to command builder (if user-facing)
|
|
317
|
+
- [ ] `docs/ai/context.md` — add to CLI Commands section
|
|
318
|
+
- [ ] `CLAUDE.md` — add to meridian subcommands list
|
|
319
|
+
- [ ] Regenerate `docs/ai/reference.md` (cat context + architecture + troubleshooting)
|
|
320
|
+
|
|
321
|
+
When adding a **new flag to setup**:
|
|
322
|
+
- [ ] `src/meridian/commands/setup.py` — add parameter + logic
|
|
323
|
+
- [ ] `src/meridian/cli.py` — add `typer.Option` to setup_cmd
|
|
324
|
+
- [ ] `docs/index.html` — add checkbox/input to setup command builder
|
|
325
|
+
- [ ] `docs/ai/context.md` — add to setup flags list
|
|
326
|
+
- [ ] `CLAUDE.md` — update subcommands line
|
|
327
|
+
- [ ] `group_vars/all.yml` — add default variable with comment
|
|
328
|
+
- [ ] Regenerate `docs/ai/reference.md`
|
|
329
|
+
|
|
330
|
+
When adding a **new inbound/transport type**:
|
|
331
|
+
- [ ] `roles/xray/tasks/` — create/update inbound task
|
|
332
|
+
- [ ] `roles/xray/tasks/main.yml` — add include gate
|
|
333
|
+
- [ ] `roles/shared/tasks/generate_client_output.yml` — VLESS URL + QR codes
|
|
334
|
+
- [ ] `roles/output/tasks/main.yml` — terminal output (QR + URL display)
|
|
335
|
+
- [ ] `roles/output/templates/connection-summary.txt.j2` — admin summary
|
|
336
|
+
- [ ] `roles/output/templates/connection-summary-client.txt.j2` — client summary
|
|
337
|
+
- [ ] `roles/output/templates/connection-info.html.j2` — local HTML page
|
|
338
|
+
- [ ] `roles/caddy/templates/connection-info.html.j2` — server HTML page (+ QR gen in caddy/tasks/main.yml)
|
|
339
|
+
- [ ] `roles/caddy/tasks/main.yml` — build URL + generate QR on server
|
|
340
|
+
- [ ] `roles/client_management/tasks/main.yml` — discover inbound
|
|
341
|
+
- [ ] `roles/client_management/tasks/add_client.yml` — add client to inbound + terminal output
|
|
342
|
+
- [ ] `roles/client_management/tasks/remove_client.yml` — remove client from inbound
|
|
343
|
+
- [ ] `roles/xray/tasks/configure_panel.yml` — save setting to credentials
|
|
344
|
+
- [ ] `tests/render_templates.py` — add mock variables (templates are auto-discovered)
|
|
345
|
+
- [ ] `docs/ai/context.md` — update port table and architecture
|
|
346
|
+
- [ ] `docs/ai/architecture.md` — update topology diagrams
|
|
347
|
+
- [ ] Regenerate `docs/ai/reference.md`
|
|
348
|
+
|
|
349
|
+
When changing **SNI recommendations**:
|
|
350
|
+
- [ ] `group_vars/all.yml` — update "Good choices" / "Avoid" comments
|
|
351
|
+
- [ ] `roles/output/templates/connection-summary.txt.j2` — update alternatives line
|
|
352
|
+
- [ ] `docs/ai/troubleshooting.md` — update SNI Target Selection section
|
|
353
|
+
- [ ] Regenerate `docs/ai/reference.md`
|
|
354
|
+
|
|
355
|
+
When changing **troubleshooting/error guidance**:
|
|
356
|
+
- [ ] `docs/ai/troubleshooting.md` — update symptom/fix sections
|
|
357
|
+
- [ ] `docs/index.html` — update troubleshooting details section
|
|
358
|
+
- [ ] `.github/ISSUE_TEMPLATE/connection_issue.yml` — update pre-report checklist
|
|
359
|
+
- [ ] Regenerate `docs/ai/reference.md`
|
|
360
|
+
|
|
361
|
+
### Current inconsistencies to fix (tracked)
|
|
362
|
+
|
|
363
|
+
All previously tracked inconsistencies have been resolved:
|
|
364
|
+
- ~~`docs/index.html` command builder~~ — added `scan` tab, `--xhttp` checkbox, `--ai` mention, SNI guidance
|
|
365
|
+
- ~~`bug_report.yml` --rage~~ → fixed to "diagnostics output"
|
|
366
|
+
- ~~`CONTRIBUTING.md` --rage~~ → fixed to `meridian diagnostics`
|
|
367
|
+
- ~~`SECURITY.md` --rage~~ → fixed to `meridian diagnostics`
|
|
368
|
+
- ~~`README.md` missing scan/xhttp~~ → added to commands table
|
|
369
|
+
- i18n translations extracted to `docs/i18n.js` (index.html reduced from 805 to ~620 lines)
|
|
370
|
+
|
|
371
|
+
## CI/CD pipelines
|
|
372
|
+
|
|
373
|
+
**Pipeline chain:** push → CI → CD (on CI success) → Release (on CD success)
|
|
374
|
+
|
|
375
|
+
### CI (`.github/workflows/ci.yml`) — runs on push and PR
|
|
376
|
+
- **Lint**: `ansible/ansible-lint@main` — catches FQCN, YAML style, deprecated patterns
|
|
377
|
+
- **Python Test**: `pytest` on Python 3.10 + 3.12 — credentials, servers, CLI, ansible, update logic
|
|
378
|
+
- **Python Lint**: `ruff check` + `ruff format --check` — style, imports, formatting
|
|
379
|
+
- **Validate**: syntax check all playbooks + auto-discovered template rendering test with Ansible filter mocks
|
|
380
|
+
- **Shell**: `bash -n` + shellcheck on `install.sh` and `setup.sh` + VERSION format validation + `body_format` policy check + connection-info app link sync check
|
|
381
|
+
- **Dry run**: `ansible-playbook --check` with local connection (validates task structure)
|
|
382
|
+
- Skipped lint rules: `var-naming[no-role-prefix]` (would require renaming 90+ variables), `command-instead-of-module` (curl/dig used intentionally)
|
|
383
|
+
|
|
384
|
+
### CD (`.github/workflows/cd.yml`) — triggers after CI succeeds (workflow_run)
|
|
385
|
+
- Syncs `install.sh` → `docs/install.sh`, `setup.sh` → `docs/setup.sh`, `VERSION` → `docs/version`
|
|
386
|
+
- Generates `SHA256SUMS` checksum file for install scripts
|
|
387
|
+
- Auto-commits with `[skip ci]`
|
|
388
|
+
- Ensures `meridian.msu.rocks/` always serves the latest installer and version
|
|
389
|
+
|
|
390
|
+
## Versioning & releases
|
|
391
|
+
|
|
392
|
+
### Semver: X.Y.Z
|
|
393
|
+
|
|
394
|
+
| Bump | When | User experience |
|
|
395
|
+
|------|------|----------------|
|
|
396
|
+
| **Z** (patch) | Bug fixes, docs, safe tweaks | Auto-updated silently (next CLI run) |
|
|
397
|
+
| **Y** (minor) | New features, opt-in changes (e.g., `--xhttp`, `scan`) | Prompted: "Update available", user runs `self-update` |
|
|
398
|
+
| **X** (major) | Breaking changes, defaults change | Prompted: "Major update available", user runs `self-update` |
|
|
399
|
+
|
|
400
|
+
### How to release
|
|
401
|
+
|
|
402
|
+
1. Update `VERSION` file with the new version
|
|
403
|
+
2. Commit and push to main → three workflows trigger in chain:
|
|
404
|
+
- **CI**: validates VERSION format, runs pytest + ruff + ansible-lint
|
|
405
|
+
- **CD**: syncs install files to GitHub Pages (`meridian.msu.rocks/version`)
|
|
406
|
+
- **Release**: creates git tag `vX.Y.Z` + GitHub Release + publishes to PyPI
|
|
407
|
+
3. Users on auto-patch get the update on next CLI run; others see "Update available" prompt
|
|
408
|
+
|
|
409
|
+
### When to bump (guidance for Claude)
|
|
410
|
+
|
|
411
|
+
After completing a feature or fix, **always bump the version as part of the commit workflow**:
|
|
412
|
+
- Fixed a bug or updated docs? → Bump Z (patch): `1.1.0` → `1.1.1`
|
|
413
|
+
- Added a new command, flag, or transport? → Bump Y (minor): `1.1.0` → `1.2.0`
|
|
414
|
+
- Changed defaults or broke backward compat? → Bump X (major): `1.2.0` → `2.0.0`
|
|
415
|
+
|
|
416
|
+
**Do NOT skip version bumps.** Every meaningful change to the CLI or playbooks should get a version bump (just edit the `VERSION` file) so users on auto-patch get fixes and users on manual update see the prompt. If multiple features are in one session, one version bump at the end is fine.
|
|
417
|
+
|
|
418
|
+
### Release artifacts
|
|
419
|
+
|
|
420
|
+
- **PyPI package**: `meridian-vpn` on PyPI (published by release workflow)
|
|
421
|
+
- **Version file**: `meridian.msu.rocks/version` (CD sync from VERSION)
|
|
422
|
+
- **Installer**: `meridian.msu.rocks/install.sh` (CD sync)
|
|
423
|
+
- **GitHub Release**: auto-created by `.github/workflows/release.yml` when VERSION changes
|
|
424
|
+
|
|
425
|
+
CI validates VERSION format (`^\d+\.\d+\.\d+$`) on every push.
|
|
426
|
+
|
|
427
|
+
## Backlog & tech debt
|
|
428
|
+
|
|
429
|
+
See `BACKLOG.md` for the full prioritized task list with completion status.
|
|
430
|
+
|
|
431
|
+
## GitHub community files
|
|
432
|
+
|
|
433
|
+
- `.github/ISSUE_TEMPLATE/bug_report.yml` — structured bug report with diagnostics prompt
|
|
434
|
+
- `.github/ISSUE_TEMPLATE/connection_issue.yml` — connection troubleshooting with --check prompt
|
|
435
|
+
- `.github/ISSUE_TEMPLATE/feature_request.yml` — feature requests by area
|
|
436
|
+
- `.github/ISSUE_TEMPLATE/config.yml` — disables blank issues, links to docs
|
|
437
|
+
- `SECURITY.md` — vulnerability reporting policy and security design overview
|
|
438
|
+
- `CONTRIBUTING.md` — development setup, PR guidelines, testing approach
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Contributing to Meridian
|
|
2
|
+
|
|
3
|
+
Thanks for your interest! Here's how to help.
|
|
4
|
+
|
|
5
|
+
## Reporting Issues
|
|
6
|
+
|
|
7
|
+
- **Bug?** Use the [bug report template](https://github.com/uburuntu/meridian/issues/new?template=bug_report.yml) — run `meridian diagnostics` first to collect info
|
|
8
|
+
- **Connection not working?** Use the [connection issue template](https://github.com/uburuntu/meridian/issues/new?template=connection_issue.yml) — run `meridian ping` and `meridian check` first
|
|
9
|
+
- **Feature idea?** Use the [feature request template](https://github.com/uburuntu/meridian/issues/new?template=feature_request.yml)
|
|
10
|
+
- **Security vulnerability?** See [SECURITY.md](SECURITY.md) — do NOT open a public issue
|
|
11
|
+
|
|
12
|
+
## Development Setup
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
git clone https://github.com/uburuntu/meridian.git && cd meridian
|
|
16
|
+
|
|
17
|
+
# Install the CLI in editable mode with dev dependencies
|
|
18
|
+
pip install -e ".[dev]"
|
|
19
|
+
|
|
20
|
+
# Install Ansible (for playbook validation)
|
|
21
|
+
pip install ansible
|
|
22
|
+
ansible-galaxy collection install -r requirements.yml
|
|
23
|
+
|
|
24
|
+
# Run tests
|
|
25
|
+
pytest tests/ -v
|
|
26
|
+
|
|
27
|
+
# Run linter
|
|
28
|
+
ruff check src/ tests/
|
|
29
|
+
ruff format --check src/ tests/
|
|
30
|
+
|
|
31
|
+
# Run Ansible lint
|
|
32
|
+
pip install ansible-lint
|
|
33
|
+
ansible-lint
|
|
34
|
+
|
|
35
|
+
# Run template rendering test
|
|
36
|
+
python3 tests/render_templates.py
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Project Structure
|
|
40
|
+
|
|
41
|
+
The CLI is a Python package (`src/meridian/`) distributed via PyPI as `meridian-vpn`. Ansible playbooks are bundled inside the package as `src/meridian/playbooks/`.
|
|
42
|
+
|
|
43
|
+
Key modules:
|
|
44
|
+
- `cli.py` — Typer app, subcommand registration
|
|
45
|
+
- `commands/` — One module per subcommand (setup, client, check, etc.)
|
|
46
|
+
- `credentials.py` — `ServerCredentials` dataclass for YAML credential management
|
|
47
|
+
- `servers.py` — `ServerRegistry` for the known servers index
|
|
48
|
+
- `ansible.py` — Playbook execution via subprocess
|
|
49
|
+
|
|
50
|
+
## Pull Requests
|
|
51
|
+
|
|
52
|
+
1. Fork the repo and create a branch from `main`
|
|
53
|
+
2. Make your changes — keep them focused and minimal
|
|
54
|
+
3. Ensure CI passes: `pytest`, `ruff check`, `ruff format --check`, `ansible-lint`
|
|
55
|
+
4. Test on a real server if possible — CI can't catch deployment issues
|
|
56
|
+
5. Open a PR with a clear description of what and why
|
|
57
|
+
|
|
58
|
+
## Architecture Notes
|
|
59
|
+
|
|
60
|
+
See [CLAUDE.md](CLAUDE.md) for detailed architecture, implicit dependencies, and conventions. Key points:
|
|
61
|
+
|
|
62
|
+
- **All Ansible tasks use FQCNs** (`ansible.builtin.uri`, not `uri`)
|
|
63
|
+
- **Secrets use `no_log: true`** — never expose credentials in output
|
|
64
|
+
- **Three connection-info templates must stay in sync** (output/caddy/output_relay)
|
|
65
|
+
- **Caddy config** goes in `/etc/caddy/conf.d/meridian.caddy`, not the main Caddyfile
|
|
66
|
+
- **Playbook sync** — when editing playbooks at repo root, copy changes to `src/meridian/playbooks/`
|
|
67
|
+
|
|
68
|
+
## Testing
|
|
69
|
+
|
|
70
|
+
There's no way to fully test without a real server. The CI pipeline validates:
|
|
71
|
+
- Python tests (`pytest`) — credentials, servers, CLI, ansible, update logic
|
|
72
|
+
- Python lint (`ruff`) — style and import checks
|
|
73
|
+
- Ansible lint and syntax
|
|
74
|
+
- Jinja2 template rendering with mock variables
|
|
75
|
+
- Shell script syntax (`install.sh`, `setup.sh`)
|
|
76
|
+
- Ansible dry-run (`--check`) with local connection
|
|
77
|
+
|
|
78
|
+
For deployment testing, use a cheap VPS and run the full uninstall → install cycle.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Meridian Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|