add SETUP.md agent guide, gitea-runner manifests
This commit is contained in:
551
SETUP.md
Normal file
551
SETUP.md
Normal file
@@ -0,0 +1,551 @@
|
||||
# StackOps Infrastructure Setup Guide
|
||||
|
||||
Полная инструкция для AI-агента по развёртыванию StackOps платформы с нуля на чистом VPS.
|
||||
|
||||
## Требования
|
||||
|
||||
- VPS с Debian 12+/Ubuntu 22+, минимум 4 CPU / 6GB RAM / 50GB disk
|
||||
- Root SSH доступ
|
||||
- Домен, направленный на IP сервера (Cloudflare DNS)
|
||||
- Docker Desktop на локальной машине (для сборки образов)
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
Перед началом определи эти переменные — они используются во всех шагах:
|
||||
|
||||
```bash
|
||||
VPS_IP="82.114.226.118"
|
||||
VPS_USER="root"
|
||||
DOMAIN="nodeup.ru"
|
||||
CF_API_TOKEN="<cloudflare api token с Zone:DNS:Edit>"
|
||||
CF_ZONE_ID="<zone id домена>"
|
||||
GITEA_ADMIN_USER="stackops"
|
||||
GITEA_ADMIN_PASS="stackops-admin-2026"
|
||||
GRAFANA_ADMIN_PASS="stackops-grafana-2026"
|
||||
STACKOPS_API_TOKEN="dev-secret-token"
|
||||
ADMIN_EMAIL="admin@stackops.dev"
|
||||
ADMIN_PASSWORD="admin"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Шаг 1: DNS записи (Cloudflare API)
|
||||
|
||||
Создай A-записи для всех поддоменов:
|
||||
|
||||
```bash
|
||||
for name in "@" "app" "git" "grafana" "prom" "*.app"; do
|
||||
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records" \
|
||||
-H "Authorization: Bearer ${CF_API_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "{\"type\":\"A\",\"name\":\"${name}\",\"content\":\"${VPS_IP}\",\"ttl\":1,\"proxied\":false}"
|
||||
done
|
||||
```
|
||||
|
||||
| Запись | Назначение |
|
||||
|--------|-----------|
|
||||
| `@` (nodeup.ru) | Корневой домен |
|
||||
| `app` | StackOps API + UI |
|
||||
| `git` | Gitea + Container Registry |
|
||||
| `grafana` | Grafana дашборды |
|
||||
| `prom` | Prometheus (резерв) |
|
||||
| `*.app` | Wildcard для пользовательских приложений |
|
||||
|
||||
**Важно:** `proxied: false` — иначе HTTP-01 ACME challenge не пройдёт.
|
||||
|
||||
---
|
||||
|
||||
## Шаг 2: Установка k3s
|
||||
|
||||
```bash
|
||||
ssh ${VPS_USER}@${VPS_IP} 'apt-get update -qq && apt-get install -y -qq curl git'
|
||||
|
||||
ssh ${VPS_USER}@${VPS_IP} 'curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable=traefik" sh -'
|
||||
```
|
||||
|
||||
Traefik отключён — вместо него используется nginx-ingress.
|
||||
|
||||
Скопируй kubeconfig локально:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.kube
|
||||
ssh ${VPS_USER}@${VPS_IP} 'cat /etc/rancher/k3s/k3s.yaml' \
|
||||
| sed "s/127.0.0.1/${VPS_IP}/" > ~/.kube/config-nodeup
|
||||
export KUBECONFIG=~/.kube/config-nodeup
|
||||
```
|
||||
|
||||
Проверь:
|
||||
```bash
|
||||
kubectl get nodes # STATUS: Ready
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Шаг 3: Helm репозитории
|
||||
|
||||
```bash
|
||||
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
|
||||
helm repo add jetstack https://charts.jetstack.io
|
||||
helm repo add kubevela https://kubevela.github.io/charts
|
||||
helm repo add ot-helm https://ot-container-kit.github.io/helm-charts/
|
||||
helm repo add gitea-charts https://dl.gitea.com/charts/
|
||||
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
|
||||
helm repo add grafana https://grafana.github.io/helm-charts
|
||||
helm repo update
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Шаг 4: nginx-ingress
|
||||
|
||||
```bash
|
||||
helm install ingress-nginx ingress-nginx/ingress-nginx \
|
||||
--namespace ingress-nginx --create-namespace \
|
||||
--set controller.service.type=LoadBalancer \
|
||||
--set controller.kind=DaemonSet \
|
||||
--set controller.hostPort.enabled=false \
|
||||
--timeout 10m --wait
|
||||
```
|
||||
|
||||
k3s ServiceLB (Klipper) привязывает порты 80/443 к хосту автоматически.
|
||||
Не используй `hostPort.enabled=true` — конфликтует с Klipper.
|
||||
|
||||
Проверь:
|
||||
```bash
|
||||
kubectl get pods -n ingress-nginx # controller Running
|
||||
curl -s http://${VPS_IP}/ # должен вернуть 404 от nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Шаг 5: cert-manager + ClusterIssuer
|
||||
|
||||
```bash
|
||||
helm install cert-manager jetstack/cert-manager \
|
||||
--namespace cert-manager --create-namespace \
|
||||
--set crds.enabled=true \
|
||||
--timeout 10m --wait
|
||||
```
|
||||
|
||||
Создай ClusterIssuer с HTTP-01 challenge (не DNS-01 — cert-manager из k3s подов может иметь проблемы с доступом к Cloudflare API из-за IPv6 таймаутов):
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<EOF
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-prod
|
||||
spec:
|
||||
acme:
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
email: ${ADMIN_EMAIL}
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-prod-key
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: nginx
|
||||
EOF
|
||||
```
|
||||
|
||||
Проверь:
|
||||
```bash
|
||||
kubectl get clusterissuer # READY: True
|
||||
```
|
||||
|
||||
**Важно:** Для получения сертификатов Ingress должен содержать аннотацию:
|
||||
```yaml
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
```
|
||||
toml-converter добавляет её автоматически при `tls = true` в stackfile.toml.
|
||||
|
||||
---
|
||||
|
||||
## Шаг 6: KubeVela
|
||||
|
||||
```bash
|
||||
helm install kubevela kubevela/vela-core \
|
||||
--namespace vela-system --create-namespace \
|
||||
--timeout 8m --wait
|
||||
```
|
||||
|
||||
Проверь:
|
||||
```bash
|
||||
kubectl get pods -n vela-system # vela-core и cluster-gateway Running
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Шаг 7: Операторы (CloudNativePG + Redis)
|
||||
|
||||
```bash
|
||||
# CloudNativePG (PostgreSQL)
|
||||
kubectl apply --server-side \
|
||||
-f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.23/releases/cnpg-1.23.0.yaml
|
||||
|
||||
# Redis operator
|
||||
helm install redis-operator ot-helm/redis-operator \
|
||||
--namespace redis-operator --create-namespace \
|
||||
--timeout 5m --wait
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Шаг 8: KubeVela ComponentDefinitions
|
||||
|
||||
Зарегистрируй кастомные типы компонентов для postgres и redis:
|
||||
|
||||
```bash
|
||||
kubectl apply -f kubevela/components/
|
||||
```
|
||||
|
||||
Файлы:
|
||||
- `kubevela/components/postgres.yaml` — обёртка над CloudNativePG Cluster CRD
|
||||
- `kubevela/components/redis.yaml` — обёртка над OT Systems Redis CRD
|
||||
|
||||
Проверь:
|
||||
```bash
|
||||
kubectl get componentdefinitions # postgres и redis
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Шаг 9: Gitea (Git + Container Registry)
|
||||
|
||||
```bash
|
||||
helm install gitea gitea-charts/gitea \
|
||||
--namespace gitea --create-namespace \
|
||||
-f infra/04-gitea/values.yaml \
|
||||
--timeout 10m --wait
|
||||
```
|
||||
|
||||
Ключевые настройки в values.yaml:
|
||||
- SQLite (без внешнего PostgreSQL)
|
||||
- `packages.ENABLED: true` — включает Container Registry
|
||||
- `actions.ENABLED: true` — включает Gitea Actions
|
||||
- Ingress с `cert-manager.io/cluster-issuer` и `proxy-body-size: 500m` (для push больших образов)
|
||||
|
||||
Проверь:
|
||||
```bash
|
||||
curl -sk https://git.${DOMAIN}/ # Gitea login page
|
||||
```
|
||||
|
||||
Создай репозитории:
|
||||
```bash
|
||||
for repo in infra stackops test-app; do
|
||||
curl -sk -X POST "https://git.${DOMAIN}/api/v1/user/repos" \
|
||||
-u "${GITEA_ADMIN_USER}:${GITEA_ADMIN_PASS}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"${repo}\",\"private\":false}"
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Шаг 10: Настройка k3s для pull из Gitea Registry
|
||||
|
||||
```bash
|
||||
ssh ${VPS_USER}@${VPS_IP} "mkdir -p /etc/rancher/k3s && cat > /etc/rancher/k3s/registries.yaml <<EOF
|
||||
mirrors:
|
||||
git.${DOMAIN}:
|
||||
endpoint:
|
||||
- \"https://git.${DOMAIN}\"
|
||||
configs:
|
||||
\"git.${DOMAIN}\":
|
||||
auth:
|
||||
username: ${GITEA_ADMIN_USER}
|
||||
password: ${GITEA_ADMIN_PASS}
|
||||
EOF
|
||||
systemctl restart k3s"
|
||||
```
|
||||
|
||||
После рестарта k3s подожди ~10 секунд и проверь:
|
||||
```bash
|
||||
kubectl get nodes # Ready
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Шаг 11: Мониторинг (Prometheus + Grafana + Loki)
|
||||
|
||||
```bash
|
||||
# Prometheus + Grafana
|
||||
helm install monitoring prometheus-community/kube-prometheus-stack \
|
||||
--namespace monitoring --create-namespace \
|
||||
-f infra/05-monitoring/values-prometheus.yaml \
|
||||
--timeout 10m --wait
|
||||
|
||||
# Loki + Promtail
|
||||
helm install loki grafana/loki-stack \
|
||||
--namespace monitoring \
|
||||
-f infra/05-monitoring/values-loki.yaml \
|
||||
--timeout 5m --wait
|
||||
```
|
||||
|
||||
Проверь:
|
||||
```bash
|
||||
curl -sk https://grafana.${DOMAIN}/ # 302 → login
|
||||
# Логин: admin / ${GRAFANA_ADMIN_PASS}
|
||||
```
|
||||
|
||||
После входа в Grafana добавь Loki datasource:
|
||||
- URL: `http://loki:3100`
|
||||
- Тип: Loki
|
||||
|
||||
---
|
||||
|
||||
## Шаг 12: Сборка и деплой StackOps API
|
||||
|
||||
### 12.1 Собрать Docker образ
|
||||
|
||||
```bash
|
||||
cd server/
|
||||
docker build --platform linux/amd64 \
|
||||
-t git.${DOMAIN}/stackops/stackops-api:v1 \
|
||||
-f api/Dockerfile .
|
||||
```
|
||||
|
||||
### 12.2 Запушить в Gitea Registry
|
||||
|
||||
```bash
|
||||
echo "${GITEA_ADMIN_PASS}" | docker login git.${DOMAIN} -u ${GITEA_ADMIN_USER} --password-stdin
|
||||
docker push git.${DOMAIN}/stackops/stackops-api:v1
|
||||
```
|
||||
|
||||
### 12.3 Создать namespace, RBAC, PVC
|
||||
|
||||
```bash
|
||||
kubectl apply -f infra/06-stackops/rbac.yaml
|
||||
```
|
||||
|
||||
Это создаёт:
|
||||
- Namespace `stackops`
|
||||
- ServiceAccount `stackops-api` с ClusterRole `stackops-deployer` (полный доступ — нужен для kubectl apply в произвольные неймспейсы)
|
||||
- PVC `stackops-data` (1Gi, local-path) для SQLite
|
||||
|
||||
### 12.4 Создать секрет
|
||||
|
||||
```bash
|
||||
kubectl create secret generic stackops-secrets \
|
||||
--namespace stackops \
|
||||
--from-literal=api-token=${STACKOPS_API_TOKEN} \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
```
|
||||
|
||||
### 12.5 Задеплоить
|
||||
|
||||
Перед деплоем обнови image в `infra/06-stackops/deployment.yaml`:
|
||||
```yaml
|
||||
image: git.<DOMAIN>/stackops/stackops-api:v1
|
||||
```
|
||||
|
||||
Также добавь env vars для логина:
|
||||
```bash
|
||||
kubectl apply -f infra/06-stackops/deployment.yaml
|
||||
kubectl set env deployment/stackops-api -n stackops \
|
||||
STACKOPS_ADMIN_EMAIL=${ADMIN_EMAIL} \
|
||||
STACKOPS_ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||
```
|
||||
|
||||
Проверь:
|
||||
```bash
|
||||
kubectl rollout status deployment/stackops-api -n stackops
|
||||
curl -sk https://app.${DOMAIN}/ # StackOps UI (login page)
|
||||
curl -sk -X POST https://app.${DOMAIN}/api/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"email\":\"${ADMIN_EMAIL}\",\"password\":\"${ADMIN_PASSWORD}\"}"
|
||||
# → {"token":"..."}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Шаг 13: Gitea Actions Runner (CI/CD)
|
||||
|
||||
### 13.1 Получить registration token
|
||||
|
||||
```bash
|
||||
# Сначала создай API token для Gitea
|
||||
GITEA_API_TOKEN=$(curl -sk -X POST \
|
||||
"https://git.${DOMAIN}/api/v1/users/${GITEA_ADMIN_USER}/tokens" \
|
||||
-u "${GITEA_ADMIN_USER}:${GITEA_ADMIN_PASS}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"ci-token","scopes":["all"]}' | python3 -c "import sys,json; print(json.load(sys.stdin)['sha1'])")
|
||||
|
||||
# Получи registration token
|
||||
RUNNER_TOKEN=$(curl -sk \
|
||||
"https://git.${DOMAIN}/api/v1/admin/runners/registration-token" \
|
||||
-H "Authorization: token ${GITEA_API_TOKEN}" | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
|
||||
```
|
||||
|
||||
### 13.2 Обновить токен в манифесте
|
||||
|
||||
В `infra/08-gitea-runner/runner.yaml` замени токен в Secret:
|
||||
```yaml
|
||||
stringData:
|
||||
token: "${RUNNER_TOKEN}"
|
||||
```
|
||||
|
||||
### 13.3 Задеплоить runner
|
||||
|
||||
```bash
|
||||
kubectl apply -f infra/08-gitea-runner/runner.yaml
|
||||
```
|
||||
|
||||
Runner содержит два контейнера:
|
||||
- `runner` — gitea/act_runner, подключается к Gitea по in-cluster DNS (`gitea-http.gitea.svc.cluster.local:3000`)
|
||||
- `dind` — Docker-in-Docker сайдкар (privileged) для `docker build` / `docker push` внутри CI джобов
|
||||
|
||||
Проверь:
|
||||
```bash
|
||||
kubectl get pods -n gitea-runner # 2/2 Running
|
||||
kubectl logs deployment/gitea-runner -n gitea-runner -c runner --tail=5
|
||||
# → "Runner registered successfully", "Starting runner daemon"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Шаг 14: Деплой приложения через StackOps
|
||||
|
||||
### 14.1 Создать проект
|
||||
|
||||
```bash
|
||||
curl -sk -X POST https://app.${DOMAIN}/stackops.v1.StackOpsService/CreateProject \
|
||||
-H "Authorization: Bearer ${STACKOPS_API_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "my-app",
|
||||
"gitUrl": "https://git.'${DOMAIN}'/stackops/my-app.git",
|
||||
"gitBranch": "main",
|
||||
"namespace": "my-app"
|
||||
}'
|
||||
```
|
||||
|
||||
Сохрани `id` из ответа.
|
||||
|
||||
### 14.2 stackfile.toml в корне репозитория
|
||||
|
||||
```toml
|
||||
[app]
|
||||
name = "my-app"
|
||||
image = "git.<DOMAIN>/stackops/my-app:latest"
|
||||
replicas = 1
|
||||
port = 8080
|
||||
|
||||
[ingress]
|
||||
host = "my-app.app.<DOMAIN>"
|
||||
path = "/"
|
||||
tls = true # ← автоматически добавит cert-manager аннотацию
|
||||
|
||||
[dependencies.postgres]
|
||||
version = "15"
|
||||
storage = "1Gi"
|
||||
|
||||
[dependencies.redis]
|
||||
version = "7"
|
||||
storage = "1Gi"
|
||||
```
|
||||
|
||||
### 14.3 Задеплоить (StackOps клонирует из git)
|
||||
|
||||
```bash
|
||||
curl -sk -X POST https://app.${DOMAIN}/stackops.v1.StackOpsService/DeployProject \
|
||||
-H "Authorization: Bearer ${STACKOPS_API_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"projectId": "<project-id>"}'
|
||||
```
|
||||
|
||||
StackOps сделает:
|
||||
1. `git clone` репозитория
|
||||
2. Прочитает `stackfile.toml`
|
||||
3. Сконвертирует в KubeVela Application YAML
|
||||
4. `kubectl create namespace` + `kubectl apply`
|
||||
5. KubeVela развернёт: PostgreSQL → Redis → App → Ingress + TLS
|
||||
|
||||
### 14.4 CI/CD workflow (`.gitea/workflows/ci.yaml`)
|
||||
|
||||
```yaml
|
||||
name: Build and Deploy
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker:27
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build and push
|
||||
run: |
|
||||
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login git.<DOMAIN> -u stackops --password-stdin
|
||||
docker build --platform linux/amd64 -t git.<DOMAIN>/stackops/my-app:${{ gitea.sha }} .
|
||||
docker tag git.<DOMAIN>/stackops/my-app:${{ gitea.sha }} git.<DOMAIN>/stackops/my-app:latest
|
||||
docker push git.<DOMAIN>/stackops/my-app:${{ gitea.sha }}
|
||||
docker push git.<DOMAIN>/stackops/my-app:latest
|
||||
- name: Deploy
|
||||
run: |
|
||||
apk add --no-cache curl
|
||||
curl -sf -X POST http://stackops-api.stackops.svc.cluster.local:8080/stackops.v1.StackOpsService/DeployProject \
|
||||
-H "Authorization: Bearer ${{ secrets.STACKOPS_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"projectId":"${{ secrets.PROJECT_ID }}"}'
|
||||
```
|
||||
|
||||
Secrets в настройках репозитория (Settings > Actions > Secrets):
|
||||
- `REGISTRY_PASSWORD` — пароль от Gitea
|
||||
- `STACKOPS_TOKEN` — API токен StackOps
|
||||
- `PROJECT_ID` — ID проекта из шага 14.1
|
||||
|
||||
---
|
||||
|
||||
## Результат
|
||||
|
||||
После выполнения всех шагов будет работать:
|
||||
|
||||
| URL | Сервис | Credentials |
|
||||
|-----|--------|-------------|
|
||||
| `https://app.<DOMAIN>` | StackOps API + UI | email/password → token |
|
||||
| `https://git.<DOMAIN>` | Gitea + Registry | GITEA_ADMIN_USER / GITEA_ADMIN_PASS |
|
||||
| `https://grafana.<DOMAIN>` | Grafana | admin / GRAFANA_ADMIN_PASS |
|
||||
| `https://<app>.app.<DOMAIN>` | Пользовательские приложения | — |
|
||||
|
||||
Namespaces в кластере:
|
||||
- `ingress-nginx` — ingress controller
|
||||
- `cert-manager` — автоматические TLS сертификаты
|
||||
- `vela-system` — KubeVela OAM engine
|
||||
- `cnpg-system` — CloudNativePG оператор
|
||||
- `redis-operator` — Redis оператор
|
||||
- `gitea` — Gitea + Valkey
|
||||
- `gitea-runner` — CI/CD runner (DinD)
|
||||
- `monitoring` — Prometheus + Grafana + Loki + Promtail
|
||||
- `stackops` — StackOps API + SQLite
|
||||
|
||||
Flow деплоя:
|
||||
```
|
||||
git push → Gitea Actions → docker build → push registry → StackOps API
|
||||
→ git clone → toml-converter → KubeVela Application → k8s
|
||||
→ CloudNativePG (postgres) + Redis operator + Deployment + Ingress + TLS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Известные нюансы
|
||||
|
||||
1. **Первый деплой на чистом VPS медленный** — k3s тянет все образы с нуля (5-10 мин на helm install). Используй `--timeout 10m` на helm install.
|
||||
|
||||
2. **nginx-ingress + k3s Klipper** — не включай `hostPort.enabled=true`, будет конфликт портов. Klipper ServiceLB сам привяжет 80/443.
|
||||
|
||||
3. **cert-manager DNS-01 vs HTTP-01** — используй HTTP-01. DNS-01 через Cloudflare API может таймаутить из k3s подов (IPv6 → fallback → timeout в Go HTTP client cert-manager'а).
|
||||
|
||||
4. **Gitea Container Registry** — нужна аннотация `nginx.ingress.kubernetes.io/proxy-body-size: 500m` на Gitea ingress, иначе docker push больших образов вернёт 413.
|
||||
|
||||
5. **k3s registry auth** — настраивается через `/etc/rancher/k3s/registries.yaml`, после изменения нужен `systemctl restart k3s`.
|
||||
|
||||
6. **KubeVela gateway trait + cert-manager** — toml-converter автоматически добавляет `cert-manager.io/cluster-issuer: letsencrypt-prod` на ингресс при `tls = true` в stackfile.toml.
|
||||
|
||||
7. **StackOps API нуждается в git** — Docker образ должен содержать `git` в runtime (для `git clone` при деплое из репозитория).
|
||||
|
||||
8. **Gitea Actions runner** — registration token одноразовый. При пересоздании runner нужно получить новый token через API.
|
||||
|
||||
9. **IPv6** — если на VPS нет рабочего IPv6, отключи: `sysctl -w net.ipv6.conf.all.disable_ipv6=1`. Иначе некоторые Go HTTP клиенты могут таймаутить пытаясь подключиться по IPv6.
|
||||
Reference in New Issue
Block a user