Skip to content

Latest commit

 

History

History
482 lines (381 loc) · 26.2 KB

README.md

File metadata and controls

482 lines (381 loc) · 26.2 KB

MegaProxyServer guide

Русская версия · Project overview

Overview

MegaProxyServer provisions HTTPS and SSH proxies on multiple Debian or Ubuntu servers. The Python CLI validates one inventory and produces Ansible variables, client configurations and a compact credential summary. Ansible installs and maintains the server-side components.

HTTPS is provided by GOST in a pinned container. HAProxy routes TLS by SNI when one entry server publishes several chain hostnames. SSH proxy users are restricted to local TCP forwarding and cannot open a shell, TTY, subsystem, remote forwarding or tunnel device.

Requirements

The control machine needs macOS or Linux, Git, uv, OpenSSH and Python 3.12+. Managed machines need a current Debian or Ubuntu release and a sudo-capable SSH account. Open TCP 443 for HTTPS and TCP 80 during standalone ACME validation in both the provider firewall and the host firewall.

Public-IP ACME certificates require Certbot 5.4+ and Let's Encrypt's short-lived profile.

First deployment

Install on the server

Connect to Debian/Ubuntu with an interactive terminal (ssh -t) or use the server console. You need root or sudo, curl and internet access. Install curl if necessary:

sudo apt-get update && sudo apt-get install -y curl ca-certificates

Start the installer (omit sudo when logged in as root):

curl -fsSL https://raw.githubusercontent.com/andre487/MegaProxyServer/main/install.sh | sudo bash

To inspect it first, download with curl -fsSL URL -o install.sh, review the file and run sudo bash install.sh, using the URL above.

  1. Confirm dependency installation. The checkout lives in /opt/megaproxy-server; Python and project dependencies are installed automatically.
  2. Enter a host name, its public IP or DNS name, administrator and current SSH port. Choose or generate an administrative key, then select HTTPS and/or SSH, certificates and proxy users. For domain ACME, point DNS at this server beforehand. Open the required provider firewall ports.
  3. Set and save an Ansible Vault password; subsequent invocations will need it.
  4. Copy the displayed administrative private key to your computer before applying. With existing root access, use another local terminal: scp root@SERVER:/PATH/TO/KEY ./megaproxy-admin, then chmod 600 ./megaproxy-admin. With a sudo account, transfer securely using sudo to read the file; do not make it publicly readable on the server.
  5. Confirm that the key is saved. Setup creates the administrator locally, checks a fresh SSH login and sudo through loopback, disables root/password administrative login, configures services and the host firewall, verifies provisioning and exports client configurations.
  6. Keep the original session open and test login from your computer: ssh -i ./megaproxy-admin -p PORT ADMIN@SERVER. Securely retrieve /opt/megaproxy-server/.generated/configs/MegaProxy.json using sudo access.

If you cancelled before applying or setup failed, rerun the installer or resume with:

sudo /opt/megaproxy-server/mega-proxy setup-local

Existing inventory is reused; the installer does not automatically update an existing checkout. For maintenance, run sudo /opt/megaproxy-server/mega-proxy with the desired command. Inventory, keys and exports stay on the server; keep private backups. In this mode local: true routes administrative SSH through loopback while client exports retain the public address. Only use this inventory on that server. To move control elsewhere, remove local: true and update key paths. Local setup handles one server; multi-host management is described below.

Manage from another machine

git clone https://github.com/andre487/MegaProxyServer.git
cd MegaProxyServer
./mega-proxy inventory
./mega-proxy bootstrap
./mega-proxy plan
./mega-proxy apply
./mega-proxy verify

The wizard can create administrative keys, proxy credentials and an encrypted inventory. An inventory outside the repository is remembered in the ignored inventory-path file. Override it at any time with --inventory PATH.

The wizard supports an initial password or a separate SSH key. Bootstrap prompts for the password without echoing it. The password is kept only in a private temporary connection file, removed after Ansible exits. Verify the server fingerprint and add its key to known_hosts first. Load passphrase-protected administrative keys with ssh-add; deriving or validating the public key also uses ssh-keygen -y, which can ask for the passphrase separately. Unattended apply requires this step to work without an interactive prompt.

bootstrap creates the administrator and sudo access, checks a fresh key-only login and sudo -n, then disables root and password-based administrative login. It verifies access again after SSH reload and immediately updates inventory per host. SSH proxy users keep password authentication. Retrying uses the administrator if already accessible, without requesting the initial password. Keep the original SSH session open until verification succeeds. apply automatically bootstraps selected pending hosts, including when tags are specified. Run bootstrap before planning new hosts.

