瀏覽代碼

feat: use relay_domain instead of raw IP in Surge config

Replace the relay server's IP address with a configurable domain
name in the Surge client template, and update example/spec files

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
kotoyuuko 3 周之前
父節點
當前提交
967f5d9650

+ 2 - 0
group_vars/relay.yml.example

@@ -1,3 +1,5 @@
+relay_domain: "YOUR_RELAY_DOMAIN"
+
 ss_port: "{{ lookup('password', 'credentials/ss_port chars=digits length=5') | int % 40000 + 10000 }}"
 ss_port: "{{ lookup('password', 'credentials/ss_port chars=digits length=5') | int % 40000 + 10000 }}"
 ss_cipher: "aes-256-gcm"
 ss_cipher: "aes-256-gcm"
 ss_password: "{{ lookup('password', 'credentials/ss_password length=32 chars=ascii_letters,digits') }}"
 ss_password: "{{ lookup('password', 'credentials/ss_password length=32 chars=ascii_letters,digits') }}"

+ 2 - 0
openspec/changes/archive/2026-04-22-add-relay-domain-for-surge-config/.openspec.yaml

@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-04-22

+ 32 - 0
openspec/changes/archive/2026-04-22-add-relay-domain-for-surge-config/design.md

@@ -0,0 +1,32 @@
+## Context
+
+The Surge template at `templates/surge-client.conf.j2` defines the relay proxy as:
+```
+Relay-SS = ss, {{ hostvars[groups['relay'][0]]['ansible_host'] }}, ...
+```
+This hardcodes the relay server's IP. The landing server already uses a `trojan_domain` variable for its proxy entry, so the relay should follow the same pattern for consistency and operational flexibility.
+
+## Goals / Non-Goals
+
+**Goals:**
+- Introduce a `relay_domain` variable in `group_vars/relay.yml`
+- Use it in the Surge template instead of the raw IP
+- Keep the variable pattern consistent with the existing `trojan_domain` approach
+
+**Non-Goals:**
+- DNS or TLS configuration for the relay server
+- Changing the Shadowsocks protocol or connection behavior
+
+## Decisions
+
+**Define `relay_domain` in `group_vars/relay.yml`**
+
+The relay domain is specific to the relay server, so it belongs in `group_vars/relay.yml` rather than `all.yml`. The Surge template already reads variables from `hostvars` for the relay (e.g., `ss_port`, `ss_password`), so `relay_domain` will be read the same way.
+
+**Use a simple `relay_domain` variable, not a lookup**
+
+Unlike `ss_port` which needs a random password lookup, the domain is a static value set by the user. A plain string variable is sufficient.
+
+## Risks / Trade-offs
+
+- [DNS dependency] → The relay domain must resolve to the relay server's IP; this is an operational prerequisite outside Ansible's scope

+ 24 - 0
openspec/changes/archive/2026-04-22-add-relay-domain-for-surge-config/proposal.md

@@ -0,0 +1,24 @@
+## Why
+
+The Surge client configuration currently uses the relay server's raw IP address (`ansible_host`) for the Shadowsocks proxy definition. Using a domain name instead of a raw IP provides better flexibility (e.g., IP changes, CDN, TLS/SNI requirements) and consistency with how the landing server is configured.
+
+## What Changes
+
+- Add a `relay_domain` variable in `group_vars/relay.yml` for the relay server's domain name
+- Update the Surge template to use `relay_domain` instead of the raw `ansible_host` for the relay proxy entry
+- Update the relay example file to document the new variable
+
+## Capabilities
+
+### New Capabilities
+<!-- none -->
+
+### Modified Capabilities
+- `shadowsocks-relay`: The relay server now has a configurable domain name used in Surge client config generation, replacing the raw IP reference
+- `surge-config-gen`: The generated config uses a domain name for the relay instead of an IP address
+
+## Impact
+
+- `group_vars/relay.yml` — adds `relay_domain` variable
+- `group_vars/relay.yml.example` — adds `relay_domain` placeholder
+- `templates/surge-client.conf.j2` — changes relay proxy to use `relay_domain` variable instead of `ansible_host`

+ 14 - 0
openspec/changes/archive/2026-04-22-add-relay-domain-for-surge-config/specs/shadowsocks-relay/spec.md

