178 lines
9.2 KiB
Bash
Executable File
178 lines
9.2 KiB
Bash
Executable File
#!/bin/bash
|
||
# Собирает публичные снапшоты Montana для скачивания.
|
||
# Запускается systemd-таймером каждые 30 минут.
|
||
# Стратегия: освобождаем место от старых архивов ПЕРЕД сборкой,
|
||
# пишем новые напрямую в OUT с суффиксом .new, потом атомарный rename.
|
||
set -euo pipefail
|
||
|
||
REPO_DIR=/var/lib/gitea/data/gitea-repositories/efir369999/montana.git
|
||
OUT=/var/www/montana_archive
|
||
LOCK=/run/montana-archive.lock
|
||
|
||
exec 9>"$LOCK"
|
||
flock -n 9 || { echo "previous build still running, skip"; exit 0; }
|
||
|
||
mkdir -p "$OUT"
|
||
|
||
ts() { date -u +%Y-%m-%dT%H:%M:%SZ; }
|
||
log() { echo "[$(ts)] $*"; }
|
||
|
||
log "=== Сборка архивов Montana ==="
|
||
HEAD_SHA=$(GIT_DIR="$REPO_DIR" git rev-parse HEAD)
|
||
SHORT_SHA="${HEAD_SHA:0:12}"
|
||
log "HEAD = $HEAD_SHA"
|
||
|
||
DISK_FREE_MB=$(df -m "$OUT" | awk 'NR==2{print $4}')
|
||
log "свободно до сборки: ${DISK_FREE_MB}MB"
|
||
|
||
# чистим старые .new если остались с упавшей предыдущей попытки
|
||
rm -f "$OUT"/*.new
|
||
|
||
# Освобождаем место — удаляем прошлые архивы.
|
||
# Окно недоступности: ~3 минуты (пока строятся новые). За это окно
|
||
# редиректы /archive/ будут отдавать 404 — это допустимо.
|
||
rm -f "$OUT/montana-protocol-latest.tar.gz" \
|
||
"$OUT/montana-protocol-latest.zip" \
|
||
"$OUT/site-essentials.tar.gz" \
|
||
"$OUT/site-latest.tar.gz" \
|
||
"$OUT/montana-full.bundle" \
|
||
"$OUT/SHA256SUMS"
|
||
log "очищено: $(df -m "$OUT" | awk 'NR==2{print $4}')MB свободно"
|
||
|
||
export GIT_LFS_SKIP_SMUDGE=1
|
||
|
||
log "→ tar.gz (git archive, без LFS smudge)"
|
||
GIT_DIR="$REPO_DIR" git -c filter.lfs.smudge= -c filter.lfs.required=false \
|
||
archive --format=tar HEAD \
|
||
| gzip -6 > "$OUT/montana-protocol-latest.tar.gz.new"
|
||
|
||
log "→ zip (git archive)"
|
||
GIT_DIR="$REPO_DIR" git -c filter.lfs.smudge= -c filter.lfs.required=false \
|
||
archive --format=zip -6 HEAD \
|
||
> "$OUT/montana-protocol-latest.zip.new"
|
||
|
||
log "→ site-essentials (без бинарных ассетов)"
|
||
tar -C /var/www -czf "$OUT/site-essentials.tar.gz.new" \
|
||
--exclude='*/node_modules' \
|
||
--exclude='*/__pycache__' \
|
||
--exclude='*.pyc' \
|
||
--exclude='*.log' \
|
||
--exclude='*.apk' \
|
||
--exclude='*.apk.bak*' \
|
||
--exclude='*.jpg' \
|
||
--exclude='*.jpeg' \
|
||
--exclude='*.png' \
|
||
--exclude='*.gif' \
|
||
--exclude='*.mp4' \
|
||
--exclude='*.webm' \
|
||
--exclude='*.zip' \
|
||
--exclude='*.tar.gz' \
|
||
--exclude='*/junona-app/dist' \
|
||
--exclude='*/messenger-backend' \
|
||
--exclude='montana_quest/vpn/city' \
|
||
montana_quest montana_hub_landing || echo "tar warn"
|
||
|
||
log "→ sha256 + атомарный rename"
|
||
( cd "$OUT" && \
|
||
sha256sum montana-protocol-latest.tar.gz.new \
|
||
montana-protocol-latest.zip.new \
|
||
site-essentials.tar.gz.new \
|
||
| sed 's/\.new$//' > SHA256SUMS.new && \
|
||
mv -f montana-protocol-latest.tar.gz.new montana-protocol-latest.tar.gz && \
|
||
mv -f montana-protocol-latest.zip.new montana-protocol-latest.zip && \
|
||
mv -f site-essentials.tar.gz.new site-essentials.tar.gz && \
|
||
mv -f SHA256SUMS.new SHA256SUMS )
|
||
|
||
log "→ листинг index.html"
|
||
SIZE_TGZ=$(numfmt --to=iec --suffix=B $(stat -c%s "$OUT/montana-protocol-latest.tar.gz"))
|
||
SIZE_ZIP=$(numfmt --to=iec --suffix=B $(stat -c%s "$OUT/montana-protocol-latest.zip"))
|
||
SIZE_SITE=$(numfmt --to=iec --suffix=B $(stat -c%s "$OUT/site-essentials.tar.gz"))
|
||
BUILT="$(ts)"
|
||
|
||
cat > "$OUT/index.html" <<HTML
|
||
<!doctype html>
|
||
<html lang="ru"><head>
|
||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Архив Монтаны</title>
|
||
<meta name="theme-color" content="#0c0a08">
|
||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||
<style>
|
||
:root{--bg:#0c0a08;--card:#1a1612;--soft:#14110d;--line:#2a2520;--ink:#e8e0d0;--mute:#8a8175;--gold:#ca9335;--gold-soft:#d9a755;--gold-deep:#8e6824}
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{background:var(--bg);color:var(--ink);font-family:Inter,-apple-system,system-ui,sans-serif;line-height:1.55;min-height:100vh}
|
||
.wrap{max-width:760px;margin:0 auto;padding:2.5rem 1.25rem 4rem}
|
||
h1{font-family:ui-serif,Georgia,serif;color:var(--gold);font-size:2rem;font-weight:500;letter-spacing:0.04em;margin-bottom:0.25rem}
|
||
.sub{color:var(--mute);font-size:0.9375rem;margin-bottom:2rem}
|
||
.file{background:var(--card);border:1px solid var(--line);border-radius:0.75rem;padding:1.125rem 1.25rem;margin-bottom:0.75rem;transition:border-color 0.15s}
|
||
.file:hover{border-color:var(--gold-deep)}
|
||
.name{font-family:ui-serif,Georgia,serif;color:var(--gold);font-size:1.0625rem;font-weight:500;margin-bottom:0.25rem}
|
||
.desc{color:var(--mute);font-size:0.875rem;margin-bottom:0.625rem}
|
||
.row{display:flex;flex-wrap:wrap;gap:0.5rem;align-items:center}
|
||
.btn{padding:0.5rem 0.875rem;border-radius:0.5rem;background:var(--gold);color:var(--bg);text-decoration:none;font-size:0.8125rem;font-weight:600;letter-spacing:0.01em}
|
||
.btn:hover{background:var(--gold-soft)}
|
||
.btn.ghost{background:transparent;color:var(--gold);border:1px solid var(--gold-deep)}
|
||
.btn.ghost:hover{background:var(--soft)}
|
||
.size{color:var(--mute);font-size:0.8125rem;font-family:ui-monospace,Menlo,monospace;margin-left:auto}
|
||
.cmd{background:var(--soft);border:1px solid var(--line);border-radius:0.5rem;padding:0.625rem 0.75rem;margin-top:0.625rem;color:var(--gold-soft);font-family:ui-monospace,Menlo,monospace;font-size:0.8125rem;overflow-x:auto;white-space:nowrap}
|
||
footer{margin-top:3rem;padding-top:1.5rem;border-top:1px solid var(--line);color:var(--mute);font-size:0.8125rem;display:flex;flex-wrap:wrap;justify-content:space-between;gap:0.75rem}
|
||
footer a{color:var(--gold-deep);text-decoration:none}
|
||
footer a:hover{color:var(--gold)}
|
||
.home-link{position:fixed;top:0.75rem;left:0.75rem;display:inline-flex;align-items:center;gap:0.5rem;padding:0.5rem 0.875rem;border:1px solid var(--line);border-radius:0.625rem;background:rgba(20,17,13,0.9);backdrop-filter:blur(8px);color:var(--ink);text-decoration:none;font-size:0.9375rem}
|
||
.home-link:hover{border-color:var(--gold-deep)}
|
||
.home-logo{color:var(--gold);font-family:ui-serif,Georgia,serif;font-size:1.25rem;line-height:1}
|
||
</style></head>
|
||
<body>
|
||
<a class="home-link" href="/"><span class="home-logo">Ɉ</span> <span>Монтана</span></a>
|
||
<div class="wrap">
|
||
<h1>Архив Монтаны</h1>
|
||
<div class="sub">Открытое хранилище — для людей, ИИ-агентов, индексаторов. Качайте пакетно. Снимки обновляются каждые 30 минут.<br>HEAD <code style="color:var(--gold-soft)">$SHORT_SHA</code> · сборка $BUILT</div>
|
||
|
||
<div class="file">
|
||
<div class="name">montana-protocol-latest.tar.gz</div>
|
||
<div class="desc">Текущее состояние main-ветки. Без истории. Бинарные LFS-файлы — указатели (для бинарей — git clone).</div>
|
||
<div class="row">
|
||
<a class="btn" href="/archive/montana-protocol-latest.tar.gz">Скачать</a>
|
||
<a class="btn ghost" href="/archive/SHA256SUMS">SHA256</a>
|
||
<span class="size">$SIZE_TGZ</span>
|
||
</div>
|
||
<div class="cmd">curl -L -o montana.tar.gz https://montana.quest/archive/montana-protocol-latest.tar.gz</div>
|
||
</div>
|
||
|
||
<div class="file">
|
||
<div class="name">montana-protocol-latest.zip</div>
|
||
<div class="desc">То же самое, но zip — для Windows и систем без gzip.</div>
|
||
<div class="row">
|
||
<a class="btn" href="/archive/montana-protocol-latest.zip">Скачать</a>
|
||
<span class="size">$SIZE_ZIP</span>
|
||
</div>
|
||
<div class="cmd">curl -L -o montana.zip https://montana.quest/archive/montana-protocol-latest.zip</div>
|
||
</div>
|
||
|
||
<div class="file">
|
||
<div class="name">site-essentials.tar.gz</div>
|
||
<div class="desc">Шаблоны и тексты публичного сайта (html/css/js/json/svg/xml/txt). Без бинарных ассетов — те качаются напрямую с самих страниц.</div>
|
||
<div class="row">
|
||
<a class="btn" href="/archive/site-essentials.tar.gz">Скачать</a>
|
||
<span class="size">$SIZE_SITE</span>
|
||
</div>
|
||
<div class="cmd">curl -L -O https://montana.quest/archive/site-essentials.tar.gz</div>
|
||
</div>
|
||
|
||
<div class="file">
|
||
<div class="name">Полный git с историей</div>
|
||
<div class="desc">Если нужна вся история коммитов и реальные LFS-файлы — клонируйте Хаб напрямую. Анонимный доступ, без логина.</div>
|
||
<div class="cmd">git clone https://hub.montana.quest/efir369999/montana.git</div>
|
||
<div class="cmd">git clone --depth=1 https://hub.montana.quest/efir369999/montana.git # быстро, без истории</div>
|
||
</div>
|
||
|
||
<footer>
|
||
<div>Алик Монтана · Genesis 9.01.2026 · <a href="https://hub.montana.quest/efir369999/montana">источник</a></div>
|
||
<div><a href="/llms.txt">llms.txt</a> · <a href="/robots.txt">robots.txt</a> · <a href="/install.sh">подними свой узел</a></div>
|
||
</footer>
|
||
</div>
|
||
</body></html>
|
||
HTML
|
||
|
||
chmod 644 "$OUT"/*
|
||
log "=== готово, свободно: $(df -m "$OUT" | awk 'NR==2{print $4}')MB ==="
|