To add a server while preserving existing settings and proxy users:

./mega-proxy add-host
./mega-proxy bootstrap --limit proxy_new
./mega-proxy plan --limit proxy_new
./mega-proxy apply --limit proxy_new

Bootstrap accepts comma-separated exact inventory host names for --limit; the flag can be repeated. The fresh login check respects local SSH configuration and reports SSH or sudo errors. A later proxy installation failure does not switch completed hosts back to root.

Commands and inventory

Use ./mega-proxy --help and ./mega-proxy COMMAND --help for commands and options. Pass the global inventory option before the command:

./mega-proxy --inventory /secure/inventory.yml validate
./mega-proxy --inventory /secure/inventory.yml apply --limit proxy_eu --tags https

Inventory lookup uses the explicit path, then inventory-path, then inventory.yml. The launcher runs from the repository root, so relative paths are resolved there. --no-hooks can appear before or after the command.

Start with inventory.example.yml, replace the example addresses and keys, and validate before applying. For a server-side HTTPS chain, use the complete two-host inventory.chains.example.yml. Accepted fields, validation rules and defaults are defined in models.py; container versions are pinned there and can be overridden in inventory.

Proxy users live in the global users section and apply to every host with that service enabled. Use dedicated SSH proxy logins, separate from the administrator. SSH key exports read the file at generated_private_key on the control machine and embed its contents; a public key alone is not enough to generate a client configuration. Android MegaProxy requires an unencrypted private key.

To link SSH accounts explicitly, set ssh_users: [tun-alice] on an HTTPS user or run ./mega-proxy link-users to choose existing accounts. The wizard offers this selection when creating users of both types. An empty or omitted ssh_users means no links; names are never matched automatically. Unknown accounts and duplicates fail inventory validation. An SSH account may be linked to multiple HTTPS users; removing an SSH account also removes its links. These links provide metadata for personalized delivery; the current export and configs commands still export all users.

SSH service and profile selection

services.ssh.enabled defaults to true and controls SSH proxy provisioning and verification. services.ssh.generate_profile defaults to false and selects hosts for direct SSH profiles in export, configs and the configuration API. Administrative SSH uses admin settings separately.

The wizard asks whether to publish direct SSH profiles, defaulting to yes; omitting the field in hand-written YAML still defaults to false. enabled: false also disables an existing proxy: HTTPS stops GOST, HAProxy and the renewal timer; SSH denies the megaproxy group and closes its sessions while preserving administrative SSH. Keep the disabled service in YAML: an absent service is unmanaged. Firewall records its own rules and revokes retired ports on the next full apply, preserving other rules. An initial upgrade records existing configured ports before later changes; older open ports already absent from inventory are not discovered automatically.

Only accounts whose primary group is megaproxy may be modified or removed; new proxy accounts are created in that group. Administrative/bootstrap accounts, reserved names and paths are rejected in removed_ssh. The administrative authorized_keys file is managed exclusively: replacing admin.public_key revokes the old key. Changing admin.user requires bootstrap through a working account: the new administrator is verified before retiring the previous managed account and sudo access, preserving its files. For key rotation, set the new public_key/private_key_file, the current admin as bootstrap_user, and the old private key as bootstrap_private_key_file. Bootstrap verifies the new login before revoking the old key; mismatched public and private keys are rejected.

Inventory and exports are replaced atomically with mode 0600. Export validation runs before replacing an existing file; country codes outside [A-Z]{2} become empty strings. Port conflicts and HTTPS without routes are rejected before connecting to a host. Docker cleanup only removes old GOST/Certbot images, without global prune operations. HTTPS chains verify the exit certificate and hostname. CI uses a local CA to test knock/chain overrides, nested TLS Jump, and rejection of bad credentials, certificates or unavailable exits, without external domains or ACME for these checks.

enabled generate_profile Result
true true Provision SSH proxy users and publish direct SSH profiles
true false Provision SSH proxy users; use the host only in declared jump pairs
false any Deny proxy access, close proxy sessions and omit profiles; jump pairs cannot reference this host

Disabling SSH retains proxy accounts but denies their access; it does not stop the server's SSH daemon. To delete an account, use remove-users and apply. A tagged apply must include firewall to reconcile ports, for example apply --tags ssh,https,firewall.

hosts:
  de_proxy:
    # address and admin as in inventory.example.yml
    services:
      ssh:
        title: DE
        enabled: true
        generate_profile: true

