Skip to content

Releases: tytsxai/anyreality-resi-stack

v2.1.1 — documentation: protocol scorecard + SEO/GEO

Choose a tag to compare

@tytsxai tytsxai released this 03 Aug 09:20
2180b3a

Summary

Documentation-only release. No installer, protocol, secrets, or runtime changes. Existing nodes do not need reinstall or client re-import.

Highlights

  • Protocol scorecard in README: side-by-side scoring for AnyReality vs VLESS+REALITY+XHTTP/Vision (China-facing path marked stagnant), bare AnyTLS, Hysteria2, Trojan/TLS, SS2022 — framed as the China-region best-pick argument for AnyTLS + REALITY (AnyReality).
  • SEO / GEO README restructure: clear what / why / who, dry-run quick start, features, use cases, limits; GitHub About and Topics suggestions; llms.txt entity card for AI retrieval.
  • FAQ, deployment, beginner, clients, comparison docs, and subscription package metadata aligned with the above.

Upgrade

No action required on already-deployed hosts. Optional: re-pull docs or re-run install only if you want the latest repo copy under /opt/anyreality-resi-stack for operator scripts.

Links

bash <(curl -fsSL https://raw.githubusercontent.com/tytsxai/anyreality-resi-stack/v2.1.1/install/install.sh) \
  --node-name "US-Resi-01" \
  --sni addons.mozilla.org \
  --with-subscription

Pin this release with ANYREALITY_RESI_STACK_REF=v2.1.1.

v2.1.0 — production readiness

Choose a tag to compare

@tytsxai tytsxai released this 28 Jul 06:18
0ca7132

Production-readiness release. No breaking changes — re-running the installer is the upgrade path, secrets.env is reused, and no client needs to re-import.

bash <(curl -fsSL https://raw.githubusercontent.com/tytsxai/anyreality-resi-stack/main/install/install.sh) \
  --node-name "US-Resi-01" --sni addons.mozilla.org --with-subscription

Re-running on an existing host creates the anyreality-sub service account, moves the subscription services onto it, installs the logrotate policy and the operator tools, and leaves the sing-box keys untouched.

Re-pull your subscription after upgrading. Client profiles are regenerated with a full routing stack (see below). Nothing needs to be re-imported — refreshing the subscription URL in your client is enough.

Why this release exists

These are the gaps that only show up after a node has been running a while, or in the middle of an incident:

Gap Consequence
Client profiles had no routing rules Everything went through the node; in TUN mode there is no direct toggle, so domestic traffic silently detoured abroad
Rule sets download from GitHub at first start A blocked download left the set empty and proxied every domestic site, with no local fallback
--ssh-port was read as "move sshd" Passing a port sshd was not listening on fenced off the real one at ufw --force enable
uninstall.sh aborted partway UFW rules and the fail2ban jail were left behind on a "successful" uninstall
box.log had no size bound Any persistent fault fills a small VPS disk, taking journald and the backup timer with it
Backups were never verified A corrupt archive silently rotates out the last good one; you find out when you need to roll back
Backups were local-only They die with the host
Subscription servers ran as root The only publicly-listening component here held root and could read secrets.env
Bad EnvironmentFile → bare traceback Endless crash loop under Restart=always with no indication of which variable was wrong
Accounting failure → failed request A full /var/lib broke every client's subscription refresh, though serving the profile is the primary job
systemd default start limit One transient fault could park sing-box permanently in a failed state
Single public-IP provider Install fell through to a vague warning and a profile with no server address
No health check, no alerting Nothing to answer "is this node healthy"
Leaked subscription URL Only remedy was a full reinstall
safe_target_path accepted .. FILE_DIR/.. got through; only the caller's is_file() stopped it

Routing

The generated sing-box profiles previously carried a single ip_is_private → direct rule and sent everything else through the node. That is merely wasteful in a mixed-inbound client and unusable in TUN mode, where there is no global/direct toggle to fall back on.

  • Four-layer rule stack in both client-single.json.tmpl and client-dual.json.tmpl: baseline (sniff, hijack-dns, private-direct) → ad/tracker reject → China-direct → a fallback that rejects UDP/443 before sending the rest to the node. The Clash templates gain the matching ad-block rule and the same inline safety net.
  • China-direct is deliberately two-tier. An inline 61-entry domain_suffix list is evaluated before the remote geosite-cn / geoip-cn rule sets, because those download from GitHub — commonly unreachable at first start, and a failed download yields an empty set that proxies every domestic site. The inline list needs no network request, so high-traffic domestic services stay direct regardless. download_detour routes the rule-set downloads themselves through the node.
  • Split DNS — domestic names resolve via 223.5.5.5, everything else over DoT through the node, with experimental.cache_file persisting rule sets across restarts.
  • Content-Disposition now emits an ASCII filename="…" alongside the percent-encoded RFC 5987 filename*=UTF-8''… form. Clients that cannot parse the header were inventing a numeric snowflake id for the imported profile.

Routing is documented in docs/zh-CN/ROUTING.md / docs/en/ROUTING.md, including how to verify a domain really goes direct without being fooled by a TUN client hijacking the test.

Stability

  • Log rotation for sing-box/etc/logrotate.d/sing-box: daily, immediate rotation past 20 MiB, 7 compressed generations, copytruncate. Validated at install time with logrotate -d.
  • Verified backups — the archive must list cleanly under tar -tzf and actually contain etc/sing-box/conf/ and etc/anyreality-resi-stack/. A failure deletes the bad archive and exits non-zero instead of rotating out the last good backup in its favour. Archives also stop carrying /etc/sing-box/logs, which the header comment already claimed was excluded.
  • StartLimitIntervalSec=0 on all three units. systemd's default 5-starts-in-10s limit can park sing-box — the only proxy on the box — in a failed state that nothing recovers from.
  • Accounting failures degrade instead of failing the request. Serving the profile is the primary job; a full or read-only /var/lib now means "the counter stops moving".

Security

  • Subscription services no longer run as root. They run as the unprivileged anyreality-sub account with only AmbientCapabilities=CAP_NET_BIND_SERVICE. systemd reads the EnvironmentFile as root before dropping privileges, so secrets.env stays 0600 root-only and is unreadable from the HTTP server itself. The subscription units additionally gain ProtectKernelTunables, RestrictRealtime, RestrictSUIDSGID, LockPersonality, and SystemCallArchitectures=native.
  • Optional TLS for the subscription endpointTLS_CERT_FILE / TLS_KEY_FILE, or --sub-tls-cert / --sub-tls-key at install time. Standard library only, TLS 1.2 floor. The subscription URL is a credential (the profile behind it contains the node password), so plain HTTP exposes it to anyone on the path. The health check fails when the certificate expires within 7 days — expiry is otherwise a silent outage where clients refuse the subscription while liveness probes stay green.
  • REMOTE_STATUS_URL is pinned to http(s)urlopen would otherwise accept file:// and read a local path into the usage cache.
  • Dot entries rejected in path resolution. pathlib does not normalise .., so the separator check let FILE_DIR/.. through; the caller's is_file() was load-bearing by accident.
  • Startup configuration validation. Every environment variable is range-checked; a typo exits 2 naming the variable instead of crash-looping on a traceback.
  • SECURITY.md documents the subscription URL exposure surface — the profile is served over plain HTTP on :80 and contains the node password, so the URL is a credential; anything placed in FILE_DIR is served under the same token path, which is why backups must not go there.

Installer and uninstaller

  • phase_firewall no longer risks locking you out. --ssh-port N only tells UFW which port to keep open — it does not move sshd, which is --harden-ssh — so passing a port sshd was not listening on fenced off the real one at ufw --force enable. The ports sshd is actually bound to are now always allowed, with a warning. A failing --harden-ssh phase is also no longer swallowed by a trailing || true.
  • uninstall.sh no longer aborts partway through. Two [[ -f … ]] && rm … loop bodies returned non-zero when the last candidate file was absent — the normal case — which set -Eeuo pipefail turned into an aborted uninstall that left UFW rules and the fail2ban jail behind. It now also removes the UFW rule for the port actually configured in /etc/sing-box/conf instead of assuming 443 (deliberately leaving SSH rules alone), plus the new /usr/local/sbin operator tools and /etc/logrotate.d/sing-box.
  • Public IP detection is no longer a single point of install failure. Detection tries four independent providers with a 5 s timeout each and validates the result is an IPv4 address; api.ipify.org alone is blocked or rate-limited often enough that fresh installs produced a profile with no server address.
  • --dry-run works on a host that already has secrets.env. The reuse path returned without defining the template variables, so the next phase died on an unbound variable; dry-run now exports placeholders instead of reading real secrets.
  • A flag given without its value (--sni as the final argument) died with a bare $2: unbound variable from set -u. The parser now names the flag.

Operations

  • anyreality-resi-stack-healthcheck — read-only, 15 checks (service state and restart count, sing-box check, inbound port listening, subscription /healthz, TLS certificate expiry, profile file, backup freshness/failure, off-box hook presence, disk, box.log size, clock sync, UFW, secrets.env permissions). Non-zero when degraded, silent while healthy, so one cron line is a complete alerting setup:

    */10 * * * * /usr/local/sbin/anyreality-resi-stack-healthcheck --quiet
    
  • anyreality-resi-stack-rotate-sub-token — rotates the subscription token in place, verifies the new path serves a profile, and rolls back automatically if the service does not come back healthy.

  • Off-box backup hooks — executables in /etc/anyreality-resi-stack/backup-hooks.d/ receive the verified archive path. A failing hook fails the backup run, because an off-box copy you believe exists but does not is the dangerous case. A non-executable 10-offsite.sh.example documents the contract on the box, and the health chec...

Read more

v2.0.0 — AnyReality default + rebrand

Choose a tag to compare

@tytsxai tytsxai released this 19 Jul 16:44
41bad70

[2.0.0] — 2026-07-19

Project renamed to anyreality-resi-stack (formerly reality-resi-stack). GitHub automatically redirects old repository URLs, so existing curl | bash install commands keep working. Runtime filesystem paths (/etc, /var/lib, /usr/local/lib, /var/backups), systemd unit names, the backup script/archives, and the environment variable (now ANYREALITY_RESI_STACK_REF, with the legacy REALITY_RESI_STACK_REF still honored) are all renamed to the anyreality-resi-stack prefix. Upgrading a v1.x host runs a migration phase (phase_migrate_legacy_paths) that moves the old reality-resi-stack directories to the new prefix and retires the old backup unit, so existing secrets, usage state, backups, and therefore already-imported clients are preserved.

Breaking — default protocol changed. Fresh installs now default to AnyReality (AnyTLS + Reality) instead of VLESS + Reality + xtls-rprx-vision. Existing servers keep their current protocol until the installer is re-run. AnyReality is sing-box-only; if you rely on Clash/mihomo clients, install (or re-run) with --protocol vless-vision to stay on the legacy protocol. Because AnyReality authenticates with a password rather than a UUID/flow and the default subscription file changed from profile.yaml to profile.json, clients must be re-imported after switching protocols.

Changed

  • AnyTLS + REALITY (AnyReality) is now the default protocol. New installs deploy a sing-box anytls inbound fronted by Reality, authenticated with a per-server password (ANYTLS_PASSWORD in secrets.env). AnyTLS's custom padding hardens against TLS-in-TLS fingerprinting while Reality keeps the certless server camouflage. Still no domain or TLS certificate required.
  • The default subscription profile is now a full sing-box client config served as profile.json (mixed inbound on 127.0.0.1:2080, AnyReality outbound, domain-based routing in dual-node mode); the legacy Clash profile.yaml is served only under --protocol vless-vision.
  • Runtime layout renamed to the anyreality-resi-stack prefix: /etc/anyreality-resi-stack, /var/lib/anyreality-resi-stack, /usr/local/lib/anyreality-resi-stack, /var/backups/anyreality-resi-stack, /opt/anyreality-resi-stack, the backup-anyreality-resi-stack.sh script and anyreality-resi-stack-*.tar.gz archives, and the anyreality-resi-stack-backup systemd units. A new phase_migrate_legacy_paths installer phase migrates existing reality-resi-stack hosts in place; uninstall.sh also cleans up either prefix.

Added

  • --protocol anytls-reality (default) / --protocol vless-vision (legacy) selects the inbound protocol. VLESS + Reality + xtls-rprx-vision remains fully supported for Clash/mihomo users, which cannot parse AnyReality.
  • AnyReality templates: templates/singbox/11_anytls-reality_inbounds.json.tmpl (server inbound), templates/singbox-client/anytls-outbound.json.tmpl, and full sing-box client configs templates/singbox-client/client-single.json.tmpl / client-dual.json.tmpl (dual-node smart routing via sing-box route rules).
  • Installers predating AnyReality mint and append an ANYTLS_PASSWORD to an existing secrets.env on re-run, so switching to --protocol anytls-reality works without regenerating the UUID or Reality keypair. Re-running with a different protocol drops the stale inbound and profile file to avoid port collisions or format mismatches.

Fixed

  • Leaf subscription accounting now samples interface counters while holding the state lock, preventing concurrent requests/background polling from applying stale samples out of order.
  • Aggregator usage cache writes now use per-thread temporary files plus atomic replace, avoiding .tmp collisions during concurrent refreshes.
  • Backup archives now exclude hidden runtime .tmp state/cache files and place manifest.txt at the archive root.
  • Leaf subscription accounting now keeps usage fresher by sampling in the background every USAGE_POLL_INTERVAL_SECONDS seconds instead of only updating when a client pulls the subscription URL.
  • Leaf subscription accounting now supports provider billing reset days via BILLING_CYCLE_DAY, so plans that reset on the 11th do not roll over on the 1st by mistake.
  • Leaf subscription accounting now counts bytes already present in the current boot on first state creation by default (COUNT_CURRENT_BOOT_ON_INIT=true), while still supporting baseline-only mode and USAGE_OFFSET_BYTES calibration.
  • Leaf accounting now carries usage forward across reboots or NIC counter rollovers by adding the new boot's current counter instead of silently dropping it.
  • Aggregator subscription accounting now refreshes the leaf status cache in the background via REMOTE_POLL_INTERVAL_SECONDS, keeping usage cards warm even before the next client request.
  • Re-running the installer with an existing secrets.env re-exports the reused UUID, Reality keys, subscription token, and short ID before rendering templates.
  • --with-subscription and --with-aggregator are now mutually exclusive, and aggregator installs fail early unless the residential-node template variables are provided.

Added

  • ANYREALITY_RESI_STACK_REF lets remote-piped installs fetch a specific branch or tag while defaulting to main.
  • Standard-library unittest coverage for leaf accounting and aggregator cache fallback, wired into make test and GitHub Actions.
  • make mdcheck now falls back to npx --yes markdown-link-check when the binary is not installed globally, retries transient link-checker failures once, and GitHub Actions runs the same Markdown link gate.

Security

  • Aggregator leaf-status polling now caps each remote status response with MAX_REMOTE_STATUS_BYTES (default 64 KiB) before parsing JSON.
  • Subscription systemd units now use basic sandboxing (NoNewPrivileges, PrivateTmp, ProtectHome, ProtectSystem=strict) and only keep /var/lib/anyreality-resi-stack writable.
  • Config backups now exclude runtime usage/cache state, set backup directory permissions to 700, and write archives as 600.

v1.0.3 — Documentation: FAQ + llms.txt + residential-IP GEO/SEO

Choose a tag to compare

@tytsxai tytsxai released this 18 May 16:44
93cbbea

📝 Documentation release

Documentation-only release on top of v1.0.2. Installer, sing-box config, subscription server, and dual-node aggregator are unchanged. No need to re-deploy if you're on v1.0.2.

Added

  • llms.txt — AI-search-engine index that explicitly positions the project for the residential-IP-as-asset use case and answers the questions people actually search for:
    • "Why is Telegram slow on my residential VPS?"
    • "Why does OpenAI block my data-center VPS but not residential?"
    • "Does Reality protocol need a domain?"
    • "Is the installer idempotent?"
    • "How is this different from 3x-ui / XHTTP-Installer?"
  • README — FAQ section with the same 7 Q&As in bilingual format.
  • README — Keywords block + nav row (Release / Docs / llms.txt / Changelog / Issues).

Why this matters for discovery

  • Generative search (ChatGPT / Claude / Perplexity / Gemini) can now ingest `llms.txt` and answer "what's the right VLESS toolkit for a residential-IP VPS that needs OpenAI on residential but Telegram fast?" with a direct pointer here.
  • Long-tail SEO keywords newly covered:
    • 中文:OpenAI 住宅 IP 出口、ChatGPT 住宅 IP 代理、Telegram 住宅 IP 卡顿、Discord 住宅 IP 降权、双节点智能分流、Clash 域名分流住宅
    • English: residential IP VLESS, sing-box residential installer, residential VPS smart routing, alternative to 3x-ui for residential VPS

Full changelog

See CHANGELOG.md.

v1.0.2 — refuse install when manual sing-box exists

Choose a tag to compare

@tytsxai tytsxai released this 17 May 11:10
1bfd8e7

Fixed

  • Preflight now refuses to proceed if a manually-installed sing-box already exists on the target host (binary at /usr/local/bin/sing-box without a matching apt-managed /usr/bin/sing-box, or any systemd unit matching sing-box*.service other than the standard sing-box.service). Without this check, apt install sing-box silently adds a second, independent binary + systemd unit + config dir alongside the existing manual install — peacefully coexisting until the next reboot, at which point both copies race on ports 443/8443. Hard-learned during v1.0.1 verification on a real host.

Upgrade

bash <(curl -fsSL https://raw.githubusercontent.com/tytsxai/reality-resi-stack/v1.0.2/install/install.sh) \
  --node-name "US-Resi-01" --sni addons.mozilla.org --with-subscription

Tested on

  • Real Ubuntu 24.04.4 LTS host with pre-existing manual sing-box → preflight correctly aborts with actionable message
  • Real Ubuntu 24.04.4 LTS host without prior sing-box install (v1.0.1) → full GPG verification + apt install path verified end-to-end

v1.0.1 — fix install-blocking GPG fingerprint

Choose a tag to compare

@tytsxai tytsxai released this 17 May 11:04
ff98fe9

Fixed (critical)

  • SINGBOX_APT_KEY_FPR was a placeholder value in v1.0.0 that did not match the real Sagernet GPG key bundle. Every real install would have died at phase_install_singbox with a fingerprint mismatch. This bug was not caught by --dry-run because dry-run intentionally skips the GPG check. Verified against the live https://sing-box.app/gpg.key on Ubuntu 24.04 LTS.
  • phase_verify ran live checks in --dry-run mode, producing fake-looking failures and a non-zero installer exit even though nothing had been installed. Now correctly no-ops in dry-run.
  • GPG verification logic now requires the pinned fingerprint to be present anywhere in the bundle rather than to be the first fingerprint — Sagernet bundles a primary key plus a signing subkey, so the first-fingerprint check was fragile against subkey rotation.

Upgrade

bash <(curl -fsSL https://raw.githubusercontent.com/tytsxai/reality-resi-stack/v1.0.1/install/install.sh) \
  --node-name "US-Resi-01" \
  --sni addons.mozilla.org \
  --with-subscription

If you already cloned v1.0.0, just git -C /opt/reality-resi-stack pull --tags && git -C /opt/reality-resi-stack checkout v1.0.1 then re-run the installer (it is idempotent).

v1.0.0 withdrawal

v1.0.0 has been marked withdrawn. It will install nothing on a fresh box because it dies at the GPG check before sing-box is installed — meaning v1.0.0 is harmless to those who tried it, just blocked.

v1.0.0 — initial release

Pre-release

Choose a tag to compare

@tytsxai tytsxai released this 17 May 11:00
a29376a

⚠️ WITHDRAWN — DO NOT USE

This release contained an install-blocking GPG fingerprint bug. Every real install would have aborted at phase_install_singbox. The bug was caught and fixed in v1.0.1 ~30 minutes after publication. v1.0.0 is harmless (it doesn't install anything before dying), just unusable.

Use v1.0.1 instead.


Original v1.0.0 release notes preserved below for historical reference, but do not deploy this version.