avtopost.kultmost.ru/build.ipynb
2026-08-17 03:33:47 -04:00

1400 lines
36 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": "code",
"execution_count": null,
"id": "80d363f7",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"!pip install -r requirements.txt"
]
},
{
"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[\"display_name\"] = \".venv\"\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": "627c905d",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%%bash\n",
"htpasswd -nbB 'user' $REGISTRY_PASS > /tmp/registry.htpasswd\n",
"kubectl -n container-registry create secret generic registry-basic-auth \\\n",
" --from-file=auth=/tmp/registry.htpasswd \\\n",
" --dry-run=client -o yaml | kubectl apply -f -\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c3339daa",
"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": "63d3463e",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"!kubectl create namespace kultmost\n",
"!kubectl create secret docker-registry regcred -n kultmost --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": 1,
"id": "2809c42c",
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import os\n",
"\n",
"IGNORE = ['build.ipynb', 'data.ipynb', 'migrate.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(f\"# `{filename_without_extension}.py` generate from `{filename_without_extension}.ipynb` by `build.ipynb` \\n\")\n",
" f.write('\\n\\n'.join(app))\n",
"\n",
"build()"
]
},
{
"cell_type": "markdown",
"id": "fef1918f",
"metadata": {},
"source": [
"### tg build"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4489de4b",
"metadata": {},
"outputs": [],
"source": [
"!docker tag mongo:latest registry.spiritofweb.ru/mongo:latest"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "81571f0b",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"!docker build -t $REGISTRY_HOST/api-tg:latest tg/."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3244466f",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"!docker push $REGISTRY_HOST/api-tg:latest"
]
},
{
"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": "92189739",
"metadata": {},
"source": [
"### containers"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f4b322e5",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%%bash\n",
"docker pull gitea/gitea:1.22\n",
"docker tag gitea/gitea:1.22 $REGISTRY_HOST/gitea/gitea:1.22\n",
"docker push $REGISTRY_HOST/gitea/gitea:1.22"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b3da4168",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%%bash\n",
"docker pull busybox:1.36\n",
"docker pull rabbitmq:3.11-management\n",
"docker pull alpine:3.14\n",
"docker pull redis:latest\n",
"docker pull mongo:latest\n",
"docker tag redis:latest $REGISTRY_HOST/redis:latest\n",
"docker tag mongo:latest $REGISTRY_HOST/mongo:latest\n",
"docker tag busybox:1.36 $REGISTRY_HOST/busybox:1.36\n",
"docker tag rabbitmq:3.11-management $REGISTRY_HOST/rabbitmq:3.11-management\n",
"docker tag alpine:3.14 $REGISTRY_HOST/alpine:3.14"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "62728f9d",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%%bash\n",
"docker push $REGISTRY_HOST/redis:latest\n",
"docker push $REGISTRY_HOST/mongo:latest\n",
"docker push $REGISTRY_HOST/busybox:1.36\n",
"docker push $REGISTRY_HOST/rabbitmq:3.11-management\n",
"docker push $REGISTRY_HOST/alpine:3.14"
]
},
{
"cell_type": "markdown",
"id": "6b8282c4",
"metadata": {},
"source": [
"### tunnel"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "496f4cc7",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"!docker build -t $REGISTRY_HOST/ssh-tunnel:2 tunnel/"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cf41b7b4",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"!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": "markdown",
"id": "97d2a6d6",
"metadata": {},
"source": [
"### k8s"
]
},
{
"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": "code",
"execution_count": null,
"id": "f047b0f2",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"!kubectl get pod -n kultmost"
]
},
{
"cell_type": "markdown",
"id": "35a1cf25",
"metadata": {},
"source": [
"### tg yaml"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d4139bf6",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%%bash\n",
"echo \"$INIT_SSH'bash -s' <<'SCRIPT'\"\n",
"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\"\n",
"echo \"SCRIPT\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2a13a8af",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%%bash\n",
"echo \"$INIT_SSH'bash -s' <<'SCRIPT'\"\n",
"echo \"kubectl apply -f - <<'EOF'\"\n",
"envsubst < k8s/tg.yaml\n",
"echo\n",
"echo \"EOF\"\n",
"echo \"SCRIPT\"\n",
"echo \"clear\"\n",
"echo \"history -c\"\n",
"echo \"history -w\""
]
},
{
"cell_type": "markdown",
"id": "0a429fca",
"metadata": {},
"source": [
"### rsync"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ff536fb8",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"!rsync -avz -ogp --chown=www-data:www-data ./static/ kultmost:/var/www/static/"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fc4aa1ca",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"!echo $(git branch --show-current)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "11c03751",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"!rsync -avh --exclude-from=.rsyncignore --exclude=.venv/ . kultmost:/app/$(git branch --show-current)/"
]
},
{
"cell_type": "markdown",
"id": "c49e0757",
"metadata": {},
"source": [
"### fastapi prod service"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b0a663d0",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%%bash\n",
"ssh kultmost 'tee /etc/systemd/system/production.service >/dev/null' <<'EOF'\n",
"[Unit]\n",
"Description=FastAPI prod service\n",
"After=network.target\n",
"\n",
"[Service]\n",
"WorkingDirectory=/app/production\n",
"ExecStart=/app/production/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30081 --workers 8\n",
"Restart=always\n",
"EnvironmentFile=/app/production/env\n",
"\n",
"[Install]\n",
"WantedBy=default.target\n",
"EOF\n",
"\n",
"ssh kultmost 'systemctl daemon-reload && systemctl enable --now production.service && systemctl status --no-pager -l production.service'\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d4552baa",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%%bash\n",
"ssh kultmost 'tee /etc/systemd/system/fastapi.service >/dev/null' <<'EOF'\n",
"[Unit]\n",
"Description=FastAPI prod service\n",
"After=network.target\n",
"\n",
"[Service]\n",
"WorkingDirectory=/app/production\n",
"ExecStart=/app/production/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30080 --workers 8\n",
"Restart=always\n",
"EnvironmentFile=/app/production/env\n",
"\n",
"[Install]\n",
"WantedBy=default.target\n",
"EOF\n",
"\n",
"ssh kultmost 'systemctl daemon-reload && systemctl enable --now fastapi.service && systemctl status --no-pager -l fastapi.service'\n"
]
},
{
"cell_type": "markdown",
"id": "dfd8deed",
"metadata": {},
"source": [
"### fastapi stag service"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3b4f2998",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%%bash\n",
"ssh kultmost 'tee /etc/systemd/system/staging.service >/dev/null' <<'EOF'\n",
"[Unit]\n",
"Description=FastAPI stag service\n",
"After=network.target\n",
"\n",
"[Service]\n",
"WorkingDirectory=/app/staging\n",
"ExecStart=/app/staging/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30082 --reload\n",
"Restart=always\n",
"EnvironmentFile=/app/staging/env\n",
"\n",
"[Install]\n",
"WantedBy=default.target\n",
"EOF\n",
"\n",
"ssh kultmost 'systemctl daemon-reload && systemctl enable --now staging.service && systemctl status --no-pager -l staging.service'\n"
]
},
{
"cell_type": "markdown",
"id": "4c434852",
"metadata": {},
"source": [
"### local service"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "45bddd01",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%%bash\n",
"'tee /etc/systemd/system/staging.service >/dev/null' <<'EOF'\n",
"[Unit]\n",
"Description=FastAPI stag service\n",
"After=network.target\n",
"\n",
"[Service]\n",
"WorkingDirectory=$pwd\n",
"ExecStart=$pwd/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30082 --reload\n",
"Restart=always\n",
"EnvironmentFile=$pwd/env\n",
"\n",
"[Install]\n",
"WantedBy=default.target\n",
"EOF"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "837698d9",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%%bash\n",
"ssh kultmost 'tee /etc/systemd/system/staging.service >/dev/null' <<'EOF'\n",
"[Unit]\n",
"Description=FastAPI stag service\n",
"After=network.target\n",
"\n",
"[Service]\n",
"WorkingDirectory=/app/staging\n",
"ExecStart=/app/staging/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30082 --reload\n",
"Restart=always\n",
"EnvironmentFile=/app/staging/env\n",
"\n",
"[Install]\n",
"WantedBy=default.target\n",
"EOF\n",
"\n",
"ssh kultmost 'systemctl daemon-reload && systemctl enable --now staging.service && systemctl status --no-pager -l staging.service'\n"
]
},
{
"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 production.service'"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "676e82ad",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"!ssh kultmost 'systemctl status production.service'"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6fdc2d37",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"!ssh kultmost 'systemctl restart fastapi.service'"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4633b242",
"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": "88961bb1",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%%bash\n",
"ssh kultmost 'tee /etc/systemd/system/vk-celery.service >/dev/null' <<'EOF'\n",
"[Unit]\n",
"Description=Celery VK\n",
"After=network.target\n",
"\n",
"[Service]\n",
"WorkingDirectory=/app/production\n",
"ExecStart=/app/production/.venv/bin/celery -A vk.celery_app_vk worker --loglevel=info\n",
"Restart=always\n",
"EnvironmentFile=/app/production/env\n",
"\n",
"[Install]\n",
"WantedBy=default.target\n",
"EOF\n",
"\n",
"ssh kultmost 'systemctl daemon-reload && systemctl enable --now vk-celery.service && systemctl status --no-pager -l vk-celery.service'\n"
]
},
{
"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": "21036b74",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%%bash\n",
"ssh kultmost 'tee /etc/systemd/system/email-celery.service >/dev/null' <<'EOF'\n",
"[Unit]\n",
"Description=Celery Emails\n",
"After=network.target\n",
"\n",
"[Service]\n",
"WorkingDirectory=/app/production\n",
"ExecStart=/app/production/.venv/bin/celery -A email_task.celery_app_email worker --loglevel=info\n",
"Restart=always\n",
"EnvironmentFile=/app/production/env\n",
"\n",
"[Install]\n",
"WantedBy=default.target\n",
"EOF\n",
"\n",
"ssh kultmost 'systemctl daemon-reload && systemctl enable --now email-celery.service && systemctl status --no-pager -l email-celery.service'\n"
]
},
{
"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",
" // === Создаём <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.13.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}