Direct SSH titles use the required services.ssh.title field. The API omits the login; file exports append / login, as for HTTPS.

Explicit client-side jump pairs

settings:
  ssh_jump_pairs:
    - entry: de_proxy
      exit: us_proxy
      title: SSH DE -> US
      country_code: US
  https_jump_pairs:
    - entry: de_proxy
      exit: us_proxy
      title: HTTPS DE -> US
      country_code: US

entry is the first hop; exit is the destination. SSH Jump uses the same account from users.ssh on both hops. export and configs generate one profile per SSH account; the API uses only accounts linked through the HTTPS user's ssh_users. No per-hop login is configured in a pair. Both SSH services must be enabled, but generate_profile may be false. HTTPS Jump uses the current HTTPS user's credentials on both hops and requires two enabled direct HTTPS endpoints. It does not require https_chains_enabled or a chain domain.

For both jump lists, title is required and country_code is optional. The country defaults to the exit's code. An explicit country must be two uppercase letters. Empty lists create no jump profiles; no combinations are inferred. Unknown hosts, disabled services, repeated pairs and identical entry/exit hosts fail validation. ./mega-proxy jumps lists configured SSH pairs for each account. The legacy --all-jumps export flag is accepted but never enables undeclared pairs.

See inventory.jumps.example.yml for a complete example. https_chain_pairs is separate: it creates server-side SNI routes with individual hostnames, exported as ordinary HTTPS profiles. See inventory.chains.example.yml.

Profile order

The API and MegaProxy JSON list direct HTTPS, direct SSH, https_chain_pairs, https_jump_pairs, then ssh_jump_pairs. Direct profiles follow inventory host order; pairs follow their respective list order. MASQUE and SOCKS5 belong to the first group. Reordering preserves IDs. FoxyProxy, SuperProxy and ProxyList list direct HTTPS routes first, then server-side chains.

Profile names and countries

HTTPS and SSH services and all chain/jump pairs require a nonblank title. Names are read directly from inventory; no names are derived from host identifiers.

Direct HTTPS country codes come from the first segment of the host identifier after removing an optional proxy_ prefix: de_entry and proxy_de yield DE. Identifiers such as px-de yield an empty code. Codes must match two uppercase letters; no country registry is consulted. Explicit HTTPS chain pairs require country_code; jump pairs can override the exit's inferred code. Direct SSH and SOCKS5 profiles currently have an empty country code. The code is a display hint; use the client connection test to check the observed exit country. HTTPS and jump profile IDs use logical host/route identifiers and the account, so changing a title or endpoint preserves those IDs. Direct SSH IDs also use the host and account. SOCKS5 exports still derive IDs from their names.

https_chain_pairs creates only declared entry/exit routes; an empty list creates no server-side chains. Each exit needs separate machine credentials in chain_password, which are excluded from client exports. Chain titles use the required pair title.

Set a knock host per HTTPS proxy using services.https.probe_resistance.knock:

probe_resistance:
  enabled: true
  mode: local_decoy
  knock: [knock.example.com]

When probe resistance is enabled, the first host becomes browser.knockHost in API and MegaProxy file profiles. Chain routes inherit the entry setting unless the pair overrides probe_resistance. The setup wizard asks for the knock hostname when probe resistance is enabled. Apply HTTPS settings to the proxy hosts and update the config API bundle after changing it. MASQUE profiles do not use knock hosts.

Probe resistance suppresses the normal authentication challenge and can break browser clients that wait for 407 before sending credentials. Keep it disabled unless tested with the required clients. A route can override the entry setting; knock allows selected hostnames to receive the normal challenge.

Masked routes serve a static site using GOST's built-in loopback file server. A standard-library Python script generates a random heading and abstract SVG once. Repeated apply runs keep the site; remove /opt/megaproxy/decoy/index.html to regenerate it on the next apply. The page includes noindex,nofollow; /robots.txt contains User-agent: * and Disallow: /. Only enabled and knock currently affect masking. The inventory model accepts mode, status_code and site_title, but they do not select another response mode or control the generated site's title.

HTTPS certificates and chains

Domain certificates are issued with Certbot standalone ACME. An entry certificate contains its direct endpoint, when enabled, and all configured chain hostnames. After a pair is added, the role detects a missing SAN, expands the certificate and restarts GOST to load it.

Every chain hostname must resolve to its entry server before apply. HAProxy reads SNI without terminating TLS and forwards the connection to a dedicated loopback GOST HTTP/2 listener. The entry then connects to the selected exit with machine credentials. A pair in https_chain_pairs may override probe_resistance for that chain route only.

