mysite/public-clearnet/posts/vpn/index.html

67 lines
No EOL
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html><html lang=en dir=auto><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name=robots content="noindex, nofollow"><title>Stealth Trojan VPN Behind Cloudflare Guide | AlipourIm journeys</title>
<meta name=keywords content="VPN,Trojan,Cloudflare,Xray,3x-ui,Bypass,Privacy,Debugging"><meta name=description content="Introduction
So you want a VPN that doesn&rsquo;t scream &ldquo;I am a VPN&rdquo; to every censor and firewall out there?
Welcome to the world of Trojan over WebSocket + TLS behind Cloudflare.
This guide not only shows you how to set it up but also sprinkles in some debugging magic so you can figure out why things break (and they will break, trust me).
Well anonymise domains and secrets, so substitute with your own:"><meta name=author content="Iman Alipour"><link rel=canonical href=https://blog.alipour.eu/posts/vpn/><link crossorigin=anonymous href=/assets/css/stylesheet.31b150d909186c48d6dbbf653acc2489945fa9ac2c8f8cd3fe8448e89e40fccf.css integrity="sha256-MbFQ2QkYbEjW279lOswkiZRfqawsj4zT/oRI6J5A/M8=" rel="preload stylesheet" as=style><link rel=icon href=https://blog.alipour.eu/favicon.ico><link rel=icon type=image/png sizes=16x16 href=https://blog.alipour.eu/favicon-16x16.png><link rel=icon type=image/png sizes=32x32 href=https://blog.alipour.eu/favicon-32x32.png><link rel=apple-touch-icon href=https://blog.alipour.eu/apple-touch-icon.png><link rel=mask-icon href=https://blog.alipour.eu/safari-pinned-tab.svg><meta name=theme-color content="#2e2e33"><meta name=msapplication-TileColor content="#2e2e33"><link rel=alternate hreflang=en href=https://blog.alipour.eu/posts/vpn/><noscript><style>#theme-toggle,.top-link{display:none}</style><style>@media(prefers-color-scheme:dark){:root{--theme:rgb(29, 30, 32);--entry:rgb(46, 46, 51);--primary:rgb(218, 218, 219);--secondary:rgb(155, 156, 157);--tertiary:rgb(65, 66, 68);--content:rgb(196, 196, 197);--code-block-bg:rgb(46, 46, 51);--code-bg:rgb(55, 56, 62);--border:rgb(51, 51, 51)}.list{background:var(--theme)}.list:not(.dark)::-webkit-scrollbar-track{background:0 0}.list:not(.dark)::-webkit-scrollbar-thumb{border-color:var(--theme)}}</style></noscript><script>(function(){window.goatcounter={endpoint:"/count"};const e=document.createElement("script");e.async=!0,e.src="/js/count.js",document.head.appendChild(e)})()</script><script>(function(){const s="",t=new Intl.NumberFormat,o="visit",i="visits";function a(e){document.readyState==="complete"||document.readyState==="interactive"?setTimeout(e,0):document.addEventListener("DOMContentLoaded",e)}function r(){try{return window.goatcounter&&window.goatcounter.get_data?window.goatcounter.get_data().p:location.pathname}catch{return location.pathname}}async function e(e,t){try{const o=new URLSearchParams({t:String(Date.now())});t&&o.set("start",t);const a=`${s}/counter/${encodeURIComponent(e)}.json?${o}`,n=await fetch(a,{credentials:"omit"});if(n.status===404)return 0;if(!n.ok)throw 0;const r=await n.json(),i=Number(String(r.count).replace(/,/g,""));return Number.isFinite(i)?i:0}catch{return null}}const n=e=>e===null?"—":`${t.format(e)} ${e===1?o:i}`;a(async function(){const s=document.querySelector(".post-single");if(s){const t=s.querySelector(".post-header .post-meta, .post-header .entry-meta, .post-meta"),o=await e(r());if(t){let e=t.querySelector(".post-views");e||(e=document.createElement("span"),e.className="post-views",t.appendChild(e)),e.textContent=n(o??0)}}const i=document.querySelectorAll("article.post-entry");await Promise.all(Array.from(i).map(async t=>{const s=t.querySelector(".entry-footer, .post-meta"),o=t.querySelector("a.entry-link, h2 a, .entry-title a");if(!s||!o)return;try{const i=new URL(o.getAttribute("href"),location.origin);if(i.origin!==location.origin)return;let t=s.querySelector(".post-views");t||(t=document.createElement("span"),t.className="post-views",s.appendChild(t)),t.textContent=n(await e(i.pathname)??0)}catch{}}));const o=document.getElementById("gc_site_visitors_week");if(o){const n=await e("TOTAL","week");o.textContent=n===null?"—":t.format(n)}})})()</script></head><body id=top><script>localStorage.getItem("pref-theme")==="dark"?document.body.classList.add("dark"):localStorage.getItem("pref-theme")==="light"?document.body.classList.remove("dark"):window.matchMedia("(prefers-color-scheme: dark)").matches&&document.body.classList.add("dark")</script><header class=header><nav class=nav><div class=logo><a href=https://blog.alipour.eu/ accesskey=h title="AlipourIm journeys (Alt + H)">AlipourIm journeys</a><div class=logo-switches><button id=theme-toggle accesskey=t title="(Alt + T)" aria-label="Toggle theme"><svg id="moon" width="24" height="18" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/></svg><svg id="sun" width="24" height="18" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg></button></div></div><ul id=menu><li><a href=https://blog.alipour.eu/posts/ title=Posts><span>Posts</span></a></li><li><a href=https://blog.alipour.eu/phd_journey/ title=PhD_journey><span>PhD_journey</span></a></li><li><a href=https://blog.alipour.eu/about/ title=About><span>About</span></a></li></ul></nav></header><main class=main><article class=post-single><header class=post-header><div class=breadcrumbs><a href=https://blog.alipour.eu/>Home</a>&nbsp;»&nbsp;<a href=https://blog.alipour.eu/posts/>Posts</a></div><h1 class="post-title entry-hint-parent">Stealth Trojan VPN Behind Cloudflare Guide</h1><div class=post-meta><span title='2025-09-09 11:05:00 +0200 +0200'>September 9, 2025</span>&nbsp;·&nbsp;4 min&nbsp;·&nbsp;Iman Alipour
<span class=post-meta-item><a href=https://blog.alipour.eu/posts/vpn/#isso-thread class=isso-comments-link>Comments</a></span></div></header><div class=post-content><h2 id=introduction>Introduction<a hidden class=anchor aria-hidden=true href=#introduction>#</a></h2><p>So you want a VPN that <strong>doesn&rsquo;t scream &ldquo;I am a VPN&rdquo;</strong> to every censor and firewall out there?<br>Welcome to the world of <strong>Trojan over WebSocket + TLS behind Cloudflare</strong>.</p><p>This guide not only shows you how to set it up but also sprinkles in some <strong>debugging magic</strong> so you can figure out why things break (and they <em>will</em> break, trust me).</p><p>Well anonymise domains and secrets, so substitute with your own:</p><ul><li>VPN domain: <code>web.example.com</code></li><li>Panel domain: <code>panel.example.com</code></li><li>Secret WS path: <code>/stealth-path_abcd1234</code></li><li>Password: <code>&lt;PASSWORD></code></li></ul><hr><h2 id=architecture-at-a-glance>Architecture at a Glance<a hidden class=anchor aria-hidden=true href=#architecture-at-a-glance>#</a></h2><p>Think of it as a disguise party:</p><ul><li><strong>Trojan</strong> = the shy guest (your VPN protocol)</li><li><strong>Nginx</strong> = the bouncer checking IDs (reverse proxy)</li><li><strong>Cloudflare</strong> = the doorman who makes sure nobody sees who&rsquo;s inside (CDN & proxy)</li><li><strong>Your fake website</strong> = the mask (camouflage page)</li></ul><p>Traffic flow:</p><div class=mermaid>Client → Cloudflare (443) → Nginx (443) → Trojan (localhost:54321)</div><hr><h2 id=step-1-panel-setup-panelexamplecom>Step 1: Panel Setup (<code>panel.example.com</code>)<a hidden class=anchor aria-hidden=true href=#step-1-panel-setup-panelexamplecom>#</a></h2><ul><li>Bind the 3x-ui panel to localhost (e.g., <code>127.0.0.1:46309</code>).</li><li>Choose a funky <strong>web base path</strong> like <code>/panel-bananas_42/</code>.</li><li>Proxy it through Nginx with HTTPS + Basic Auth.</li><li>Test it at <code>https://panel.example.com/panel-bananas_42/</code>.</li></ul><p><strong>Pro tip:</strong> If you see a blank page → your base path is mismatched or Nginx is eating it. Check logs!</p><hr><h2 id=step-2-trojan-inbound>Step 2: Trojan Inbound<a hidden class=anchor aria-hidden=true href=#step-2-trojan-inbound>#</a></h2><p>In 3x-ui, create a Trojan inbound:</p><ul><li>Local port: <code>54321</code></li><li>Transport: WebSocket</li><li>Path: <code>/stealth-path_abcd1234</code></li><li>Security: none</li><li>Password: <code>&lt;PASSWORD></code></li></ul><hr><h2 id=step-3-nginx-for-vpn-domain-webexamplecom>Step 3: Nginx for VPN Domain (<code>web.example.com</code>)<a hidden class=anchor aria-hidden=true href=#step-3-nginx-for-vpn-domain-webexamplecom>#</a></h2><p>Your Nginx is the gatekeeper. Sample config:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-nginx data-lang=nginx><span style=display:flex><span><span style=color:#66d9ef>server</span> {
</span></span><span style=display:flex><span> <span style=color:#f92672>listen</span> <span style=color:#ae81ff>443</span> <span style=color:#e6db74>ssl</span> <span style=color:#e6db74>http2</span>;
</span></span><span style=display:flex><span> <span style=color:#f92672>server_name</span> <span style=color:#e6db74>web.example.com</span>;
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#f92672>ssl_certificate</span> <span style=color:#e6db74>/etc/letsencrypt/live/web.example.com/fullchain.pem</span>;
</span></span><span style=display:flex><span> <span style=color:#f92672>ssl_certificate_key</span> <span style=color:#e6db74>/etc/letsencrypt/live/web.example.com/privkey.pem</span>;
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#75715e># Fake website at root
</span></span></span><span style=display:flex><span><span style=color:#75715e></span> <span style=color:#f92672>location</span> <span style=color:#e6db74>/</span> {
</span></span><span style=display:flex><span> <span style=color:#f92672>root</span> <span style=color:#e6db74>/var/www/html</span>;
</span></span><span style=display:flex><span> <span style=color:#f92672>index</span> <span style=color:#e6db74>index.html</span>;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#75715e># Real VPN under secret WS path
</span></span></span><span style=display:flex><span><span style=color:#75715e></span> <span style=color:#f92672>location</span> <span style=color:#e6db74>/stealth-path_abcd1234</span> {
</span></span><span style=display:flex><span> <span style=color:#f92672>proxy_pass</span> <span style=color:#e6db74>http://127.0.0.1:54321</span>;
</span></span><span style=display:flex><span> <span style=color:#f92672>proxy_http_version</span> <span style=color:#ae81ff>1</span><span style=color:#e6db74>.1</span>;
</span></span><span style=display:flex><span> <span style=color:#f92672>proxy_set_header</span> <span style=color:#e6db74>Upgrade</span> $http_upgrade;
</span></span><span style=display:flex><span> <span style=color:#f92672>proxy_set_header</span> <span style=color:#e6db74>Connection</span> <span style=color:#e6db74>&#34;upgrade&#34;</span>;
</span></span><span style=display:flex><span> <span style=color:#f92672>proxy_set_header</span> <span style=color:#e6db74>Host</span> $host;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><hr><h2 id=step-4-firewall-rules>Step 4: Firewall Rules<a hidden class=anchor aria-hidden=true href=#step-4-firewall-rules>#</a></h2><p>Lock things down! Only Cloudflare should reach you:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-bash data-lang=bash><span style=display:flex><span>ufw allow 22/tcp
</span></span><span style=display:flex><span>ufw allow 80/tcp
</span></span><span style=display:flex><span>ufw allow 443/tcp
</span></span><span style=display:flex><span>ufw deny 54321/tcp
</span></span></code></pre></div><hr><h2 id=step-5-client-config>Step 5: Client Config<a hidden class=anchor aria-hidden=true href=#step-5-client-config>#</a></h2><p>Trojan URI:</p><div class=mermaid>trojan://&lt;PASSWORD>@web.example.com:443?type=ws&amp;security=tls&amp;host=web.example.com&amp;path=%2Fstealth-path_abcd1234&amp;sni=web.example.com#MyStealthVPN</div><p>iOS clients: Shadowrocket, Stash, FoXray<br>Android clients: v2rayNG, Clash Meta, NekoBox</p><hr><h2 id=debugging-time-aka-why-the-heck-doesnt-it-work>Debugging Time (a.k.a. “Why the heck doesnt it work?!”)<a hidden class=anchor aria-hidden=true href=#debugging-time-aka-why-the-heck-doesnt-it-work>#</a></h2><h3 id=symptom-520-unknown-error-cloudflare>Symptom: <strong>520 Unknown Error (Cloudflare)</strong><a hidden class=anchor aria-hidden=true href=#symptom-520-unknown-error-cloudflare>#</a></h3><ul><li>Likely cause: Nginx couldnt talk to Trojan.</li><li>Check Nginx error log:<div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-bash data-lang=bash><span style=display:flex><span>tail -n <span style=color:#ae81ff>50</span> /var/log/nginx/error.log
</span></span></code></pre></div></li><li>If you see <code>upstream sent no valid HTTP/1.0 header</code> → youre mixing HTTPS/HTTP between Nginx and Trojan.</li></ul><hr><h3 id=symptom-526-invalid-ssl-certificate>Symptom: <strong>526 Invalid SSL certificate</strong><a hidden class=anchor aria-hidden=true href=#symptom-526-invalid-ssl-certificate>#</a></h3><ul><li>Cloudflare → Nginx cert mismatch.</li><li>Run:<div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-bash data-lang=bash><span style=display:flex><span>openssl s_client -connect web.example.com:443 -servername web.example.com -showcerts
</span></span></code></pre></div></li><li>Make sure CN = <code>web.example.com</code>. If not, fix your cert.</li></ul><hr><h3 id=symptom-blank-page-on-panel>Symptom: <strong>Blank page on panel</strong><a hidden class=anchor aria-hidden=true href=#symptom-blank-page-on-panel>#</a></h3><ul><li>Check if the base path matches exactly (case-sensitive, slash-sensitive).</li><li>Watch for sneaky trailing spaces!</li><li>Test locally:<div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-bash data-lang=bash><span style=display:flex><span>curl -s -D- http://127.0.0.1:46309/panel-bananas_42/
</span></span></code></pre></div></li></ul><hr><h3 id=symptom-client-wont-connect>Symptom: <strong>Client wont connect</strong><a hidden class=anchor aria-hidden=true href=#symptom-client-wont-connect>#</a></h3><ul><li>Run a WebSocket test:<div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-bash data-lang=bash><span style=display:flex><span>curl -i -k -H <span style=color:#e6db74>&#34;Connection: Upgrade&#34;</span> -H <span style=color:#e6db74>&#34;Upgrade: websocket&#34;</span> https://web.example.com/stealth-path_abcd1234
</span></span></code></pre></div>Expect <code>101 Switching Protocols</code>. If not, check Nginx config.</li></ul><hr><h3 id=symptom-censor-still-blocks-you>Symptom: <strong>Censor still blocks you</strong><a hidden class=anchor aria-hidden=true href=#symptom-censor-still-blocks-you>#</a></h3><ul><li>Did you expose another service (like SSH, Matrix, or Minecraft) on the same IP?<br>→ Theyll find your origin IP. Use a second VPS or lock those ports down.</li><li>Did you use an obvious path like <code>/ws</code>?<br>→ Use a random-looking one like <code>/cdn-assets-329df/</code>.</li></ul><hr><h2 id=pro-tips--fun-tricks>Pro Tips & Fun Tricks<a hidden class=anchor aria-hidden=true href=#pro-tips--fun-tricks>#</a></h2><ul><li>Serve a fake blog or portfolio at root so your domain looks legit.</li><li>Rotate WebSocket paths occasionally.</li><li>Use Cloudflare <strong>Page Rules</strong> to disable Rocket Loader & Minify for your VPN domain.</li><li>Make friends with your Nginx error.log — it will roast you but it tells the truth.</li></ul><hr><h2 id=conclusion>Conclusion<a hidden class=anchor aria-hidden=true href=#conclusion>#</a></h2><p>By putting Trojan behind Cloudflare, youve given your VPN a shiny new disguise:</p><ul><li>Looks like normal HTTPS.</li><li>Hides your origin IP.</li><li>Forces censors into a tough choice: block Cloudflare (and half the web) or let you pass.</li></ul><p>Congrats — youve built a VPN with both <strong>style</strong> and <strong>stealth</strong>. 🥷</p><hr><div class=signature-block style=margin-top:1rem><p><strong>Downloads:</strong>
<a href=/sources/posts/vpn.md>Markdown</a> ·
<a href=/sources/posts/vpn.md.asc>Signature (.asc)</a></p><details class=signature><summary>View OpenPGP signature</summary><pre style=font-size:.85em;overflow-x:auto;padding:.75rem>-----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEEVaKl3oR5K6zGyu4/tYgoUOBMjSoFAmpkbuAACgkQtYgoUOBM
jSo4Yw//T40cakj/BOL/Zh0gxoW4PnH014B7h67Yirq6pB3epUix6bFaZCJnndbD
9ZIhmnWMRzbkcA3SVhW1Y3NLpHvhK5UMXNY1qGqrGATQcoVCP7EewhL/3vXgTo5l
jFsQFzNxcgOXKKWevuRtL5MY8RuC&#43;PbsfG2ry2jiOtJa9H2UixVJ5E8Imj&#43;VS47O
S3XAlxr85O9CEh/TFkS/TuY5P5&#43;VPoOLn6WfdCH5W2IdkW&#43;Vi3bZFJ46LBm6cNBh
Iydo&#43;r2msTrqOozimc9hVorPjHZVZLMADJhcsa4pSZ4pDShBYMOZWATQErROPsdl
5iyRbmdFb4zWcG5SgjngHnRHFrJ8PVgnFXObb3yZMqA&#43;38RGmRNFgtR0mhMdtKNt
QxQDOO/IfO/oNfZzIERUqUnUy/iXs44v8ttTXXE6SkYYoHW335xmDuk8ntrmQA6g
YfXO4rJCXxsMaT6RkJaoK5YirKF/9hJYaUYY62SzdfhTmY1TFGGK0&#43;CD&#43;M1kQILC
E8pXSfGhaG2bFCzQ&#43;BRMe4o1BXLNjUhvovUgWEBnYTdGF5oXNS1MM29WxD/HC3md
d17noEPwOujrtLxEQcAOUcQe02VOfYcX36pbr&#43;ql32hsQMQHZtho1nx5HEGCoCWG
3sO7WQTpdBDtkwdHnRJqKBcuWqrVd3YNMwtZE0S6oXVyWkEbB4Y=
=IovZ
-----END PGP SIGNATURE-----
</pre></details><p><em>Verify locally:</em></p><pre style=font-size:.85em;overflow-x:auto;padding:.75rem>curl -fSLO https://blog.alipour.eu/sources/posts/vpn.md
curl -fSLO https://blog.alipour.eu/sources/posts/vpn.md.asc
gpg --verify vpn.md.asc vpn.md
</pre></div></div><footer class=post-footer><ul class=post-tags><li><a href=https://blog.alipour.eu/tags/vpn/>VPN</a></li><li><a href=https://blog.alipour.eu/tags/trojan/>Trojan</a></li><li><a href=https://blog.alipour.eu/tags/cloudflare/>Cloudflare</a></li><li><a href=https://blog.alipour.eu/tags/xray/>Xray</a></li><li><a href=https://blog.alipour.eu/tags/3x-ui/>3x-Ui</a></li><li><a href=https://blog.alipour.eu/tags/bypass/>Bypass</a></li><li><a href=https://blog.alipour.eu/tags/privacy/>Privacy</a></li><li><a href=https://blog.alipour.eu/tags/debugging/>Debugging</a></li></ul><nav class=paginav><a class=prev href=https://blog.alipour.eu/posts/tor_clearnet_blog/><span class=title>« Prev</span><br><span>Running my blog on Tor (.onion) and the Clearnet with Hugo + PaperMod</span>
</a><a class=next href=https://blog.alipour.eu/phd_journey/augest_2025/><span class=title>Next »</span><br><span>Augest '25</span></a></nav></footer><div class=comments-switch style="display:flex;gap:.5rem;margin:.5rem 0 1rem"><button id=btn-isso type=button aria-pressed=true>Anonymous (Isso)</button>
<button id=btn-giscus type=button aria-pressed=false>GitHub (Giscus)</button>
<a href=https://github.com/AlipourIm/blog-comments/discussions/categories/comments target=_blank rel=noopener>Open on GitHub ↗</a></div><div id=panel-giscus style=display:none><section id=giscus-thread></section></div><div id=panel-isso><section id=isso-thread data-isso-id=/posts/vpn/ data-title="Stealth Trojan VPN Behind Cloudflare Guide"><noscript>Javascript needs to be activated to view comments.</noscript></section><script src=/isso/js/embed.min.js data-isso=/isso/ data-isso-css=false data-isso-lang=en data-isso-max-comments-nested=5 data-isso-sorting=newest async></script><small class=isso-powered style="display:block;margin:.5rem 0;color:var(--secondary,#888)">Comments powered by <a href=https://isso-comments.de target=_blank rel=noopener>Isso</a></small></div><script>(function(){var i=document.getElementById("panel-isso"),a=document.getElementById("panel-giscus"),e=document.getElementById("btn-isso"),t=document.getElementById("btn-giscus"),s=!1;function r(){if(n=localStorage.getItem("pref-theme"),n==="dark")return!0;if(n==="light")return!1;var n,e=document.documentElement,t=document.body;return!!(e.classList&&e.classList.contains("dark")||t.classList&&t.classList.contains("dark")||e.getAttribute("data-theme")==="dark"||t.getAttribute("data-theme")==="dark"||e.classList&&e.classList.contains("theme-dark")||t.classList&&t.classList.contains("theme-dark"))||window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches}function o(){return r()?"dark_dimmed":"noborder_light"}function c(){if(s)return;var t,n,e=document.createElement("script");e.src="https://giscus.app/client.js",e.async=!0,e.crossOrigin="anonymous",e.setAttribute("data-repo","AlipourIm/blog-comments"),e.setAttribute("data-repo-id","R_kgDOQGARyA"),e.setAttribute("data-category","Comments"),e.setAttribute("data-category-id","DIC_kwDOQGARyM4Cw3x-"),e.setAttribute("data-mapping","pathname"),e.setAttribute("data-strict","0"),e.setAttribute("data-reactions-enabled","1"),e.setAttribute("data-emit-metadata","0"),e.setAttribute("data-input-position","bottom"),e.setAttribute("data-lang","en"),e.setAttribute("data-theme",o()),document.getElementById("giscus-thread").appendChild(e);function i(){var e=document.querySelector("iframe.giscus-frame");e&&e.contentWindow&&e.contentWindow.postMessage({giscus:{setConfig:{theme:o()}}},"https://giscus.app")}t=document.getElementById("theme-toggle"),t&&t.addEventListener("click",function(){setTimeout(i,0)}),n=new MutationObserver(function(){i()}),n.observe(document.documentElement,{attributes:!0,attributeFilter:["class","data-theme"]}),n.observe(document.body,{attributes:!0,attributeFilter:["class","data-theme"]}),s=!0}function n(n){var s=n==="isso";i.style.display=s?"block":"none",a.style.display=s?"none":"block",e&&e.setAttribute("aria-pressed",s?"true":"false"),t&&t.setAttribute("aria-pressed",s?"false":"true"),s||c()}e&&e.addEventListener("click",function(e){e.preventDefault(),n("isso")}),t&&t.addEventListener("click",function(e){e.preventDefault(),n("giscus")}),n("isso")})()</script></article></main><footer class=footer><span>&copy; 2026 <a href=https://blog.alipour.eu/>AlipourIm journeys</a></span> ·
<span>Powered by
<a href=https://gohugo.io/ rel="noopener noreferrer" target=_blank>Hugo</a> &
<a href=https://github.com/adityatelange/hugo-PaperMod/ rel=noopener target=_blank>PaperMod</a></span></footer><a href=#top aria-label="go to top" title="Go to Top (Alt + G)" class=top-link id=top-link accesskey=g><svg viewBox="0 0 12 6" fill="currentcolor"><path d="M12 6H0l6-6z"/></svg></a><div class=site-meta-row style=display:flex;align-items:center;gap:.75rem;flex-wrap:wrap><a href=/index.xml rel=alternate type=application/rss+xml title=RSS class=rss-link style=display:inline-flex;align-items:center;gap:.35rem><svg viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 11a9 9 0 019 9"/><path d="M4 4a16 16 0 0116 16"/><circle cx="5" cy="19" r="1"/></svg>
<span>RSS</span>
</a><span aria-hidden=true>·</span>
<span>Visitors this week: <span id=gc_site_visitors_week></span></span></div><script>let menu=document.getElementById("menu");menu&&(menu.scrollLeft=localStorage.getItem("menu-scroll-position"),menu.onscroll=function(){localStorage.setItem("menu-scroll-position",menu.scrollLeft)}),document.querySelectorAll('a[href^="#"]').forEach(e=>{e.addEventListener("click",function(e){e.preventDefault();var t=this.getAttribute("href").substr(1);window.matchMedia("(prefers-reduced-motion: reduce)").matches?document.querySelector(`[id='${decodeURIComponent(t)}']`).scrollIntoView():document.querySelector(`[id='${decodeURIComponent(t)}']`).scrollIntoView({behavior:"smooth"}),t==="top"?history.replaceState(null,null," "):history.pushState(null,null,`#${t}`)})})</script><script>var mybutton=document.getElementById("top-link");window.onscroll=function(){document.body.scrollTop>800||document.documentElement.scrollTop>800?(mybutton.style.visibility="visible",mybutton.style.opacity="1"):(mybutton.style.visibility="hidden",mybutton.style.opacity="0")}</script><script>document.getElementById("theme-toggle").addEventListener("click",()=>{document.body.className.includes("dark")?(document.body.classList.remove("dark"),localStorage.setItem("pref-theme","light")):(document.body.classList.add("dark"),localStorage.setItem("pref-theme","dark"))})</script><script>document.querySelectorAll("pre > code").forEach(e=>{const n=e.parentNode.parentNode,t=document.createElement("button");t.classList.add("copy-code"),t.innerHTML="copy";function s(){t.innerHTML="copied!",setTimeout(()=>{t.innerHTML="copy"},2e3)}t.addEventListener("click",t=>{if("clipboard"in navigator){navigator.clipboard.writeText(e.textContent),s();return}const n=document.createRange();n.selectNodeContents(e);const o=window.getSelection();o.removeAllRanges(),o.addRange(n);try{document.execCommand("copy"),s()}catch{}o.removeRange(n)}),n.classList.contains("highlight")?n.appendChild(t):n.parentNode.firstChild==n||(e.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName=="TABLE"?e.parentNode.parentNode.parentNode.parentNode.parentNode.appendChild(t):e.parentNode.appendChild(t))})</script></body></html>