瀏覽代碼

feat: auto-generate random ports/passwords and render Surge config

- SS port and password auto-generated via Ansible password lookup on first run
- Trojan password auto-generated (port stays 443 for HTTPS camouflage)
- Credentials persisted in credentials/ (gitignored)
- Surge client config now rendered from Jinja2 template with actual parameters
- Output to output/surge-client.conf after deployment
- Removed static docs/surge-client.conf and vault.yml.example

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kotoyuuko 3 周之前
父節點
當前提交
1a97d299df

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+credentials/
+output/
+*.retry

+ 28 - 27
README.md

@@ -38,37 +38,34 @@ all:
           ansible_host: "5.6.7.8"
 ```
 
-### 2. Configure secrets
-
-```bash
-cp group_vars/vault.yml.example group_vars/vault.yml
-# Edit vault.yml with your passwords
-ansible-vault encrypt group_vars/vault.yml
-```
-
-### 3. Configure variables
-
-Edit `group_vars/relay.yml`:
-- `ss_port`: Shadowsocks listen port (default: 8388)
-- `ss_cipher`: Encryption method (default: aes-256-gcm)
+### 2. Configure variables
 
 Edit `group_vars/landing.yml`:
 - `trojan_domain`: Your domain name
 - `certbot_email`: Email for Let's Encrypt notifications
 
-### 4. Run the playbook
+Ports and passwords are **auto-generated** on first run and persisted in `credentials/`. No manual password setup needed.
 
+To override auto-generated values:
 ```bash
-ansible-playbook site.yml --ask-vault-pass
+ansible-playbook site.yml --extra-vars "ss_password=my-custom-pass ss_port=12345"
 ```
 
-## Client Configuration
+### 3. Run the playbook
 
-See `docs/surge-client.conf` for a reference Surge client configuration with:
-- Proxy definitions (Relay-SS, Landing-Trojan, Landing-Chain)
-- Routing rules using [Sukka's rulesets](https://github.com/SukkaW/Surge)
-- AI and streaming traffic → chained through landing server
-- Default traffic → relay server
+```bash
+ansible-playbook site.yml
+```
+
+After deployment, the Surge client config is generated at `output/surge-client.conf` with all connection parameters filled in. Import this file into Surge directly.
+
+### 4. Backup credentials
+
+The `credentials/` directory contains auto-generated passwords and ports. Back it up — if lost, new credentials will be generated and servers must be re-provisioned.
+
+```bash
+cp -r credentials/ /path/to/backup/
+```
 
 ## Project Structure
 
@@ -79,14 +76,16 @@ See `docs/surge-client.conf` for a reference Surge client configuration with:
 ├── group_vars/
 │   ├── all.yml
 │   ├── relay.yml
-│   ├── landing.yml
-│   └── vault.yml.example
+│   └── landing.yml
 ├── roles/
 │   ├── base/           # SSH hardening, UFW, fail2ban
+│   ├── geoblock/       # Block outbound to China IPs
 │   ├── shadowsocks/    # shadowsocks-rust (relay)
 │   └── trojan/         # trojan-go + certbot (landing)
-├── docs/
-│   └── surge-client.conf
+├── templates/
+│   └── surge-client.conf.j2
+├── credentials/        # Auto-generated (gitignored)
+├── output/             # Generated Surge config (gitignored)
 └── site.yml
 ```
 
@@ -94,11 +93,13 @@ See `docs/surge-client.conf` for a reference Surge client configuration with:
 
 | Variable | Default | Description |
 |---|---|---|
-| `ss_port` | 8388 | Shadowsocks listen port |
+| `ss_port` | auto-generated (10000–49999) | Shadowsocks listen port |
 | `ss_cipher` | aes-256-gcm | Shadowsocks encryption method |
+| `ss_password` | auto-generated (32 chars) | Shadowsocks password |
 | `ss_version` | 1.21.2 | shadowsocks-rust release version |
-| `trojan_port` | 443 | Trojan listen port |
+| `trojan_port` | 443 (fixed) | Trojan listen port |
 | `trojan_domain` | — | Domain name for TLS certificate |
