colloq 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. colloq/__init__.py +12 -0
  2. colloq/__main__.py +315 -0
  3. colloq/_app/.colloq-dist.json +66 -0
  4. colloq/_app/LICENSE +21 -0
  5. colloq/_app/THIRD_PARTY_NOTICES.md +106 -0
  6. colloq/_app/cli/colloq.mjs +2517 -0
  7. colloq/_app/cli/launch.mjs +1572 -0
  8. colloq/_app/cli/package.json +5 -0
  9. colloq/_app/cli/public-url-lease.mjs +198 -0
  10. colloq/_app/kernel/Dockerfile +93 -0
  11. colloq/_app/kernel/environments/base-gpu.txt +21 -0
  12. colloq/_app/kernel/environments/base.txt +12 -0
  13. colloq/_app/kernel/environments/cv.txt +15 -0
  14. colloq/_app/kernel/environments/gpu.txt +17 -0
  15. colloq/_app/kernel/environments/kaggle-base.txt +20 -0
  16. colloq/_app/kernel/environments/pvz-geo.txt +6 -0
  17. colloq/_app/kernel/requirements.txt +18 -0
  18. colloq/_app/package.json +20 -0
  19. colloq/_app/scripts/backup-local.sh +165 -0
  20. colloq/_app/scripts/backup.sh +34 -0
  21. colloq/_app/scripts/cluster.sh +492 -0
  22. colloq/_app/scripts/dns.sh +251 -0
  23. colloq/_app/scripts/host.sh +1514 -0
  24. colloq/_app/scripts/lib.sh +62 -0
  25. colloq/_app/scripts/restore.sh +338 -0
  26. colloq/_app/scripts/runtime-backup.py +373 -0
  27. colloq/_app/scripts/state-lock.py +85 -0
  28. colloq/_app/server/assets/fonts/HSESans-Black.otf +0 -0
  29. colloq/_app/server/assets/fonts/HSESans-Bold.otf +0 -0
  30. colloq/_app/server/assets/fonts/HSESans-Regular.otf +0 -0
  31. colloq/_app/server/assets/fonts/JetBrainsMono-Medium.ttf +0 -0
  32. colloq/_app/server/assets/fonts/JetBrainsMono-OFL.txt +93 -0
  33. colloq/_app/server/dist/local-cleanup.js +15987 -0
  34. colloq/_app/server/dist/runtime-smoke.js +17026 -0
  35. colloq/_app/server/dist/server.js +54333 -0
  36. colloq/_app/server/package.json +6 -0
  37. colloq/_app/web/dist/assets/AdminScreen-C-cg0GlJ.css +1 -0
  38. colloq/_app/web/dist/assets/AdminScreen-C-cg0GlJ.css.br +0 -0
  39. colloq/_app/web/dist/assets/AdminScreen-C-cg0GlJ.css.gz +0 -0
  40. colloq/_app/web/dist/assets/AdminScreen-fu4cB6Ra.js +105 -0
  41. colloq/_app/web/dist/assets/AdminScreen-fu4cB6Ra.js.br +0 -0
  42. colloq/_app/web/dist/assets/AdminScreen-fu4cB6Ra.js.gz +0 -0
  43. colloq/_app/web/dist/assets/App-CMXNqOjp.css +1 -0
  44. colloq/_app/web/dist/assets/App-CMXNqOjp.css.br +0 -0
  45. colloq/_app/web/dist/assets/App-CMXNqOjp.css.gz +0 -0
  46. colloq/_app/web/dist/assets/App-DvCPzIRO.js +26 -0
  47. colloq/_app/web/dist/assets/App-DvCPzIRO.js.br +0 -0
  48. colloq/_app/web/dist/assets/App-DvCPzIRO.js.gz +0 -0
  49. colloq/_app/web/dist/assets/CellOutputs-Dl-AZL45.js +42 -0
  50. colloq/_app/web/dist/assets/CellOutputs-Dl-AZL45.js.br +0 -0
  51. colloq/_app/web/dist/assets/CellOutputs-Dl-AZL45.js.gz +0 -0
  52. colloq/_app/web/dist/assets/CommandPalette-DhBGho7_.js +3 -0
  53. colloq/_app/web/dist/assets/CommandPalette-DhBGho7_.js.br +0 -0
  54. colloq/_app/web/dist/assets/CommandPalette-DhBGho7_.js.gz +0 -0
  55. colloq/_app/web/dist/assets/CompetitionsScreen-Vu3RpHmf.js +3 -0
  56. colloq/_app/web/dist/assets/CompetitionsScreen-Vu3RpHmf.js.br +0 -0
  57. colloq/_app/web/dist/assets/CompetitionsScreen-Vu3RpHmf.js.gz +0 -0
  58. colloq/_app/web/dist/assets/ConsoleView-B2uyYvFh.js +1 -0
  59. colloq/_app/web/dist/assets/ConsoleView-B2uyYvFh.js.br +0 -0
  60. colloq/_app/web/dist/assets/ConsoleView-B2uyYvFh.js.gz +0 -0
  61. colloq/_app/web/dist/assets/ConsoleView-DLbp0Jul.css +1 -0
  62. colloq/_app/web/dist/assets/DependencyBundleCard-B392XMVZ.js +4 -0
  63. colloq/_app/web/dist/assets/DependencyBundleCard-B392XMVZ.js.br +0 -0
  64. colloq/_app/web/dist/assets/DependencyBundleCard-B392XMVZ.js.gz +0 -0
  65. colloq/_app/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  66. colloq/_app/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  67. colloq/_app/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  68. colloq/_app/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  69. colloq/_app/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  70. colloq/_app/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  71. colloq/_app/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  72. colloq/_app/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  73. colloq/_app/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  74. colloq/_app/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  75. colloq/_app/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  76. colloq/_app/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  77. colloq/_app/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  78. colloq/_app/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  79. colloq/_app/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  80. colloq/_app/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  81. colloq/_app/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  82. colloq/_app/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  83. colloq/_app/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  84. colloq/_app/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  85. colloq/_app/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  86. colloq/_app/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  87. colloq/_app/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  88. colloq/_app/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  89. colloq/_app/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  90. colloq/_app/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  91. colloq/_app/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  92. colloq/_app/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  93. colloq/_app/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  94. colloq/_app/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  95. colloq/_app/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  96. colloq/_app/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  97. colloq/_app/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  98. colloq/_app/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  99. colloq/_app/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  100. colloq/_app/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  101. colloq/_app/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  102. colloq/_app/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  103. colloq/_app/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  104. colloq/_app/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  105. colloq/_app/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  106. colloq/_app/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  107. colloq/_app/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  108. colloq/_app/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  109. colloq/_app/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  110. colloq/_app/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  111. colloq/_app/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  112. colloq/_app/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  113. colloq/_app/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  114. colloq/_app/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  115. colloq/_app/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  116. colloq/_app/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  117. colloq/_app/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  118. colloq/_app/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  119. colloq/_app/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  120. colloq/_app/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  121. colloq/_app/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  122. colloq/_app/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  123. colloq/_app/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  124. colloq/_app/web/dist/assets/PultWindow-B3cc8537.css +1 -0
  125. colloq/_app/web/dist/assets/PultWindow-B3cc8537.css.br +0 -0
  126. colloq/_app/web/dist/assets/PultWindow-B3cc8537.css.gz +0 -0
  127. colloq/_app/web/dist/assets/PultWindow-DPOUhSqt.js +8 -0
  128. colloq/_app/web/dist/assets/PultWindow-DPOUhSqt.js.br +0 -0
  129. colloq/_app/web/dist/assets/PultWindow-DPOUhSqt.js.gz +0 -0
  130. colloq/_app/web/dist/assets/ReaderScreen-CV_0VAvW.js +9 -0
  131. colloq/_app/web/dist/assets/ReaderScreen-CV_0VAvW.js.br +0 -0
  132. colloq/_app/web/dist/assets/ReaderScreen-CV_0VAvW.js.gz +0 -0
  133. colloq/_app/web/dist/assets/SessionScreen-BrcB6CLH.js +132 -0
  134. colloq/_app/web/dist/assets/SessionScreen-BrcB6CLH.js.br +0 -0
  135. colloq/_app/web/dist/assets/SessionScreen-BrcB6CLH.js.gz +0 -0
  136. colloq/_app/web/dist/assets/SessionScreen-Dek9dK3C.css +1 -0
  137. colloq/_app/web/dist/assets/SessionScreen-Dek9dK3C.css.br +0 -0
  138. colloq/_app/web/dist/assets/SessionScreen-Dek9dK3C.css.gz +0 -0
  139. colloq/_app/web/dist/assets/actions-DLPyolgo.js +1 -0
  140. colloq/_app/web/dist/assets/admin-en-BHmR7Co0.js +9 -0
  141. colloq/_app/web/dist/assets/admin-en-BHmR7Co0.js.br +0 -0
  142. colloq/_app/web/dist/assets/admin-en-BHmR7Co0.js.gz +0 -0
  143. colloq/_app/web/dist/assets/admin-ru-Dr6Z36Zi.js +11 -0
  144. colloq/_app/web/dist/assets/admin-ru-Dr6Z36Zi.js.br +0 -0
  145. colloq/_app/web/dist/assets/admin-ru-Dr6Z36Zi.js.gz +0 -0
  146. colloq/_app/web/dist/assets/codemirror-6LUh0q1l.js +32 -0
  147. colloq/_app/web/dist/assets/codemirror-6LUh0q1l.js.br +0 -0
  148. colloq/_app/web/dist/assets/codemirror-6LUh0q1l.js.gz +0 -0
  149. colloq/_app/web/dist/assets/competitions-en-zw57uAGL.js +5 -0
  150. colloq/_app/web/dist/assets/competitions-en-zw57uAGL.js.br +0 -0
  151. colloq/_app/web/dist/assets/competitions-en-zw57uAGL.js.gz +0 -0
  152. colloq/_app/web/dist/assets/competitions-ru-BBQUjAx0.js +5 -0
  153. colloq/_app/web/dist/assets/competitions-ru-BBQUjAx0.js.br +0 -0
  154. colloq/_app/web/dist/assets/competitions-ru-BBQUjAx0.js.gz +0 -0
  155. colloq/_app/web/dist/assets/editor-theme-CVYtpZDy.js +1 -0
  156. colloq/_app/web/dist/assets/editor-theme-CVYtpZDy.js.br +0 -0
  157. colloq/_app/web/dist/assets/editor-theme-CVYtpZDy.js.gz +0 -0
  158. colloq/_app/web/dist/assets/index-CwbYK31L.css +1 -0
  159. colloq/_app/web/dist/assets/index-CwbYK31L.css.br +0 -0
  160. colloq/_app/web/dist/assets/index-CwbYK31L.css.gz +0 -0
  161. colloq/_app/web/dist/assets/index-Di16xdmz.js +2 -0
  162. colloq/_app/web/dist/assets/index-Di16xdmz.js.br +0 -0
  163. colloq/_app/web/dist/assets/index-Di16xdmz.js.gz +0 -0
  164. colloq/_app/web/dist/assets/katex-CS_ezkZp.css +1 -0
  165. colloq/_app/web/dist/assets/katex-CS_ezkZp.css.br +0 -0
  166. colloq/_app/web/dist/assets/katex-CS_ezkZp.css.gz +0 -0
  167. colloq/_app/web/dist/assets/katex-DLorvT8M.js +257 -0
  168. colloq/_app/web/dist/assets/katex-DLorvT8M.js.br +0 -0
  169. colloq/_app/web/dist/assets/katex-DLorvT8M.js.gz +0 -0
  170. colloq/_app/web/dist/assets/pdf-DEp6sk-T.js +55 -0
  171. colloq/_app/web/dist/assets/pdf-DEp6sk-T.js.br +0 -0
  172. colloq/_app/web/dist/assets/pdf-DEp6sk-T.js.gz +0 -0
  173. colloq/_app/web/dist/assets/persistence.svelte-vcuCBcT5.js +1 -0
  174. colloq/_app/web/dist/assets/persistence.svelte-vcuCBcT5.js.br +0 -0
  175. colloq/_app/web/dist/assets/persistence.svelte-vcuCBcT5.js.gz +0 -0
  176. colloq/_app/web/dist/assets/reader-en-UCGE3U6V.js +3 -0
  177. colloq/_app/web/dist/assets/reader-en-UCGE3U6V.js.br +0 -0
  178. colloq/_app/web/dist/assets/reader-en-UCGE3U6V.js.gz +0 -0
  179. colloq/_app/web/dist/assets/reader-ru-CaiNj0K8.js +3 -0
  180. colloq/_app/web/dist/assets/reader-ru-CaiNj0K8.js.br +0 -0
  181. colloq/_app/web/dist/assets/reader-ru-CaiNj0K8.js.gz +0 -0
  182. colloq/_app/web/dist/assets/render-CjP3RuRT.js +155 -0
  183. colloq/_app/web/dist/assets/render-CjP3RuRT.js.br +0 -0
  184. colloq/_app/web/dist/assets/render-CjP3RuRT.js.gz +0 -0
  185. colloq/_app/web/dist/assets/render.svelte-B4_hAJmX.js +0 -0
  186. colloq/_app/web/dist/assets/render.svelte-B4_hAJmX.js.br +0 -0
  187. colloq/_app/web/dist/assets/render.svelte-B4_hAJmX.js.gz +0 -0
  188. colloq/_app/web/dist/assets/room-en-Bh8L6NcO.js +3 -0
  189. colloq/_app/web/dist/assets/room-en-Bh8L6NcO.js.br +0 -0
  190. colloq/_app/web/dist/assets/room-en-Bh8L6NcO.js.gz +0 -0
  191. colloq/_app/web/dist/assets/room-ru-Bar2GgS0.js +3 -0
  192. colloq/_app/web/dist/assets/room-ru-Bar2GgS0.js.br +0 -0
  193. colloq/_app/web/dist/assets/room-ru-Bar2GgS0.js.gz +0 -0
  194. colloq/_app/web/dist/assets/seminar-link-CRaZl9Ku.js +1 -0
  195. colloq/_app/web/dist/assets/seminar-link-CRaZl9Ku.js.br +0 -0
  196. colloq/_app/web/dist/assets/seminar-link-CRaZl9Ku.js.gz +0 -0
  197. colloq/_app/web/dist/assets/seminar-link-DoTugxxA.css +1 -0
  198. colloq/_app/web/dist/assets/seminar-link-DoTugxxA.css.br +0 -0
  199. colloq/_app/web/dist/assets/seminar-link-DoTugxxA.css.gz +0 -0
  200. colloq/_app/web/dist/assets/yjs-rjB3hdnL.js +6 -0
  201. colloq/_app/web/dist/assets/yjs-rjB3hdnL.js.br +0 -0
  202. colloq/_app/web/dist/assets/yjs-rjB3hdnL.js.gz +0 -0
  203. colloq/_app/web/dist/fonts/JetBrainsMono-OFL.txt +93 -0
  204. colloq/_app/web/dist/fonts/hse-sans-400.woff2 +0 -0
  205. colloq/_app/web/dist/fonts/hse-sans-600.woff2 +0 -0
  206. colloq/_app/web/dist/fonts/hse-sans-700.woff2 +0 -0
  207. colloq/_app/web/dist/fonts/hse-sans-900.woff2 +0 -0
  208. colloq/_app/web/dist/fonts/jetbrains-mono-cyrillic-ext.woff2 +0 -0
  209. colloq/_app/web/dist/fonts/jetbrains-mono-cyrillic.woff2 +0 -0
  210. colloq/_app/web/dist/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
  211. colloq/_app/web/dist/fonts/jetbrains-mono-latin.woff2 +0 -0
  212. colloq/_app/web/dist/index.html +268 -0
  213. colloq/_app/web/dist/og/colloq.png +0 -0
  214. colloq/_app/web/dist/pdf/pdf.worker.min.mjs +29 -0
  215. colloq/_app/web/dist/pdf/pdf.worker.min.mjs.br +0 -0
  216. colloq/_app/web/dist/pdf/pdf.worker.min.mjs.gz +0 -0
  217. colloq/_app/web/dist/plotly/plotly.min.js +3620 -0
  218. colloq/_app/web/dist/plotly/plotly.min.js.br +0 -0
  219. colloq/_app/web/dist/plotly/plotly.min.js.gz +0 -0
  220. colloq/_version.py +5 -0
  221. colloq-0.2.0.dist-info/METADATA +124 -0
  222. colloq-0.2.0.dist-info/RECORD +224 -0
  223. colloq-0.2.0.dist-info/WHEEL +4 -0
  224. colloq-0.2.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,62 @@
