ADDED Requirements
Requirement: Trojan users are configured via YAML file
The trojan role SHALL read user credentials from a users.yml file located at the playbook root. The file SHALL define a trojan_users list where each entry contains name and password fields.
Scenario: Users file exists with multiple entries
- WHEN
users.yml contains trojan_users with entries [{name: alice, password: pass1}, {name: bob, password: pass2}]
- THEN the Trojan configuration includes both users
Scenario: Users file is missing
- WHEN
users.yml does not exist and no trojan_users variable is defined elsewhere
- THEN the playbook fails with a clear error message
Scenario: Users file is gitignored
- WHEN the repository is inspected
- THEN
.gitignore contains an entry for users.yml
Requirement: Trojan binary is installed
The trojan role SHALL download and install the trojan-go binary from release artifacts to /usr/local/bin/trojan-go. The version SHALL be configurable via trojan_version.
Scenario: Fresh installation
- WHEN the trojan role runs on a server without trojan-go installed
- THEN the specified version of the binary is downloaded and installed
- THEN the binary is executable
Scenario: Version upgrade
- WHEN
trojan_version is changed and the playbook is re-run
- THEN the binary is replaced with the new version
- THEN the Trojan service is restarted
Requirement: TLS certificate is provisioned via Let's Encrypt
The trojan role SHALL use certbot to obtain a TLS certificate for the domain configured on each individual host. After provisioning or renewal, the certificate and key SHALL be copied to /etc/trojan-go/tls/ so the service user can read them.
Scenario: Certificate provisioning with per-host domain
- WHEN the trojan role runs on a host with
trojan_domain: "proxy1.example.com"
- THEN certbot obtains a TLS certificate for
proxy1.example.com
- THEN the certificate and key are copied to
/etc/trojan-go/tls/ owned by the trojan service user
Scenario: Certificate provisioning on a second host with different domain
- WHEN the trojan role runs on a host with
trojan_domain: "proxy2.example.com"
- THEN certbot obtains a TLS certificate for
proxy2.example.com
- THEN the certificate is independent from other hosts
Scenario: Certificate auto-renewal
- WHEN the certificate is within 30 days of expiry
- THEN certbot renews it automatically
- THEN a deploy-hook copies the renewed certs to
/etc/trojan-go/tls/
- THEN the Trojan service is reloaded after renewal
Requirement: Trojan runs as a systemd service
The trojan role SHALL create a systemd unit file for trojan-go 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
- THEN the service user can read the TLS certificate and key files
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.
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 endpoint (if configured)
Requirement: Trojan configuration is templated
The trojan role SHALL generate a JSON configuration file from a Jinja2 template. The config SHALL include: run_type, local_addr, local_port, remote_addr, remote_port, password array (from trojan_users), ssl section with cert/key paths, and log_level.
Scenario: Multi-user configuration is generated
- WHEN the trojan role runs with multiple users defined
- THEN the config file contains a
password array with all user passwords
- THEN each user can authenticate with their respective password
Scenario: Configuration change triggers restart
- WHEN
users.yml or trojan variables are changed and the playbook is re-run
- THEN the configuration file is updated
- THEN the Trojan service is restarted via handler
Requirement: Trojan domain is configured per-host
Each host in the trojan group SHALL define its own trojan_domain and certbot_email variables in the inventory. The trojan role SHALL fail with a clear error if a host lacks these variables.
Scenario: Host defines its own domain
- WHEN a host in inventory has
trojan_domain: "proxy.example.com" and certbot_email: "admin@example.com"
- THEN the trojan role uses these values for that host
Scenario: Host missing domain variable
- WHEN a host in the
trojan group does not define trojan_domain
- THEN the playbook fails with an error indicating the missing variable
Scenario: Inventory example shows per-host domain configuration
- WHEN the user inspects
inventory/hosts.yml.example
- THEN it contains per-host
trojan_domain and certbot_email examples
Requirement: Trojan port 443 is allowed through UFW
The trojan role SHALL allow port 443 through UFW.
Scenario: Firewall allows HTTPS port
- WHEN the trojan role runs
- THEN UFW allows incoming TCP traffic on port 443