+| `trojan_password` | auto-generated (32 chars) | Trojan password |
 | `trojan_fallback_port` | 8080 | Fallback port for non-Trojan traffic |
 | `trojan_version` | 0.10.6 | trojan-go release version |
 | `certbot_email` | — | Email for Let's Encrypt |

+ 1 - 1
group_vars/landing.yml

@@ -1,5 +1,5 @@
 trojan_domain: "YOUR_DOMAIN"
-trojan_password: "{{ vault_trojan_password }}"
+trojan_password: "{{ lookup('password', 'credentials/trojan_password length=32 chars=ascii_letters,digits') }}"
 trojan_port: 443
 trojan_fallback_port: 8080
 

+ 2 - 2
group_vars/relay.yml

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

+ 0 - 7
group_vars/vault.yml.example

@@ -1,7 +0,0 @@
-# Copy this file to vault.yml and encrypt with:
-#   ansible-vault encrypt group_vars/vault.yml
-#
-# Then reference these variables in relay.yml and landing.yml
-
-vault_ss_password: "your-shadowsocks-password-here"
-vault_trojan_password: "your-trojan-password-here"

+ 2 - 0
openspec/changes/archive/2026-04-22-auto-generate-surge-config/.openspec.yaml

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

+ 53 - 0
openspec/changes/archive/2026-04-22-auto-generate-surge-config/design.md

