{ "cells": [ { "cell_type": "markdown", "id": "750a3ea8", "metadata": {}, "source": [ "### git hook" ] }, { "cell_type": "code", "execution_count": null, "id": "50691f34", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "%%bash\n", "cat > .git/hooks/pre-commit <<'EOF'\n", "#!/usr/bin/env bash\n", "set -euo pipefail\n", "\n", "FILES=$(git diff --cached --name-only -- '*.ipynb' || true)\n", "if [ -z \"$FILES\" ]; then\n", " exit 0\n", "fi\n", "\n", "export FILES\n", "python3 - <<'PY'\n", "import json, subprocess, os\n", "from pathlib import Path\n", "\n", "files = os.environ.get(\"FILES\",\"\").splitlines()\n", "for f in files:\n", " p = Path(f)\n", " nb = json.loads(p.read_text(encoding=\"utf-8\"))\n", " for cell in nb.get(\"cells\", []):\n", " if cell.get(\"cell_type\") == \"code\":\n", " cell[\"outputs\"] = []\n", " cell[\"execution_count\"] = None\n", " p.write_text(json.dumps(nb, ensure_ascii=False, indent=1) + \"\\n\", encoding=\"utf-8\")\n", "\n", "subprocess.run([\"git\",\"add\"] + files, check=True)\n", "PY\n", "EOF\n", "\n", "chmod +x .git/hooks/pre-commit\n" ] }, { "cell_type": "markdown", "id": "2657898e", "metadata": {}, "source": [ "### env setup" ] }, { "cell_type": "code", "execution_count": null, "id": "d0c8af1f", "metadata": {}, "outputs": [], "source": [ "# ignore\n", "import os\n", "\n", "def load_env(file_path):\n", " \"\"\"Загружает переменные окружения из файла .env с поддержкой булевых значений.\"\"\"\n", " with open(file_path) as f:\n", " for line in f:\n", " line = line.strip()\n", " if not line or line.startswith('#'):\n", " continue\n", " \n", " try:\n", " key, value = line.split('=', 1)\n", " key = key.strip()\n", " value = value.strip('\"\\' ')\n", " # Преобразование строковых значений в булевые\n", " if value.lower() in ['true', '1', 'yes']:\n", " value = True\n", " elif value.lower() in ['false', '0', 'no']:\n", " value = False\n", " \n", " # Проверка, что ключ не пустой\n", " if key:\n", " os.environ[key] = str(value) # Храним как строку\n", " except ValueError:\n", " print(f\"Ошибка: неверный формат в строке '{line}'\")\n", "\n", "try:\n", " load_env('env')\n", "except:\n", " load_env('env_example')" ] }, { "cell_type": "markdown", "id": "d0947a0b", "metadata": {}, "source": [ "### setup" ] }, { "cell_type": "code", "execution_count": null, "id": "63d3463e", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!kubectl create secret docker-registry regcred --docker-server=$REGISTRY_HOST --docker-username='user' --docker-password=$REGISTRY_PASS --docker-email=\"$(git config --global --get user.email)\"" ] }, { "cell_type": "code", "execution_count": null, "id": "ff732fa4", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!docker login $REGISTRY_HOST -u 'user' -p $REGISTRY_PASS" ] }, { "cell_type": "markdown", "id": "75172234", "metadata": {}, "source": [ "### pre pull" ] }, { "cell_type": "code", "execution_count": null, "id": "ff55c6cf", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!docker pull $REGISTRY_HOST/python:3.13-alpine" ] }, { "cell_type": "code", "execution_count": null, "id": "34437078", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!docker pull $REGISTRY_HOST/python:3.13-slim" ] }, { "cell_type": "markdown", "id": "74d7ec58", "metadata": {}, "source": [ "### build (ipynb -> py)" ] }, { "cell_type": "code", "execution_count": null, "id": "2809c42c", "metadata": {}, "outputs": [], "source": [ "import json\n", "import os\n", "\n", "IGNORE = ['build.ipynb', 'data.ipynb']\n", "def build():\n", " for _filename in [i for i in os.listdir() if 'ipynb' in i and i not in IGNORE]:\n", " filename_without_extension = os.path.splitext(os.path.basename(_filename))[0]\n", "\n", " with open(f'{filename_without_extension}.ipynb', 'r') as f:\n", " data = f.read()\n", " main = json.loads(data)\n", " app = []\n", " for cell in main['cells']:\n", " if '## Run' in cell['source']:\n", " break\n", " if '# ignore\\n' in cell['source']:\n", " continue\n", " app.append(''.join(cell['source']))\n", "\n", " with open(f'{filename_without_extension}.py', 'w') as f:\n", " f.write('\\n\\n'.join(app))\n", "\n", "build()" ] }, { "cell_type": "markdown", "id": "37eabaef", "metadata": {}, "source": [ "### quick" ] }, { "cell_type": "code", "execution_count": null, "id": "d62a9efa", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!docker build -t $REGISTRY_HOST/avtopost-api:latest ." ] }, { "cell_type": "code", "execution_count": null, "id": "fa6370f6", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!docker image push $REGISTRY_HOST/avtopost-api:latest" ] }, { "cell_type": "code", "execution_count": null, "id": "0309c9b3", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!kubectl rollout restart deployments/kultmost-api -n kultmost" ] }, { "cell_type": "markdown", "id": "b1c2c66c", "metadata": {}, "source": [ "### rollout restart celery" ] }, { "cell_type": "code", "execution_count": null, "id": "8c359276", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!kubectl rollout restart deployments/vk-celery -n kultmost" ] }, { "cell_type": "code", "execution_count": null, "id": "a75776f0", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!kubectl rollout restart deployments/email-celery -n kultmost" ] }, { "cell_type": "markdown", "id": "97d2a6d6", "metadata": {}, "source": [ "### k8s" ] }, { "cell_type": "code", "execution_count": null, "id": "496f4cc7", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!docker build -t $REGISTRY_HOST/ssh-tunnel:2 tunnel/\n", "!docker push $REGISTRY_HOST/ssh-tunnel:2" ] }, { "cell_type": "code", "execution_count": null, "id": "4419ce48", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!kubectl create secret generic ssh-id-rsa --from-file=id_rsa=/home/user/.ssh/id_rsa -n kultmost" ] }, { "cell_type": "code", "execution_count": null, "id": "aa7b001d", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!kubectl apply -n kultmost -f k8s/stunnel.yaml" ] }, { "cell_type": "code", "execution_count": null, "id": "c472ba3a", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!envsubst < k8s/prod.yaml | kubectl apply -n kultmost -f -" ] }, { "cell_type": "code", "execution_count": null, "id": "b8579fa2", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!envsubst < k8s/kultmost.yaml | kubectl apply -n kultmost -f -" ] }, { "cell_type": "code", "execution_count": null, "id": "01c430ae", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!envsubst < k8s/mq.yaml | kubectl apply -n kultmost -f -" ] }, { "cell_type": "code", "execution_count": null, "id": "598e0bc3", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!envsubst < k8s/gitea.yaml | kubectl apply -n gitea -f -" ] }, { "cell_type": "markdown", "id": "35a1cf25", "metadata": {}, "source": [ "### tg yaml" ] }, { "cell_type": "code", "execution_count": null, "id": "d4139bf6", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!echo \"kubectl create secret docker-registry regcred --docker-server=$REGISTRY_HOST --docker-username='user' --docker-password=$REGISTRY_PASS --docker-email=\\\"$(git config --global --get user.email)\\\" && clear && history -c && history -w\"" ] }, { "cell_type": "code", "execution_count": null, "id": "5aed91fa", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "%%bash\n", "set -euo pipefail\n", "EXPANDED=\"$(envsubst < k8s/tg.yaml)\"\n", "echo \"$EXPANDED\"\n", "echo \"kubectl apply -f - <<'EOF'\"\n", "echo \"$(envsubst < k8s/tg.yaml)\"\n", "echo \"EOF\"\n", "echo \"clear\"\n", "echo \"history -c\"\n" ] }, { "cell_type": "code", "execution_count": null, "id": "2a13a8af", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "%%bash\n", "echo \"kubectl apply -f - <<'EOF'\"\n", "envsubst < k8s/tg.yaml\n", "echo\n", "echo \"EOF\"\n", "echo \"clear\"\n", "echo \"history -c\"\n", "echo \"history -w\"\n" ] }, { "cell_type": "markdown", "id": "0a429fca", "metadata": {}, "source": [ "### rsync" ] }, { "cell_type": "code", "execution_count": null, "id": "ff536fb8", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "#!rsync -avz ./static kultmost:/var/www/static/" ] }, { "cell_type": "code", "execution_count": null, "id": "3aba3afb", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!rsync -avh --exclude-from=.rsyncignore . /app/" ] }, { "cell_type": "code", "execution_count": null, "id": "11c03751", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!rsync -avh --exclude-from=.rsyncignore --exclude=.venv/ /app/ kultmost:/app/" ] }, { "cell_type": "markdown", "id": "0474516d", "metadata": {}, "source": [ "### restart fastapi" ] }, { "cell_type": "code", "execution_count": null, "id": "001d41fe", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!ssh kultmost 'systemctl restart fastapi.service'" ] }, { "cell_type": "code", "execution_count": null, "id": "8714f4cc", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!ssh kultmost 'systemctl status fastapi.service'" ] }, { "cell_type": "markdown", "id": "004c5bea", "metadata": {}, "source": [ "### restart celery" ] }, { "cell_type": "code", "execution_count": null, "id": "3cc3fd1c", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!ssh kultmost 'systemctl restart vk-celery.service'" ] }, { "cell_type": "code", "execution_count": null, "id": "3f77084c", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!ssh kultmost 'systemctl status vk-celery.service'" ] }, { "cell_type": "code", "execution_count": null, "id": "68340709", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!ssh kultmost 'systemctl restart email-celery.service'" ] }, { "cell_type": "code", "execution_count": null, "id": "d9285803", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!ssh kultmost 'systemctl status email-celery.service'" ] }, { "cell_type": "markdown", "id": "1a723121", "metadata": {}, "source": [ "### nginx" ] }, { "cell_type": "code", "execution_count": null, "id": "003f0324", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "# !scp kultmost:/etc/nginx/sites-enabled/default nginx_conf " ] }, { "cell_type": "code", "execution_count": null, "id": "dbfffccd", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!scp nginx_conf kultmost:/tmp/default\n", "!ssh kultmost 'sudo mv /tmp/default /etc/nginx/sites-enabled/default'\n", "!ssh kultmost 'nginx -t'" ] }, { "cell_type": "code", "execution_count": null, "id": "f7d27751", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "!ssh kultmost 'nginx -s reload'" ] }, { "cell_type": "markdown", "id": "a26138d2", "metadata": {}, "source": [ "### microk8s" ] }, { "cell_type": "code", "execution_count": null, "id": "e10becc3", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "sudo snap install microk8s --classic\n", "sudo usermod -a -G microk8s $USER\n", "newgrp microk8s\n", "microk8s status" ] }, { "cell_type": "code", "execution_count": null, "id": "b0761e24", "metadata": { "vscode": { "languageId": "shellscript" } }, "outputs": [], "source": [ "curl -LO https://cert-manager.io/public-keys/cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg\n", "helm install cert-manager oci://quay.io/jetstack/charts/cert-manager --version v1.20.2 --namespace cert-manager --create-namespace --verify --keyring ./cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg --set crds.enabled=true\n", "microk8s helm install cert-manager oci://quay.io/jetstack/charts/cert-manager --version v1.20.2 --namespace cert-manager --create-namespace --verify --keyring ./cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg --set crds.enabled=true" ] }, { "cell_type": "markdown", "id": "622f00a3", "metadata": {}, "source": [ "### Figma" ] }, { "cell_type": "code", "execution_count": null, "id": "8fe7cf08", "metadata": { "vscode": { "languageId": "javascript" } }, "outputs": [], "source": [ "m1 = figma.currentPage.children[0]\n", "HTML_TAGS = new Set(['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr']);\n", "\n", "function isHtmlTag(name) {\n", " return typeof name === 'string' && HTML_TAGS.has(name.toLowerCase());\n", "}\n", "\n", "function randomString5() { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let s = ''; for (let i = 0; i < 5; i++) s += chars.charAt(Math.floor(Math.random() * chars.length)); return s; }\n", "\n", "pc = m1.children[0]\n", "tablet = m1.children[1]\n", "mob = m1.children[2]\n", "console.log(pc.name, tablet.name, mob.name)\n", "// helpers (примерные реализации, если у вас другие — замените)\n", "function randomString5() {\n", " return Math.random().toString(36).slice(2, 7);\n", "}\n", "function isHtmlTag(name) {\n", " return typeof name === 'string' && /^[a-zA-Z][\\w-]*$/.test(name);\n", "}\n", "function cssObjectToString(obj) {\n", " return Object.entries(obj || {})\n", " .map(([k, v]) => `${k.replace(/[A-Z]/g, m => '-' + m.toLowerCase())}: ${v};`)\n", " .join(' ');\n", "}\n", "// helper: конвертирует объект CSS в строку\n", "function cssObjectToString(obj = {}) {\n", " return Object.entries(obj)\n", " .map(([k, v]) => `${k}: ${v};`)\n", " .join(' ');\n", "}\n", "\n", "async function element_create(pc_node, tablet_node, mob_node) {\n", " const cssRules = [];\n", "\n", " const randomString5 = () => Math.random().toString(36).substring(2, 5);\n", "\n", " function nodeExists(node, idx) {\n", " return node && Array.isArray(node.children) && node.children[idx];\n", " }\n", "\n", " async function getStyle(node) {\n", " if (!node) return {};\n", " if (typeof node.getCSSAsync === 'function') {\n", " return await node.getCSSAsync();\n", " }\n", " return node.style || {};\n", " }\n", "\n", " function cssObjectToString(cssObj) {\n", " if (!cssObj || typeof cssObj !== 'object') return '';\n", " return Object.entries(cssObj)\n", " .map(([prop, value]) => `${prop}: ${value};`)\n", " .sort()\n", " .join(' ');\n", " }\n", "\n", " function findExistingClass(pcBody, tableBody, mobBody) {\n", " for (const r of cssRules) {\n", " if (r.pcBody === pcBody && \n", " r.tableBody === tableBody && \n", " r.mobBody === mobBody) {\n", " return r.cls;\n", " }\n", " }\n", " return null;\n", " }\n", "\n", " async function build(pcN, tabN, mobN) {\n", " if (!pcN?.name) return document.createElement('div');\n", "\n", " const tagName = pcN.name.replace(/Desktop|desktop/gi, 'section').toLowerCase();\n", " const el = document.createElement(tagName);\n", "\n", " if (pcN.characters != null) {\n", " el.textContent = pcN.characters;\n", " }\n", "\n", " // Рекурсия по детям\n", " if (Array.isArray(pcN.children) && pcN.children.length) {\n", " for (let i = 0; i < pcN.children.length; i++) {\n", " const pcChild = pcN.children[i];\n", " const tabChild = nodeExists(tabN, i) ? tabN.children[i] : pcChild;\n", " const mobChild = nodeExists(mobN, i) ? mobN.children[i] : pcChild;\n", "\n", " if (typeof isHtmlTag === 'function' && isHtmlTag(pcChild.name)) {\n", " const childEl = await build(pcChild, tabChild, mobChild);\n", " el.appendChild(childEl);\n", " }\n", " }\n", " }\n", "\n", " const pcCss = await getStyle(pcN);\n", " const tabCss = await getStyle(tabN);\n", " const mobCss = await getStyle(mobN);\n", "\n", " const pcBody = cssObjectToString(pcCss);\n", " const tableBody = cssObjectToString(tabCss);\n", " const mobBody = cssObjectToString(mobCss);\n", "\n", " let cls = findExistingClass(pcBody, tableBody, mobBody);\n", "\n", " if (!cls) {\n", " cls = 'c_' + randomString5();\n", " const baseCls = 'b_' + randomString5();\n", "\n", " cssRules.push({\n", " cls,\n", " baseCls,\n", " pcBody,\n", " tableBody,\n", " mobBody,\n", " baseStyle: '' // заполним ниже\n", " });\n", " }\n", "\n", " // Добавляем оба класса к элементу\n", " el.classList.add(cls);\n", " // base-класс добавим только если есть общие стили (позже)\n", "\n", " return el;\n", " }\n", "\n", " const rootEl = await build(pc_node, tablet_node, mob_node);\n", "\n", " // === Определяем общие стили и различия ===\n", " const baseBlockParts = [];\n", "\n", " for (const rule of cssRules) {\n", " const { cls, baseCls, pcBody, tableBody, mobBody } = rule;\n", "\n", " let baseStyle = '';\n", "\n", " // Определяем, есть ли общие стили\n", " if (pcBody && pcBody === tableBody && tableBody === mobBody) {\n", " baseStyle = pcBody;\n", " } else if (pcBody && pcBody === tableBody) {\n", " baseStyle = pcBody; // desktop + tablet одинаковые\n", " } else if (pcBody && pcBody === mobBody) {\n", " baseStyle = pcBody;\n", " } else if (tableBody && tableBody === mobBody) {\n", " baseStyle = tableBody;\n", " }\n", "\n", " rule.baseStyle = baseStyle;\n", "\n", " // Добавляем base-класс только если есть общие стили\n", " if (baseStyle) {\n", " baseBlockParts.push(`.${baseCls} { ${baseStyle} }`);\n", " \n", " // Находим все элементы с этим cls и добавляем им baseCls\n", " // (поскольку build уже завершён, делаем это через DOM)\n", " rootEl.querySelectorAll(`.${cls}`).forEach(el => {\n", " el.classList.add(baseCls);\n", " });\n", " }\n", "\n", " // Различия для медиа-запросов (только то, что отличается от base)\n", " rule.mobDiff = (mobBody && mobBody !== baseStyle) ? `.${cls} { ${mobBody} }` : '';\n", " rule.tableDiff = (tableBody && tableBody !== baseStyle) ? `.${cls} { ${tableBody} }` : '';\n", " rule.pcDiff = (pcBody && pcBody !== baseStyle) ? `.${cls} { ${pcBody} }` : '';\n", " }\n", "\n", " const baseBlock = baseBlockParts.join('\\n');\n", " const mobBlock = cssRules.map(r => r.mobDiff).filter(Boolean).join('\\n');\n", " const tableBlock = cssRules.map(r => r.tableDiff).filter(Boolean).join('\\n');\n", " const pcBlock = cssRules.map(r => r.pcDiff).filter(Boolean).join('\\n');\n", "\n", " // === Создаём