{"id":6188,"date":"2026-09-05T15:46:43","date_gmt":"2026-09-05T06:46:43","guid":{"rendered":"https:\/\/eternalsphere.net\/echoes\/?p=6188"},"modified":"2026-09-12T17:17:54","modified_gmt":"2026-09-12T08:17:54","slug":"20mh6y1v4uyynnv","status":"publish","type":"post","link":"https:\/\/blog.eternalsphere.net\/index.php\/2026\/09\/05\/20mh6y1v4uyynnv\/","title":{"rendered":"Designing a Portable, Isolated FRPC Distribution Package for Windows and Linux"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">A small FRP client package can be more useful than a full installer when the goal is temporary remote access, rapid deployment, or distribution to a limited number of trusted users.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The key is not simply to copy <code>frpc.exe<\/code> together with a configuration file. A good portable package should be:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>self-contained;<\/li>\n\n\n\n<li>easy to start and stop;<\/li>\n\n\n\n<li>able to report its own state;<\/li>\n\n\n\n<li>safe when multiple FRPC instances exist on the same system;<\/li>\n\n\n\n<li>isolated from other recipients;<\/li>\n\n\n\n<li>usable on both Windows and Linux;<\/li>\n\n\n\n<li>easy to verify before distribution.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">A clean design can achieve all of this without installing FRPC as a system service on the client machine.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">1. The Core Design Goal<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The intended architecture is based on <strong>one independent FRPS environment per distributed FRPC package<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of sharing one server-side FRPS process among multiple recipients:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"> <code>                Shared FRPS\n                \/     |      \\\n             User A User B  User C<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">each package is assigned its own FRPS instance:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FRPC-A  \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba FRPS-A\nFRPC-B  \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba FRPS-B\nFRPC-C  \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba FRPS-C<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Each pair has its own:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FRPS process\nFRPS configuration\ncontrol port\nauthentication token\nremote-port whitelist\nFRPC configuration<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FRPC-A\n    \u2193 QUIC\/TLS\n&lt;FRPS_DOMAIN_A&gt;:&lt;FRPS_PORT_A&gt;\n    \u2193\nFRPS-A\n    \u2193\n&lt;REMOTE_SSH_PORT_A&gt;\n    \u2193\nFRPC-A host:22<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and independently:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FRPC-B\n    \u2193 QUIC\/TLS\n&lt;FRPS_DOMAIN_B&gt;:&lt;FRPS_PORT_B&gt;\n    \u2193\nFRPS-B\n    \u2193\n&lt;REMOTE_SSH_PORT_B&gt;\n    \u2193\nFRPC-B host:22<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A compromised configuration from one package therefore does not automatically grant access to another FRPS security domain.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">2. Server-Side Multi-Instance Layout<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A simple FRPS directory can keep one executable while using multiple configuration files:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;FRPS_DIRECTORY&gt;\/\n\u251c\u2500\u2500 frps\n\u251c\u2500\u2500 frps1.toml\n\u251c\u2500\u2500 frps2.toml\n\u2514\u2500\u2500 frps3.toml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Each FRPS instance is then managed separately by systemd:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>frps1.service \u2192 frps1.toml\nfrps2.service \u2192 frps2.toml\nfrps3.service \u2192 frps3.toml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A generic service definition looks like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#091;Unit]\nDescription=frps&lt;N&gt;-server\n\n&#091;Service]\nUser=&lt;SERVICE_USER&gt;\nWorkingDirectory=&lt;FRPS_DIRECTORY&gt;\nExecStart=&lt;FRPS_DIRECTORY&gt;\/frps -c &lt;FRPS_DIRECTORY&gt;\/frps&lt;N&gt;.toml\nRestart=always\n\n&#091;Install]\nWantedBy=multi-user.target<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The benefit is operational isolation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Restarting:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>systemctl restart frps2.service<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">does not affect:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>frps1.service\nfrps3.service<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">3. Minimal FRPS Configuration for Portable Clients<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A portable-client FRPS instance does not necessarily need a web dashboard.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A minimal configuration can look like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#################################### Communication ###############################\n\nbindPort = &lt;FRPS_TCP_PORT&gt;\nquicBindPort = &lt;FRPS_UDP_PORT&gt;\nuserConnTimeout = 30\n\n#################################### Authentication ##############################\n\nauth.method = \"token\"\nauth.token = \"&lt;UNIQUE_AUTH_TOKEN&gt;\"\nauth.additionalScopes = &#091;\"HeartBeats\", \"NewWorkConns\"]\n\n#################################### Allowed Remote Ports ########################\n\nallowPorts = &#091;\n  { single = &lt;REMOTE_SSH_PORT&gt; }\n]\n\n#################################### Transport ###################################\n\ntransport.tls.force = true<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If TCP and QUIC intentionally use the same numerical port:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>bindPort = &lt;FRPS_PORT&gt;\nquicBindPort = &lt;FRPS_PORT&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">the firewall must allow both:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;FRPS_PORT&gt;\/tcp\n&lt;FRPS_PORT&gt;\/udp<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The SSH proxy itself only requires:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;REMOTE_SSH_PORT&gt;\/tcp<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">There is no reason to open the SSH remote port over UDP.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">4. Why <code>allowPorts<\/code> Matters<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A client possessing a valid FRPS token can request remote proxy ports.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Without a restriction, the client may potentially request arbitrary available ports on the FRPS machine.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Therefore each isolated FRPS instance should ideally contain a strict whitelist:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>allowPorts = &#091;\n  { single = &lt;REMOTE_SSH_PORT&gt; }\n]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A temporary SSH-only FRPS instance therefore has access to exactly one server-side published port.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This creates a useful security boundary:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Authentication token\n        +\nRemote-port whitelist\n        +\nIndependent FRPS process<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">5. Client Configuration<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The client configuration can remain very small.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For an SSH-only package:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#################################### Server ######################################\n\nserverAddr = \"&lt;FRPS_DOMAIN&gt;\"\nserverPort = &lt;FRPS_PORT&gt;\n\n#################################### Transport ###################################\n\ntransport.protocol = \"quic\"\ntransport.tls.enable = true\n\n#################################### Authentication ##############################\n\nauth.method = \"token\"\nauth.token = \"&lt;UNIQUE_AUTH_TOKEN&gt;\"\nauth.additionalScopes = &#091;\"HeartBeats\", \"NewWorkConns\"]\n\n#################################### Proxy ########################################\n\n&#091;&#091;proxies]]\nname = \"&lt;PACKAGE_NAME&gt; SSH\"\ntype = \"tcp\"\nlocalIP = \"127.0.0.1\"\nlocalPort = 22\nremotePort = &lt;REMOTE_SSH_PORT&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Using:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>localIP = \"127.0.0.1\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">instead of:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>localIP = \"localhost\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">removes hostname-resolution ambiguity and makes the local target explicit.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">6. Portable Package Structure<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A cross-platform package can include both Linux and Windows binaries:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>frpc&lt;N&gt;\/\n\u251c\u2500\u2500 frpc\n\u251c\u2500\u2500 frpc.exe\n\u251c\u2500\u2500 frpc&lt;N&gt;.toml\n\u251c\u2500\u2500 FRPC-Start.bat\n\u251c\u2500\u2500 FRPC-Status.bat\n\u251c\u2500\u2500 FRPC-Stop.bat\n\u251c\u2500\u2500 FRPC-Start.sh\n\u251c\u2500\u2500 FRPC-Status.sh\n\u2514\u2500\u2500 FRPC-Stop.sh<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The lowercase package directory is convenient for Linux and other case-sensitive systems:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>frpc2\/\nfrpc3\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">while the control scripts retain the explicit <code>FRPC<\/code> prefix:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FRPC-Start\nFRPC-Status\nFRPC-Stop<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is useful if one of the scripts is copied outside its intended directory. A generic filename such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Start.bat<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">reveals almost nothing about what it controls.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">By contrast:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FRPC-Start.bat<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">is immediately recognizable.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">7. Why Start, Status, and Stop Scripts Are Worth Adding<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A raw FRPC package normally requires the user to open a terminal and run something like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>frpc -c frpc&lt;N&gt;.toml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That is acceptable for administrators, but inconvenient for casual recipients.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A portable distribution benefits from three simple operations:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FRPC-Start\nFRPC-Status\nFRPC-Stop<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The package then behaves almost like a small application without requiring installation.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">8. Windows Start Behavior<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The Windows start script should:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>locate its own directory;<\/li>\n\n\n\n<li>locate the matching TOML file;<\/li>\n\n\n\n<li>detect whether the same FRPC instance is already running;<\/li>\n\n\n\n<li>start <code>frpc.exe<\/code> in the background;<\/li>\n\n\n\n<li>avoid leaving a console window open;<\/li>\n\n\n\n<li>record runtime information;<\/li>\n\n\n\n<li>display a success or failure popup.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">A hidden runtime directory can store temporary state:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.runtime\\\n\u251c\u2500\u2500 frpc.pid\n\u251c\u2500\u2500 frpc.log\n\u2514\u2500\u2500 frpc-error.log<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The user-facing directory remains clean.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">9. Do Not Kill FRPC by Process Name<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A dangerous stop implementation would be:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>taskkill \/IM frpc.exe \/F<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This kills <strong>every FRPC process<\/strong> on the machine.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That becomes a serious problem if a Windows host happens to run several independent FRPC instances.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A safer design identifies the intended process using a combination of:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>executable path\nconfiguration filename\nPID\ncommand line<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For example, a process should only be considered part of package <code>frpc2<\/code> if it matches something equivalent to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;PACKAGE_DIRECTORY&gt;\\frpc.exe\n-c &lt;PACKAGE_DIRECTORY&gt;\\frpc2.toml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The stop script can then terminate only that exact process.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">10. PID Files Should Not Be Trusted Blindly<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A PID file is useful:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.runtime\/frpc.pid<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">but operating systems eventually reuse process IDs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Therefore a robust status or stop script should not simply read:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>12345<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and kill PID 12345.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It should verify that the PID still belongs to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>the expected FRPC executable\n+\nthe expected configuration<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If not, the PID file is stale and should be removed.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">11. Status Script Design<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The status script should answer one simple question:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Is this package's FRPC process running?<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On Windows, a popup is convenient:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FRPC is running.\nPID: &lt;PID&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">or:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FRPC is not running.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A popup avoids the awkward behavior of a command window appearing briefly and immediately disappearing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For terminal-only environments, a status script can instead print the result and optionally wait for:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Press any key to exit...<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">12. Linux Script Behavior<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Linux packages can use the same conceptual Start \/ Status \/ Stop interface.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A startup script can launch FRPC with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>nohup .\/frpc -c .\/frpc&lt;N&gt;.toml &gt; .runtime\/frpc.log 2&gt;&amp;1 &lt;\/dev\/null &amp;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and record:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$!<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">as the PID.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On desktop Linux, scripts can display graphical dialogs when available:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>KDE       \u2192 kdialog\nGNOME\/etc \u2192 zenity<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and fall back to terminal output otherwise.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This makes the same package usable both from:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>desktop file manager<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SSH \/ terminal<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">13. Linux Process Validation<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Linux provides an especially reliable way to verify process identity through <code>\/proc<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a stored PID:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/proc\/&lt;PID&gt;\/exe\n\/proc\/&lt;PID&gt;\/cmdline<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">can be inspected.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A valid package FRPC process should satisfy both conditions:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/proc\/&lt;PID&gt;\/exe\n    =\n&lt;PACKAGE_DIRECTORY&gt;\/frpc<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and its command line should contain:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>frpc&lt;N&gt;.toml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This prevents a stop script from terminating another FRPC process merely because the process name happens to be the same.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">14. Cross-Platform Binary Packaging<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">If a package targets x86-64 systems, it can contain:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>frpc      \u2192 Linux x86-64 ELF\nfrpc.exe  \u2192 Windows x86-64 PE<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The configuration file is shared between operating systems.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For ARM64 Linux machines, the Linux executable must instead be replaced with the ARM64 build:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>frpc \u2192 Linux ARM64<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The scripts and TOML configuration generally do not need to change.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">15. Matching Client and Server Versions<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">For controlled deployments, using the same FRP release on client and server simplifies compatibility testing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FRPS: &lt;FRP_VERSION&gt;\nFRPC Linux: &lt;FRP_VERSION&gt;\nFRPC Windows: &lt;FRP_VERSION&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A portable package should ideally be assembled from one explicitly selected FRP release rather than mixing binaries downloaded at different times.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">16. Distribution Archive<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Each recipient should receive a separate archive:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>frpc2.zip\nfrpc3.zip<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">rather than one archive containing both configurations.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The separation is important because the TOML contains credentials.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Conceptually:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Recipient A\n\u251c\u2500\u2500 frpc2.zip\n\u2514\u2500\u2500 frpc2.zip.sha256\n\nRecipient B\n\u251c\u2500\u2500 frpc3.zip\n\u2514\u2500\u2500 frpc3.zip.sha256<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">No recipient needs to know another package exists.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">17. SHA-256 Verification<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A professional-looking portable package should include an external checksum file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Generate it with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sha256sum frpc&lt;N&gt;.zip &gt; frpc&lt;N&gt;.zip.sha256<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The result contains:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;SHA256_HASH&gt;  frpc&lt;N&gt;.zip<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The recipient can verify it with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sha256sum -c frpc&lt;N&gt;.zip.sha256<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected result:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>frpc&lt;N&gt;.zip: OK<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>.sha256<\/code> file should remain <strong>outside<\/strong> the ZIP it verifies.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Otherwise it cannot independently validate the archive.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">18. Why Separate Checksum Files Are Better Here<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A project distributing many public release artifacts often uses:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SHA256SUMS<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">containing all package hashes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For private recipient-specific packages, individual files are cleaner:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>frpc2.zip.sha256\nfrpc3.zip.sha256<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Each recipient only receives information about their own package.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">19. Test the Actual Archive, Not Just the Development Directory<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">One of the easiest packaging mistakes is validating only the source directory.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A more reliable release procedure is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>build package\n    \u2193\ncreate ZIP\n    \u2193\ngenerate SHA-256\n    \u2193\ncopy ZIP elsewhere\n    \u2193\nverify SHA-256\n    \u2193\nextract into a new directory\n    \u2193\ntest extracted package<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This simulates what the recipient will actually experience.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It catches problems such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>missing files\nwrong directory names\nlost executable permissions\nstale configuration\nincorrect archive contents\nbroken scripts<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">20. Recommended Linux Validation Procedure<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A clean Linux validation sequence is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sha256sum -c frpc&lt;N&gt;.zip.sha256<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">then:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>unzip frpc&lt;N&gt;.zip<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">verify the binary:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/frpc --version<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">verify configuration syntax:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/frpc verify -c .\/frpc&lt;N&gt;.toml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">start through the distributed script:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/FRPC-Start.sh<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">check status:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/FRPC-Status.sh<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and verify the remote SSH path:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh-keyscan -p &lt;REMOTE_SSH_PORT&gt; &lt;PUBLIC_DOMAIN&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Finally:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/FRPC-Stop.sh<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and confirm no FRPC process remains.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">21. End-to-End Validation Is More Important Than Config Validation<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A valid TOML file only proves syntax.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A complete test should verify the entire path:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Local SSH :22\n      \u2191\nPortable FRPC\n      \u2191\nQUIC + TLS\n      \u2191\nIndependent FRPS\n      \u2191\nServer firewall\n      \u2191\nRouter\/NAT\n      \u2191\nPublic remote SSH port<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A command such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh-keyscan -p &lt;REMOTE_SSH_PORT&gt; &lt;PUBLIC_DOMAIN&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">is useful because it confirms that an actual SSH server is reachable through the complete chain without requiring an interactive login.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A successful result resembles:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># &lt;PUBLIC_DOMAIN&gt;:&lt;REMOTE_SSH_PORT&gt; SSH-2.0-OpenSSH_&lt;VERSION&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">followed by host keys.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At that point the forwarding path is operational.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">22. Firewall Design<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The FRPS host needs two different categories of firewall rules.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For the FRPC-to-FRPS transport:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;FRPS_PORT&gt;\/tcp\n&lt;FRPS_PORT&gt;\/udp<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For a TCP SSH proxy:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;REMOTE_SSH_PORT&gt;\/tcp<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Therefore:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FRPS control transport:\nTCP + UDP\n\nPublished SSH proxy:\nTCP only<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The same distinction should also be reflected in the edge router&#8217;s NAT rules.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">23. A Useful Security Property of This Design<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Each package has an independent revocation path.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If package A should no longer work, the administrator can remove or disable:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FRPS-A service\nFRPS-A firewall rule\nrouter forwarding rule\nFRPS-A token<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">without touching package B.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is much cleaner than several recipients sharing:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>one FRPS instance\none token\none control port\none policy set<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">24. Final Architecture<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The resulting architecture resembles:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"> <code>                      Internet\n                           \u2502\n                    &lt;EDGE_ROUTER&gt;\n                           \u2502\n          \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n          \u2502                                 \u2502\n &lt;FRPS_PORT_A&gt;                        &lt;FRPS_PORT_B&gt;\n TCP + UDP                            TCP + UDP\n          \u2502                                 \u2502\n          \u25bc                                 \u25bc\n       FRPS-A                             FRPS-B\n          \u2502                                 \u2502\n &lt;REMOTE_SSH_A&gt;\/TCP                 &lt;REMOTE_SSH_B&gt;\/TCP\n          \u2502                                 \u2502\n          \u25bc                                 \u25bc\n       FRPC-A                             FRPC-B\n          \u2502                                 \u2502\n       localhost:22                     localhost:22<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The two paths share physical infrastructure but not FRPS processes, credentials, control ports, or published SSH ports.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">25. Final Package Layout<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A polished distribution can ultimately look like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>frpc&lt;N&gt;\/\n\u251c\u2500\u2500 frpc\n\u251c\u2500\u2500 frpc.exe\n\u251c\u2500\u2500 frpc&lt;N&gt;.toml\n\u2502\n\u251c\u2500\u2500 FRPC-Start.bat\n\u251c\u2500\u2500 FRPC-Status.bat\n\u251c\u2500\u2500 FRPC-Stop.bat\n\u2502\n\u251c\u2500\u2500 FRPC-Start.sh\n\u251c\u2500\u2500 FRPC-Status.sh\n\u2514\u2500\u2500 FRPC-Stop.sh<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">distributed as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>frpc&lt;N&gt;.zip\nfrpc&lt;N&gt;.zip.sha256<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Internally, runtime files are created only after execution:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.runtime\/\n\u251c\u2500\u2500 frpc.pid\n\u251c\u2500\u2500 frpc.log\n\u2514\u2500\u2500 frpc-error.log<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">Conclusion<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A portable FRPC package can be much more than an executable and a TOML file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With a small amount of structure, it becomes a controlled, cross-platform remote-access component with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>independent authentication\nindependent FRPS processes\nrestricted remote ports\nQUIC\/TLS transport\nWindows and Linux binaries\nStart \/ Status \/ Stop controls\nprecise process identification\nruntime logging\nSHA-256 verification\nrecipient-level isolation<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The most important design principle is isolation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Each recipient should be treated as a separate security boundary rather than merely another configuration pointing at the same FRPS instance.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once that principle is combined with strict port whitelisting, precise process management, reproducible packaging, and end-to-end testing of the actual archive, FRPC becomes suitable for temporary, portable deployment without sacrificing operational clarity.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A small FRP client package can be more useful than a full installer when the goal is temporary remote access, rapid deployment, or distribution to a limited number of trusted users. The key is not simply to copy frpc.exe together with a configuration file. A good portable package should be: &hellip;<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[120],"tags":[335],"class_list":["post-6188","post","type-post","status-publish","format-standard","hentry","category-1s3b6h7r2zay02x","tag-tunneling"],"_links":{"self":[{"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/posts\/6188","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/comments?post=6188"}],"version-history":[{"count":1,"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/posts\/6188\/revisions"}],"predecessor-version":[{"id":6189,"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/posts\/6188\/revisions\/6189"}],"wp:attachment":[{"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/media?parent=6188"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/categories?post=6188"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/tags?post=6188"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}