Repository navigation
Expand file tree
/
Copy pathSTART.html
More file actions
552 lines (485 loc) · 42.2 KB
/
Copy pathSTART.html
File metadata and controls
552 lines (485 loc) · 42.2 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
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
<!DOCTYPE html>
<html lang="ru" data-tint="sand">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>brink.watch — запуск шаг за шагом</title>
<!-- страница локальная, лежит в корне репозитория -->
<link rel="icon" href="brand/favicon.svg" type="image/svg+xml">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600&display=swap" rel="stylesheet">
<style>
:root{
--paper:#FCFBF8; --paper-2:#F6F4EE; --paper-3:#EFEBE2;
--ink:#191817; --ink-2:#6E6A62; --ink-3:#A7A299; --ink-4:#C9C4B9;
--t0:#7E8B92; --t2:#BE9750; --t3:#C4703B; --t4:#A83F2B;
--sans:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
--mono:"JetBrains Mono",ui-monospace,"SF Mono",Menlo,monospace;
}
*{box-sizing:border-box;margin:0;padding:0}
body{background:var(--paper);color:var(--ink);font-family:var(--sans);
font-size:15px;line-height:1.6;-webkit-font-smoothing:antialiased}
.wrap{max-width:780px;margin:0 auto;padding:48px clamp(18px,5vw,36px) 96px}
.micro{font-size:10px;font-weight:600;letter-spacing:.2em;text-transform:uppercase;color:var(--ink-3)}
h1{font-size:clamp(28px,5vw,40px);font-weight:200;letter-spacing:-.035em;line-height:1.1;margin-top:12px}
h1 em{font-style:normal;color:var(--t3)}
.lede{font-size:15.5px;color:var(--ink-2);font-weight:300;margin-top:18px;line-height:1.75;max-width:58ch}
.fade{height:1px;border:0;margin:38px 0;background:linear-gradient(90deg,transparent,
color-mix(in srgb,var(--ink) 11%,transparent) 22% 62%,transparent)}
.prog{position:sticky;top:0;z-index:30;background:color-mix(in srgb,var(--paper) 92%,transparent);
backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);padding:12px 0 10px;margin:22px 0 4px}
.prog .bar{height:3px;border-radius:2px;background:color-mix(in srgb,var(--ink) 8%,transparent);
position:relative;overflow:hidden}
.prog .bar i{position:absolute;inset:0 auto 0 0;width:0;border-radius:2px;
background:linear-gradient(90deg,color-mix(in srgb,var(--t2) 50%,transparent),var(--t3));
transition:width .35s ease}
.prog .lab{display:flex;justify-content:space-between;font-size:11.5px;color:var(--ink-3);margin-top:7px}
.block{margin-top:44px}
.block .head{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
.block h2{font-size:21px;font-weight:300;letter-spacing:-.02em}
.block .when{font-size:11.5px;color:var(--ink-3)}
.block .intro{font-size:13.5px;color:var(--ink-2);margin-top:8px;max-width:60ch}
.step{display:grid;grid-template-columns:30px 1fr;gap:14px;padding:16px 0;align-items:start}
.tick{width:24px;height:24px;border-radius:50%;margin-top:1px;cursor:pointer;flex:none;
background:var(--paper-2);transition:background .2s;font-size:11.5px;color:var(--ink-3);
display:flex;align-items:center;justify-content:center;font-family:var(--mono)}
.tick:hover{background:var(--paper-3)}
.step.done .tick{background:var(--t3);color:#fff}
.step.done .tick::after{content:"✓"}
.step.done .tick span{display:none}
.step.done .b{opacity:.42}
.b .t{font-size:14.5px;line-height:1.55}
.b .t b{font-weight:500}
.where{font-size:12px;color:var(--ink-3);margin-top:5px}
.where::before{content:"где: ";color:var(--ink-4)}
.res{font-size:12px;color:var(--ink-2);margin-top:7px;padding-left:11px;
border-left:2px solid color-mix(in srgb,var(--t2) 45%,transparent)}
.res::before{content:"должно получиться: ";color:var(--ink-3)}
.warn{font-size:12px;color:var(--ink-2);margin-top:7px;padding-left:11px;
border-left:2px solid color-mix(in srgb,var(--t4) 40%,transparent)}
kbd{font-family:var(--sans);font-size:13.5px;background:var(--paper-3);border-radius:5px;
padding:1px 6px;color:var(--ink);white-space:nowrap}
code{font-family:var(--mono);font-size:12.5px;background:var(--paper-2);border-radius:5px;padding:1px 5px}
pre{font-family:var(--mono);font-size:12.5px;background:var(--paper-2);border-radius:10px;
padding:12px 14px;margin-top:9px;overflow-x:auto;line-height:1.75;color:var(--ink)}
a{color:var(--t3)}
.note{font-size:12.5px;color:var(--ink-2);margin-top:10px;padding-left:11px;
border-left:2px solid color-mix(in srgb,var(--t2) 45%,transparent)}
.card{background:var(--paper-2);border-radius:20px;padding:22px 24px;margin-top:22px}
.card h3{font-size:15px;font-weight:400;letter-spacing:-.01em}
.card p{font-size:13.5px;color:var(--ink-2);margin-top:9px}
.card p b{color:var(--ink);font-weight:500}
.qa{padding:13px 0;border-top:1px solid color-mix(in srgb,var(--ink) 8%,transparent)}
.qa:first-of-type{border-top:0}
.qa .q{font-size:14px;font-weight:500}
.qa .a{font-size:13.5px;color:var(--ink-2);margin-top:5px}
footer{margin-top:44px;font-size:12px;color:var(--ink-4);line-height:1.8}
@media(max-width:560px){.step{grid-template-columns:26px 1fr;gap:10px}}
@media(prefers-reduced-motion:reduce){*{transition:none!important}}
.brandline{display:flex;align-items:center;gap:9px;font-size:18px;font-weight:300;
letter-spacing:-.02em;color:var(--ink);margin-bottom:26px}
.brandline .wdot{color:var(--t3)}
</style>
</head>
<body>
<div class="wrap">
<div class="brandline"><svg viewBox="0 0 100 100" width="26" height="26" fill="none" aria-hidden="true"><defs>
<linearGradient id="gstl" gradientUnits="userSpaceOnUse" x1="34.5" y1="0" x2="75.4" y2="0">
<stop offset="0" stop-color="#7E8B92" stop-opacity=".28"/>
<stop offset=".55" stop-color="#C4703B" stop-opacity=".72"/>
<stop offset="1" stop-color="#C4703B"/></linearGradient>
<radialGradient id="hstl"><stop offset="0" stop-color="#C4703B" stop-opacity=".45"/>
<stop offset="1" stop-color="#C4703B" stop-opacity="0"/></radialGradient></defs>
<path d="M 34.50 76.85 A 31 31 0 1 1 75.39 32.22" fill="none" stroke="url(#gstl)"
stroke-width="10" stroke-linecap="round"/>
<circle cx="75.4" cy="32.2" r="17.5" fill="url(#hstl)"/>
<circle cx="75.4" cy="32.2" r="5" fill="#C4703B"/></svg>
<span>brink<span class="wdot">·</span>watch</span></div>
<div class="micro">Запуск</div>
<h1>Каждый шаг<br>по порядку<em>.</em></h1>
<p class="lede">
Тридцать один шаг, четыре площадки, примерно час с перерывами на ожидание.
У каждого шага написано, где именно нажимать и что должно получиться —
если получилось не то, значит что-то пошло не так именно здесь, а не двумя экранами раньше.
</p>
<div class="prog">
<div class="bar"><i id="pbar"></i></div>
<div class="lab"><span id="pcount">Сделано 0 из 31</span><span id="pnow">Блок А · GitHub</span></div>
</div>
<!-- ============================ А ============================ -->
<div class="block" data-block="Блок А · GitHub">
<div class="head"><h2>А. Положить проект на GitHub</h2><span class="when">15 минут · бесплатно</span></div>
<p class="intro">GitHub нужен по двум причинам: оттуда Cloudflare берёт файлы сайта, и там же бесплатно работает робот, собирающий данные.</p>
<div class="step" data-step><div class="tick"><span>1</span></div><div class="b">
<div class="t">Распаковать <code>escx.zip</code>. Запомнить, куда — папка понадобится дальше.</div>
<div class="res">папка <b>escx</b> с файлами внутри</div>
</div></div>
<div class="step" data-step><div class="tick"><span>2</span></div><div class="b">
<div class="t">Завести аккаунт на GitHub. Почта, пароль, имя пользователя, подтверждение по письму.</div>
<div class="where"><a href="https://github.com/signup" target="_blank" rel="noopener">github.com/signup</a></div>
<div class="res">вы вошли и видите свою страницу профиля</div>
</div></div>
<div class="step" data-step><div class="tick"><span>3</span></div><div class="b">
<div class="t">Скачать и установить <b>GitHub Desktop</b>, войти в нём своим аккаунтом.</div>
<div class="where"><a href="https://desktop.github.com/" target="_blank" rel="noopener">desktop.github.com</a></div>
<div class="warn">Так не нужен терминал. Если предпочитаете команды — см. карточку «Через терминал» в конце.</div>
</div></div>
<div class="step" data-step><div class="tick"><span>4</span></div><div class="b">
<div class="t">В GitHub Desktop: <kbd>File</kbd> → <kbd>Add local repository</kbd> → выбрать папку <b>escx</b>.</div>
<div class="res">программа видит папку и показывает историю изменений</div>
</div></div>
<div class="step" data-step><div class="tick"><span>5</span></div><div class="b">
<div class="t">Нажать <kbd>Publish repository</kbd>. <b>Снять галочку</b> «Keep this code private». Подтвердить.</div>
<div class="res">репозиторий открывается на github.com и помечен <b>Public</b></div>
<div class="warn">Галочку снимаем не из идеализма: у публичного репозитория робот работает без ограничения по времени, у приватного — 2000 минут в месяц.</div>
</div></div>
<div class="step" data-step><div class="tick"><span>6</span></div><div class="b">
<div class="t">Открыть вкладку <kbd>Actions</kbd> и убедиться, что слева есть список: <b>deploy, daily, pull-gdelt, tests</b>.</div>
<div class="where">github.com → ваш репозиторий → вкладка Actions</div>
<div class="res">список процессов виден — значит всё уже включено, нажимать ничего не надо</div>
<div class="warn">Зелёная кнопка «I understand my workflows» появляется только у форков чужих репозиториев. У своего собственного её нет и быть не должно. Если список пуст — Settings → Actions → General → выбрать «Allow all actions».</div>
</div></div>
</div>
<hr class="fade">
<!-- ============================ Б ============================ -->
<div class="block" data-block="Блок Б · сайт">
<div class="head"><h2>Б. Поднять сайт на Cloudflare Pages</h2><span class="when">10 минут · бесплатно, карта не нужна</span></div>
<p class="intro">Домен здесь не нужен. На выходе получите рабочий адрес, который уже можно показывать.</p>
<div class="step" data-step><div class="tick"><span>7</span></div><div class="b">
<div class="t">Зарегистрироваться в Cloudflare, подтвердить почту.</div>
<div class="where"><a href="https://dash.cloudflare.com/sign-up" target="_blank" rel="noopener">dash.cloudflare.com/sign-up</a></div>
</div></div>
<div class="step" data-step><div class="tick"><span>8</span></div><div class="b">
<div class="t">В левом меню выбрать <kbd>Compute (Workers & Pages)</kbd>.</div>
</div></div>
<div class="step" data-step><div class="tick"><span>9</span></div><div class="b">
<div class="t">Нажать <kbd>Create</kbd> → <b>переключиться на вкладку</b> <kbd>Pages</kbd> → <kbd>Connect to Git</kbd>.</div>
<div class="warn"><b>Самая частая ошибка во всей инструкции.</b> Cloudflare открывает вкладку Workers первой,
и она выглядит как единственный вариант. Worker — это программа, обрабатывающая запросы; сайт он показывать
не умеет. Проверить, что попали куда надо, можно по адресной строке: у Pages в ней есть <code>/pages/</code>,
у Worker — <code>/workers/services/</code>. Если увидели вкладки Bindings и Observability и метрику
«Invocations» — это Worker, вернитесь и выберите Pages.</div>
</div></div>
<div class="step" data-step><div class="tick"><span>10</span></div><div class="b">
<div class="t">Разрешить Cloudflare доступ к GitHub, в списке выбрать репозиторий <b>escx</b>, нажать <kbd>Begin setup</kbd>.</div>
</div></div>
<div class="step" data-step><div class="tick"><span>11</span></div><div class="b">
<div class="t">Заполнить настройки сборки. Остальное не трогать.</div>
<pre>Project name: escx
Production branch: main
Build command: python3 web/build.py
Build output directory: site</pre>
</div></div>
<div class="step" data-step><div class="tick"><span>12</span></div><div class="b">
<div class="t">Раскрыть <kbd>Environment variables</kbd> и добавить одну переменную.</div>
<pre>PYTHON_VERSION = 3.11</pre>
<div class="warn">Без неё сборка упадёт: по умолчанию там старый Python.</div>
</div></div>
<div class="step" data-step><div class="tick"><span>13</span></div><div class="b">
<div class="t">Нажать <kbd>Save and Deploy</kbd> и подождать 1–3 минуты.</div>
<div class="res">зелёная надпись <b>Success</b> и ссылка вида <code>escx.pages.dev</code></div>
</div></div>
<div class="step" data-step><div class="tick"><span>14</span></div><div class="b">
<div class="t">Открыть эту ссылку и убедиться, что сайт работает.</div>
<div class="res">страница с дугой, числом и списком диад; сверху плашка «демонстрационные данные»</div>
<div class="warn">Плашка — это нормально. Робот ещё не накопил настоящих данных, она сменится сама.</div>
</div></div>
</div>
<hr class="fade">
<!-- ============================ В ============================ -->
<div class="block" data-block="Блок В · домен">
<div class="head"><h2>В. Домен: серверы имён</h2><span class="when">10 минут + ожидание</span></div>
<p class="intro">Домен покупается у Google, а управлять им будет Cloudflare. Для этого в форме Google указываются серверы имён Cloudflare — но сначала их надо получить.</p>
<div class="step" data-step><div class="tick"><span>15</span></div><div class="b">
<div class="t">В Cloudflare вверху нажать <kbd>+ Add</kbd> → <kbd>Existing domain</kbd> (в старом интерфейсе — <kbd>Add a site</kbd>).</div>
</div></div>
<div class="step" data-step><div class="tick"><span>16</span></div><div class="b">
<div class="t">Ввести имя домена без <code>https://</code> и без <code>www</code>. Например <code>brink.watch</code>.</div>
</div></div>
<div class="step" data-step><div class="tick"><span>17</span></div><div class="b">
<div class="t">Выбрать план <kbd>Free</kbd> — он в самом низу списка — и продолжить.</div>
<div class="warn">Платные планы предлагаются первыми и выглядят как обязательные. Бесплатного хватает полностью.</div>
</div></div>
<div class="step" data-step><div class="tick"><span>18</span></div><div class="b">
<div class="t">Cloudflare покажет <b>два сервера имён</b>. Выписать обе строки целиком.</div>
<pre>ada.ns.cloudflare.com
rick.ns.cloudflare.com</pre>
<div class="warn">Это пример. У вашего аккаунта имена будут другие — берите те, что показаны у вас.</div>
</div></div>
<div class="step" data-step><div class="tick"><span>19</span></div><div class="b">
<div class="t">Вернуться в форму Google. В разделе <b>DNS configuration</b> оставить <kbd>Use custom name servers</kbd> и вписать обе строки в поля <b>Name server 1</b> и <b>Name server 2</b>.</div>
<div class="res">красные подписи «cannot be empty» исчезли</div>
</div></div>
<div class="step" data-step><div class="tick"><span>20</span></div><div class="b">
<div class="t">Заполнить <b>Privacy protection</b> — выбрать скрытие контактных данных.</div>
</div></div>
<div class="step" data-step><div class="tick"><span>21</span></div><div class="b">
<div class="t">Заполнить <b>Contact details</b> — реальные имя, адрес, телефон, почта.</div>
<div class="warn">Данные должны быть настоящими: за выдуманные домен могут разделегировать. Приватность их всё равно скроет от посторонних.</div>
</div></div>
<div class="step" data-step><div class="tick"><span>22</span></div><div class="b">
<div class="t">Оплатить регистрацию.</div>
<div class="res">домен появился в списке ваших доменов у Google</div>
</div></div>
<div class="step" data-step><div class="tick"><span>23</span></div><div class="b">
<div class="t">Вернуться в Cloudflare и нажать <kbd>Check nameservers now</kbd>. Дальше ждать.</div>
<div class="res">статус домена меняется на <b>Active</b>; приходит письмо</div>
<div class="warn">Обычно 5–30 минут, иногда до суток. Это нормально и от вас не зависит — просто периодически проверяйте.</div>
</div></div>
</div>
<hr class="fade">
<!-- ============================ Г ============================ -->
<div class="block" data-block="Блок Г · привязка">
<div class="head"><h2>Г. Привязать домен к сайту</h2><span class="when">5 минут</span></div>
<p class="intro">Делается только после того, как домен в Cloudflare стал <b>Active</b>.</p>
<div class="step" data-step><div class="tick"><span>24</span></div><div class="b">
<div class="t">Открыть <kbd>Compute (Workers & Pages)</kbd> → проект <b>escx</b> → вкладка <kbd>Custom domains</kbd>.</div>
</div></div>
<div class="step" data-step><div class="tick"><span>25</span></div><div class="b">
<div class="t">Нажать <kbd>Set up a domain</kbd>, ввести свой домен, нажать <kbd>Activate domain</kbd>.</div>
<div class="res">Cloudflare сам создаёт нужные DNS-записи — вручную ничего не прописываете</div>
</div></div>
<div class="step" data-step><div class="tick"><span>26</span></div><div class="b">
<div class="t">Подождать выпуск сертификата и открыть свой домен в браузере.</div>
<div class="res">сайт открывается по <code>https://ваш-домен</code> с замком в адресной строке</div>
<div class="warn">Первые минуты возможна ошибка сертификата — это штатно, он выпускается не мгновенно.</div>
</div></div>
<div class="step" data-step><div class="tick"><span>27</span></div><div class="b">
<div class="t">В файле <code>api/wrangler.toml</code> заменить <code>SITE_URL</code> на свой домен и раскомментировать блок <code>routes</code> — он там уже заготовлен.</div>
</div></div>
<div class="step" data-step><div class="tick"><span>28</span></div><div class="b">
<div class="t">В GitHub Desktop написать описание изменения и нажать <kbd>Commit to main</kbd>, затем <kbd>Push origin</kbd>.</div>
<div class="res">Cloudflare пересобирает сайт автоматически, через минуту изменения на месте</div>
</div></div>
</div>
<hr class="fade">
<!-- ============================ Д ============================ -->
<div class="block" data-block="Блок Д · подписки">
<div class="head"><h2>Д. Подписки — когда понадобятся</h2><span class="when">20 минут · можно отложить</span></div>
<p class="intro">Сайт уже работает и без этого. Делайте, только когда решите брать деньги.</p>
<div class="step" data-step><div class="tick"><span>29</span></div><div class="b">
<div class="t">Установить <a href="https://nodejs.org" target="_blank" rel="noopener">Node.js</a> версии 20 или новее.</div>
</div></div>
<div class="step" data-step><div class="tick"><span>30</span></div><div class="b">
<div class="t">Открыть терминал в папке проекта и выполнить:</div>
<pre>cd escx/api
bash setup.sh</pre>
<div class="res">скрипт прогонит тесты, создаст базу, спросит секреты и опубликует API</div>
</div></div>
<div class="step" data-step><div class="tick"><span>31</span></div><div class="b">
<div class="t">В Paddle: <kbd>Developer tools</kbd> → <kbd>Notifications</kbd> → <kbd>New destination</kbd>. Адрес — <code>https://ваш-домен/api/webhook</code>, события: <code>subscription.created</code>, <code>subscription.updated</code>, <code>subscription.canceled</code>.</div>
<div class="res">секрет вида <code>pdl_ntfset_…</code> — его вводили на шаге 30</div>
</div></div>
</div>
<hr class="fade">
<div class="card">
<h3>Чтобы пошли данные — три кнопки</h3>
<p>Сбор устроен так, что после первого запуска он идёт сам. Но первый запуск
нужно сделать руками, и порядок здесь важен.</p>
<p><b>1. Разовый исторический прогон.</b> GitHub → вкладка <b>Actions</b> →
слева <b>backfill</b> → справа <b>Run workflow</b> → зелёная кнопка
<b>Run workflow</b>. Даты можно не трогать.</p>
<div class="res">через несколько минут напротив прогона зелёная галочка, а в
репозитории появляется коммит «бэкфилл»</div>
<div class="warn">Если увидите <code>HTTP Error 401: Unauthorized</code> — у вас
старая версия пайплайна. UCDP закрыл API токеном в 2026 году; свежая версия берёт
те же данные открытыми файлами и токена не требует.</div>
<p class="note">Почему без него нельзя. Накал — это отклонение от нормы, а норма
считается по прошлому самой пары. Без истории первое число оказывается отклонением
от самого себя, то есть ровно пятьюдесятью у всех.</p>
<p style="margin-top:18px"><b>2. Проверить, что сайт ожил.</b> Откройте
<code>brink.watch</code> и обновите страницу с <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>R</kbd>.</p>
<div class="res">вместо «Пайплайн собирает данные» — числа, фазы и спидометр</div>
<p style="margin-top:18px"><b>3. Включить расписание.</b> На вкладке <b>Actions</b>
убедитесь, что прогоны <b>daily</b> и <b>pull-gdelt</b> не отключены (у отключённых
рядом написано <code>Disabled</code>, включаются кнопкой <b>Enable workflow</b>).</p>
<div class="res">daily раз в сутки досчитывает и публикует, pull-gdelt раз в час
собирает свежие новости</div>
<div class="warn">GitHub отключает расписания после 60 дней без активности в
репозитории. Ежедневный прогон сам коммитит снимок данных — активность есть,
и отключения не происходит.</div>
<p style="margin-top:18px"><b>Что будет на сайте после первого прогона.</b>
Не всё сразу, и это нормально:</p>
<p>Из пяти блоков накала работают два — боевые события (UCDP) и медиапоток (GDELT).
Военный, дипломатический и экономический блоков источников пока не имеют, поэтому
у каждой пары написано <b>покрытие 40 %</b>, а числа приглушены. Это сделано
намеренно: два блока не выдаются за пять.</p>
<p><b>50 — это норма для пары, а не половина пути к войне.</b> Накал показывает
отклонение от обычного для этой пары уровня. У спокойной пары он и будет около 50.</p>
<p>Глобальный спидометр появляется только после того, как загрузятся доли населения,
ВВП и военных расходов — это делает тот же прогон, шагом «веса последствий».
Если Всемирный банк в этот момент недоступен, на месте индекса останется прочерк
до следующего прогона. Прочерк здесь честнее числа.</p>
</div>
<div class="card">
<h3>Как обновить сайт после изменений</h3>
<p><b>Сначала выясните, какого типа ваш проект Pages.</b> От этого зависит всё остальное,
и именно здесь чаще всего теряют время: файлы на GitHub поменялись, а сайт остался прежним.</p>
<p>Cloudflare → <b>Compute (Workers & Pages)</b> → ваш проект → вкладка <b>Deployments</b>.
Посмотрите на строку последней сборки:</p>
<div class="qa" style="margin-top:10px">
<div class="q">Написано <code>Direct Upload</code></div>
<div class="a">Проект живёт загруженным архивом и <b>про GitHub ничего не знает</b>. Сколько
ни меняйте файлы в репозитории, сайт не изменится — это не поломка, так устроен этот тип
проекта. Два выхода — ниже.</div>
</div>
<div class="qa">
<div class="q">Написана ветка и номер коммита (<code>main · a1b2c3d</code>)</div>
<div class="a">Проект связан с GitHub и пересобирается сам. Если сайт всё равно старый —
смотрите «Сборка прошла, а сайт прежний» ниже.</div>
</div>
<div class="qa">
<div class="q">Ветка в строке сборки — не та, в которую вы пушите</div>
<div class="a">Cloudflare следит ровно за одной веткой. По умолчанию он ставит
<code>main</code>, а в вашем репозитории ветка называется <code>master</code> —
тогда пуш проходит, а сборка не запускается вообще. Лечится в
<b>Settings</b> → <b>Builds</b> → <b>Production branch</b>: поставить <code>master</code>.
Проверить имя ветки можно на GitHub — оно написано на кнопке-переключателе
слева над списком файлов.</div>
</div>
<p style="margin-top:18px"><b>Выход 1 — связать проект с GitHub раз и навсегда.</b>
Рекомендуется: дальше достаточно будет менять файлы в репозитории.</p>
<p>В проекте: <b>Settings</b> → <b>Builds</b> → <b>Connect to Git</b> → выбрать репозиторий.
Затем заполнить настройки сборки ровно так:</p>
<pre>Production branch master <- как называется ветка в репозитории
Build command python3 web/build.py
Build output directory site
Root directory (оставить пустым)</pre>
<p>И переменную окружения: <b>Settings</b> → <b>Variables and Secrets</b> →
<code>PYTHON_VERSION</code> = <code>3.11</code>. Без неё сборка упадёт.
После этого — <b>Deployments</b> → <b>Retry deployment</b>, и через минуту-две сайт обновится.</p>
<p class="note">Если команда сборки не задана, а <b>Build output directory</b> оставлен
пустым, Cloudflare публикует корень репозитория как есть. Это тоже рабочая схема — при
ней собранные файлы (<code>index.html</code>, папка <code>data/</code>, иконки) должны
лежать в корне репозитория. Положить их туда: <code>python3 web/build.py --to-root</code>.</p>
<p style="margin-top:18px"><b>Выход 2 — загрузить новый архив руками.</b> Быстрее один раз,
но повторять придётся при каждом изменении.</p>
<p>Соберите папку: <code>python3 web/build.py</code>, упакуйте <b>содержимое</b> папки
<code>site/</code> (не саму папку) в zip. Затем: проект → <b>Deployments</b> →
<b>Create deployment</b> → перетащить архив → <b>Deploy</b>.</p>
<p class="note">Кнопки <b>Create deployment</b> нет, если проект связан с Git: у таких
проектов деплой создаётся только пушем. Это признак, по которому тип проекта видно
и без вкладки Deployments.</p>
<div class="qa" style="margin-top:18px">
<div class="q">Домен открывает старый сайт, а новый — только на <code>*.pages.dev</code></div>
<div class="a">Домен привязан к <b>другому проекту</b>. Один домен может принадлежать
только одному проекту сразу. Откройте каждый проект → <b>Custom domains</b> и найдите,
в каком из них числится <code>brink.watch</code>. Обновлять надо именно его —
либо снять домен там и добавить в нужном.</div>
</div>
<div class="qa">
<div class="q">В логе прогона: <code>! [rejected] master -> master (fetch first)</code></div>
<div class="a">Пока прогон считал, в репозиторий прилетел другой коммит — ваша
заливка или соседний прогон. Робот пытается опубликовать поверх устаревшего
состояния, и GitHub его останавливает. Данные при этом целы: они лежат в кэше
прогона, и следующий запуск их подхватит. Свежая версия пайплайна такое
разрешает сама — подтягивает чужие изменения и повторяет публикацию до трёх раз.</div>
</div>
<div class="qa">
<div class="q">Сборка прошла, а сайт прежний</div>
<div class="a">Сначала проверьте, что сборка действительно <b>Success</b>, а не
<b>Failed</b>: при неудаче Cloudflare оставляет в эфире прошлую удачную версию, и внешне
это выглядит как «ничего не произошло». Если Success — обновите страницу с
<kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>R</kbd> (<kbd>Cmd</kbd>+<kbd>Shift</kbd>+<kbd>R</kbd>
на Mac): браузер держит копию. Не помогло — Cloudflare → домен →
<b>Caching</b> → <b>Configuration</b> → <b>Purge Everything</b>.</div>
</div>
</div>
<div class="card">
<h3>Если пошло не так</h3>
<div class="qa" style="margin-top:10px">
<div class="q">Сборка упала с ошибкой про Python</div>
<div class="a">Не добавлена переменная <code>PYTHON_VERSION = 3.11</code> (шаг 12). Settings → Environment variables, добавить и запустить сборку заново кнопкой <kbd>Retry deployment</kbd>.</div>
</div>
<div class="qa">
<div class="q">Сайт открылся, но пустой и без чисел</div>
<div class="a">Скорее всего указана не та папка. В настройках сборки <b>Build output directory</b> должно быть ровно <code>site</code>.</div>
</div>
<div class="qa">
<div class="q">Домен в Cloudflare сутки висит «Pending»</div>
<div class="a">Серверы имён у Google прописаны с опечаткой или не сохранились. Откройте домен в Google Cloud Domains → DNS и сверьте обе строки посимвольно с тем, что показывает Cloudflare.</div>
</div>
<div class="qa">
<div class="q">Создал проект, а вместо сайта — вкладки Bindings, Observability и «Latest build failed»</div>
<div class="a">Это Worker, а не Pages: их легко перепутать на шаге 9. Worker сайт показывать не умеет.
Создайте заново через <b>Create → вкладка Pages</b>, дав другое имя проекта, чтобы не конфликтовало
со старым. Ненужный Worker удаляется в <b>Settings → Delete</b>.</div>
</div>
<div class="qa">
<div class="q">Вкладка Actions пустая</div>
<div class="a">Не нажата зелёная кнопка включения (шаг 6). Без неё робот не запустится, но на работу сайта это не влияет.</div>
</div>
<div class="qa">
<div class="q">Cloudflare не даёт добавить домен на шаге 15</div>
<div class="a">Бывает, пока домен не зарегистрирован. Тогда в форме Google включите <b>Cloud DNS</b>, завершите покупку, а потом вернитесь к шагам 15–19: серверы имён меняются в любой момент после регистрации.</div>
</div>
</div>
<div class="card">
<h3>Быстрый путь: без GitHub, загрузкой архива</h3>
<p>Если хочется увидеть сайт живым прямо сейчас, блоки А и Б можно пропустить.
Возьмите готовый архив <code>escx-site.zip</code> и загрузите его напрямую:</p>
<p><b>Compute (Workers & Pages)</b> → <b>Create</b> → вкладка <b>Pages</b> →
<b>Upload assets</b> → имя проекта → перетащить архив <b>не распаковывая</b> → <b>Deploy site</b>.</p>
<p>Минус один: обновляться сам такой сайт не будет, для изменений надо загружать архив заново.
Подключить GitHub можно позже — <b>Settings → Builds → Connect to Git</b>, адрес при этом не меняется.</p>
<p>Собрать архив самому: <code>python3 web/build.py --demo</code>, затем упаковать содержимое папки
<code>site/</code> — именно содержимое, а не саму папку.</p>
</div>
<div class="card">
<h3>Публичный репозиторий — не страшно, но выбор за вами</h3>
<p>Да, публичный репозиторий видят все, и код может скопировать кто угодно.
Но <b>код здесь — не то, что вас защищает</b>: это пара тысяч строк простого Python
и JavaScript, грамотный разработчик повторит их за две недели.</p>
<p>Защищает другое: журнал калибровки, который копится полтора года и не копируется
в принципе; выверенный вручную реестр диад; домен, имя и аудитория; и просто то,
что вы делаете это каждый день, а копирующий — нет.</p>
<p>Больше того: <b>открытая методология и есть главный аргумент продукта.</b>
У RANE и Verisk она закрыта — именно поэтому им нельзя задать вопрос «а почему 79?».
Спрятав свою, вы теряете то, чем отличаетесь.</p>
<p><b>Если всё равно некомфортно</b> — делайте приватным, это рабочий вариант.
Cloudflare Pages подключается к приватным репозиториям без проблем. Цена: 2000 минут
Actions в месяц вместо безлимита, поэтому в <code>.github/workflows/hourly.yml</code>
надо поменять расписание на <code>"7 */3 * * *"</code> — раз в три часа вместо часа.
На точность это почти не влияет: горизонты у нас месячные.</p>
<p><b>Разумный компромисс:</b> код приватный, а папку <code>docs/</code> — методологию —
выложить отдельно или просто страницей на сайте. Тогда и проверяемость на месте,
и внутренности закрыты.</p>
</div>
<div class="card">
<h3>Через терминал, если так привычнее</h3>
<p>Шаги 3–5 заменяются двумя командами. Нужен установленный <a href="https://cli.github.com/" target="_blank" rel="noopener">GitHub CLI</a>.</p>
<pre>cd escx
gh repo create escx --public --source=. --push</pre>
</div>
<div class="card">
<h3>Сколько это стоит</h3>
<p>Блоки А, Б, Г, Д — <b>ноль</b>. Единственная трата — домен в блоке В, примерно <b>$12 в год</b>.</p>
<p>Если домен пока не нужен, блоки В и Г можно пропустить целиком: сайт работает на бесплатном адресе <code>escx.pages.dev</code>, и домен подключается к нему в любой момент позже.</p>
</div>
<footer>
Обоснование выбора хостинга — <code>docs/06-deploy.md</code>. Подписки и доступ — <code>docs/07-billing.md</code>.<br>
Названия кнопок приведены как в английском интерфейсе — он включён по умолчанию.
</footer>
</div>
<script>
const steps = [...document.querySelectorAll('[data-step]')];
const blocks = [...document.querySelectorAll('[data-block]')];
const bar = document.getElementById('pbar');
const cnt = document.getElementById('pcount');
const now = document.getElementById('pnow');
function sync(){
const done = steps.filter(s => s.classList.contains('done')).length;
bar.style.width = (done / steps.length * 100) + '%';
cnt.textContent = `Сделано ${done} из ${steps.length}`;
const next = steps.find(s => !s.classList.contains('done'));
const blk = next ? next.closest('[data-block]') : null;
now.textContent = blk ? blk.dataset.block : 'Готово';
}
steps.forEach(s => s.querySelector('.tick').onclick = () => {
s.classList.toggle('done'); sync();
});
sync();
</script>
</body>
</html>