Certificate renewal runs from a systemd timer. Self-signed mode is an explicit fallback and requires clients to allow an invalid proxy certificate.

Client configurations

./mega-proxy configs
./mega-proxy configs --output-dir /secure/path

The default .generated/configs directory contains:

  • MegaProxy.json: HTTPS, enabled MASQUE/SOCKS5 transports, direct SSH for hosts with services.ssh.generate_profile: true, and explicitly configured SSH/HTTPS jump profiles;
  • FoxyProxy.json: HTTPS profiles;
  • SuperProxy.txt: HTTPS URLs with the Super Proxy v1 header;
  • ProxyList.txt: URL-encoded HTTPS proxy URLs.

Directories use mode 0700 and files 0600. Generation is idempotent and does not contact managed servers. Exports contain plaintext passwords and private keys; handle them as secrets.

Android MegaProxy

Optional services.config_api deploys personalized HTTPS configuration feeds to separate, equal peers. See the inventory example and configuration API guide (Russian). Feeds use canonical v8, scrypt access verification, password-encrypted SSH secrets, client projections, bootstrap subscriptions and authenticated ETags. /robots.txt is public; other paths and invalid credentials return 403.

The exporter writes schema net.megaproxy487.config v7 for basic HTTPS/SSH profiles and v8 when https_jump_pairs, HTTP/3 or SOCKS5 is configured. MASQUE profiles require HTTP/3. The configuration API always returns canonical v8 and filters profiles for the requested client; browser projections omit SSH, jump and MASQUE profiles.

Import and connect

  1. Install MegaProxy using the client installation guide.

  2. Run ./mega-proxy configs and privately transfer MegaProxy.json to the device. The app can also import the generated FoxyProxy, SuperProxy and ProxyList files, but those contain HTTPS routes only. Prefer MegaProxy JSON to retain SSH profiles, IDs and certificate settings.

  3. Import the file and select the intended profile. Review routing, DNS and failover settings.

  4. Choose Test from the main-screen menu. For SSH, compare the displayed host-key fingerprint with one obtained through the server console or an already trusted administrative connection. SSH jump profiles require verification of both hosts. On the server, inspect public host keys with:

    for key in /etc/ssh/ssh_host_*_key.pub; do ssh-keygen -lf "$key" -E sha256; done
  5. Connect and approve Android's VPN permission. The app test checks the route and observed exit IP; ./mega-proxy verify checks server deployment and does not replace this device test.

Exports leave SSH trust fingerprints empty and keep host-key checking enabled. Self-signed HTTPS routes explicitly allow an invalid proxy certificate in MegaProxy JSON; the other export formats cannot carry that setting. Prefer ACME certificates for normal operation.

Updating imported profiles

Repeat configs after server or credential changes, then reimport the JSON. Matching IDs update existing profiles; new IDs add profiles. Generated HTTPS, MASQUE and jump IDs depend on logical host/route identifiers and accounts; changing their title, country or endpoint preserves IDs. Renaming an inventory host or account changes those IDs. Direct SSH IDs also use logical host/account identifiers. SOCKS5 exports derive IDs from names; renaming those profiles changes IDs. The API uses logical identities for all generated profiles. Additional manual profiles must supply unique IDs.

The app offers optional removal of local profiles missing from the file; they are not deleted automatically. Removing a profile on the phone does not revoke access: use remove-users and apply to revoke server credentials.

The client preserves stored passwords and private keys when their JSON fields are omitted; explicit empty fields clear them. This generator includes credentials, so reimport replaces them. It also imports global connection settings, including routing and disabled failover, and replaces profile settings. Review device customizations after importing. Because generated SSH trust fields are empty, reimport can require host-key confirmation again.

Two ways to chain HTTPS proxies

Route Setup What the phone imports
Server-side SNI chain Configure entry/exit and DNS using the chain inventory example, then apply An ordinary HTTPS profile pointing to the entry's chain hostname; GOST selects the exit
Client-side HTTPS with Jump Provision two direct HTTPS endpoints and configure settings.https_jump_pairs A HTTPS_JUMP profile in JSON v8

For HTTPS with Jump, enter the exit in the main proxy fields and the entry in Jump HTTPS proxy. Use global HTTPS user credentials on each hop; machine chain_password credentials belong to server-side chains. The entry must resolve and allow CONNECT to the exit hostname and port. Each hop verifies its own certificate. This mode does not require a chain domain or server-side https_chains_enabled. A failed hop does not cause a direct fallback to the exit.