@@ -0,0 +1,14 @@
+## MODIFIED Requirements
+
+### Requirement: shadowsocks-rust configuration is templated
+The relay server's shadowsocks configuration SHALL be generated from a Jinja2 template with variables for port, password, and cipher method. The relay server SHALL have a configurable domain name (`relay_domain`) for use in client configuration generation.
+
+#### Scenario: Configuration is generated from template
+- **WHEN** the shadowsocks role runs
+- **THEN** a JSON config file is rendered from template with port, password, and encryption method
+- **THEN** the config file permissions are restricted (readable only by the service user)
+
+#### Scenario: Configuration change triggers restart
+- **WHEN** a variable in `group_vars/relay.yml` is changed and the playbook is re-run
+- **THEN** the configuration file is updated
+- **THEN** the ssserver service is restarted

+ 9 - 0
openspec/changes/archive/2026-04-22-add-relay-domain-for-surge-config/specs/surge-config-gen/spec.md

@@ -0,0 +1,9 @@
+## MODIFIED Requirements
+
+### Requirement: Surge client config is generated after deployment
+The playbook SHALL render a Surge client configuration file on the Ansible controller using the actual deployed parameters (relay domain, ports, passwords, landing domain, Trojan password).
+
+#### Scenario: Surge config generated
+- **WHEN** the playbook completes server deployment
+- **THEN** a Surge client config is written to `output/surge-client.conf` on the Ansible controller
+- **THEN** the config contains relay domain, SS port, SS password, landing domain, Trojan password

+ 12 - 0
openspec/changes/archive/2026-04-22-add-relay-domain-for-surge-config/tasks.md

@@ -0,0 +1,12 @@
+## 1. Add relay_domain variable
+
+- [x] 1.1 Add `relay_domain` to `group_vars/relay.yml`
+- [x] 1.2 Add `relay_domain` placeholder to `group_vars/relay.yml.example`
+
+## 2. Update Surge template
+
+- [x] 2.1 Replace `hostvars[groups['relay'][0]]['ansible_host']` with `hostvars[groups['relay'][0]]['relay_domain']` in `templates/surge-client.conf.j2`
+
+## 3. Verify
+
+- [x] 3.1 Run `ansible-playbook site.yml --syntax-check` to confirm playbook parses

+ 2 - 43
openspec/specs/shadowsocks-relay/spec.md

@@ -1,28 +1,7 @@
-## ADDED Requirements
-
-### Requirement: shadowsocks-rust is installed on the relay server
-The shadowsocks role SHALL download and install the shadowsocks-rust `ssserver` binary from GitHub releases to a configurable path, with a configurable version.
-
-#### Scenario: Fresh installation
-- **WHEN** the shadowsocks role runs on a relay server without shadowsocks-rust installed
-- **THEN** the specified version of `ssserver` binary is downloaded and installed
-- **THEN** the binary is executable and owned by a dedicated service user
-
-#### Scenario: Version upgrade
-- **WHEN** the shadowsocks version variable is changed and the playbook is re-run
-- **THEN** the binary is replaced with the new version
-- **THEN** the service is restarted
-
-### Requirement: shadowsocks-rust runs as a systemd service
-The shadowsocks role SHALL create a systemd unit file for `ssserver` and ensure it is enabled and started.
-
-#### Scenario: Service is running
-- **WHEN** the shadowsocks role completes
-- **THEN** the `ssserver` systemd service is enabled and running
-- **THEN** the service runs under a dedicated non-root user
+## MODIFIED Requirements
 
 
 ### Requirement: shadowsocks-rust configuration is templated
 ### Requirement: shadowsocks-rust configuration is templated
-The relay server's shadowsocks configuration SHALL be generated from a Jinja2 template with variables for port, password, and cipher method.
+The relay server's shadowsocks configuration SHALL be generated from a Jinja2 template with variables for port, password, and cipher method. The relay server SHALL have a configurable domain name (`relay_domain`) for use in client configuration generation.
 
 
 #### Scenario: Configuration is generated from template
 #### Scenario: Configuration is generated from template
 - **WHEN** the shadowsocks role runs
 - **WHEN** the shadowsocks role runs
@@ -33,23 +12,3 @@ The relay server's shadowsocks configuration SHALL be generated from a Jinja2 te
 - **WHEN** a variable in `group_vars/relay.yml` is changed and the playbook is re-run
 - **WHEN** a variable in `group_vars/relay.yml` is changed and the playbook is re-run
 - **THEN** the configuration file is updated
 - **THEN** the configuration file is updated
 - **THEN** the ssserver service is restarted
 - **THEN** the ssserver service is restarted