@@ -0,0 +1,53 @@
+## Context
+
+Currently SS port (8388), Trojan port (443), and passwords are manually configured in `group_vars` and Ansible Vault. The Surge client config at `docs/surge-client.conf` is a static reference with placeholders users must replace manually. This change automates credential generation and produces a ready-to-use Surge config.
+
+## Goals / Non-Goals
+
+**Goals:**
+- Auto-generate random ports (within high-port range) and strong passwords on first deploy
+- Persist generated credentials so subsequent playbook runs don't regenerate them
+- Render a complete Surge client config with actual parameters after deployment
+- Keep manual override possible for users who prefer to set their own values
+
+**Non-Goals:**
+- Key rotation automation (out of scope)
+- Distributing the generated config to client devices
+
+## Decisions
+
+### 1. Credential generation: Ansible `password` lookup with file persistence
+
+Use Ansible's `lookup('password', ...)` to generate credentials. This generates a random value on first run and persists it to a file on the Ansible controller, so subsequent runs reuse the same value.
+
+```yaml
+ss_password: "{{ lookup('password', 'credentials/ss_password length=32 chars=ascii_letters,digits') }}"
+ss_port: "{{ lookup('password', 'credentials/ss_port chars=digits length=5') | int % 40000 + 10000 }}"
+```
+
+Credentials stored in `credentials/` directory (gitignored). Users can still override by setting variables in `group_vars` or via `--extra-vars`.
+
+**Why over Ansible Vault with manual entry**: Zero-touch setup. New users don't need to manually create passwords or encrypt vault files.
+
+### 2. Port ranges
+
+- SS port: random in 10000–49999 range
+- Trojan port: fixed at 443 (required for TLS camouflage — Trojan must listen on 443 to look like HTTPS)
+
+Trojan port stays 443 because changing it defeats the purpose of HTTPS camouflage.
+
+### 3. Surge config generation: local Jinja2 template rendered by Ansible
+
+Create a new playbook task that runs on `localhost` (Ansible controller) after server deployment. It renders `templates/surge-client.conf.j2` using the actual deployed variables (relay IP, landing domain, ports, passwords) and writes to `output/surge-client.conf`.
+
+The old static `docs/surge-client.conf` is removed. The template preserves the same rule structure (Sukka's rulesets, China direct, etc.).
+
+### 4. Credential directory gitignored
+
+Add `credentials/` and `output/` to `.gitignore` to prevent secrets and generated configs from being committed.
+
+## Risks / Trade-offs
+
+- **[Credential loss]** → If `credentials/` directory is deleted, new credentials are generated and servers must be re-provisioned. Mitigation: document backup instructions in README.
+- **[Trojan port fixed at 443]** → Cannot randomize without breaking HTTPS camouflage. This is by design.
+- **[Controller-local files]** → Credentials and output depend on the Ansible controller's filesystem. Mitigation: users can back up the `credentials/` directory.

+ 29 - 0
openspec/changes/archive/2026-04-22-auto-generate-surge-config/proposal.md

@@ -0,0 +1,29 @@
+## Why
+
+Currently ports and passwords are manually configured. Randomizing them on first deploy reduces the chance of detection and simplifies initial setup. After deployment, a usable Surge client configuration should be automatically generated with the actual connection parameters — no manual placeholder replacement.
+
+## What Changes
+
+- Generate random SS port, SS password, Trojan port, and Trojan password during first playbook run (persisted so subsequent runs don't regenerate)
+- Convert `docs/surge-client.conf` from a static reference file to a Jinja2 template rendered by Ansible with actual deployment parameters
+- Output the generated Surge config to a local file after deployment
+- Remove manual placeholder approach from the reference config
+
+## Capabilities
+
+### New Capabilities
+- `auto-credentials`: Random port and password generation with persistence across playbook runs
+- `surge-config-gen`: Ansible-driven Surge client configuration generation from deployed parameters
+
+### Modified Capabilities
+- `shadowsocks-relay`: SS port and password become auto-generated instead of manually configured
+- `trojan-landing`: Trojan port and password become auto-generated instead of manually configured
+- `proxy-rules`: Surge config is now generated from template, not a static reference file
+
+## Impact
+
+- `group_vars/relay.yml` and `group_vars/landing.yml` no longer require manual password/port configuration
+- `group_vars/vault.yml.example` simplified (no manual password entry needed)
+- `docs/surge-client.conf` replaced by `roles/surge-config/templates/surge-client.conf.j2`
+- Generated Surge config output to `output/surge-client.conf` on the Ansible controller after each run
+- Existing Ansible Vault workflow still supported for users who prefer manual credentials

+ 42 - 0
openspec/changes/archive/2026-04-22-auto-generate-surge-config/specs/auto-credentials/spec.md

@@ -0,0 +1,42 @@
+## ADDED Requirements
+
+### Requirement: SS password is auto-generated on first run
+The playbook SHALL generate a random 32-character alphanumeric password for Shadowsocks on first run, persisted to `credentials/ss_password` on the Ansible controller.
+
+#### Scenario: First run generates password
+- **WHEN** the playbook runs for the first time and `credentials/ss_password` does not exist
+- **THEN** a random 32-character password is generated and saved to `credentials/ss_password`
+- **THEN** the generated password is used for SS configuration
+
+#### Scenario: Subsequent runs reuse password
+- **WHEN** the playbook runs again and `credentials/ss_password` exists
+- **THEN** the existing password is read and reused
+
+### Requirement: SS port is auto-generated on first run
+The playbook SHALL generate a random port in the 10000–49999 range for Shadowsocks on first run, persisted to `credentials/ss_port`.
+
+#### Scenario: First run generates port
+- **WHEN** the playbook runs for the first time and `credentials/ss_port` does not exist
+- **THEN** a random port number (10000–49999) is generated and saved
+- **THEN** the generated port is used for SS configuration and UFW rules
+
+### Requirement: Trojan password is auto-generated on first run
+The playbook SHALL generate a random 32-character alphanumeric password for Trojan on first run, persisted to `credentials/trojan_password`.
+
+#### Scenario: First run generates password
+- **WHEN** the playbook runs for the first time and `credentials/trojan_password` does not exist
+- **THEN** a random 32-character password is generated and saved
+
+### Requirement: Credentials directory is gitignored
+The `credentials/` directory SHALL be listed in `.gitignore` to prevent secrets from being committed.
+
+#### Scenario: Gitignore includes credentials
+- **WHEN** the repository is inspected
+- **THEN** `.gitignore` contains entries for `credentials/` and `output/`
+
+### Requirement: Manual override is supported
+Users SHALL be able to override auto-generated values by setting variables via `--extra-vars` or in `group_vars`.
+
+#### Scenario: Manual override via extra-vars
+- **WHEN** the playbook is run with `--extra-vars "ss_password=my-custom-pass"`
+- **THEN** the custom value is used instead of the auto-generated one

+ 13 - 0
openspec/changes/archive/2026-04-22-auto-generate-surge-config/specs/proxy-rules/spec.md

@@ -0,0 +1,13 @@
+## MODIFIED Requirements
+
+### Requirement: Reference Surge client config documents the chained proxy setup
+The project SHALL generate a Surge client configuration file from a Jinja2 template after deployment, replacing the previous static reference file with placeholders. The generated config SHALL contain actual connection parameters.
+
+#### Scenario: Config is generated, not static
+- **WHEN** the playbook completes
+- **THEN** `output/surge-client.conf` is generated with real IPs, ports, and passwords
+- **THEN** no manual placeholder replacement is needed
+
+#### Scenario: Static reference file removed
+- **WHEN** the repository is inspected
+- **THEN** `docs/surge-client.conf` no longer exists as a static placeholder file

+ 13 - 0
openspec/changes/archive/2026-04-22-auto-generate-surge-config/specs/shadowsocks-relay/spec.md

@@ -0,0 +1,13 @@
+## MODIFIED Requirements
+
+### 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

+ 23 - 0
openspec/changes/archive/2026-04-22-auto-generate-surge-config/specs/surge-config-gen/spec.md

@@ -0,0 +1,23 @@
+## ADDED 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 (IPs, ports, passwords, domain).
+
+#### 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 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/`

+ 12 - 0
openspec/changes/archive/2026-04-22-auto-generate-surge-config/specs/trojan-landing/spec.md

@@ -0,0 +1,12 @@
+## MODIFIED Requirements
+
+### Requirement: Trojan credentials are managed via Ansible Vault
+The landing server's Trojan password SHALL default to an auto-generated value from `credentials/trojan_password`, with manual override supported. The Trojan port SHALL remain fixed at 443 for HTTPS camouflage.
+
+#### Scenario: Password uses auto-generated default
+- **WHEN** the trojan role runs without manual overrides
+- **THEN** the password comes from `credentials/` lookup value
+
+#### Scenario: Port remains 443
+- **WHEN** the trojan role runs
+- **THEN** the Trojan port is 443 (not randomized, required for HTTPS camouflage)

+ 16 - 0
openspec/changes/archive/2026-04-22-auto-generate-surge-config/tasks.md

@@ -0,0 +1,16 @@
+## 1. Credential Auto-Generation
+
+- [x] 1.1 Update `group_vars/relay.yml` to use `lookup('password', ...)` for `ss_password` and random port generation for `ss_port`
+- [x] 1.2 Update `group_vars/landing.yml` to use `lookup('password', ...)` for `trojan_password` (keep `trojan_port: 443` fixed)
+- [x] 1.3 Create `.gitignore` with `credentials/` and `output/` entries
+- [x] 1.4 Remove `group_vars/vault.yml.example` (no longer needed for passwords)
+
+## 2. Surge Config Template & Generation
+
+- [x] 2.1 Convert `docs/surge-client.conf` into `templates/surge-client.conf.j2` with Jinja2 variables for relay IP, SS port, SS password, landing domain, Trojan password
+- [x] 2.2 Delete the old static `docs/surge-client.conf`
+- [x] 2.3 Add a new play in `site.yml` that runs on `localhost` after server deployment to render the Surge config template to `output/surge-client.conf`
+
+## 3. Documentation
+
+- [x] 3.1 Update `README.md` to reflect auto-generated credentials, remove vault setup instructions, document `credentials/` backup and `output/` location

+ 42 - 0
openspec/specs/auto-credentials/spec.md

@@ -0,0 +1,42 @@
+## ADDED Requirements
+
+### Requirement: SS password is auto-generated on first run
+The playbook SHALL generate a random 32-character alphanumeric password for Shadowsocks on first run, persisted to `credentials/ss_password` on the Ansible controller.
+
+#### Scenario: First run generates password
+- **WHEN** the playbook runs for the first time and `credentials/ss_password` does not exist
+- **THEN** a random 32-character password is generated and saved to `credentials/ss_password`
+- **THEN** the generated password is used for SS configuration
+
+#### Scenario: Subsequent runs reuse password
+- **WHEN** the playbook runs again and `credentials/ss_password` exists
+- **THEN** the existing password is read and reused
+
+### Requirement: SS port is auto-generated on first run
+The playbook SHALL generate a random port in the 10000–49999 range for Shadowsocks on first run, persisted to `credentials/ss_port`.
+
+#### Scenario: First run generates port
+- **WHEN** the playbook runs for the first time and `credentials/ss_port` does not exist
+- **THEN** a random port number (10000–49999) is generated and saved
+- **THEN** the generated port is used for SS configuration and UFW rules
+
+### Requirement: Trojan password is auto-generated on first run
+The playbook SHALL generate a random 32-character alphanumeric password for Trojan on first run, persisted to `credentials/trojan_password`.
+
+#### Scenario: First run generates password
+- **WHEN** the playbook runs for the first time and `credentials/trojan_password` does not exist
+- **THEN** a random 32-character password is generated and saved
+
+### Requirement: Credentials directory is gitignored
+The `credentials/` directory SHALL be listed in `.gitignore` to prevent secrets from being committed.
+
+#### Scenario: Gitignore includes credentials
+- **WHEN** the repository is inspected
+- **THEN** `.gitignore` contains entries for `credentials/` and `output/`
+
+### Requirement: Manual override is supported
+Users SHALL be able to override auto-generated values by setting variables via `--extra-vars` or in `group_vars`.
+
+#### Scenario: Manual override via extra-vars
+- **WHEN** the playbook is run with `--extra-vars "ss_password=my-custom-pass"`
+- **THEN** the custom value is used instead of the auto-generated one

+ 9 - 4
openspec/specs/proxy-rules/spec.md

@@ -1,10 +1,15 @@
 ## ADDED Requirements
 
-### Requirement: Reference Surge client config documents the chained proxy setup
-The project SHALL include a reference Surge client configuration file showing how to configure the relay (Shadowsocks) and landing (Trojan) proxies with underlying-proxy chaining.
+### Requirement: Surge client config is generated from template after deployment
+The project SHALL generate a Surge client configuration file from a Jinja2 template after deployment, containing actual connection parameters (IPs, ports, passwords). The generated config SHALL contain all proxy definitions and routing rules.
 
-#### Scenario: Chain proxy definition
-- **WHEN** a user reads the reference Surge config
+#### Scenario: Config is generated, not static
+- **WHEN** the playbook completes
+- **THEN** `output/surge-client.conf` is generated with real IPs, ports, and passwords
+- **THEN** no manual placeholder replacement is needed
+
+#### Scenario: Config contains all proxy definitions
+- **WHEN** the generated config is loaded in Surge
 - **THEN** it defines a Shadowsocks proxy pointing to the relay server
 - **THEN** it defines a Trojan proxy pointing to the landing server with `underlying-proxy` set to the relay SS proxy
 - **THEN** it defines a direct Trojan proxy to the landing server without underlying-proxy

+ 10 - 6
openspec/specs/shadowsocks-relay/spec.md

@@ -35,17 +35,21 @@ The relay server's shadowsocks configuration SHALL be generated from a Jinja2 te
 - **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 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
 
-### Requirement: shadowsocks credentials are managed via Ansible Vault
-The relay server's shadowsocks password SHALL be defined in `group_vars` and encrypted with Ansible Vault.
+#### 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
 
-#### Scenario: Password is not in plaintext in repository
-- **WHEN** the shadowsocks role generates the configuration
-- **THEN** the password comes from an Ansible Vault-encrypted variable
+### 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

+ 23 - 0
openspec/specs/surge-config-gen/spec.md

@@ -0,0 +1,23 @@
+## ADDED 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 (IPs, ports, passwords, domain).
+
+#### 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 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/`

+ 9 - 6
openspec/specs/trojan-landing/spec.md

@@ -58,10 +58,13 @@ The landing server's Trojan configuration SHALL be generated from a Jinja2 templ
 - **THEN** the configuration file is updated
 - **THEN** the Trojan service is restarted
 
-### Requirement: Trojan credentials are managed via Ansible Vault
-The landing server's Trojan password SHALL be defined in `group_vars` and encrypted with Ansible Vault.
+### Requirement: Trojan credentials are auto-generated and persisted
+The landing server's Trojan password SHALL default to an auto-generated value from `credentials/trojan_password`, with manual override supported. The Trojan port SHALL remain fixed at 443 for HTTPS camouflage.
 
-#### Scenario: Password is not in plaintext in repository
-- **WHEN** the trojan role generates the configuration
-- **THEN** the password comes from an Ansible Vault-encrypted variable
-- **THEN** no plaintext password exists in version-controlled files
+#### Scenario: Password uses auto-generated default
+- **WHEN** the trojan role runs without manual overrides
+- **THEN** the password comes from `credentials/` lookup value
+
+#### Scenario: Port remains 443
+- **WHEN** the trojan role runs
+- **THEN** the Trojan port is 443 (not randomized, required for HTTPS camouflage)

+ 21 - 0
site.yml

@@ -14,3 +14,24 @@
   hosts: landing
   roles:
     - trojan
+
+- name: Generate Surge client configuration
+  hosts: localhost
+  connection: local
+  gather_facts: no
+  tasks:
+    - name: Create output directory
+      ansible.builtin.file:
+        path: output
+        state: directory
+        mode: "0755"
+
+    - name: Render Surge client config
+      ansible.builtin.template:
+        src: templates/surge-client.conf.j2
+        dest: output/surge-client.conf
+        mode: "0600"
+
+    - name: Display Surge config location
+      ansible.builtin.debug:
+        msg: "Surge client config generated at: output/surge-client.conf"

+ 3 - 15
docs/surge-client.conf → templates/surge-client.conf.j2

@@ -1,25 +1,15 @@
-# Surge Client Reference Configuration
-# Chained proxy setup: Relay (Shadowsocks) + Landing (Trojan)
-#
-# Replace placeholders:
-#   RELAY_IP        - Relay server IP address
-#   SS_PORT         - Shadowsocks port (default: 8388)
-#   SS_PASSWORD     - Shadowsocks password
-#   LANDING_DOMAIN  - Landing server domain name
-#   TROJAN_PASSWORD - Trojan password
-
 [General]
 loglevel = notify
 
 [Proxy]
 # Relay server - Shadowsocks (中转机)
-Relay-SS = ss, RELAY_IP, SS_PORT, encrypt-method=aes-256-gcm, password=SS_PASSWORD
+Relay-SS = ss, {{ hostvars[groups['relay'][0]]['ansible_host'] }}, {{ ss_port }}, encrypt-method={{ ss_cipher }}, password={{ ss_password }}
 
 # Landing server - Trojan direct (落地机直连)
-Landing-Trojan = trojan, LANDING_DOMAIN, 443, password=TROJAN_PASSWORD
+Landing-Trojan = trojan, {{ trojan_domain }}, {{ trojan_port }}, password={{ trojan_password }}
 
 # Landing server - chained through relay (落地机经中转)
-Landing-Chain = trojan, LANDING_DOMAIN, 443, password=TROJAN_PASSWORD, underlying-proxy=Relay-SS
+Landing-Chain = trojan, {{ trojan_domain }}, {{ trojan_port }}, password={{ trojan_password }}, underlying-proxy=Relay-SS
 
 [Proxy Group]
 # For services needing landing server's local IP (AI, streaming)
@@ -28,10 +18,8 @@ Chain = select, Landing-Chain
 Direct-Landing = select, Landing-Trojan
 
 [Rule]
-# =============================================
 # Sukka's Rulesets (https://github.com/SukkaW/Surge)
 # DOMAIN-SET and non_ip rules MUST come before ip rules
-# =============================================
 
 # --- AI Services → Chain (exit from landing IP) ---
 DOMAIN-SET,https://ruleset.skk.moe/List/domainset/ai.conf,Chain