See the client's HTTPS with Jump guide. Client-side HTTPS jump profiles require a v8-capable app; older clients reject v8 files. Server-side SNI routes remain usable through the HTTPS-only export formats. SSH jump profiles are generated by configs and export from settings.ssh_jump_pairs. Both jump lists default to empty; --all-jumps is a compatibility flag and never generates undeclared combinations.

Client limits and diagnostics

HTTPS and SSH forward TCP; MASQUE and SOCKS5 also forward UDP in compatible Android builds. Availability depends on the installed client release; see its transport guide. SSH keys protected by a passphrase are unsupported. DNS, per-app routing, failover and Android Always-on VPN are configured in the app. Exported profiles do not enable failover.

Use Test to distinguish proxy setup, destination access and exit-IP failures. It contacts external test services through the proxy; proxy-hostname bootstrap may query external DoH providers directly before the tunnel exists. Diagnostic logs stay on the device unless shared explicitly. See the client's privacy policy for destinations and handling of shared reports. Review reports before sharing and keep generated credentials out of issue trackers.

Summary

./mega-proxy summary prints endpoints once and then each global credential once:

Hosts:
proxy-eu.example.com (EU, HTTPS, SSH)
eu-via-us.example.com (EU -> US, HTTPS)

HTTPS:
alice
PASSWORD

SSH:
tun-alice
/path/to/private/key

summary alice selects an exact proxy login. Administrative users are omitted. Never write this output to an unprotected file or CI log.

Users, Vault and hooks

remove-users marks users for removal in inventory; run apply to remove HTTPS users from GOST. Removed SSH users also enter the global removed_ssh tombstone list; the next apply deletes their system accounts and root-owned authorized-key files on every SSH host. The last user of an enabled service cannot be removed.

vault-secrets encrypts individual secrets as tagged Ansible Vault values while leaving hosts, ports and flags readable. Existing encryption is preserved. Set ANSIBLE_VAULT_PASSWORD_FILE for unattended use; otherwise the CLI prompts for a password.

Whenever encrypted inventory is saved, the CLI encrypts all secret fields again, including unchanged values. Ansible Vault uses a random salt, so every !vault block may change in the diff while the passwords themselves remain unchanged. For example, bootstrap saves inventory after each successfully configured host when switching to the permanent administrator. This is a current limitation of saving: original encrypted blocks are not retained for unchanged secrets. These changes alone do not indicate password rotation on the servers or require updating client credentials.

After a successful apply, executable .hooks/post-config-change and files in .hooks/post-config-change.d/ run in name order. They receive MEGAPROXY_INVENTORY, MEGAPROXY_ANSIBLE_INVENTORY, MEGAPROXY_ROOT and MEGAPROXY_EVENT. The ignored .hooks directory is suitable for machine-local integrations such as px-manager.

Security and operations

  • Treat inventory, .secrets, .generated, summaries and exports as secrets.
  • Use one proxy account/key per person or device for independent revocation.
  • Verify SSH fingerprints through a trusted channel.
  • Root SSH login is disabled; the permanent administrator is key-only and has sudo.
  • Provider firewall rules remain the operator's responsibility.
  • Optional probe resistance hides the protocol from unauthenticated HTTP probes, not the server IP or SNI; enable it only after testing the required clients.

Development and troubleshooting

./mega-proxy check runs Ruff, pytest, byte-compilation and Ansible syntax checks. CI runs this suite on Python 3.12 and 3.14. Separate integration jobs run real GOST and nginx on loopback and provision Ubuntu 24.04 plus two Debian 12 LXD guests. They cover SSH Jump, credential rotation, account removal, proxy disable/re-enable, firewall cleanup and administrative access. They use temporary credentials and self-signed or local-CA certificates; they do not test production DNS, public ACME issuance or an Android device. These jobs are not run by check.

main requires a pull request, all five CI checks, an up-to-date branch and resolved conversations. Force pushes and deletion are forbidden, including for administrators; approval by a second person is not required. The required check names match the workflows.

  • ACME fails: check DNS, TCP 80 and provider firewall rules.
  • A chain domain serves an old certificate: run apply --limit ENTRY --tags https, then inspect certificate SANs and megaproxy-gost status.
  • Unexpected inventory: pass --inventory PATH or inspect inventory-path.
  • px-manager did not update: ensure the post-change hook is executable and --no-hooks is absent.