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. The trojan role SHALL ensure port 80 is allowed through UFW before attempting certificate provisioning. 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 UFW allowing port 80
- WHEN the trojan role runs on a host with
trojan_domain configured
- THEN port 80 is allowed through UFW before certbot attempts ACME validation
- THEN certbot obtains a TLS certificate for the configured domain
- 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