67 lines
No EOL
26 KiB
HTML
67 lines
No EOL
26 KiB
HTML
<!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’t scream “I am a VPN” 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).
|
||
We’ll 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> » <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> · 4 min · 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’t scream “I am a VPN”</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>We’ll 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><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’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><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>"upgrade"</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://<PASSWORD>@web.example.com:443?type=ws&security=tls&host=web.example.com&path=%2Fstealth-path_abcd1234&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 doesn’t 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 couldn’t 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> → you’re 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 won’t 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>"Connection: Upgrade"</span> -H <span style=color:#e6db74>"Upgrade: websocket"</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>→ They’ll 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, you’ve 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 — you’ve 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+PbsfG2ry2jiOtJa9H2UixVJ5E8Imj+VS47O
|
||
S3XAlxr85O9CEh/TFkS/TuY5P5+VPoOLn6WfdCH5W2IdkW+Vi3bZFJ46LBm6cNBh
|
||
Iydo+r2msTrqOozimc9hVorPjHZVZLMADJhcsa4pSZ4pDShBYMOZWATQErROPsdl
|
||
5iyRbmdFb4zWcG5SgjngHnRHFrJ8PVgnFXObb3yZMqA+38RGmRNFgtR0mhMdtKNt
|
||
QxQDOO/IfO/oNfZzIERUqUnUy/iXs44v8ttTXXE6SkYYoHW335xmDuk8ntrmQA6g
|
||
YfXO4rJCXxsMaT6RkJaoK5YirKF/9hJYaUYY62SzdfhTmY1TFGGK0+CD+M1kQILC
|
||
E8pXSfGhaG2bFCzQ+BRMe4o1BXLNjUhvovUgWEBnYTdGF5oXNS1MM29WxD/HC3md
|
||
d17noEPwOujrtLxEQcAOUcQe02VOfYcX36pbr+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>© 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> |