spec.md 4.5 KB

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)