Compare commits
5 Commits
6fc20e0dc4
...
e3f1deefe3
| Author | SHA1 | Date | |
|---|---|---|---|
| e3f1deefe3 | |||
| 332c6357e0 | |||
| aeb11b3b6d | |||
| df7de213d2 | |||
| a3a99396ef |
@@ -17,3 +17,4 @@ helm/wrapped/charts/
|
||||
*.tgz
|
||||
.coverage
|
||||
htmlcov/
|
||||
.cursor/
|
||||
|
||||
Vendored
+163
@@ -0,0 +1,163 @@
|
||||
// Wrapped — CI: multi-arch (amd64+arm64) build & push to Harbor + Docker Hub
|
||||
//
|
||||
// Триггер: push в main (webhook Gitea → Jenkins Multibranch / Pipeline from SCM).
|
||||
// После успешного Build & push → trigger job DEPLOY_JOB (Jenkinsfile.deploy).
|
||||
//
|
||||
// Credentials в Jenkins (Manage Credentials → System → Global):
|
||||
// harbor-devops-tools-push-pull-access — Username with password (Harbor devops-tools)
|
||||
// docker-hub — Username with password (Docker Hub)
|
||||
//
|
||||
// Agent: cloud kubernetes, pod template default (labels docker / dind)
|
||||
//
|
||||
// Образы:
|
||||
// hub.antropoff.ru/devops-tools/wrapped:<sha|0.1.0|latest>
|
||||
// inecs/wrapped:<sha|0.1.0|latest>
|
||||
//
|
||||
// Связка Build → Deploy:
|
||||
// Multibranch Deploy job, Script Path = Jenkinsfile.deploy
|
||||
// путь по умолчанию: «Devops Tools/Wrapped Deploy/main» (поменяй DEPLOY_JOB при другом имени)
|
||||
|
||||
pipeline {
|
||||
agent { label 'docker' }
|
||||
|
||||
options {
|
||||
buildDiscarder(logRotator(numToKeepStr: '20'))
|
||||
disableConcurrentBuilds()
|
||||
timeout(time: 60, unit: 'MINUTES')
|
||||
timestamps()
|
||||
}
|
||||
|
||||
environment {
|
||||
HARBOR_REGISTRY = 'hub.antropoff.ru'
|
||||
HARBOR_IMAGE = 'hub.antropoff.ru/devops-tools/wrapped'
|
||||
DOCKERHUB_IMAGE = 'inecs/wrapped'
|
||||
IMAGE_VERSION = '0.1.0'
|
||||
RELEASE_TAG = "${env.GIT_COMMIT?.take(7) ?: 'dev'}"
|
||||
BUILDX_BUILDER = "jenkins-wrapped-${env.BUILD_NUMBER}"
|
||||
// Multibranch: Folder/Job/branch — как в UI «Devops Tools » Wrapped Deploy » main»
|
||||
DEPLOY_JOB = 'Devops Tools/Wrapped Deploy/main'
|
||||
TZ = 'Europe/Moscow'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build & push') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'main'
|
||||
branch 'master'
|
||||
}
|
||||
}
|
||||
steps {
|
||||
withCredentials([
|
||||
usernamePassword(
|
||||
credentialsId: 'harbor-devops-tools-push-pull-access',
|
||||
usernameVariable: 'HARBOR_USER',
|
||||
passwordVariable: 'HARBOR_PASS'
|
||||
),
|
||||
usernamePassword(
|
||||
credentialsId: 'docker-hub',
|
||||
usernameVariable: 'DOCKERHUB_USER',
|
||||
passwordVariable: 'DOCKERHUB_PASS'
|
||||
)
|
||||
]) {
|
||||
container('docker') {
|
||||
sh '''
|
||||
set -eux
|
||||
|
||||
echo "$HARBOR_PASS" | docker login "$HARBOR_REGISTRY" -u "$HARBOR_USER" --password-stdin
|
||||
echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USER" --password-stdin
|
||||
|
||||
docker buildx rm "$BUILDX_BUILDER" 2>/dev/null || true
|
||||
docker buildx create --name "$BUILDX_BUILDER" --driver docker-container --use
|
||||
docker buildx inspect --bootstrap >/dev/null
|
||||
|
||||
# Per-arch images → both registries (Harbor is happier than a single multi-arch --push)
|
||||
docker buildx build \
|
||||
--platform linux/amd64 \
|
||||
--provenance=false --sbom=false --push \
|
||||
-t "${HARBOR_IMAGE}:${RELEASE_TAG}-amd64" \
|
||||
-t "${DOCKERHUB_IMAGE}:${RELEASE_TAG}-amd64" \
|
||||
-f Dockerfile .
|
||||
|
||||
docker buildx build \
|
||||
--platform linux/arm64 \
|
||||
--provenance=false --sbom=false --push \
|
||||
-t "${HARBOR_IMAGE}:${RELEASE_TAG}-arm64" \
|
||||
-t "${DOCKERHUB_IMAGE}:${RELEASE_TAG}-arm64" \
|
||||
-f Dockerfile .
|
||||
|
||||
# Multi-arch manifests: :sha, :0.1.0, :latest on each registry
|
||||
for IMAGE in "$HARBOR_IMAGE" "$DOCKERHUB_IMAGE"; do
|
||||
docker buildx imagetools create \
|
||||
-t "${IMAGE}:${RELEASE_TAG}" \
|
||||
-t "${IMAGE}:${IMAGE_VERSION}" \
|
||||
-t "${IMAGE}:latest" \
|
||||
"${IMAGE}:${RELEASE_TAG}-amd64" \
|
||||
"${IMAGE}:${RELEASE_TAG}-arm64"
|
||||
done
|
||||
|
||||
echo "--- Harbor ---"
|
||||
docker buildx imagetools inspect "${HARBOR_IMAGE}:${RELEASE_TAG}" | sed -n '1,40p'
|
||||
echo "--- Docker Hub ---"
|
||||
docker buildx imagetools inspect "${DOCKERHUB_IMAGE}:${RELEASE_TAG}" | sed -n '1,40p'
|
||||
|
||||
docker buildx rm "$BUILDX_BUILDER" || true
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Trigger deploy') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'main'
|
||||
branch 'master'
|
||||
}
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
echo "Triggering ${DEPLOY_JOB} → ${DOCKERHUB_IMAGE}:${IMAGE_VERSION}"
|
||||
build(
|
||||
job: env.DEPLOY_JOB,
|
||||
wait: true,
|
||||
propagate: true,
|
||||
parameters: [
|
||||
string(name: 'IMAGE_REPOSITORY', value: env.DOCKERHUB_IMAGE),
|
||||
string(name: 'IMAGE_TAG', value: env.IMAGE_VERSION),
|
||||
string(name: 'IMAGE_PULL_POLICY', value: 'Always'),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
success {
|
||||
echo "✓ Harbor: ${HARBOR_IMAGE}:{${RELEASE_TAG},${IMAGE_VERSION},latest}"
|
||||
echo "✓ Docker Hub: ${DOCKERHUB_IMAGE}:{${RELEASE_TAG},${IMAGE_VERSION},latest}"
|
||||
echo "✓ Deploy: ${DEPLOY_JOB} (${DOCKERHUB_IMAGE}:${IMAGE_VERSION})"
|
||||
}
|
||||
failure {
|
||||
echo "✗ Сборка, push или trigger deploy не удались — см. лог стадий"
|
||||
}
|
||||
always {
|
||||
script {
|
||||
try {
|
||||
container('docker') {
|
||||
sh 'docker buildx rm "$BUILDX_BUILDER" 2>/dev/null || true'
|
||||
}
|
||||
} catch (Ignored) {
|
||||
// agent may already be gone
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Wrapped — CD: helm upgrade в k3s (как make addon-wrapped, только смена образа)
|
||||
//
|
||||
// Первый install — только через DevOpsTools/K3S: make addon-wrapped
|
||||
// (секреты, Ingress, Postgres, MinIO). Этот pipeline лишь обновляет image.*
|
||||
// через helm --reuse-values, не затирая values с кластера.
|
||||
//
|
||||
// Job: отдельный Multibranch Pipeline, Script Path = Jenkinsfile.deploy
|
||||
// Имя по умолчанию (см. DEPLOY_JOB в Jenkinsfile): «Devops Tools/Wrapped Deploy»
|
||||
// Триггер: вручную (Build with Parameters) или автоматически из Jenkinsfile после Build & push
|
||||
// Credentials:
|
||||
// k3s-kubeconfig — Secret file (kubeconfig), как у devops.org.ru
|
||||
//
|
||||
// Agent: cloud kubernetes, label docker (pod template с container docker)
|
||||
//
|
||||
// Эквивалент addon:
|
||||
// helm upgrade --install wrapped … -n wrapped --atomic --wait
|
||||
// kubectl -n wrapped rollout status deployment/wrapped
|
||||
|
||||
pipeline {
|
||||
agent { label 'docker' }
|
||||
|
||||
options {
|
||||
buildDiscarder(logRotator(numToKeepStr: '20'))
|
||||
disableConcurrentBuilds()
|
||||
timeout(time: 20, unit: 'MINUTES')
|
||||
timestamps()
|
||||
}
|
||||
|
||||
parameters {
|
||||
string(
|
||||
name: 'IMAGE_REPOSITORY',
|
||||
defaultValue: 'inecs/wrapped',
|
||||
description: 'Как wrapped_image_repository в k3s addon (Hub или Harbor)'
|
||||
)
|
||||
string(
|
||||
name: 'IMAGE_TAG',
|
||||
defaultValue: '0.1.0',
|
||||
description: 'Тег образа (0.1.0 / latest / short-sha после CI build)'
|
||||
)
|
||||
choice(
|
||||
name: 'IMAGE_PULL_POLICY',
|
||||
choices: ['Always', 'IfNotPresent'],
|
||||
description: 'Always — как в addon (mutable tag подтягивается заново)'
|
||||
)
|
||||
}
|
||||
|
||||
environment {
|
||||
HELM_RELEASE = 'wrapped'
|
||||
HELM_NAMESPACE = 'wrapped'
|
||||
HELM_CHART = './helm/wrapped'
|
||||
HELM_TIMEOUT = '600s'
|
||||
DEPLOYMENT = 'wrapped'
|
||||
TZ = 'Europe/Moscow'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
|
||||
stage('Helm upgrade') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'main'
|
||||
branch 'master'
|
||||
}
|
||||
}
|
||||
steps {
|
||||
withCredentials([
|
||||
file(credentialsId: 'k3s-kubeconfig', variable: 'KUBECONFIG')
|
||||
]) {
|
||||
container('docker') {
|
||||
sh '''
|
||||
set -eux
|
||||
|
||||
# helm + kubectl (alpine docker:*-cli)
|
||||
if ! command -v helm >/dev/null 2>&1; then
|
||||
apk add --no-cache curl tar openssl bash >/dev/null
|
||||
ARCH="$(uname -m)"
|
||||
case "$ARCH" in
|
||||
x86_64) ARCH=amd64 ;;
|
||||
aarch64|arm64) ARCH=arm64 ;;
|
||||
*) echo "unsupported arch: $ARCH" >&2; exit 1 ;;
|
||||
esac
|
||||
HELM_VERSION=v3.16.4
|
||||
curl -fsSL "https://get.helm.sh/helm-${HELM_VERSION}-linux-${ARCH}.tar.gz" \
|
||||
| tar -xz -C /tmp
|
||||
install -m 0755 "/tmp/linux-${ARCH}/helm" /usr/local/bin/helm
|
||||
fi
|
||||
if ! command -v kubectl >/dev/null 2>&1; then
|
||||
apk add --no-cache curl >/dev/null
|
||||
ARCH="$(uname -m)"
|
||||
case "$ARCH" in
|
||||
x86_64) ARCH=amd64 ;;
|
||||
aarch64|arm64) ARCH=arm64 ;;
|
||||
esac
|
||||
KVER="$(curl -fsSL https://dl.k8s.io/release/stable.txt)"
|
||||
curl -fsSLo /usr/local/bin/kubectl \
|
||||
"https://dl.k8s.io/release/${KVER}/bin/linux/${ARCH}/kubectl"
|
||||
chmod +x /usr/local/bin/kubectl
|
||||
fi
|
||||
|
||||
helm version --short
|
||||
kubectl version --client --short || kubectl version --client
|
||||
|
||||
# Релиз должен уже существовать (первый install — make addon-wrapped)
|
||||
if ! helm status "$HELM_RELEASE" -n "$HELM_NAMESPACE" >/dev/null 2>&1; then
|
||||
echo "Release ${HELM_RELEASE} not found in ns ${HELM_NAMESPACE}." >&2
|
||||
echo "Сначала: make addon-wrapped в DevOpsTools/K3S." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Upgrading ${HELM_RELEASE}: ${IMAGE_REPOSITORY}:${IMAGE_TAG} (${IMAGE_PULL_POLICY})"
|
||||
|
||||
# Как addon: helm upgrade --install … --atomic --wait
|
||||
# --reuse-values сохраняет Ingress/секреты/DB/S3 с кластера
|
||||
helm upgrade "$HELM_RELEASE" "$HELM_CHART" \
|
||||
--namespace "$HELM_NAMESPACE" \
|
||||
--reuse-values \
|
||||
--set "image.repository=${IMAGE_REPOSITORY}" \
|
||||
--set "image.tag=${IMAGE_TAG}" \
|
||||
--set "image.pullPolicy=${IMAGE_PULL_POLICY}" \
|
||||
--atomic \
|
||||
--wait \
|
||||
--timeout "$HELM_TIMEOUT"
|
||||
|
||||
kubectl -n "$HELM_NAMESPACE" rollout status "deployment/${DEPLOYMENT}" --timeout=300s
|
||||
kubectl -n "$HELM_NAMESPACE" get deploy,po,ing -o wide
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
success {
|
||||
echo "✓ Helm ${HELM_RELEASE}/${HELM_NAMESPACE}: ${params.IMAGE_REPOSITORY}:${params.IMAGE_TAG}"
|
||||
}
|
||||
failure {
|
||||
echo "✗ Helm upgrade не удался — см. лог. Первый install только через make addon-wrapped."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
- RU / EN, светлая и тёмная тема
|
||||
- CAPTCHA: Cloudflare Turnstile и/или hCaptcha
|
||||
- админка: **статистика**, настройки, audit (пагинация + очистка), danger purge
|
||||
- Docker Compose и Helm; multi-arch образ (`amd64`/`arm64`) на Docker Hub: [`inecs/wrapped`](https://hub.docker.com/r/inecs/wrapped)
|
||||
- Docker Compose и Helm; multi-arch образ (`amd64`/`arm64`) на Docker Hub: [`inecs/wrapped`](https://hub.docker.com/r/inecs/wrapped) и Harbor `hub.antropoff.ru/devops-tools/wrapped` (Jenkins CI)
|
||||
|
||||
Ссылка для получателя: `/w/<id>#<key>` или токен `wrapped_v1.<id>.<key>`. Ключ шифрования в URL-фрагменте (`#...`) на сервер не уходит.
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
| **Расшифровка** | Прокрутка превью текста по вертикали и горизонтали; кнопка **Копировать** рядом со скачиванием |
|
||||
| **Пароль** | Argon2-проверка *до* выдачи ciphertext; лимит попыток; пустой пароль попытку не тратит; структурированные ошибки в UI |
|
||||
| **Админка** | Стартовая **Статистика** (MinIO, wraps, audit); очистка audit с подтверждением; пагинация журнала |
|
||||
| **Релиз** | `make release` собирает **multi-arch** манифест `linux/amd64` + `linux/arm64` через Buildx и пушит только `inecs/wrapped:<tag>` |
|
||||
| **Релиз** | `make release` / Jenkins CI+CD: multi-arch → Harbor и Docker Hub; Helm upgrade в k3s (`Jenkinsfile.deploy`, как addon-wrapped) |
|
||||
|
||||
Подробности — в разделах ниже (безопасность, админка, сборка образа).
|
||||
|
||||
@@ -423,6 +423,63 @@ docker buildx build \
|
||||
После успешного push Makefile печатает краткий `imagetools inspect` (платформы в манифесте).
|
||||
Образ при старте: `alembic upgrade head` + uvicorn `:8000`. Healthcheck: `GET /health`.
|
||||
|
||||
### Jenkins CI (Harbor + Docker Hub)
|
||||
|
||||
После push в ветку **`main`** job со Script Path [`Jenkinsfile`](Jenkinsfile) собирает multi-arch образ (`linux/amd64` + `linux/arm64`) и пушит в оба реестра:
|
||||
|
||||
| Реестр | Образ |
|
||||
|--------|--------|
|
||||
| Harbor | `hub.antropoff.ru/devops-tools/wrapped` |
|
||||
| Docker Hub | `inecs/wrapped` |
|
||||
|
||||
Теги на каждый реестр: `:<short-sha>`, `:0.1.0`, `:latest` (плюс служебные `:<sha>-amd64` / `:<sha>-arm64`).
|
||||
|
||||
Credentials (как в актуальном `Jenkinsfile`):
|
||||
|
||||
| ID | Тип | Назначение |
|
||||
|----|-----|------------|
|
||||
| `harbor-devops-tools-push-pull-access` | Username/password | Harbor `devops-tools` |
|
||||
| `docker-hub` | Username/password | Docker Hub |
|
||||
|
||||
Agent: label `docker` (pod template с container `docker` / DinD).
|
||||
|
||||
### Jenkins CD (Helm → k3s)
|
||||
|
||||
Как в аддоне [DevOpsTools/K3S `addons/wrapped`](https://git.antropoff.ru/DevOpsTools/K3S): релиз **`wrapped`**, namespace **`wrapped`**, образ по умолчанию `inecs/wrapped:0.1.0`, `pullPolicy: Always`.
|
||||
|
||||
**Первый install** — только с хоста/CI ansible:
|
||||
|
||||
```bash
|
||||
make addon-wrapped # в репозитории K3S / k3s-ansible
|
||||
```
|
||||
|
||||
Дальше обновление образа — отдельный Jenkins job, Script Path = [`Jenkinsfile.deploy`](Jenkinsfile.deploy):
|
||||
|
||||
```text
|
||||
helm upgrade wrapped ./helm/wrapped \
|
||||
--namespace wrapped \
|
||||
--reuse-values \
|
||||
--set image.repository=… \
|
||||
--set image.tag=… \
|
||||
--set image.pullPolicy=Always \
|
||||
--atomic --wait --timeout 600s
|
||||
kubectl -n wrapped rollout status deployment/wrapped
|
||||
```
|
||||
|
||||
`--reuse-values` сохраняет Ingress, секреты, Postgres/MinIO с кластера (то, что зашил addon).
|
||||
|
||||
| Параметр job | Умолч. | Смысл |
|
||||
|--------------|--------|--------|
|
||||
| `IMAGE_REPOSITORY` | `inecs/wrapped` | как `wrapped_image_repository` |
|
||||
| `IMAGE_TAG` | `0.1.0` | как `wrapped_image_tag` (`latest` / short-sha после build) |
|
||||
| `IMAGE_PULL_POLICY` | `Always` | как в addon |
|
||||
|
||||
Credential: `k3s-kubeconfig` (Secret file) — тот же, что у devops.org.ru.
|
||||
|
||||
Рекомендуемая связка job’ов уже в [`Jenkinsfile`](Jenkinsfile): после успешного **Build & push** стадия **Trigger deploy** вызывает Multibranch job `Devops Tools/Wrapped Deploy/main` с параметрами `inecs/wrapped` / `0.1.0` / `Always` (`wait: true`). Если имя folder/job другое — поменяй `DEPLOY_JOB` в `Jenkinsfile`.
|
||||
|
||||
Ручной запуск Deploy по-прежнему: **Build with Parameters**.
|
||||
|
||||
### Git (`make push`)
|
||||
|
||||
Как в proxmox_api_simulator:
|
||||
|
||||
+30
-2
@@ -205,8 +205,11 @@ html[data-theme="dark"] .icon-btn:hover {
|
||||
|
||||
.shell {
|
||||
max-width: 860px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0.5rem 1.25rem 3rem;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.hero-panel {
|
||||
@@ -217,6 +220,10 @@ html[data-theme="dark"] .icon-btn:hover {
|
||||
box-shadow: var(--shadow);
|
||||
backdrop-filter: blur(16px);
|
||||
animation: rise 0.55s ease both;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
@keyframes rise {
|
||||
@@ -242,7 +249,12 @@ html[data-theme="dark"] .icon-btn:hover {
|
||||
|
||||
.lede { color: var(--muted); margin: 0 0 1.5rem; max-width: 42rem; }
|
||||
|
||||
.composer, .success-panel, .result-panel { display: grid; gap: 0.9rem; }
|
||||
.composer, .success-panel, .result-panel {
|
||||
display: grid;
|
||||
gap: 0.9rem;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
.result-panel > .success-callout {
|
||||
justify-self: stretch;
|
||||
}
|
||||
@@ -548,6 +560,8 @@ body.busy-open { overflow: hidden; }
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
.item-card-head > div {
|
||||
min-width: 0;
|
||||
@@ -963,20 +977,33 @@ html[data-theme="light"] .footer-pillars li {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.items { display: grid; gap: 1rem; }
|
||||
.items {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
.item-card {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
background: var(--input);
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
.item-card pre,
|
||||
.item-card .item-text-pre {
|
||||
display: block;
|
||||
margin: 0.6rem 0 0;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
max-height: min(60vh, 28rem);
|
||||
padding: 0.8rem;
|
||||
border-radius: 8px;
|
||||
background: rgba(0,0,0,0.25);
|
||||
box-sizing: border-box;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
@@ -987,6 +1014,7 @@ html[data-theme="light"] .footer-pillars li {
|
||||
.item-card .item-text-pre code {
|
||||
display: block;
|
||||
width: max-content;
|
||||
max-width: none;
|
||||
min-width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
Reference in New Issue
Block a user