## 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