-
-### Requirement: shadowsocks uses AEAD cipher
-The shadowsocks configuration SHALL use an AEAD cipher (e.g., `aes-256-gcm` or `chacha20-ietf-poly1305`), configurable via Ansible variable. The SS port and password SHALL default to auto-generated values from the `credentials/` directory, with manual override supported.
-
-#### Scenario: AEAD cipher is configured
-- **WHEN** the shadowsocks configuration is generated
-- **THEN** the `method` field uses the configured AEAD cipher
-- **THEN** the default cipher is `aes-256-gcm` if not overridden
-
-#### Scenario: Port and password use auto-generated defaults
-- **WHEN** the shadowsocks role runs without manual overrides
-- **THEN** the port and password come from `credentials/` lookup values
-
-### Requirement: shadowsocks credentials are auto-generated and persisted
-The relay server's shadowsocks password and port SHALL be auto-generated on first run via Ansible `password` lookup and persisted in `credentials/`. Manual override via `--extra-vars` is supported.
-
-#### Scenario: Credentials are auto-generated
-- **WHEN** the playbook runs for the first time
-- **THEN** a random password and port are generated and persisted to `credentials/`
-- **THEN** no plaintext password exists in version-controlled files

+ 3 - 17
openspec/specs/surge-config-gen/spec.md

@@ -1,23 +1,9 @@
-## ADDED Requirements
+## MODIFIED Requirements
 
 
 ### Requirement: Surge client config is generated after deployment
 ### Requirement: Surge client config is generated after deployment
-The playbook SHALL render a Surge client configuration file on the Ansible controller using the actual deployed parameters (IPs, ports, passwords, domain).
+The playbook SHALL render a Surge client configuration file on the Ansible controller using the actual deployed parameters (relay domain, ports, passwords, landing domain, Trojan password).
 
 
 #### Scenario: Surge config generated
 #### Scenario: Surge config generated
 - **WHEN** the playbook completes server deployment
 - **WHEN** the playbook completes server deployment
 - **THEN** a Surge client config is written to `output/surge-client.conf` on the Ansible controller
 - **THEN** a Surge client config is written to `output/surge-client.conf` on the Ansible controller
-- **THEN** the config contains actual relay IP, SS port, SS password, landing domain, Trojan password
-
-### Requirement: Generated config includes all proxy definitions and rules
-The generated Surge config SHALL include the same proxy definitions, proxy groups, and routing rules as the previous reference config (Relay-SS, Landing-Trojan, Landing-Chain, Sukka's rulesets, China direct).
-
-#### Scenario: Config structure matches reference
-- **WHEN** the generated config is loaded in Surge
-- **THEN** it contains [Proxy], [Proxy Group], and [Rule] sections with all expected entries
-
-### Requirement: Generated config is not committed to git
-The `output/` directory SHALL be listed in `.gitignore`.
-
-#### Scenario: Output directory gitignored
-- **WHEN** the repository is inspected
-- **THEN** `.gitignore` contains `output/`
+- **THEN** the config contains relay domain, SS port, SS password, landing domain, Trojan password

+ 1 - 1
templates/surge-client.conf.j2

@@ -3,7 +3,7 @@ loglevel = notify
 
 
 [Proxy]
 [Proxy]
 # Relay server - Shadowsocks (中转机)
 # Relay server - Shadowsocks (中转机)
-Relay-SS = ss, {{ hostvars[groups['relay'][0]]['ansible_host'] }}, {{ hostvars[groups['relay'][0]]['ss_port'] }}, encrypt-method={{ hostvars[groups['relay'][0]]['ss_cipher'] }}, password={{ hostvars[groups['relay'][0]]['ss_password'] }}
+Relay-SS = ss, {{ hostvars[groups['relay'][0]]['relay_domain'] }}, {{ hostvars[groups['relay'][0]]['ss_port'] }}, encrypt-method={{ hostvars[groups['relay'][0]]['ss_cipher'] }}, password={{ hostvars[groups['relay'][0]]['ss_password'] }}
 
 
 # Landing server - Trojan direct (落地机直连)
 # Landing server - Trojan direct (落地机直连)
 Landing-Trojan = trojan, {{ trojan_domain }}, {{ trojan_port }}, password={{ trojan_password }}
 Landing-Trojan = trojan, {{ trojan_domain }}, {{ trojan_port }}, password={{ trojan_password }}