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 in
/etc/letsencrypt/renewal-hooks/post/ 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)