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.
- colloq/__init__.py +12 -0
- colloq/__main__.py +315 -0
- colloq/_app/.colloq-dist.json +66 -0
- colloq/_app/LICENSE +21 -0
- colloq/_app/THIRD_PARTY_NOTICES.md +106 -0
- colloq/_app/cli/colloq.mjs +2517 -0
- colloq/_app/cli/launch.mjs +1572 -0
- colloq/_app/cli/package.json +5 -0
- colloq/_app/cli/public-url-lease.mjs +198 -0
- colloq/_app/kernel/Dockerfile +93 -0
- colloq/_app/kernel/environments/base-gpu.txt +21 -0
- colloq/_app/kernel/environments/base.txt +12 -0
- colloq/_app/kernel/environments/cv.txt +15 -0
- colloq/_app/kernel/environments/gpu.txt +17 -0
- colloq/_app/kernel/environments/kaggle-base.txt +20 -0
- colloq/_app/kernel/environments/pvz-geo.txt +6 -0
- colloq/_app/kernel/requirements.txt +18 -0
- colloq/_app/package.json +20 -0
- colloq/_app/scripts/backup-local.sh +165 -0
- colloq/_app/scripts/backup.sh +34 -0
- colloq/_app/scripts/cluster.sh +492 -0
- colloq/_app/scripts/dns.sh +251 -0
- colloq/_app/scripts/host.sh +1514 -0
- colloq/_app/scripts/lib.sh +62 -0
- colloq/_app/scripts/restore.sh +338 -0
- colloq/_app/scripts/runtime-backup.py +373 -0
- colloq/_app/scripts/state-lock.py +85 -0
- colloq/_app/server/assets/fonts/HSESans-Black.otf +0 -0
- colloq/_app/server/assets/fonts/HSESans-Bold.otf +0 -0
- colloq/_app/server/assets/fonts/HSESans-Regular.otf +0 -0
- colloq/_app/server/assets/fonts/JetBrainsMono-Medium.ttf +0 -0
- colloq/_app/server/assets/fonts/JetBrainsMono-OFL.txt +93 -0
- colloq/_app/server/dist/local-cleanup.js +15987 -0
- colloq/_app/server/dist/runtime-smoke.js +17026 -0
- colloq/_app/server/dist/server.js +54333 -0
- colloq/_app/server/package.json +6 -0
- colloq/_app/web/dist/assets/AdminScreen-C-cg0GlJ.css +1 -0
- colloq/_app/web/dist/assets/AdminScreen-C-cg0GlJ.css.br +0 -0
- colloq/_app/web/dist/assets/AdminScreen-C-cg0GlJ.css.gz +0 -0
- colloq/_app/web/dist/assets/AdminScreen-fu4cB6Ra.js +105 -0
- colloq/_app/web/dist/assets/AdminScreen-fu4cB6Ra.js.br +0 -0
- colloq/_app/web/dist/assets/AdminScreen-fu4cB6Ra.js.gz +0 -0
- colloq/_app/web/dist/assets/App-CMXNqOjp.css +1 -0
- colloq/_app/web/dist/assets/App-CMXNqOjp.css.br +0 -0
- colloq/_app/web/dist/assets/App-CMXNqOjp.css.gz +0 -0
- colloq/_app/web/dist/assets/App-DvCPzIRO.js +26 -0
- colloq/_app/web/dist/assets/App-DvCPzIRO.js.br +0 -0
- colloq/_app/web/dist/assets/App-DvCPzIRO.js.gz +0 -0
- colloq/_app/web/dist/assets/CellOutputs-Dl-AZL45.js +42 -0
- colloq/_app/web/dist/assets/CellOutputs-Dl-AZL45.js.br +0 -0
- colloq/_app/web/dist/assets/CellOutputs-Dl-AZL45.js.gz +0 -0
- colloq/_app/web/dist/assets/CommandPalette-DhBGho7_.js +3 -0
- colloq/_app/web/dist/assets/CommandPalette-DhBGho7_.js.br +0 -0
- colloq/_app/web/dist/assets/CommandPalette-DhBGho7_.js.gz +0 -0
- colloq/_app/web/dist/assets/CompetitionsScreen-Vu3RpHmf.js +3 -0
- colloq/_app/web/dist/assets/CompetitionsScreen-Vu3RpHmf.js.br +0 -0
- colloq/_app/web/dist/assets/CompetitionsScreen-Vu3RpHmf.js.gz +0 -0
- colloq/_app/web/dist/assets/ConsoleView-B2uyYvFh.js +1 -0
- colloq/_app/web/dist/assets/ConsoleView-B2uyYvFh.js.br +0 -0
- colloq/_app/web/dist/assets/ConsoleView-B2uyYvFh.js.gz +0 -0
- colloq/_app/web/dist/assets/ConsoleView-DLbp0Jul.css +1 -0
- colloq/_app/web/dist/assets/DependencyBundleCard-B392XMVZ.js +4 -0
- colloq/_app/web/dist/assets/DependencyBundleCard-B392XMVZ.js.br +0 -0
- colloq/_app/web/dist/assets/DependencyBundleCard-B392XMVZ.js.gz +0 -0
- colloq/_app/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- colloq/_app/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- colloq/_app/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- colloq/_app/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- colloq/_app/web/dist/assets/PultWindow-B3cc8537.css +1 -0
- colloq/_app/web/dist/assets/PultWindow-B3cc8537.css.br +0 -0
- colloq/_app/web/dist/assets/PultWindow-B3cc8537.css.gz +0 -0
- colloq/_app/web/dist/assets/PultWindow-DPOUhSqt.js +8 -0
- colloq/_app/web/dist/assets/PultWindow-DPOUhSqt.js.br +0 -0
- colloq/_app/web/dist/assets/PultWindow-DPOUhSqt.js.gz +0 -0
- colloq/_app/web/dist/assets/ReaderScreen-CV_0VAvW.js +9 -0
- colloq/_app/web/dist/assets/ReaderScreen-CV_0VAvW.js.br +0 -0
- colloq/_app/web/dist/assets/ReaderScreen-CV_0VAvW.js.gz +0 -0
- colloq/_app/web/dist/assets/SessionScreen-BrcB6CLH.js +132 -0
- colloq/_app/web/dist/assets/SessionScreen-BrcB6CLH.js.br +0 -0
- colloq/_app/web/dist/assets/SessionScreen-BrcB6CLH.js.gz +0 -0
- colloq/_app/web/dist/assets/SessionScreen-Dek9dK3C.css +1 -0
- colloq/_app/web/dist/assets/SessionScreen-Dek9dK3C.css.br +0 -0
- colloq/_app/web/dist/assets/SessionScreen-Dek9dK3C.css.gz +0 -0
- colloq/_app/web/dist/assets/actions-DLPyolgo.js +1 -0
- colloq/_app/web/dist/assets/admin-en-BHmR7Co0.js +9 -0
- colloq/_app/web/dist/assets/admin-en-BHmR7Co0.js.br +0 -0
- colloq/_app/web/dist/assets/admin-en-BHmR7Co0.js.gz +0 -0
- colloq/_app/web/dist/assets/admin-ru-Dr6Z36Zi.js +11 -0
- colloq/_app/web/dist/assets/admin-ru-Dr6Z36Zi.js.br +0 -0
- colloq/_app/web/dist/assets/admin-ru-Dr6Z36Zi.js.gz +0 -0
- colloq/_app/web/dist/assets/codemirror-6LUh0q1l.js +32 -0
- colloq/_app/web/dist/assets/codemirror-6LUh0q1l.js.br +0 -0
- colloq/_app/web/dist/assets/codemirror-6LUh0q1l.js.gz +0 -0
- colloq/_app/web/dist/assets/competitions-en-zw57uAGL.js +5 -0
- colloq/_app/web/dist/assets/competitions-en-zw57uAGL.js.br +0 -0
- colloq/_app/web/dist/assets/competitions-en-zw57uAGL.js.gz +0 -0
- colloq/_app/web/dist/assets/competitions-ru-BBQUjAx0.js +5 -0
- colloq/_app/web/dist/assets/competitions-ru-BBQUjAx0.js.br +0 -0
- colloq/_app/web/dist/assets/competitions-ru-BBQUjAx0.js.gz +0 -0
- colloq/_app/web/dist/assets/editor-theme-CVYtpZDy.js +1 -0
- colloq/_app/web/dist/assets/editor-theme-CVYtpZDy.js.br +0 -0
- colloq/_app/web/dist/assets/editor-theme-CVYtpZDy.js.gz +0 -0
- colloq/_app/web/dist/assets/index-CwbYK31L.css +1 -0
- colloq/_app/web/dist/assets/index-CwbYK31L.css.br +0 -0
- colloq/_app/web/dist/assets/index-CwbYK31L.css.gz +0 -0
- colloq/_app/web/dist/assets/index-Di16xdmz.js +2 -0
- colloq/_app/web/dist/assets/index-Di16xdmz.js.br +0 -0
- colloq/_app/web/dist/assets/index-Di16xdmz.js.gz +0 -0
- colloq/_app/web/dist/assets/katex-CS_ezkZp.css +1 -0
- colloq/_app/web/dist/assets/katex-CS_ezkZp.css.br +0 -0
- colloq/_app/web/dist/assets/katex-CS_ezkZp.css.gz +0 -0
- colloq/_app/web/dist/assets/katex-DLorvT8M.js +257 -0
- colloq/_app/web/dist/assets/katex-DLorvT8M.js.br +0 -0
- colloq/_app/web/dist/assets/katex-DLorvT8M.js.gz +0 -0
- colloq/_app/web/dist/assets/pdf-DEp6sk-T.js +55 -0
- colloq/_app/web/dist/assets/pdf-DEp6sk-T.js.br +0 -0
- colloq/_app/web/dist/assets/pdf-DEp6sk-T.js.gz +0 -0
- colloq/_app/web/dist/assets/persistence.svelte-vcuCBcT5.js +1 -0
- colloq/_app/web/dist/assets/persistence.svelte-vcuCBcT5.js.br +0 -0
- colloq/_app/web/dist/assets/persistence.svelte-vcuCBcT5.js.gz +0 -0
- colloq/_app/web/dist/assets/reader-en-UCGE3U6V.js +3 -0
- colloq/_app/web/dist/assets/reader-en-UCGE3U6V.js.br +0 -0
- colloq/_app/web/dist/assets/reader-en-UCGE3U6V.js.gz +0 -0
- colloq/_app/web/dist/assets/reader-ru-CaiNj0K8.js +3 -0
- colloq/_app/web/dist/assets/reader-ru-CaiNj0K8.js.br +0 -0
- colloq/_app/web/dist/assets/reader-ru-CaiNj0K8.js.gz +0 -0
- colloq/_app/web/dist/assets/render-CjP3RuRT.js +155 -0
- colloq/_app/web/dist/assets/render-CjP3RuRT.js.br +0 -0
- colloq/_app/web/dist/assets/render-CjP3RuRT.js.gz +0 -0
- colloq/_app/web/dist/assets/render.svelte-B4_hAJmX.js +0 -0
- colloq/_app/web/dist/assets/render.svelte-B4_hAJmX.js.br +0 -0
- colloq/_app/web/dist/assets/render.svelte-B4_hAJmX.js.gz +0 -0
- colloq/_app/web/dist/assets/room-en-Bh8L6NcO.js +3 -0
- colloq/_app/web/dist/assets/room-en-Bh8L6NcO.js.br +0 -0
- colloq/_app/web/dist/assets/room-en-Bh8L6NcO.js.gz +0 -0
- colloq/_app/web/dist/assets/room-ru-Bar2GgS0.js +3 -0
- colloq/_app/web/dist/assets/room-ru-Bar2GgS0.js.br +0 -0
- colloq/_app/web/dist/assets/room-ru-Bar2GgS0.js.gz +0 -0
- colloq/_app/web/dist/assets/seminar-link-CRaZl9Ku.js +1 -0
- colloq/_app/web/dist/assets/seminar-link-CRaZl9Ku.js.br +0 -0
- colloq/_app/web/dist/assets/seminar-link-CRaZl9Ku.js.gz +0 -0
- colloq/_app/web/dist/assets/seminar-link-DoTugxxA.css +1 -0
- colloq/_app/web/dist/assets/seminar-link-DoTugxxA.css.br +0 -0
- colloq/_app/web/dist/assets/seminar-link-DoTugxxA.css.gz +0 -0
- colloq/_app/web/dist/assets/yjs-rjB3hdnL.js +6 -0
- colloq/_app/web/dist/assets/yjs-rjB3hdnL.js.br +0 -0
- colloq/_app/web/dist/assets/yjs-rjB3hdnL.js.gz +0 -0
- colloq/_app/web/dist/fonts/JetBrainsMono-OFL.txt +93 -0
- colloq/_app/web/dist/fonts/hse-sans-400.woff2 +0 -0
- colloq/_app/web/dist/fonts/hse-sans-600.woff2 +0 -0
- colloq/_app/web/dist/fonts/hse-sans-700.woff2 +0 -0
- colloq/_app/web/dist/fonts/hse-sans-900.woff2 +0 -0
- colloq/_app/web/dist/fonts/jetbrains-mono-cyrillic-ext.woff2 +0 -0
- colloq/_app/web/dist/fonts/jetbrains-mono-cyrillic.woff2 +0 -0
- colloq/_app/web/dist/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
- colloq/_app/web/dist/fonts/jetbrains-mono-latin.woff2 +0 -0
- colloq/_app/web/dist/index.html +268 -0
- colloq/_app/web/dist/og/colloq.png +0 -0
- colloq/_app/web/dist/pdf/pdf.worker.min.mjs +29 -0
- colloq/_app/web/dist/pdf/pdf.worker.min.mjs.br +0 -0
- colloq/_app/web/dist/pdf/pdf.worker.min.mjs.gz +0 -0
- colloq/_app/web/dist/plotly/plotly.min.js +3620 -0
- colloq/_app/web/dist/plotly/plotly.min.js.br +0 -0
- colloq/_app/web/dist/plotly/plotly.min.js.gz +0 -0
- colloq/_version.py +5 -0
- colloq-0.2.0.dist-info/METADATA +124 -0
- colloq-0.2.0.dist-info/RECORD +224 -0
- colloq-0.2.0.dist-info/WHEEL +4 -0
- colloq-0.2.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# One-node production deployment. No release is inferred from this working tree.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
ORIGINAL_ARGS=("$@")
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
STATE="${COLLOQ_STATE_DIR:-/var/lib/colloq}"
|
|
7
|
+
NS=colloq
|
|
8
|
+
PORT=30080
|
|
9
|
+
CMD="${1:-help}"; [ "$#" -eq 0 ] || shift
|
|
10
|
+
RELEASE="${RELEASE_MANIFEST:-}"
|
|
11
|
+
ENV_FILE=""
|
|
12
|
+
REGISTRY_CONFIG=""
|
|
13
|
+
PUBLIC_URL=""
|
|
14
|
+
EXPECT_URL=""
|
|
15
|
+
if [ "$CMD" = public-url ]; then PUBLIC_URL="${1:-}"; [ "$#" -eq 0 ] || shift; fi
|
|
16
|
+
while [ "$#" -gt 0 ]; do
|
|
17
|
+
case "$1" in
|
|
18
|
+
--release) RELEASE="${2:?--release requires a file}"; shift 2 ;;
|
|
19
|
+
--if-current) EXPECT_URL="${2:?--if-current requires a URL}"; shift 2 ;;
|
|
20
|
+
--env-file) ENV_FILE="${2:?--env-file requires a file}"; shift 2 ;;
|
|
21
|
+
--registry-config) REGISTRY_CONFIG="${2:?--registry-config requires docker config JSON}"; shift 2 ;;
|
|
22
|
+
--*) printf 'unknown argument: %s\n' "$1" >&2; exit 1 ;;
|
|
23
|
+
*) [ -z "$RELEASE" ] || { echo 'only one release file is allowed' >&2; exit 1; }; RELEASE="$1"; shift ;;
|
|
24
|
+
esac
|
|
25
|
+
done
|
|
26
|
+
die() { printf 'cluster: %s\n' "$*" >&2; exit 1; }
|
|
27
|
+
release() { python3 "$SCRIPT_DIR/release.py" "$@"; }
|
|
28
|
+
kube() (
|
|
29
|
+
# Use the matching multicall client directly. Some packaged k3s builds pass
|
|
30
|
+
# the literal subcommand through to kubectl when invoked as `k3s kubectl`.
|
|
31
|
+
# Never inherit an operator's unrelated cluster context.
|
|
32
|
+
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
|
|
33
|
+
exec -a kubectl "$(command -v k3s)" "$@"
|
|
34
|
+
)
|
|
35
|
+
k() { kube -n "$NS" "$@"; }
|
|
36
|
+
need_root() {
|
|
37
|
+
[ "$(uname -s)" = Linux ] || die 'production deployment requires a Linux VM'
|
|
38
|
+
[ "$(id -u)" = 0 ] || die 'run this host provisioning command as root'
|
|
39
|
+
}
|
|
40
|
+
need_cluster() { command -v k3s >/dev/null || die 'k3s is not installed; install an explicit release first'; }
|
|
41
|
+
guard_restore() {
|
|
42
|
+
case "$CMD" in
|
|
43
|
+
prepare|install|update|rollback|start|public-url|smoke|gpu-preflight)
|
|
44
|
+
if [ -e "$STATE/.restore-in-progress" ] || [ -L "$STATE/.restore-in-progress" ]; then
|
|
45
|
+
die 'an incomplete restore is in progress; recover the verified backup before starting or changing workloads'
|
|
46
|
+
fi ;;
|
|
47
|
+
esac
|
|
48
|
+
}
|
|
49
|
+
data_release() {
|
|
50
|
+
if [ -f "$STATE/recovery/release.json" ]; then printf '%s\n' "$STATE/recovery/release.json";
|
|
51
|
+
else printf '%s\n' "$STATE/releases/current.json"; fi
|
|
52
|
+
}
|
|
53
|
+
# Файл оператора, по которому рендерится брокер: новый --env-file или
|
|
54
|
+
# config.env прошлой установки — тот же выбор, что делает prepare. Из него
|
|
55
|
+
# брокер получает RUNTIME_KERNEL_MEMORY[_MAX]; без этого память комнат по
|
|
56
|
+
# умолчанию задавалась только правкой Deployment, и update её стирал.
|
|
57
|
+
operator_env() {
|
|
58
|
+
if [ -n "$ENV_FILE" ]; then printf '%s\n' "$ENV_FILE"
|
|
59
|
+
elif [ -r "$STATE/config.env" ]; then printf '%s\n' "$STATE/config.env"; fi
|
|
60
|
+
}
|
|
61
|
+
# Проверить эти числа, пока ничего не остановлено: иначе плохое значение
|
|
62
|
+
# всплыло бы после бэкапа и остановки комнат — или CrashLoop-ом брокера.
|
|
63
|
+
check_operator_env() {
|
|
64
|
+
local file
|
|
65
|
+
file="$(operator_env)"
|
|
66
|
+
[ -z "$file" ] || release render --release "$1" --state-dir "$STATE" --env-file "$file" >/dev/null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
case "$CMD" in
|
|
70
|
+
validate|render|prepare|install|update|rollback)
|
|
71
|
+
[ -n "$RELEASE" ] || die 'an explicit --release FILE is required'
|
|
72
|
+
release validate --release "$RELEASE" >/dev/null
|
|
73
|
+
;;
|
|
74
|
+
esac
|
|
75
|
+
case "$CMD" in
|
|
76
|
+
validate) exec python3 "$SCRIPT_DIR/release.py" validate --release "$RELEASE" ;;
|
|
77
|
+
render)
|
|
78
|
+
runtime=(); env_file="$(operator_env)"; [ -z "$env_file" ] || runtime=(--env-file "$env_file")
|
|
79
|
+
exec python3 "$SCRIPT_DIR/release.py" render --release "$RELEASE" --state-dir "$STATE" --node-name "${COLLOQ_NODE_NAME:-colloq}" ${runtime[@]+"${runtime[@]}"} ;;
|
|
80
|
+
help)
|
|
81
|
+
cat <<'HELP'
|
|
82
|
+
cluster.sh validate|render --release FILE inspect before host changes
|
|
83
|
+
cluster.sh prepare --release FILE [--env-file FILE] [--registry-config FILE]
|
|
84
|
+
install resources, all writers stopped
|
|
85
|
+
cluster.sh install|update|rollback --release FILE [--env-file FILE]
|
|
86
|
+
prepare, start, verify readiness
|
|
87
|
+
(env file: app settings, RUNTIME_KERNEL_MEMORY[_MAX])
|
|
88
|
+
cluster.sh start|stop|status|logs manage the installed application
|
|
89
|
+
cluster.sh public-url https://example.edu update public URL, restart app if running
|
|
90
|
+
cluster.sh public-url URL --if-current OLD only while it is still OLD; exit 4 otherwise
|
|
91
|
+
cluster.sh smoke|gpu-preflight disposable room/CUDA execution checks
|
|
92
|
+
|
|
93
|
+
State: /var/lib/colloq (COLLOQ_STATE_DIR overrides). App: 127.0.0.1:30080.
|
|
94
|
+
Install/prepare require an explicit release with immutable image digests.
|
|
95
|
+
Updates require a verified backup first. k3s upgrades are separate operations.
|
|
96
|
+
HELP
|
|
97
|
+
exit 0 ;;
|
|
98
|
+
esac
|
|
99
|
+
guard_restore
|
|
100
|
+
case "$CMD" in
|
|
101
|
+
prepare|install|update|rollback)
|
|
102
|
+
release verify-tooling --release "$RELEASE" --tooling-root "$SCRIPT_DIR/.." >/dev/null ;;
|
|
103
|
+
esac
|
|
104
|
+
need_root
|
|
105
|
+
umask 077
|
|
106
|
+
case "$CMD" in
|
|
107
|
+
prepare|install|update|rollback|start|stop|public-url|smoke|gpu-preflight|restore-services)
|
|
108
|
+
if ! python3 "$SCRIPT_DIR/state-lock.py" held --state "$STATE"; then
|
|
109
|
+
exec python3 "$SCRIPT_DIR/state-lock.py" run --state "$STATE" -- bash "$SCRIPT_DIR/cluster.sh" "${ORIGINAL_ARGS[@]}"
|
|
110
|
+
fi
|
|
111
|
+
# A restore may have begun after the early read-only check but before the lock.
|
|
112
|
+
guard_restore
|
|
113
|
+
;;
|
|
114
|
+
esac
|
|
115
|
+
|
|
116
|
+
stop_writers() {
|
|
117
|
+
k scale deployment/colloq-app deployment/colloq-runtime --replicas=0
|
|
118
|
+
k wait --for=delete pod -l 'colloq.dev/role in (app,runtime)' --timeout=90s
|
|
119
|
+
# Stop the broker before deleting kernels: it must not recreate a room.
|
|
120
|
+
k delete pods -l colloq.kind=room-kernel --wait=true --timeout=120s
|
|
121
|
+
k delete services -l 'app.kubernetes.io/managed-by=colloq-runtime,colloq.dev/retired!=true' --ignore-not-found
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
firewall() {
|
|
125
|
+
command -v iptables >/dev/null || die 'iptables is required to keep Kubernetes host ports private'
|
|
126
|
+
# This does not block the NetworkPolicy local-node exception. See deploy/k3s/README.md.
|
|
127
|
+
iptables -N COLLOQ-HOST 2>/dev/null || true
|
|
128
|
+
iptables -F COLLOQ-HOST
|
|
129
|
+
iptables -A COLLOQ-HOST -i lo -j RETURN
|
|
130
|
+
iptables -A COLLOQ-HOST -s 10.42.0.0/16 -j RETURN
|
|
131
|
+
iptables -A COLLOQ-HOST -p tcp -m multiport --dports 6443,10250,30080 -j DROP
|
|
132
|
+
iptables -A COLLOQ-HOST -p udp --dport 8472 -j DROP
|
|
133
|
+
iptables -A COLLOQ-HOST -j RETURN
|
|
134
|
+
iptables -C INPUT -j COLLOQ-HOST 2>/dev/null || iptables -I INPUT 1 -j COLLOQ-HOST
|
|
135
|
+
if [ -s /proc/net/if_inet6 ]; then
|
|
136
|
+
command -v ip6tables >/dev/null || die 'ip6tables is required on IPv6-enabled hosts'
|
|
137
|
+
ip6tables -N COLLOQ-HOST 2>/dev/null || true
|
|
138
|
+
ip6tables -F COLLOQ-HOST
|
|
139
|
+
ip6tables -A COLLOQ-HOST -i lo -j RETURN
|
|
140
|
+
ip6tables -A COLLOQ-HOST -p tcp -m multiport --dports 6443,10250,30080 -j DROP
|
|
141
|
+
ip6tables -A COLLOQ-HOST -p udp --dport 8472 -j DROP
|
|
142
|
+
ip6tables -A COLLOQ-HOST -j RETURN
|
|
143
|
+
ip6tables -C INPUT -j COLLOQ-HOST 2>/dev/null || ip6tables -I INPUT 1 -j COLLOQ-HOST
|
|
144
|
+
fi
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
bootstrap() {
|
|
148
|
+
local wanted have tmp
|
|
149
|
+
wanted="$(release field --release "$RELEASE" --field k3sVersion)"
|
|
150
|
+
if systemctl cat colloq.service >/dev/null 2>&1; then
|
|
151
|
+
die 'legacy colloq.service exists; stop/remove that root service and migrate its data explicitly before installing k3s workloads'
|
|
152
|
+
fi
|
|
153
|
+
gpu_toolkit
|
|
154
|
+
if command -v k3s >/dev/null; then
|
|
155
|
+
have="$(k3s --version | head -1 | awk '{print $3}')"
|
|
156
|
+
[ "$have" = "$wanted" ] || die "installed k3s $have differs from release $wanted; upgrade k3s separately after backing up its datastore and token"
|
|
157
|
+
[ -f /etc/rancher/k3s/colloq-managed ] || die 'existing k3s is not Colloq-managed; review deploy/k3s/README.md before migration'
|
|
158
|
+
if [ "${GPU_RUNTIME_CHANGED:-0}" = 1 ]; then systemctl restart k3s; fi
|
|
159
|
+
systemctl is-active --quiet k3s || systemctl start k3s
|
|
160
|
+
return
|
|
161
|
+
fi
|
|
162
|
+
[ "$(uname -m)" = x86_64 ] || die 'this release installer currently supports Linux amd64 only'
|
|
163
|
+
command -v systemctl >/dev/null || die 'systemd is required'
|
|
164
|
+
command -v iptables >/dev/null || die 'install iptables before bootstrapping k3s'
|
|
165
|
+
command -v curl >/dev/null || die 'curl is required'
|
|
166
|
+
tmp="$(mktemp -d)"
|
|
167
|
+
# Both artifacts come from the explicit upstream release, never a latest channel.
|
|
168
|
+
curl -fL --retry 3 "https://github.com/k3s-io/k3s/releases/download/$wanted/k3s" -o "$tmp/k3s"
|
|
169
|
+
curl -fL --retry 3 "https://github.com/k3s-io/k3s/releases/download/$wanted/sha256sum-amd64.txt" -o "$tmp/checksums"
|
|
170
|
+
(cd "$tmp" && awk '$2 == "k3s" || $2 == "*k3s" {print}' checksums > selected && test -s selected && sha256sum -c selected)
|
|
171
|
+
install -m 0755 "$tmp/k3s" /usr/local/bin/k3s
|
|
172
|
+
mkdir -p /etc/rancher/k3s
|
|
173
|
+
cat > /etc/rancher/k3s/config.yaml <<'CONFIG'
|
|
174
|
+
disable:
|
|
175
|
+
- traefik
|
|
176
|
+
- servicelb
|
|
177
|
+
- local-storage
|
|
178
|
+
write-kubeconfig-mode: "0600"
|
|
179
|
+
secrets-encryption: true
|
|
180
|
+
cluster-cidr: 10.42.0.0/16
|
|
181
|
+
service-cidr: 10.43.0.0/16
|
|
182
|
+
kube-proxy-arg:
|
|
183
|
+
- nodeport-addresses=127.0.0.0/8
|
|
184
|
+
kubelet-arg:
|
|
185
|
+
- pod-max-pids=256
|
|
186
|
+
CONFIG
|
|
187
|
+
cat > /etc/systemd/system/k3s.service <<'UNIT'
|
|
188
|
+
[Unit]
|
|
189
|
+
Description=Colloq single-node k3s
|
|
190
|
+
Wants=network-online.target
|
|
191
|
+
After=network-online.target colloq-firewall.service
|
|
192
|
+
Requires=colloq-firewall.service
|
|
193
|
+
[Service]
|
|
194
|
+
Type=notify
|
|
195
|
+
ExecStart=/usr/local/bin/k3s server
|
|
196
|
+
Delegate=yes
|
|
197
|
+
KillMode=process
|
|
198
|
+
Restart=always
|
|
199
|
+
RestartSec=5
|
|
200
|
+
LimitNOFILE=1048576
|
|
201
|
+
LimitNPROC=infinity
|
|
202
|
+
TasksMax=infinity
|
|
203
|
+
[Install]
|
|
204
|
+
WantedBy=multi-user.target
|
|
205
|
+
UNIT
|
|
206
|
+
# Persist host exposure restrictions across reboot without a broad firewall reset.
|
|
207
|
+
install -m 0755 "$SCRIPT_DIR/cluster.sh" /usr/local/sbin/colloq-cluster-firewall
|
|
208
|
+
cat > /etc/systemd/system/colloq-firewall.service <<'UNIT'
|
|
209
|
+
[Unit]
|
|
210
|
+
Description=Keep Colloq Kubernetes host ports private
|
|
211
|
+
Before=k3s.service
|
|
212
|
+
[Service]
|
|
213
|
+
Type=oneshot
|
|
214
|
+
ExecStart=/usr/local/sbin/colloq-cluster-firewall firewall
|
|
215
|
+
RemainAfterExit=yes
|
|
216
|
+
[Install]
|
|
217
|
+
WantedBy=multi-user.target
|
|
218
|
+
UNIT
|
|
219
|
+
touch /etc/rancher/k3s/colloq-managed
|
|
220
|
+
systemctl daemon-reload
|
|
221
|
+
systemctl enable --now colloq-firewall.service
|
|
222
|
+
systemctl enable --now k3s
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
gpu_toolkit() {
|
|
226
|
+
[ "$(release field --release "$RELEASE" --field gpu)" = true ] || return 0
|
|
227
|
+
command -v nvidia-smi >/dev/null || die 'GPU release requires a preinstalled working NVIDIA host driver; the installer will not replace it'
|
|
228
|
+
nvidia-smi --query-gpu=index,name --format=csv,noheader || die 'host NVIDIA driver is not working'
|
|
229
|
+
local wanted have package matches=1
|
|
230
|
+
wanted="$(release field --release "$RELEASE" --field toolkitVersion)"
|
|
231
|
+
command -v apt-get >/dev/null || die 'pinned GPU toolkit installation supports Debian/Ubuntu apt hosts'
|
|
232
|
+
for package in nvidia-container-toolkit nvidia-container-toolkit-base libnvidia-container-tools libnvidia-container1; do
|
|
233
|
+
have="$(dpkg-query -W -f='${Version}' "$package" 2>/dev/null || true)"
|
|
234
|
+
if [ -n "$have" ] && dpkg --compare-versions "$have" gt "$wanted"; then
|
|
235
|
+
die "$package $have is newer than release pin $wanted; select a release with compatible tooling or explicitly downgrade the toolkit before retrying"
|
|
236
|
+
fi
|
|
237
|
+
if [ "$have" != "$wanted" ]; then matches=0; fi
|
|
238
|
+
done
|
|
239
|
+
if [ "$matches" = 0 ]; then
|
|
240
|
+
command -v gpg >/dev/null || die 'install gnupg before configuring the signed NVIDIA package repository'
|
|
241
|
+
mkdir -p /usr/share/keyrings
|
|
242
|
+
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | gpg --dearmor --yes -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
|
|
243
|
+
curl -fsSL https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \
|
|
244
|
+
| sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#' \
|
|
245
|
+
> /etc/apt/sources.list.d/nvidia-container-toolkit.list
|
|
246
|
+
apt-get update -qq
|
|
247
|
+
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
248
|
+
"nvidia-container-toolkit=$wanted" "nvidia-container-toolkit-base=$wanted" \
|
|
249
|
+
"libnvidia-container-tools=$wanted" "libnvidia-container1=$wanted"
|
|
250
|
+
GPU_RUNTIME_CHANGED=1
|
|
251
|
+
fi
|
|
252
|
+
command -v nvidia-container-runtime >/dev/null || die 'NVIDIA container runtime is missing after pinned toolkit installation'
|
|
253
|
+
# k3s detects nvidia-container-runtime and generates its own containerd config.
|
|
254
|
+
# Do not configure Docker or edit k3s's generated containerd TOML.
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
gpu_plugin() {
|
|
258
|
+
[ "$(release field --release "$1" --field gpu)" = true ] || return 0
|
|
259
|
+
# The plugin runs in a trusted namespace; carry only the pull credential there.
|
|
260
|
+
k get secret colloq-registry -o json | python3 -c 'import json,sys; value=json.load(sys.stdin); value["metadata"]={"name":"colloq-registry","namespace":"kube-system"}; print(json.dumps(value))' \
|
|
261
|
+
| kube apply -f - >/dev/null
|
|
262
|
+
release gpu --release "$1" | kube apply -f -
|
|
263
|
+
kube -n kube-system rollout status daemonset/colloq-nvidia-device-plugin --timeout=180s
|
|
264
|
+
# A running plugin can precede the kubelet's next node-status update.
|
|
265
|
+
local count deadline=$((SECONDS + 60))
|
|
266
|
+
while [ "$SECONDS" -lt "$deadline" ]; do
|
|
267
|
+
count="$(k get nodes --request-timeout=5s -o 'jsonpath={.items[0].status.allocatable.nvidia\.com/gpu}' || true)"
|
|
268
|
+
if [[ "$count" =~ ^[1-9][0-9]*$ ]]; then return 0; fi
|
|
269
|
+
sleep 2
|
|
270
|
+
done
|
|
271
|
+
die 'NVIDIA plugin has no allocatable GPU after waiting for registration; check driver, toolkit and k3s runtime logs'
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
gpu_preflight() {
|
|
275
|
+
local manifest="$STATE/releases/current.json"
|
|
276
|
+
[ "$(release field --release "$manifest" --field gpu)" = true ] || return 0
|
|
277
|
+
nvidia-smi --query-gpu=index,name --format=csv,noheader || die 'NVIDIA host driver failed'
|
|
278
|
+
# Never evict a room to obtain a GPU. A busy device makes the bounded probe fail.
|
|
279
|
+
k delete pod colloq-gpu-check --ignore-not-found --wait=true >/dev/null
|
|
280
|
+
release gpu-smoke --release "$manifest" | k create -f - >/dev/null
|
|
281
|
+
if ! k wait --for=jsonpath='{.status.phase}'=Succeeded pod/colloq-gpu-check --timeout=200s; then
|
|
282
|
+
k describe pod colloq-gpu-check >&2
|
|
283
|
+
k logs colloq-gpu-check >&2 || true
|
|
284
|
+
k delete pod colloq-gpu-check --wait=true >/dev/null
|
|
285
|
+
die 'CUDA probe failed or no exclusive GPU was free; existing rooms were not evicted'
|
|
286
|
+
fi
|
|
287
|
+
k logs colloq-gpu-check
|
|
288
|
+
k delete pod colloq-gpu-check --wait=true >/dev/null
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
wait_for_node() {
|
|
292
|
+
local nodes state deadline=$((SECONDS + 180))
|
|
293
|
+
while [ "$SECONDS" -lt "$deadline" ]; do
|
|
294
|
+
# The API can be reachable before the kubelet creates its first Node.
|
|
295
|
+
if nodes="$(k get nodes --request-timeout=5s -o json 2>/dev/null)"; then
|
|
296
|
+
state="$(printf '%s' "$nodes" | python3 -c '
|
|
297
|
+
import json,sys
|
|
298
|
+
nodes=json.load(sys.stdin)["items"]
|
|
299
|
+
if len(nodes)>1:
|
|
300
|
+
print("multiple")
|
|
301
|
+
elif len(nodes)==1:
|
|
302
|
+
node=nodes[0]
|
|
303
|
+
hostname=node.get("metadata",{}).get("labels",{}).get("kubernetes.io/hostname")
|
|
304
|
+
ready=any(c.get("type")=="Ready" and c.get("status")=="True" for c in node.get("status",{}).get("conditions",[]))
|
|
305
|
+
if ready and hostname:
|
|
306
|
+
print("ready " + hostname)
|
|
307
|
+
')" || die 'cannot parse Kubernetes node status'
|
|
308
|
+
case "$state" in
|
|
309
|
+
multiple) die 'this deployment supports exactly one node' ;;
|
|
310
|
+
ready\ *) printf '%s\n' "${state#ready }"; return 0 ;;
|
|
311
|
+
esac
|
|
312
|
+
fi
|
|
313
|
+
sleep 2
|
|
314
|
+
done
|
|
315
|
+
die 'no single ready Kubernetes node registered within 180 seconds; check k3s and kubelet logs'
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
prepare() {
|
|
319
|
+
local effective node current policy_version data extras=() runtime=()
|
|
320
|
+
guard_restore
|
|
321
|
+
mkdir -p "$STATE/releases" "$STATE/secrets" "$STATE/data" "$STATE/workspace"
|
|
322
|
+
chmod 0750 "$STATE/data" "$STATE/workspace"
|
|
323
|
+
chown 1000:1000 "$STATE/data" "$STATE/workspace"
|
|
324
|
+
current="$STATE/releases/current.json"
|
|
325
|
+
data="$(data_release)"
|
|
326
|
+
if [ -f "$current" ]; then extras=(--previous "$current"); fi
|
|
327
|
+
if [ -f "$data" ]; then extras+=(--data-release "$data"); fi
|
|
328
|
+
if [ "$CMD" = rollback ]; then extras+=(--rollback); fi
|
|
329
|
+
effective="$(mktemp "$STATE/releases/.release.XXXXXX")"
|
|
330
|
+
release merge --release "$RELEASE" "${extras[@]}" > "$effective"
|
|
331
|
+
if [ -f "$STATE/recovery/release.json" ]; then
|
|
332
|
+
release merge --release "$effective" --previous "$STATE/recovery/release.json" --data-release "$data" > "$effective.recovery"
|
|
333
|
+
mv "$effective.recovery" "$effective"
|
|
334
|
+
fi
|
|
335
|
+
check_operator_env "$effective"
|
|
336
|
+
RELEASE="$effective" bootstrap
|
|
337
|
+
need_cluster
|
|
338
|
+
node="$(wait_for_node)"
|
|
339
|
+
k create namespace "$NS" --dry-run=client -o json | k apply -f - >/dev/null
|
|
340
|
+
policy_version="$(release field --release "$effective" --field policyVersion)"
|
|
341
|
+
k label namespace "$NS" pod-security.kubernetes.io/enforce=restricted \
|
|
342
|
+
"pod-security.kubernetes.io/enforce-version=$policy_version" \
|
|
343
|
+
pod-security.kubernetes.io/audit=restricted "pod-security.kubernetes.io/audit-version=$policy_version" \
|
|
344
|
+
pod-security.kubernetes.io/warn=restricted "pod-security.kubernetes.io/warn-version=$policy_version" --overwrite >/dev/null
|
|
345
|
+
if [ -n "$REGISTRY_CONFIG" ]; then
|
|
346
|
+
k create secret generic colloq-registry --type=kubernetes.io/dockerconfigjson --from-file=".dockerconfigjson=$REGISTRY_CONFIG" --dry-run=client -o json | k apply -f - >/dev/null
|
|
347
|
+
elif ! k get secret colloq-registry >/dev/null 2>&1; then
|
|
348
|
+
k create secret generic colloq-registry --type=kubernetes.io/dockerconfigjson --from-literal='.dockerconfigjson={"auths":{}}' >/dev/null
|
|
349
|
+
fi
|
|
350
|
+
gpu_plugin "$effective"
|
|
351
|
+
# Kubernetes uses the same imagePullSecret as the actual workloads. ctr/crictl
|
|
352
|
+
# pulls would bypass it and break otherwise valid private-registry releases.
|
|
353
|
+
k delete pod colloq-image-check --ignore-not-found --wait=true >/dev/null
|
|
354
|
+
release prepull --release "$effective" | k create -f - >/dev/null
|
|
355
|
+
if ! k wait --for=jsonpath='{.status.phase}'=Succeeded pod/colloq-image-check --timeout=900s; then
|
|
356
|
+
k describe pod colloq-image-check >&2
|
|
357
|
+
die 'release images could not be pulled/run as UID 1000; inspect the preflight Pod before retrying (updates leave writers stopped after backup)'
|
|
358
|
+
fi
|
|
359
|
+
k delete pod colloq-image-check --wait=true >/dev/null
|
|
360
|
+
if k get deployment colloq-app >/dev/null 2>&1; then stop_writers; fi
|
|
361
|
+
for secret in runtime-token room-secret; do
|
|
362
|
+
if [ ! -s "$STATE/secrets/$secret" ]; then
|
|
363
|
+
python3 -c 'import secrets; print(secrets.token_hex(32))' > "$STATE/secrets/$secret"
|
|
364
|
+
fi
|
|
365
|
+
chmod 0600 "$STATE/secrets/$secret"
|
|
366
|
+
done
|
|
367
|
+
k create secret generic colloq-runtime-auth --from-file="runtime-token=$STATE/secrets/runtime-token" --dry-run=client -o json | k apply -f - >/dev/null
|
|
368
|
+
k create secret generic colloq-room-secret --from-file="room-secret=$STATE/secrets/room-secret" --dry-run=client -o json | k apply -f - >/dev/null
|
|
369
|
+
if [ -n "$ENV_FILE" ]; then cp "$ENV_FILE" "$STATE/config.env"; chmod 0600 "$STATE/config.env"; fi
|
|
370
|
+
if [ -f "$STATE/config.env" ]; then
|
|
371
|
+
release config --release "$effective" --env-file "$STATE/config.env" | k apply -f - >/dev/null
|
|
372
|
+
elif ! k get secret colloq-app-config >/dev/null 2>&1; then
|
|
373
|
+
release config --release "$effective" | k apply -f - >/dev/null
|
|
374
|
+
fi
|
|
375
|
+
[ ! -f "$STATE/config.env" ] || runtime=(--env-file "$STATE/config.env")
|
|
376
|
+
release render --release "$effective" --node-name "$node" --state-dir "$STATE" ${runtime[@]+"${runtime[@]}"} | k apply -f -
|
|
377
|
+
[ ! -f "$current" ] || cp "$current" "$STATE/releases/previous.json"
|
|
378
|
+
mv "$effective" "$current"
|
|
379
|
+
printf 'Release prepared; all writers stopped. Restore if needed, then cluster.sh start.\n'
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
wait_for_app() {
|
|
383
|
+
local timeout deadline=$((SECONDS + 60))
|
|
384
|
+
# Pod readiness can precede kube-proxy programming the localhost NodePort.
|
|
385
|
+
while [ "$SECONDS" -lt "$deadline" ]; do
|
|
386
|
+
timeout=$((deadline - SECONDS))
|
|
387
|
+
[ "$timeout" -gt 0 ] || break
|
|
388
|
+
if [ "$timeout" -gt 5 ]; then timeout=5; fi
|
|
389
|
+
if curl -fsS --connect-timeout 2 --max-time "$timeout" "http://127.0.0.1:$PORT/api/health" >/dev/null 2>&1; then
|
|
390
|
+
return 0
|
|
391
|
+
fi
|
|
392
|
+
if [ "$SECONDS" -lt "$deadline" ]; then sleep 1; fi
|
|
393
|
+
done
|
|
394
|
+
die "app health endpoint http://127.0.0.1:$PORT/api/health did not become reachable within 60 seconds; check app logs, Service endpoints and kube-proxy"
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
start() {
|
|
398
|
+
guard_restore
|
|
399
|
+
need_cluster
|
|
400
|
+
if [ -f "$STATE/recovery/release.json" ]; then
|
|
401
|
+
release validate --release "$STATE/releases/current.json" --data-release "$STATE/recovery/release.json" >/dev/null
|
|
402
|
+
fi
|
|
403
|
+
k scale deployment/colloq-runtime --replicas=1
|
|
404
|
+
k rollout status deployment/colloq-runtime --timeout=180s
|
|
405
|
+
# Once the app is admitted it may migrate SQLite even if readiness later fails.
|
|
406
|
+
# Consume the restored-schema override before that first possible writer.
|
|
407
|
+
release consume-recovery --release "$STATE/releases/current.json" --state-dir "$STATE"
|
|
408
|
+
k scale deployment/colloq-app --replicas=1
|
|
409
|
+
k rollout status deployment/colloq-app --timeout=180s
|
|
410
|
+
wait_for_app
|
|
411
|
+
gpu_preflight
|
|
412
|
+
printf 'Colloq ready on http://127.0.0.1:%s; use cluster.sh smoke to verify two-room execution.\n' "$PORT"
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
case "$CMD" in
|
|
416
|
+
firewall) firewall ;;
|
|
417
|
+
prepare) prepare ;;
|
|
418
|
+
install) prepare; start ;;
|
|
419
|
+
update|rollback)
|
|
420
|
+
[ -f "$STATE/releases/current.json" ] || die 'no installed release; use install'
|
|
421
|
+
# Validate schema compatibility before taking writers down for a backup.
|
|
422
|
+
data="$(data_release)"
|
|
423
|
+
release merge --release "$RELEASE" --previous "$STATE/releases/current.json" --data-release "$data" >/dev/null
|
|
424
|
+
check_operator_env "$RELEASE"
|
|
425
|
+
[ -x "$SCRIPT_DIR/backup.sh" ] || die 'backup.sh is required before an update'
|
|
426
|
+
MODE=consistent RESUME=0 RELEASE="$data" COLLOQ_STATE_DIR="$STATE" "$SCRIPT_DIR/backup.sh"
|
|
427
|
+
prepare; start ;;
|
|
428
|
+
start) start ;;
|
|
429
|
+
stop) need_cluster; stop_writers ;;
|
|
430
|
+
restore-services)
|
|
431
|
+
need_cluster
|
|
432
|
+
STATE="$STATE" python3 - <<'PY'
|
|
433
|
+
import json, os, pathlib
|
|
434
|
+
try:
|
|
435
|
+
marker = json.loads((pathlib.Path(os.environ['STATE']) / '.restore-in-progress').read_text())
|
|
436
|
+
if marker.get('phase') != 'files-restored':
|
|
437
|
+
raise ValueError('invalid phase')
|
|
438
|
+
except (OSError, ValueError, AttributeError):
|
|
439
|
+
raise SystemExit('restore-services requires the verified files-restored recovery phase')
|
|
440
|
+
PY
|
|
441
|
+
writers="$(k get pods -l 'colloq.dev/role in (app,runtime,kernel)' -o name)"
|
|
442
|
+
[ -z "$writers" ] \
|
|
443
|
+
|| die 'restore-services refuses while app, broker or kernel Pods exist'
|
|
444
|
+
writers="$(k get pods -l colloq.kind=room-kernel -o name)"
|
|
445
|
+
[ -z "$writers" ] \
|
|
446
|
+
|| die 'restore-services refuses while room Pods exist'
|
|
447
|
+
k delete services -l app.kubernetes.io/managed-by=colloq-runtime --ignore-not-found --wait=true --timeout=90s
|
|
448
|
+
;;
|
|
449
|
+
status) need_cluster; k get deployments,pods,pvc; curl -fsS --max-time 5 "http://127.0.0.1:$PORT/api/health" ;;
|
|
450
|
+
logs) need_cluster; k logs -f deployment/colloq-app --tail=80 ;;
|
|
451
|
+
gpu-preflight) need_cluster; gpu_preflight ;;
|
|
452
|
+
smoke) need_cluster; k exec deployment/colloq-app -- node /app/dist/runtime-smoke.js ;;
|
|
453
|
+
public-url)
|
|
454
|
+
need_cluster
|
|
455
|
+
changed=0
|
|
456
|
+
PUBLIC_URL="$PUBLIC_URL" EXPECT_URL="$EXPECT_URL" STATE="$STATE" python3 - <<'PY' || changed=$?
|
|
457
|
+
import os, pathlib, urllib.parse
|
|
458
|
+
url = os.environ['PUBLIC_URL']
|
|
459
|
+
parsed = urllib.parse.urlsplit(url)
|
|
460
|
+
if parsed.scheme not in ('http', 'https') or not parsed.netloc or '\n' in url or '\r' in url:
|
|
461
|
+
raise SystemExit('public-url requires an absolute HTTP(S) URL')
|
|
462
|
+
file = pathlib.Path(os.environ['STATE']) / 'config.env'
|
|
463
|
+
lines = file.read_text().splitlines() if file.exists() else []
|
|
464
|
+
current = next((x[11:] for x in reversed(lines) if x.startswith('PUBLIC_URL=')), None)
|
|
465
|
+
if current == url:
|
|
466
|
+
raise SystemExit(3)
|
|
467
|
+
# --if-current: снять адрес может только тот, кто его ставил. scripts/host.sh
|
|
468
|
+
# на выходе возвращает localhost, и без этой сверки его уборка затирала адрес,
|
|
469
|
+
# который за это время поставил кто-то другой (второй туннель, оператор руками),
|
|
470
|
+
# — живая ссылка молча превращалась в localhost. Сверка стоит здесь, под замком
|
|
471
|
+
# состояния, рядом с записью: сверить снаружи и записать потом — это гонка.
|
|
472
|
+
expected = os.environ.get('EXPECT_URL') or ''
|
|
473
|
+
if expected and current != expected:
|
|
474
|
+
raise SystemExit(4)
|
|
475
|
+
temp = file.with_suffix('.tmp')
|
|
476
|
+
temp.write_text('\n'.join([x for x in lines if not x.startswith('PUBLIC_URL=')] + ['PUBLIC_URL=' + url, '']))
|
|
477
|
+
temp.replace(file)
|
|
478
|
+
PY
|
|
479
|
+
[ "$changed" != 3 ] || exit 0
|
|
480
|
+
if [ "$changed" = 4 ]; then
|
|
481
|
+
printf 'cluster: public URL is no longer %s; left unchanged\n' "$EXPECT_URL" >&2
|
|
482
|
+
exit 4
|
|
483
|
+
fi
|
|
484
|
+
[ "$changed" = 0 ] || die 'could not update public URL'
|
|
485
|
+
release config --release "$STATE/releases/current.json" --env-file "$STATE/config.env" | k apply -f - >/dev/null
|
|
486
|
+
if [ "$(k get deployment colloq-app -o jsonpath='{.spec.replicas}')" != 0 ]; then
|
|
487
|
+
k rollout restart deployment/colloq-app
|
|
488
|
+
k rollout status deployment/colloq-app --timeout=180s
|
|
489
|
+
fi
|
|
490
|
+
;;
|
|
491
|
+
*) die "unknown command: $CMD" ;;
|
|
492
|
+
esac
|