Repository navigation
Expand file tree
/
Copy pathindex.html
More file actions
424 lines (354 loc) · 19 KB
/
Copy pathindex.html
File metadata and controls
424 lines (354 loc) · 19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>linko — دليل الاستخدام</title>
<style>
:root {
--bg:#0d1117; --panel:#161b22; --code:#010409; --line:#21262d;
--fg:#e6edf3; --fg2:#8b949e; --orange:#f6821f; --orange-bg:rgba(246,130,31,.12);
--green:#3fb950; --blue:#58a6ff; --red:#f85149; --yellow:#d29922; --r:10px;
}
@media (prefers-color-scheme: light) {
:root { --bg:#fff; --panel:#f6f8fa; --code:#1c2128; --line:#d0d7de;
--fg:#1f2328; --fg2:#59636e; --orange-bg:rgba(246,130,31,.1); }
}
*{box-sizing:border-box}
html{scroll-behavior:smooth;scroll-padding-top:20px}
body{margin:0;background:var(--bg);color:var(--fg);line-height:1.9;font-size:16.5px;
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans Arabic",Arial,sans-serif}
.wrap{max-width:760px;margin:0 auto;padding:0 22px 120px}
header{padding:56px 0 8px}
h1{font-size:52px;margin:0 0 6px;letter-spacing:-2px;font-weight:800}
h1 span{color:var(--orange)}
.lede{font-size:20px;color:var(--fg2);margin:0 0 8px}
h2{font-size:26px;margin:64px 0 6px;letter-spacing:-.5px;padding-top:36px;border-top:1px solid var(--line)}
h3{font-size:18px;margin:34px 0 4px}
p{margin:12px 0}
a{color:var(--blue)}
ul,ol{padding-inline-start:22px}
li{margin:7px 0}
code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.87em}
p code,li code,td code{background:var(--panel);border:1px solid var(--line);
padding:1px 6px;border-radius:5px;direction:ltr;display:inline-block}
.box{position:relative;direction:ltr;text-align:left;background:var(--code);
border:1px solid var(--line);border-radius:var(--r);margin:14px 0;overflow:hidden}
.box pre{margin:0;padding:15px 17px;overflow-x:auto;line-height:1.7}
.box code{color:#e6edf3;font-size:13.5px}
.c{color:#6e7681}.g{color:var(--green)}.b{color:var(--blue)}
.y{color:var(--yellow)}.r{color:var(--red)}.o{color:var(--orange)}
.cp{position:absolute;top:7px;right:7px;background:rgba(255,255,255,.07);color:#c9d1d9;
border:1px solid rgba(255,255,255,.13);border-radius:6px;font-size:11px;padding:3px 9px;
cursor:pointer;font-family:inherit;opacity:0;transition:.15s}
.box:hover .cp{opacity:1}.cp:hover{background:rgba(255,255,255,.15)}
.cp.ok{color:var(--green);border-color:var(--green)}
table{width:100%;border-collapse:collapse;margin:16px 0;font-size:15px}
th,td{text-align:start;padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:top}
th{color:var(--fg2);font-size:12px;text-transform:uppercase;letter-spacing:.7px;font-weight:700}
.note,.warn,.good{border-radius:var(--r);padding:13px 16px;margin:18px 0;font-size:15.5px;
background:var(--panel);border-inline-start:3px solid var(--blue)}
.warn{border-inline-start-color:var(--yellow)}
.good{border-inline-start-color:var(--green)}
.note b,.warn b,.good b{display:block;margin-bottom:3px}
.steps{counter-reset:s;list-style:none;padding:0;margin:22px 0}
.steps>li{counter-increment:s;position:relative;padding-inline-start:44px;margin-bottom:30px}
.steps>li::before{content:counter(s);position:absolute;inset-inline-start:0;top:2px;
width:29px;height:29px;border-radius:50%;background:var(--orange-bg);color:var(--orange);
display:grid;place-items:center;font-weight:700;font-size:14px}
.steps>li>b{display:block;font-size:17px;margin-bottom:2px}
.fix{border:1px solid var(--line);border-radius:var(--r);margin:20px 0;overflow:hidden}
.fix .err{background:var(--code);direction:ltr;text-align:left;padding:12px 16px;
color:#ff7b72;font-family:ui-monospace,Menlo,monospace;font-size:13px;border-bottom:1px solid var(--line)}
.fix .body{padding:14px 16px;font-size:15.5px}
.fix .body b{color:var(--fg)}
footer{border-top:1px solid var(--line);margin-top:70px;padding-top:22px;color:var(--fg2);font-size:14px}
</style>
</head>
<body>
<div class="wrap">
<header>
<h1>lin<span>ko</span></h1>
<p class="lede">أمر واحد يحوّل مشروعك المحلي إلى رابط <b>HTTPS</b> عام على دومينك.</p>
</header>
<div class="box">
<button class="cp">نسخ</button>
<pre><code>$ linko 3000
<span class="g">✓</span> DNS record created (x92ka.example.com)
<span class="g">✓</span> Route published (x92ka.example.com -> http://localhost:3000)
<span class="g">✓</span> Tunnel connected
Public URL <span class="b">https://x92ka.example.com</span>
Forwards http://localhost:3000
<span class="c">Press Ctrl+C to stop</span></code></pre>
</div>
<p>بدون فتح بورت، بدون Port Forwarding، بدون شهادات SSL، بدون إنشاء سجلات DNS يدويًا.</p>
<h2 id="need">ما تحتاجه</h2>
<ul>
<li>حساب Cloudflare مجاني</li>
<li>دومين موجّهة nameservers الخاصة به إلى Cloudflare</li>
</ul>
<p><code>cloudflared</code> يُنزَّل تلقائيًا عند أول استخدام — لا تثبّته بنفسك.</p>
<h2 id="install">١ · التثبيت</h2>
<p><b>macOS و Linux</b></p>
<div class="box">
<button class="cp">نسخ</button>
<pre><code>curl -fsSL https://raw.githubusercontent.com/Devehab/linko/main/install.sh | bash</code></pre>
</div>
<p>للتحقق:</p>
<div class="box">
<button class="cp">نسخ</button>
<pre><code>linko --version</code></pre>
</div>
<div class="note">
<b><code>command not found</code> بعد التثبيت؟</b>
مجلد التثبيت ليس في مسارك. أضف هذا السطر إلى <code>~/.zshrc</code> ثم أعد فتح الطرفية:
<code>export PATH="$HOME/.local/bin:$PATH"</code>
</div>
<p><b>طرق أخرى:</b> عبر Go بـ <code>go install github.com/Devehab/linko@latest</code>، أو نزّل الملف
الجاهز لنظامك من <a href="https://github.com/Devehab/linko/releases" target="_blank" rel="noopener">صفحة الإصدارات</a>
(ويندوز: ملف <code>.zip</code>).</p>
<h2 id="token">٢ · توكن Cloudflare</h2>
<p>هذه الخطوة هي التي تتعثّر فيها الأغلبية. القاعدة في سطر واحد:</p>
<div class="good">
<b>توكن واحد، صلاحيتان معًا.</b>
ليس توكنًا للـ DNS وآخر للنفق — بل توكن واحد يحمل الاثنتين. وكلتاهما <b>Edit</b> لا Read.
</div>
<ol class="steps">
<li>
<b>افتح صفحة الرموز</b>
<a href="https://dash.cloudflare.com/profile/api-tokens" target="_blank" rel="noopener">dash.cloudflare.com/profile/api-tokens</a>
← <b>Create Token</b> ← انزل إلى الأسفل واختر <b>Create Custom Token</b>.
</li>
<li>
<b>أضف السطرين في قسم Permissions</b>
اضغط <b>+ Add more</b> لإضافة السطر الثاني.
<table>
<thead><tr><th>#</th><th>الأول</th><th>الثاني</th><th>الثالث</th></tr></thead>
<tbody>
<tr><td>١</td><td><code>Zone</code></td><td><code>DNS</code></td><td><code>Edit</code></td></tr>
<tr><td>٢</td><td><code>Account</code></td><td><code>Cloudflare Tunnel</code></td><td><code>Edit</code></td></tr>
</tbody>
</table>
</li>
<li>
<b>حدّد الموارد</b>
في <b>Account Resources</b> اختر حسابك.
وفي <b>Zone Resources</b> اختر <code>Include → Specific zone → دومينك</code>.
<div class="warn" style="margin-top:10px">
إن تركت <b>Zone Resources</b> فارغًا فسيتصل التوكن بنجاح لكنه لن يرى أي دومين،
وستحصل على <code>no zone named …</code>.
</div>
</li>
<li>
<b>انسخ الرمز</b>
يُعرض مرة واحدة فقط.
</li>
</ol>
<h2 id="init">٣ · الإعداد</h2>
<div class="box">
<button class="cp">نسخ</button>
<pre><code>linko init</code></pre>
</div>
<p>يسألك ثلاثة أسئلة: الرمز، ثم الدومين، ثم اسم النفق. اضغط Enter لقبول الافتراضي.</p>
<div class="box">
<button class="cp">نسخ</button>
<pre><code><span class="o">Cloudflare credentials</span>
Cloudflare API token: <span class="c">(مخفي أثناء الكتابة)</span>
<span class="g">✓</span> Cloudflare connected
<span class="o">Domain</span>
Domain: <span class="c">[example.com]</span>
<span class="g">✓</span> DNS zone found (example.com)
Base subdomain: <span class="c">[example.com]</span>
<span class="g">✓</span> URLs will look like https://abc12.example.com
<span class="o">Tunnel</span>
Tunnel name: <span class="c">[example-linko-tunnel]</span>
<span class="g">✓</span> Tunnel created
<span class="g">✓</span> Configuration saved to ~/.linko/config.json
<span class="g">✓</span> cloudflared installed
You're ready.</code></pre>
</div>
<h3>سؤال «Base subdomain» — انتبه هنا</h3>
<div class="warn">
<b>اترك الإجابة على الدومين نفسه: <code>example.com</code></b>
لا تكتب <code>demo.example.com</code>. السبب في القسم التالي.
</div>
<h2 id="ssl">٤ · قاعدة المستوى الواحد</h2>
<p>شهادة Cloudflare المجانية تُصدَر لاسمين اثنين فقط:</p>
<div class="box">
<pre><code>example.com <span class="c">← الجذر</span>
*.example.com <span class="c">← مستوى واحد</span></code></pre>
</div>
<p>و<code>*</code> لا تعبر النقطة — تطابق تسمية واحدة فقط:</p>
<table>
<thead><tr><th>الرابط</th><th>يعمل؟</th></tr></thead>
<tbody>
<tr><td><code>demo.example.com</code></td><td>✅ يطابق <code>*.example.com</code></td></tr>
<tr><td><code>demo.app.example.com</code></td><td>❌ مستويان — لا شهادة تغطيه</td></tr>
</tbody>
</table>
<div class="warn">
<b>هذا العطل هو الأصعب في التشخيص كله.</b>
الـ DNS صحيح، والنفق متصل، و<code>linko doctor</code> أخضر بالكامل — ومع ذلك يرفض المتصفح
الاتصال بـ <code>ERR_SSL_VERSION_OR_CIPHER_MISMATCH</code>، لأن الرفض يحدث قبل وصول
أي طلب إلى نفقك. لهذا يحذّرك <code>linko init</code> منه مسبقًا.
</div>
<p>تريد مستويين مثل <code>demo.app.example.com</code>؟ يلزمك
<b>Advanced Certificate Manager</b> من Cloudflare (١٠ دولارات شهريًا للدومين)، ثم فعّل
<b>Total TLS</b>. البدائل المجانية (Subdomain setup، إعداد CNAME جزئي) محصورة بخطط
Enterprise وBusiness.</p>
<div class="good">
<b>الأبسط والمجاني:</b> استخدم المستوى الأول واجعل التمييز في الاسم نفسه —
<code>myapp-dev.example.com</code> بدلًا من <code>myapp.dev.example.com</code>.
</div>
<h2 id="use">٥ · الاستخدام</h2>
<p>شغّل مشروعك أولًا (مثلًا على المنفذ 3000)، ثم:</p>
<div class="box">
<button class="cp">نسخ</button>
<pre><code>linko 3000</code></pre>
</div>
<p>تحصل على اسم عشوائي مثل <code>x92ka</code>. الرابط يعيش ما دامت الأداة تعمل، وعند
<code>Ctrl+C</code> يُحذف الاسم وسجل الـ DNS تلقائيًا — لا تتراكم لديك سجلات.</p>
<h3>رابط ثابت</h3>
<div class="box">
<button class="cp">نسخ</button>
<pre><code>linko 3000 --name crm</code></pre>
</div>
<p>النتيجة <code>https://crm.example.com</code>، ويبقى محفوظًا بعد الإيقاف.</p>
<h3>الأوامر كلها</h3>
<table>
<thead><tr><th>الأمر</th><th>الوظيفة</th></tr></thead>
<tbody>
<tr><td><code>linko init</code></td><td>الإعداد لمرة واحدة</td></tr>
<tr><td><code>linko 3000</code></td><td>رابط عشوائي مؤقت</td></tr>
<tr><td><code>linko 3000 --name crm</code></td><td>رابط باسم ثابت</td></tr>
<tr><td><code>linko 3000 --keep</code></td><td>يبقي الاسم العشوائي بعد الإيقاف</td></tr>
<tr><td><code>linko list</code></td><td>عرض روابطك</td></tr>
<tr><td><code>linko status</code></td><td>حالة النفق وعدد الاتصالات</td></tr>
<tr><td><code>linko remove crm</code></td><td>حذف رابط (المسار + سجل DNS)</td></tr>
<tr><td><code>linko remove --all</code></td><td>حذف كل الروابط</td></tr>
<tr><td><code>linko doctor</code></td><td>فحص شامل للإعداد</td></tr>
</tbody>
</table>
<h3>صيغ المنفذ</h3>
<table>
<thead><tr><th>ما تكتبه</th><th>ما يعنيه</th></tr></thead>
<tbody>
<tr><td><code>linko 3000</code></td><td><code>http://localhost:3000</code></td></tr>
<tr><td><code>linko 127.0.0.1:8080</code></td><td>عنوان محدد</td></tr>
<tr><td><code>linko https://localhost:8443</code></td><td>أصل يعمل بـ HTTPS</td></tr>
</tbody>
</table>
<h3>عدة مشاريع معًا</h3>
<div class="box">
<button class="cp">نسخ</button>
<pre><code><span class="c"># نافذة طرفية لكل مشروع</span>
linko 3000 -n web
linko 8080 -n api</code></pre>
</div>
<h2 id="fix">٦ · حل المشاكل</h2>
<p>كل ما يلي أخطاء حقيقية ظهرت أثناء التطوير، وهذه أسبابها الفعلية.</p>
<div class="fix">
<div class="err">Error: no zone named "example.com" on this account</div>
<div class="body">
<b>السبب:</b> التوكن ينقصه صلاحية <code>Zone</code>، أو أن <b>Zone Resources</b> فيه فارغة.<br>
<b>الحل:</b> عدّل التوكن وأضف <code>Zone → DNS → Edit</code> مع
<code>Zone Resources → Include → Specific zone → دومينك</code>.<br>
<span style="color:var(--fg2)"><code>linko</code> يعرض لك النطاقات التي يراها التوكن فعلًا، فتعرف فورًا أين الخلل.</span>
</div>
</div>
<div class="fix">
<div class="err">✗ Cloudflare refused to create the DNS record.<br> Authentication error (code 10000)</div>
<div class="body">
<b>السبب:</b> صلاحية DNS مضبوطة على <code>Read</code> بدل <code>Edit</code>. العثور على
الدومين يحتاج قراءة فقط، لذلك يمر الإعداد كاملًا ثم يفشل عند أول كتابة.<br>
<b>الحل:</b> غيّر القائمة المنسدلة من <b>Read</b> إلى <b>Edit</b>.
</div>
</div>
<div class="fix">
<div class="err">✗ Could not create the tunnel<br> Authentication error (code 10000)</div>
<div class="body">
<b>السبب:</b> التوكن يغطي DNS فقط ولا يملك صلاحية النفق.<br>
<b>الحل:</b> أضف السطر الثاني <code>Account → Cloudflare Tunnel → Edit</code>
على <b>نفس</b> التوكن.
</div>
</div>
<div class="fix">
<div class="err">This site can't provide a secure connection<br>ERR_SSL_VERSION_OR_CIPHER_MISMATCH</div>
<div class="body">
<b>السبب:</b> رابطك بعمق مستويين. لا شهادة مجانية تغطيه.<br>
<b>الحل:</b> أعد الإعداد بـ <code>linko init --base example.com</code> ليصبح الرابط
<code>name.example.com</code>. أو اشترِ Advanced Certificate Manager.
</div>
</div>
<div class="fix">
<div class="err">Error: no API token: pass --token or set LINKO_API_TOKEN</div>
<div class="body">
<b>السبب:</b> شغّلت <code>linko init --yes</code> بلا رمز.<br>
<b>الحل:</b> إما <code>export LINKO_API_TOKEN='...'</code> قبل الأمر، أو مرّر <code>--token '...'</code>.
</div>
</div>
<div class="fix">
<div class="err">HTTP 502 · Error 1033</div>
<div class="body">
<b>السبب:</b> النفق يعمل لكن لا شيء يستمع على المنفذ.<br>
<b>الحل:</b> تأكد بـ <code>curl http://localhost:3000</code>. وإن كان تطبيقك يستمع على
<code>127.0.0.1</code> فقط، جرّب <code>linko 127.0.0.1:3000</code>.
</div>
</div>
<h3>عند الشك، ابدأ من هنا</h3>
<div class="box">
<button class="cp">نسخ</button>
<pre><code>linko doctor</code></pre>
</div>
<p>يفحص ثمانية أشياء بالترتيب — من وجود <code>cloudflared</code> إلى الاتصالات الحيّة —
ويقول لك أين توقّف بالضبط.</p>
<h3>بداية نظيفة تمامًا</h3>
<div class="box">
<button class="cp">نسخ</button>
<pre><code>linko remove --all --yes
rm -rf ~/.linko
linko init</code></pre>
</div>
<h2 id="how">كيف يعمل</h2>
<p><code>linko</code> ينشئ نفقًا واحدًا في حسابك، وكل مشروع يصبح اسمًا داخله موجّهًا إلى منفذ مختلف.</p>
<div class="box">
<pre><code> crm.example.com ──┐
│ ┌──▶ localhost:3000
api.example.com ──┼──▶ Cloudflare Edge ─┼──▶ localhost:8080
│ (نفق واحد) │
test.example.com ──┘ └──▶ localhost:5173
▲
│ اتصال صادر فقط
cloudflared على جهازك</code></pre>
</div>
<p>عند تشغيل <code>linko 3000</code> تحدث ثلاثة أشياء: يُنشأ سجل <code>CNAME</code> يشير إلى
نفقك، وتُضاف قاعدة توجيه تربط الاسم بـ <code>localhost:3000</code>، ثم يُشغَّل
<code>cloudflared</code> فيفتح اتصالًا <b>صادرًا</b> نحو Cloudflare. لا شيء على جهازك ينتظر
اتصالات واردة — لهذا لا تحتاج لمسّ الراوتر أو جدار الحماية.</p>
<h2 id="safe">أمان</h2>
<ul>
<li><b>الرابط عام.</b> أي شخص يملكه يصل إلى مشروعك. لا تنشر بيانات حساسة. للحماية أضف
Cloudflare Access من لوحة Zero Trust.</li>
<li><b>الرمز محفوظ في <code>~/.linko/config.json</code></b> بصلاحيات <code>0600</code>
— لا ترفعه إلى Git. للفرق وCI استخدم <code>LINKO_API_TOKEN</code>.</li>
<li><b>لا يحذف سجلًا ليس له.</b> إن كان الاسم يشير إلى موقعك الحقيقي فلن يلمسه
<code>linko</code> إطلاقًا.</li>
</ul>
<footer>
<p><b>linko</b> · <a href="https://github.com/Devehab/linko" target="_blank" rel="noopener">github.com/Devehab/linko</a> · رخصة MIT</p>
</footer>
</div>
<script>
document.querySelectorAll('.cp').forEach(function (b) {
b.addEventListener('click', function () {
var t = b.parentElement.querySelector('code').innerText.replace(/^\$ /gm, '');
navigator.clipboard.writeText(t).then(function () {
var o = b.textContent;
b.textContent = 'تم ✓'; b.classList.add('ok');
setTimeout(function () { b.textContent = o; b.classList.remove('ok'); }, 1400);
});
});
});
</script>
</body>
</html>