## ADDED Requirements ### Requirement: Trojan variables are available on localhost for config generation The variables `trojan_domain`, `trojan_port`, `trojan_password`, and `trojan_fallback_port` SHALL be defined in `group_vars/all.yml` so they are accessible during the localhost Surge config generation play, in addition to the landing server deployment role. #### Scenario: Variables available on localhost - **WHEN** the localhost play renders `surge-client.conf` - **THEN** `trojan_domain`, `trojan_port`, `trojan_password` resolve without undefined errors ### Requirement: Trojan is installed on the landing server The trojan role SHALL download and install the Trojan binary (trojan-go or trojan-gfw) from release artifacts to a configurable path. #### Scenario: Fresh installation - **WHEN** the trojan role runs on a landing server without Trojan installed - **THEN** the specified version of the Trojan binary is downloaded and installed - **THEN** the binary is executable and owned by a dedicated service user #### Scenario: Version upgrade - **WHEN** the Trojan 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: Trojan runs as a systemd service The trojan role SHALL create a systemd unit file for Trojan and ensure it is enabled and started. The unit SHALL include both `AmbientCapabilities` and `CapabilityBoundingSet` for `CAP_NET_BIND_SERVICE`. #### Scenario: Service is running - **WHEN** the trojan role completes - **THEN** the Trojan systemd service is enabled and running - **THEN** the service runs under a dedicated non-root user (with `CAP_NET_BIND_SERVICE` for port 443) - **THEN** the trojan user can read the TLS certificate and key files from `/etc/trojan-go/tls/` ### Requirement: TLS certificate is provisioned via Let's Encrypt The trojan role SHALL use certbot to obtain a TLS certificate for the landing server's domain, with automatic renewal. After provisioning or renewal, the certificate and key SHALL be copied to a trojan-owned directory (`/etc/trojan-go/tls/`) so the service user can read them. #### Scenario: Certificate provisioning - **WHEN** the trojan role runs with a configured domain name - **THEN** certbot obtains a TLS certificate for that domain - **THEN** the certificate and key are copied to `/etc/trojan-go/tls/` owned by the trojan user #### Scenario: Certificate auto-renewal - **WHEN** the certificate is within 30 days of expiry - **THEN** certbot renews it automatically via systemd timer or cron - **THEN** a deploy-hook copies the renewed certs to `/etc/trojan-go/tls/` - **THEN** the Trojan service is reloaded after renewal ### Requirement: Trojan listens on port 443 with TLS The Trojan service SHALL listen on port 443 and terminate TLS using the Let's Encrypt certificate, presenting as a normal HTTPS server to non-Trojan clients. #### Scenario: Trojan accepts connections on 443 - **WHEN** a Trojan client connects to port 443 with valid credentials - **THEN** the connection is accepted and proxied #### Scenario: Non-Trojan traffic is handled by fallback - **WHEN** a non-Trojan HTTPS request arrives on port 443 - **THEN** Trojan forwards it to a local fallback web server (camouflage) ### Requirement: Trojan configuration is templated The landing server's Trojan configuration SHALL be generated from a Jinja2 template with variables for password, domain, TLS paths, and fallback settings. #### Scenario: Configuration is generated from template - **WHEN** the trojan role runs - **THEN** a JSON config file is rendered from template - **THEN** the config file permissions are restricted (readable only by the service user) #### Scenario: Configuration change triggers restart - **WHEN** a variable in `group_vars/landing.yml` is changed and the playbook is re-run - **THEN** the configuration file is updated - **THEN** the Trojan service is restarted ### 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 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)