avtopost.kultmost.ru/build.ipynb

717 lines
21 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"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": {},
"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": {},
"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": {},
"outputs": [],
"source": [
"!docker pull $REGISTRY_HOST/python:3.13-alpine"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "34437078",
"metadata": {},
"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": {},
"outputs": [],
"source": [
"!docker build -t $REGISTRY_HOST/avtopost-api:latest ."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fa6370f6",
"metadata": {},
"outputs": [],
"source": [
"!docker image push $REGISTRY_HOST/avtopost-api:latest"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0309c9b3",
"metadata": {},
"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": {},
"outputs": [],
"source": [
"!kubectl rollout restart deployments/vk-celery -n kultmost"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a75776f0",
"metadata": {},
"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": {},
"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": {},
"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": {},
"outputs": [],
"source": [
"!kubectl apply -n kultmost -f k8s/stunnel.yaml"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c472ba3a",
"metadata": {},
"outputs": [],
"source": [
"!envsubst < k8s/prod.yaml | kubectl apply -n kultmost -f -"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b8579fa2",
"metadata": {},
"outputs": [],
"source": [
"!envsubst < k8s/kultmost.yaml | kubectl apply -n kultmost -f -"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "01c430ae",
"metadata": {},
"outputs": [],
"source": [
"!envsubst < k8s/mq.yaml | kubectl apply -n kultmost -f -"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "598e0bc3",
"metadata": {},
"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": {},
"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)\\\"\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fa824c3b",
"metadata": {},
"outputs": [],
"source": [
"!echo \"kubectl apply -f - <<'EOF'\"\n",
"!echo \"$(envsubst < k8s/tg.yaml)\"\n",
"!echo \"EOF\"\n",
"!echo \"\"\n"
]
},
{
"cell_type": "markdown",
"id": "0a429fca",
"metadata": {},
"source": [
"### rsync"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ff536fb8",
"metadata": {},
"outputs": [],
"source": [
"!rsync -avz ./static kultmost:/var/www/static/"
]
},
{
"cell_type": "markdown",
"id": "1a723121",
"metadata": {},
"source": [
"### nginx"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "003f0324",
"metadata": {},
"outputs": [],
"source": [
"# !scp kultmost:/etc/nginx/sites-enabled/default nginx_conf "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dbfffccd",
"metadata": {},
"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": {},
"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",
" // === Создаём <style> ===\n",
" const styleEl = document.createElement('style');\n",
" styleEl.type = 'text/css';\n",
" styleEl.textContent = `\n",
"/* Base (common) styles */\n",
"${baseBlock}\n",
"\n",
"/* Mobile */\n",
"@media (max-width: 699px) {\n",
"${mobBlock}\n",
"}\n",
"\n",
"/* Tablet */\n",
"@media (min-width: 700px) and (max-width: 900px) {\n",
"${tableBlock}\n",
"}\n",
"\n",
"/* Desktop */\n",
"@media (min-width: 901px) {\n",
"${pcBlock}\n",
"}\n",
"`.trim();\n",
"\n",
" const ret = document.createElement('div');\n",
" ret.appendChild(rootEl);\n",
" ret.appendChild(styleEl);\n",
"\n",
" return ret;\n",
"}\n",
"\n",
"sss = await element_create(pc, tablet, mob)\n",
"sss.innerHTML"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.14.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}