1
+ # Общее для скриптов эксплуатации. Не программа — его сорсят.
2
+ #
3
+ # . ./scripts/lib.sh из корня репозитория (host.sh, service.sh, vast.sh)
4
+ # . ./lib.sh из scripts/, назвав ENV_FILE заранее (dns.sh)
5
+ #
6
+ # Живут здесь две вещи, и обе — про один и тот же файл: где искать каталог
7
+ # состояния (COLLOQ_STATE_ROOT, см. ниже) и как прочитать из .env строку.
8
+ #
9
+ # Чтение строки было списано в трёх скриптах слово в слово, и все три несли
10
+ # одну и ту же ошибку — `tr -d ' \r'`, то есть «удалить каждый пробел, где бы
11
+ # он ни стоял». Имена карт в API vast пишутся с пробелом («RTX 4090»), и
12
+ # `VAST_GPU=RTX 4090` из .env превращался в «RTX4090», под который на рынке нет
13
+ # ни одного предложения; скрипт при этом советовал вписать в .env ровно то,
14
+ # что уже вписано. Название учреждения, путь с пробелом, имя ретранслятора —
15
+ # та же история.
16
+ #
17
+ # Правило теперь одно и записано один раз: обрезаются только края, кавычки по
18
+ # краям снимаются парой — как это делает сервер, читая PUBLIC_URL
19
+ # (server/src/config.ts). Внутренность значения не трогается вовсе.
20
+
21
+ # Корень СОСТОЯНИЯ: .env, расписка идущего занятия, .colloq.pid, data/.
22
+ #
23
+ # Корней два, и совпадают они не везде. Каталог ПРИЛОЖЕНИЯ — тот, что над
24
+ # scripts/: web/dist, kernel/, эти самые скрипты. У поставленного через pip
25
+ # colloq он лежит в <site-packages>/colloq/_app, доступен только на чтение и
26
+ # сносится целиком каждым `pip install -U`; состояние живёт отдельно, в
27
+ # ~/.colloq, и его адрес приезжает сюда переменной COLLOQ_HOME.
28
+ #
29
+ # Скрипты же считали корнем состояния себя — «cd $(dirname $0)/..» в шапке, —
30
+ # и у установленного colloq публикация «удавалась» вникуда: PUBLIC_URL уходил
31
+ # в .env каталога приложения, которого не читает никто, а `colloq link` смотрел
32
+ # в <home>/.env и продолжал говорить «наружу не выставлен».
33
+ #
34
+ # Без COLLOQ_HOME — ровно как раньше: текущий каталог, то есть тот самый
35
+ # каталог над scripts/, в который скрипт уже перешёл сам. В репозитории оба
36
+ # корня — это он и есть, поэтому там не сдвинулся ни один путь.
37
+ COLLOQ_STATE_ROOT="${COLLOQ_HOME:-.}"
38
+
39
+ # Файл настроек. Скрипты работают из корня репозитория, dns.sh — из scripts/,
40
+ # и путь у него другой; больше между ними разницы нет.
41
+ ENV_FILE="${ENV_FILE:-$COLLOQ_STATE_ROOT/.env}"
42
+
43
+ # read_env ИМЯ — значение строки `ИМЯ=…` из .env, пустая строка, если её нет.
44
+ # Берётся последняя: дописанное в конец файла главнее, и так же смотрит на
45
+ # него сервер.
46
+ read_env() {
47
+ local v
48
+ v="$(grep -E "^$1=" "$ENV_FILE" 2>/dev/null | tail -1 | cut -d= -f2- || true)"
49
+ # CRLF: .env, побывавший в Windows, иначе принёс бы возврат каретки внутрь
50
+ # имени контейнера и адреса.
51
+ v="${v%$'\r'}"
52
+ # Пробелы и табуляции по краям — и только по краям.
53
+ v="${v#"${v%%[![:space:]]*}"}"
54
+ v="${v%"${v##*[![:space:]]}"}"
55
+ # Кавычки снимаются только парой: одиночная кавычка внутри значения — это
56
+ # часть значения, а не половина обёртки.
57
+ case "$v" in
58
+ '"'*'"') v="${v#\"}"; v="${v%\"}" ;;
59
+ "'"*"'") v="${v#\'}"; v="${v%\'}" ;;
60
+ esac
61
+ printf '%s' "$v"
62
+ }
@@ -0,0 +1,338 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Развернуть снятую копию — пара к `make backup`.
4
+ #
5
+ # scripts/restore.sh самую свежую пару из backups/
6
+ # scripts/restore.sh backups/colloq-20260905-120000.db
7
+ # NAME=demo scripts/restore.sh самую свежую пару среды demo
8
+ #
9
+ # Про NAME. Сред бывает несколько — по арендованной машине на каждую, — и копии
10
+ # у них разложены по подкаталогам: backups/demo/, backups/hse/. Без NAME
11
+ # смотрим в корень backups/: это копии здешнего инстанса, у него имени нет.
12
+ # Именно поэтому «самая свежая» ищется в одном каталоге, а не во всех сразу:
13
+ # развернуть базу чужого семинара — это чужие тетради, чужие преподаватели и
14
+ # ссылки, ведущие в чужие файлы, причём молча.
15
+ #
16
+ # Зачем это отдельной командой. `make backup` снимает базу и архив с файлами
17
+ # семинаров, а обратная дорога до сих пор описывалась словами «положите два
18
+ # файла на место». Мест на самом деле три, и одно из них умеет тихо испортить
19
+ # базу: рядом с colloq.db в режиме WAL лежат colloq.db-wal и colloq.db-shm, и
20
+ # если подложить базу из копии, оставив старый журнал, sqlite накатит на неё
21
+ # чужие страницы. Поэтому журнал уезжает вместе с той базой, которую он
22
+ # описывает, а не выбрасывается и не остаётся.
23
+ #
24
+ # Что переживает пересоздание машины и лежит в копии:
25
+ #
26
+ # colloq.db семинары, преподаватели, история версий, настройки оракула
27
+ # workspace/ файлы семинаров — то, что загрузили и создали ячейки
28
+ # data/session-secret ключ подписи: без него все выданные ссылки и куки мертвы
29
+ # data/setup-token токен установки — вход в панель, когда ссылка потеряна
30
+ # kernel/environments/*.txt списки пакетов, в том числе заведённые из панели
31
+ # прямо на машине: их больше негде взять, а образ по ним
32
+ # пересобирается одной командой
33
+ #
34
+ # Чего в копии нет и почему: собранные образы окружений (их дешевле пересобрать
35
+ # одной командой, чем возить десятки гигабайт: make env-build NAME=…) и .env
36
+ # (он едет на новую машину сам, вместе с репозиторием, — см. scripts/vast.sh).
37
+ set -euo pipefail
38
+ SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
39
+
40
+ cd "$(dirname "$0")/.."
41
+
42
+ # Portable cluster recovery is explicit; a legacy archive is never silently
43
+ # overlaid onto a PVC. Validate before stopping writers or moving any data.
44
+ if [ "${1:-}" = --archive ]; then
45
+ STATE="${COLLOQ_STATE_DIR:-/var/lib/colloq}"
46
+ if ! python3 scripts/state-lock.py held --state "$STATE"; then
47
+ exec python3 scripts/state-lock.py run --state "$STATE" -- bash "$SCRIPT_PATH" "$@"
48
+ fi
49
+ shift
50
+ ARCHIVE="${1:?archive path required}"; shift
51
+ STATE="${COLLOQ_STATE_DIR:-/var/lib/colloq}"
52
+ RELEASE="${RELEASE:-$STATE/releases/current.json}"
53
+ extra=()
54
+ while [ "$#" -gt 0 ]; do
55
+ case "$1" in
56
+ --release) RELEASE="${2:?release required}"; shift 2;;
57
+ --replace) extra+=(--replace); shift;;
58
+ --recover) extra+=(--recover); shift;;
59
+ *) echo "Unknown restore argument: $1" >&2; exit 1;;
60
+ esac
61
+ done
62
+ [ -f "$RELEASE" ] || { echo 'Select the matching RELEASE before restoring.' >&2; exit 1; }
63
+ python3 scripts/runtime-backup.py validate --archive "$ARCHIVE" --name "${NAME:-}" --release "$RELEASE"
64
+ mkdir -p "$STATE"
65
+ bash scripts/cluster.sh stop
66
+ python3 scripts/runtime-backup.py restore --root "$STATE" --release "$RELEASE" --archive "$ARCHIVE" --name "${NAME:-}" --defer-finalize "${extra[@]}"
67
+ # A restored older backup may deliberately contain previously retired IDs.
68
+ # Keep the durable marker until those old reservations are removed, with
69
+ # every writer still stopped and the same operation lock held throughout.
70
+ bash scripts/cluster.sh restore-services
71
+ python3 scripts/runtime-backup.py finalize --root "$STATE" --release "$RELEASE" --archive "$ARCHIVE" --name "${NAME:-}"
72
+ # Ownership was set on the validated stage before the durable marker cleared.
73
+ echo 'Restore completed with writers stopped. Run cluster.sh prepare --release, then cluster.sh start.'
74
+ exit 0
75
+ fi
76
+ if [ -f "${COLLOQ_STATE_DIR:-/var/lib/colloq}/releases/current.json" ]; then
77
+ echo 'Cluster restore requires --archive PORTABLE.tar.gz --release RELEASE.json; legacy overlay is refused.' >&2
78
+ exit 1
79
+ fi
80
+
81
+ RED=$'\033[31m'; DIM=$'\033[2m'; BOLD=$'\033[1m'; OFF=$'\033[0m'
82
+ say() { printf '%s\n' "$*"; }
83
+ die() { printf '%s%s%s\n' "$RED" "$*" "$OFF" >&2; exit 1; }
84
+
85
+ # Корень СОСТОЯНИЯ — оттуда берутся data/, workspace/ и backups/.
86
+ #
87
+ # Пара к scripts/backup-local.sh, и по той же причине. Скрипт пришёл сюда из
88
+ # мира, где корень был один, и считал data/ с workspace/ от каталога над собой,
89
+ # то есть от каталога ПРИЛОЖЕНИЯ. У поставленного через pip colloq это
90
+ # site-packages: `colloq restore --legacy` распаковал бы копию занятия рядом с
91
+ # кодом — туда, куда не смотрит ни сервер, ни следующий запуск, — и стёр бы её
92
+ # первым же `pip install -U`. Адрес состояния приезжает переменной COLLOQ_HOME,
93
+ # разбор — в scripts/lib.sh · COLLOQ_STATE_ROOT.
94
+ #
95
+ # Каталог приложения при этом нужен и дальше: в нём лежат сами скрипты,
96
+ # docker-compose.yml и cluster.sh. Поэтому он запоминается, а не теряется.
97
+ . ./scripts/lib.sh
98
+ APP="$PWD"
99
+ STATE="$(cd "$COLLOQ_STATE_ROOT" 2>/dev/null && pwd || true)"
100
+ [ -n "$STATE" ] || die "no state directory \"${COLLOQ_STATE_ROOT}\" — check COLLOQ_HOME."
101
+ cd "$STATE"
102
+
103
+ # Каталог копий одной среды. Имя приезжает переменной окружения, как HOST у
104
+ # `make host`: так его передаёт Makefile, и так оно не мешает старому вызову с
105
+ # путями в аргументах. Проверяем те же знаки, что и vast.sh: имя уходит в путь,
106
+ # и «../» увело бы восстановление за пределы backups/.
107
+ ENV_NAME="${NAME:-}"
108
+ case "$ENV_NAME" in
109
+ '') : ;;
110
+ *[!A-Za-z0-9-]*|-*|*-) die "deployment name \"${ENV_NAME}\" is not valid: letters, digits and a hyphen inside." ;;
111
+ esac
112
+ BACKUP_DIR="backups${ENV_NAME:+/$ENV_NAME}"
113
+
114
+ DB=""
115
+ FILES=""
116
+ for arg in "$@"; do
117
+ case "$arg" in
118
+ *.db) DB="$arg" ;;
119
+ *.tar.gz) FILES="$arg" ;;
120
+ *) die "I do not understand \"${arg}\". Expected backups/colloq-<date>.db and/or backups/colloq-<date>-files.tar.gz" ;;
121
+ esac
122
+ done
123
+
124
+ if [ -z "$DB" ] && [ -z "$FILES" ]; then
125
+ DB="$(ls -1t "$BACKUP_DIR"/colloq-*.db 2>/dev/null | head -1 || true)"
126
+ if [ -z "$DB" ]; then
127
+ # Список сред печатается прямо в отказе: «копий нет» при полном backups/hse
128
+ # — это не отсутствие копии, а не то имя, и выяснять это на пустой машине
129
+ # посреди занятия незачем.
130
+ others="$(ls -1d backups/*/ 2>/dev/null | sed 's#backups/##;s#/##' | tr '\n' ' ' | sed 's/ *$//' || true)"
131
+ # Совет называется командой, которая есть на ЭТОЙ машине: у поставленного
132
+ # пакета Makefile нет, и «make backup» было бы вторым отказом подряд.
133
+ take="colloq backup"; [ "$STATE" = "$APP" ] && take="make backup"
134
+ die "there is not a single backup in $BACKUP_DIR/.
135
+ Take one on a running instance: $take${others:+
136
+ Backups of other deployments live in subdirectories: $others
137
+ Restore a deployment backup: make restore NAME=<name>}"
138
+ fi
139
+ fi
140
+ # Архив ищется по имени базы, а не по «самому свежему»: пара из базы одного дня
141
+ # и файлов другого — это семинар, у которого в тетради есть ссылка на файл,
142
+ # которого нет.
143
+ if [ -z "$FILES" ] && [ -n "$DB" ] && [ -f "${DB%.db}-files.tar.gz" ]; then
144
+ FILES="${DB%.db}-files.tar.gz"
145
+ fi
146
+
147
+ [ -z "$DB" ] || [ -f "$DB" ] || die "no file $DB"
148
+ [ -z "$FILES" ] || [ -f "$FILES" ] || die "no file $FILES"
149
+
150
+ say "${BOLD}1/3${OFF} checking there is somewhere to restore into"
151
+
152
+ # Под работающим сервером базу не подменяют. sqlite держит открытым тот файл,
153
+ # который открыл: старый inode останется живым до последнего закрытия, семинар
154
+ # продолжит писать в файл, которого уже нет на диске, а после перезапуска эта
155
+ # работа просто исчезнет. Причём на экране до самого перезапуска всё выглядит
156
+ # исправно — поэтому проверка здесь, а не в напутствии внизу.
157
+ # Читает .env общий read_env (scripts/lib.sh) — тот же, что у host.sh и
158
+ # service.sh: одна копия правила на все скрипты. Подключён он выше, вместе с
159
+ # корнем состояния: оттуда же берётся и путь к .env.
160
+ PORT="$(read_env PORT)"; PORT="${PORT:-3000}"
161
+ if command -v systemctl >/dev/null 2>&1 && systemctl is-active --quiet colloq 2>/dev/null; then
162
+ die "the colloq service is running — stop it first: make service-stop.
163
+ A stopped service is not yet an empty machine: restoring a backup over today's
164
+ work still has to be deliberate, see REPLACE=1 further down."
165
+ fi
166
+ # Спрашиваем из каталога ПРИЛОЖЕНИЯ: docker-compose.yml лежит там, а мы стоим
167
+ # в состоянии. У поставленного пакета compose нет вовсе — вопрос останется без
168
+ # ответа, и это верный ответ: контейнером там никто не поднимается.
169
+ if (cd "$APP" && docker compose ps --status running --services 2>/dev/null) | grep -qx app; then
170
+ die "app is running in docker — stop it first: make down"
171
+ fi
172
+ if [ -f .colloq.pid ] && kill -0 "$(cat .colloq.pid 2>/dev/null)" 2>/dev/null; then
173
+ die "a class is running on this machine — first: colloq stop"
174
+ fi
175
+ if curl -sf -o /dev/null --max-time 3 "http://localhost:$PORT/api/health" 2>/dev/null; then
176
+ die "something answers on localhost:$PORT — that is a second Colloq.
177
+ Stop it (make down · make stop) and try again."
178
+ fi
179
+
180
+ # Поверх непустой машины — только по просьбе.
181
+ #
182
+ # Разворачивание необратимо ровно наполовину, и это худший из вариантов: база
183
+ # откладывается в colloq.db.replaced-<штамп> (о котором потом никто не
184
+ # вспомнит), а архив ложится на workspace/ поверх, перезаписывая одноимённые
185
+ # файлы без всякой копии. Поэтому «здесь уже что-то есть» — это вопрос, а не
186
+ # повод действовать: скрипт зовут и руками («откатить на вчера»), и из
187
+ # scripts/vast.sh, где повод бывает случайным.
188
+ #
189
+ # Спрашиваем, только когда есть у кого: на той стороне ssh терминала нет, на
190
+ # stdin висит heredoc — и `read` съел бы остаток скрипта. Без терминала нужен
191
+ # REPLACE=1, названный вслух.
192
+ if [ -n "$DB" ] && [ -s data/colloq.db ]; then
193
+ if [ "${REPLACE:-}" = 1 ]; then
194
+ say "${DIM} a database is already here — restoring over it (REPLACE=1)${OFF}"
195
+ elif [ -t 0 ]; then
196
+ say "${RED}There is already a data/colloq.db here${OFF} — classes, teachers, version history."
197
+ say "${DIM}It will be set aside as data/colloq.db.replaced-<stamp>, and the archive will${OFF}"
198
+ say "${DIM}land on workspace/ — same-named files are overwritten with no backup.${OFF}"
199
+ printf '%srestore the backup over it? [y/N] %s' "$BOLD" "$OFF"
200
+ read -r answer
201
+ case "$answer" in y|Y|yes|YES|Yes) : ;; *) die "not restoring." ;; esac
202
+ else
203
+ die "there is already a data/colloq.db here, and no terminal to ask in.
204
+ This is not an empty machine: restoring a backup over it means setting the
205
+ current database aside as data/colloq.db.replaced-<stamp> and unpacking the
206
+ archive on top of workspace/.
207
+ If that is what you want: REPLACE=1 $0 $*"
208
+ fi
209
+ fi
210
+
211
+ mkdir -p data workspace
212
+ STAMP="$(date +%Y%m%d-%H%M%S)"
213
+
214
+ say "${BOLD}2/3${OFF} database"
215
+ if [ -n "$DB" ]; then
216
+ # Дешёвая проверка вместо доверия расширению: первые шестнадцать байт файла
217
+ # sqlite — это «SQLite format 3». Восстановить пустой файл, скачавшийся до
218
+ # половины, значит потерять и то, что было.
219
+ head -c 16 "$DB" | LC_ALL=C grep -qa 'SQLite format 3' \
220
+ || die "$DB does not look like an sqlite database — the backup is broken or incomplete."
221
+ if command -v sqlite3 >/dev/null 2>&1; then
222
+ [ "$(sqlite3 "$DB" 'pragma quick_check')" = ok ] \
223
+ || die "$DB does not pass the sqlite check. Take another backup."
224
+ fi
225
+ if [ -f data/colloq.db ]; then
226
+ # Прежняя база уезжает целиком со своим журналом. Не удаляется — «я нажал
227
+ # restore не той копией» должно быть поправимо; и не остаётся на месте —
228
+ # старый -wal, накаченный на новую базу, портит её молча.
229
+ mv data/colloq.db "data/colloq.db.replaced-$STAMP"
230
+ for j in wal shm; do
231
+ if [ -f "data/colloq.db-$j" ]; then
232
+ mv "data/colloq.db-$j" "data/colloq.db.replaced-$STAMP-$j"
233
+ fi
234
+ done
235
+ say "${DIM} the previous database is set aside as data/colloq.db.replaced-$STAMP${OFF}"
236
+ fi
237
+ cp "$DB" data/colloq.db
238
+ chmod 600 data/colloq.db
239
+ say " $DB → data/colloq.db"
240
+ else
241
+ say "${DIM} not touching the database — it was not in the arguments${OFF}"
242
+ fi
243
+
244
+ say "${BOLD}3/3${OFF} class files and keys"
245
+ if [ -n "$FILES" ]; then
246
+ # Разворачивается поверх, а не вместо: комнаты, которых в копии нет, остаются
247
+ # на месте. Забрать лишнее всегда можно, а вернуть стёртое — нет.
248
+ #
249
+ # -p обязателен: у data/session-secret права 0600, и без сохранения режима
250
+ # ключ подписи стал бы читаемым для всех, кто есть на машине.
251
+ tar -xzpf "$FILES"
252
+ # Куда легли списки окружений — зависит от того, разошлись ли корни: в
253
+ # репозитории это kernel/environments, у поставленного пакета — свой каталог
254
+ # рядом с .env. Ровно так их и клал scripts/backup-local.sh, и называть надо
255
+ # то же место, иначе человек пойдёт искать файлы не туда.
256
+ if [ "$STATE" = "$APP" ]; then lists=kernel/environments; else lists=environments; fi
257
+ say " $FILES → workspace/, data/, $lists/"
258
+ if [ -f data/session-secret ]; then
259
+ chmod 600 data/session-secret
260
+ say "${DIM} the signing key is in place — issued links and cookies survive the move${OFF}"
261
+ fi
262
+ if [ -f data/setup-token ]; then
263
+ chmod 600 data/setup-token
264
+ fi
265
+ else
266
+ say "${DIM} no file archive — only the database was restored.${OFF}"
267
+ say "${DIM} Notebooks and settings are in place, uploaded files are not.${OFF}"
268
+ fi
269
+
270
+ # Хозяин файлов — тот, от кого работает сервер, а не тот, кто восстанавливал.
271
+ #
272
+ # Под `make up` сервер в контейнере работает от `node` (uid 1000), а копию на
273
+ # новой машине разворачивает root: база и папки оставались root:root, и
274
+ # контейнер падал по кругу с «unable to open database file». Локально этого не
275
+ # видно вовсе — при `make run` сервер и есть тот, кто распаковал.
276
+ #
277
+ # А на выделенной машине сервер стоит службой и работает от root — там uid 1000
278
+ # у базы означал бы ровно обратную ошибку. Поэтому форму спрашиваем, а не
279
+ # угадываем: есть юнит — сервер root, нет — сервер в контейнере. И спрашиваем с
280
+ # оговоркой: на новой машине копию разворачивают ДО установки службы (иначе
281
+ # сервер откроет базу, которую мы собираемся подменить), и юнита там ещё нет —
282
+ # поэтому scripts/vast.sh говорит форму прямо, FORM=service.
283
+ #
284
+ # Что не зависит от формы: workspace/. В него пишет ядро комнаты, а оно всегда
285
+ # uid 1000 (kernel/Dockerfile, пользователь runner). Под службой сервер кладёт
286
+ # туда же свои файлы от root, поэтому каталогам ставится группа 1000 и бит
287
+ # setgid: всё новое внутри достаётся этой группе, а право писать ей даёт
288
+ # UMask=0002 из юнита. Без пары «setgid + umask» первая ячейка с open(…,'w')
289
+ # в свежей комнате падает PermissionError на зелёном экране.
290
+ #
291
+ # Тысяча здесь — умолчание, а не истина: группу ядру выдаёт useradd, и в
292
+ # окружении поверх чужой базы она может оказаться другой. Точный номер ставит
293
+ # scripts/service.sh, спросив его у самого образа; там же это и повторяется
294
+ # после каждой установки.
295
+ #
296
+ # Только когда мы root и только на этих двух каталогах: чужие права здесь не
297
+ # трогаются, а на macOS (там uid другой и всё и так своё) шаг пропускается.
298
+ FORM="${FORM:-}"
299
+ if [ -z "$FORM" ] && [ -f /etc/systemd/system/colloq.service ]; then FORM=service; fi
300
+ if [ "$(id -u)" = 0 ] && [ "$(uname -s)" = Linux ]; then
301
+ if [ "$FORM" = service ]; then
302
+ chown -R root:root data
303
+ # Группа, а не владелец. Владельца у файлов внутри workspace/ менять нельзя:
304
+ # то, что распаковалось от uid 1000, ядро писало как хозяин, и став
305
+ # root:1000 с правами 0644 оно стало бы для ядра нечитаемым на запись.
306
+ # Группа же 1000 плюс setgid на каталогах дают обеим сторонам заводить и
307
+ # удалять файлы внутри, не трогая уже лежащие.
308
+ chgrp -R 1000 workspace
309
+ find workspace -type d -exec chmod 2775 {} + 2>/dev/null || true
310
+ say "${DIM} data/ — root's (the server is a service), workspace/ — group 1000 (the kernel)${OFF}"
311
+ else
312
+ chown -R 1000:1000 data workspace
313
+ say "${DIM} data/ and workspace/ — uid 1000: that is who the server is in the container${OFF}"
314
+ fi
315
+ fi
316
+
317
+ printf '\n'
318
+ say "${BOLD}done${OFF}"
319
+ # Куда идти дальше — словом, которое есть у того, кто это читает.
320
+ #
321
+ # Прежнее напутствие звало `make up / make run / make service-install` и
322
+ # `make env-build NAME=…`. У поставленного через pip colloq make нет вовсе
323
+ # (в колесо едут приложение и scripts/, Makefile не едет — scripts/pack.mts ·
324
+ # SCRIPTS), и обе строки врали ровно тому, кто только что развернул копию на
325
+ # новой машине и ищет, чем её запустить. У пакета это `colloq start`: образ
326
+ # окружения он собирает сам, первым запуском (cli/src/commands/env.ts · env
327
+ # use). Рядом с исходниками — make: там же живёт и цель service-install
328
+ # выделенной машины.
329
+ if [ "$STATE" = "$APP" ]; then
330
+ say "${DIM}Start a class: make run (make dev for development)${OFF}"
331
+ say "${DIM}On a dedicated machine the class is held by a service: make service-install.${OFF}"
332
+ else
333
+ say "${DIM}Start a class: colloq start${OFF}"
334
+ fi
335
+ say "${DIM}Kernel environments are not restored here —${OFF}"
336
+ say "${DIM}the image is built on the next start of a class.${OFF}"
337
+ say "${DIM}Pinned competition images must be restored separately with their exact image IDs (see backup/restore docs).${OFF}"
338
+ say "${DIM}The Oracle key, RELAY_* and PUBLIC_URL live in .env, not in the backup.${OFF}"