feat(landing): static site + nginx image + Gitea CI (landing + macOS DMG)

- landing/: the spaceshell.ru terminal-dark landing (index.html + screenshots),
  containerized as an nginx:alpine image (Dockerfile + nginx.conf with gzip and
  asset caching, VERSION, .dockerignore).
- .gitea/workflows/build.yaml: adapted from the coddykinder pipeline to this repo.
  Path-gated jobs — `landing` builds & pushes the nginx image to the Gitea
  registry on landing changes; `dmg` builds a universal (Intel + Apple Silicon)
  .dmg via `tauri build` on app/crates changes and uploads it as an artifact;
  Max notification summarizes both. Tags build everything (release).
- DOCS/landing/spaceshell-landing.md: build brief + copy + SEO meta.

Notes: the DMG job needs a self-hosted macOS runner labelled `macos` (Tauri
can't cross-compile macOS from Linux); the DMG is unsigned until Developer ID
secrets are wired. Landing image verified locally (HTTP 200, assets served).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 10:54:11 +07:00
parent a9836f28b7
commit 2f2159a468
9 changed files with 1578 additions and 0 deletions
+166
View File
@@ -0,0 +1,166 @@
name: Build
on:
push:
branches: [main, master]
tags: ["v*"]
paths:
- "landing/**"
- "app/**"
- "crates/**"
- "Cargo.toml"
- "Cargo.lock"
- ".gitea/workflows/build.yaml"
env:
REGISTRY: git.realmanual.ru
IMAGE_PREFIX: ${{ gitea.repository }}
permissions:
contents: read
packages: write
jobs:
# ---------------------------------------------------------------------------
# Decide what changed so we don't rebuild the (slow) DMG on a landing-only edit
# and vice versa. Tags always build everything (release).
# ---------------------------------------------------------------------------
changes:
name: Detect changes
runs-on: ubuntu-22.04
container: catthehacker/ubuntu:act-latest
outputs:
landing: ${{ steps.filter.outputs.landing }}
app: ${{ steps.filter.outputs.app }}
steps:
- uses: actions/checkout@v3
with: { fetch-depth: 2 }
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
landing:
- 'landing/**'
app:
- 'app/**'
- 'crates/**'
- 'Cargo.toml'
- 'Cargo.lock'
# ---------------------------------------------------------------------------
# Landing → static nginx image pushed to the Gitea registry.
# ---------------------------------------------------------------------------
landing:
name: Build & push landing
needs: changes
if: needs.changes.outputs.landing == 'true' || startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-22.04
container: catthehacker/ubuntu:act-latest
outputs:
version: ${{ steps.version.outputs.VERSION }}
status: ${{ steps.build.outcome }}
steps:
- uses: actions/checkout@v3
- name: Read version
id: version
run: echo "VERSION=$(cat ./landing/VERSION)" >> $GITHUB_OUTPUT
- name: Log in to Gitea Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.TOKEN }}
- name: Build and push
id: build
uses: docker/build-push-action@v6
with:
context: ./landing
file: ./landing/Dockerfile
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/spacesh-landing:${{ steps.version.outputs.VERSION }}
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/spacesh-landing:latest
# ---------------------------------------------------------------------------
# macOS app → universal (Intel + Apple Silicon) .dmg.
# REQUIRES a self-hosted macOS runner labelled `macos` — Tauri cannot
# cross-compile a macOS bundle from Linux. The DMG is UNSIGNED (no Developer
# ID secrets configured); Gatekeeper will warn on first open. To sign+notarize
# later, set APPLE_CERTIFICATE / APPLE_SIGNING_IDENTITY / APPLE_ID secrets and
# pass them through to `tauri build`.
# ---------------------------------------------------------------------------
dmg:
name: Build macOS DMG
needs: changes
if: needs.changes.outputs.app == 'true' || startsWith(github.ref, 'refs/tags/')
runs-on: macos
outputs:
version: ${{ steps.version.outputs.VERSION }}
status: ${{ steps.build.outcome }}
steps:
- uses: actions/checkout@v3
- name: Read version
id: version
run: echo "VERSION=$(node -p "require('./app/src-tauri/tauri.conf.json').version")" >> $GITHUB_OUTPUT
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Rust + macOS targets
run: |
if ! command -v rustup >/dev/null; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
export PATH="$HOME/.cargo/bin:$PATH"
fi
rustup target add aarch64-apple-darwin x86_64-apple-darwin
- name: Install frontend deps
working-directory: app
run: npm ci
- name: Build universal DMG
id: build
working-directory: app
run: npm run tauri build -- --target universal-apple-darwin
- name: Collect DMG
run: |
set -euo pipefail
mkdir -p dist
cp app/src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg dist/
ls -lh dist
- name: Upload DMG artifact
uses: actions/upload-artifact@v3
with:
name: spacesh-dmg-${{ steps.version.outputs.VERSION }}
path: dist/*.dmg
# ---------------------------------------------------------------------------
# Summary → Max.
# ---------------------------------------------------------------------------
notify:
name: Notify Max
needs: [landing, dmg]
if: always()
runs-on: ubuntu-22.04
container: catthehacker/ubuntu:act-latest
steps:
- name: Compose & send summary
run: |
line_for() {
local name="$1" result="$2" ver="$3"
case "$result" in
success) echo "✅ $name собран (\`$ver\`)";;
failure) echo "❌ $name — ошибка сборки";;
skipped) echo " $name без изменений";;
*) echo "❔ $name — $result";;
esac
}
summary=""
summary="$summary"$'\n'"$(line_for spacesh-landing '${{ needs.landing.result }}' '${{ needs.landing.outputs.version }}')"
summary="$summary"$'\n'"$(line_for spacesh-dmg '${{ needs.dmg.result }}' '${{ needs.dmg.outputs.version }}')"
url="${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_number }}"
text=$(printf '**Build**%s\n\n[лог](%s)' "$summary" "$url")
curl -s -X POST "https://platform-api.max.ru/messages?chat_id=${{ secrets.MAX_CHAT_ID }}" \
-H "Authorization: ${{ secrets.MAX_BOT_TOKEN }}" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg t "$text" '{text:$t,format:"markdown"}')"
+183
View File
@@ -0,0 +1,183 @@
# spacesh — лендинг (бриф + текст)
Домен: **spaceshell.ru** · Язык: русский · Стиль: terminal-dark · CTA: Скачать для macOS
---
## 1. Промпт для разработки
> Скопировать целиком в AI-билдер (v0 / Lovable / Claude) или дать фронтенд-разработчику.
```
Построй одностраничный лендинг для продукта «spacesh» — терминал-воркспейс
для параллельного запуска AI-агентов на macOS. Домен spaceshell.ru. Язык
интерфейса — русский. Стадия — early access (pre-v1), будь честен в формулировках.
# Стек и поставка
- Astro + Tailwind CSS. Один статический маршрут, деплой на любой статический хост.
- Анимации hero — один React-остров (Astro island) или чистый CSS/Canvas; остальная
страница — zero-JS. Цель Lighthouse: 95+ по всем осям, LCP < 1.5s.
- Семантический HTML, корректная иерархия заголовков, prefers-reduced-motion.
# Позиционирование (одной фразой)
«spacesh держит живые сессии AI-агентов в фоновом демоне — закрой окно, обнови
приложение, словив краш: агенты продолжают работать».
# Визуальный язык — terminal-dark
- Фон: #0A0D12 (база), панели #0E1116 / #11161F, границы #232A33 / #323C49.
- Текст: #E6EDF3 (основной), #8B97A6 (вторичный), #5A6573 (приглушённый).
- Акцент: бирюзовый неон #34D3C2 (primary), синий #4F9CF9 (secondary). Статусы:
work #4C8DFF, wait #F2B84B, done #3FB950, error #F4544E.
- Шрифты: JetBrains Mono (заголовки-акценты, код, лейблы, цифры) + Inter (тело).
- Текстуры: едва заметная сетка/scanlines на фоне hero, мягкое свечение (glow) под
акцентными элементами, скруглённые панели radius 8–14 как в самом приложении.
- Курсор-каретка (мигающий блок ▌) как лейтмотив бренда; иконка приложения —
тёмная плитка с промптом «>_» бирюзой (есть в app/src-tauri/icons/icon.svg).
# Структура секций (сверху вниз)
1. Хедер: лого «spacesh» (mono) + nav (Возможности · Как работает · CLI · GitHub) +
кнопка «Скачать для macOS». Sticky, прозрачный → размытие при скролле.
2. Hero:
- Eyebrow: «Терминал-воркспейс для AI-агентов · macOS».
- H1 (крупно, mono-акцент в части слова): см. текст ниже.
- Подзаголовок, две кнопки: primary «Скачать для macOS», secondary «Как это работает».
- Микро-строка под кнопками: «macOS 13+ · Apple Silicon и Intel · открытый исходник».
- Справа/снизу — АНИМИРОВАННЫЙ макет приложения: сетка из 3–4 терминал-панелей,
в каждой «агент» (Claude Code, Codex, Gemini, shell) со status-кольцом; в одной
идёт печать вывода (typewriter), кольца переключаются work→done. Ключевой момент
анимации: окно «закрывается» (затемняется), а маленький бейдж «daemon · alive»
продолжает гореть, затем окно возвращается и мгновенно перерисовывается из снапшота.
3. Лента агентов: «Работает с: Claude Code · Codex · Gemini · opencode · shell».
4. Проблема → решение (короткий контраст-блок): «GUI падает — агент умирает вместе с
ним» → ответ spacesh.
5. Сетка возможностей (6 карточек, terminal-dark, с mono-заголовками и иконкой):
демон-источник истины, параллельные агенты в гриде, статусы пушем, гибридный
терминал (поиск/снапшоты), CLI, тема/настройки. Тексты — ниже.
6. «Как это работает» — 3 шага с мини-диаграммой (spawn → daemon владеет PTY → reattach
из снапшота). Подпись про один Unix-сокет и length-prefixed JSON.
7. Полоса «В планах» (roadmap, честно): внешние уведомления Telegram + MAX,
diff-просмотр изменений агента, remote через SSH-туннель.
8. Tech-полоса: «Rust · Tauri 2 · tokio · xterm.js · alacritty» + бюджет «< 16 мс на нажатие».
9. Финальный CTA: крупная кнопка «Скачать для macOS» + ссылка «Исходники на GitHub».
Опционально мини-форма email «Сообщить о релизе».
10. Футер: spaceshell.ru, © 2026, ссылки (GitHub, Документация, Лицензия), строка
«Сделано для тех, кто гоняет агентов пачками».
# Интерактив и анимации
- Hero-терминал: печать вывода через requestAnimationFrame, мигающая каретка,
плавная смена status-колец. Уважать prefers-reduced-motion (показывать статичный кадр).
- Карточки возможностей: лёгкий lift + свечение границы на hover.
- Скролл-ревилы (fade/translate, ≤ 300мс), без тяжёлых либ.
# SEO / мета (RU)
- <title>spacesh — терминал-воркспейс для AI-агентов на macOS</title>
- description: «Запускай Claude Code, Codex, Gemini и shell параллельно. Фоновый демон
держит сессии живыми: закрыл окно — агенты работают. Скачать для macOS.»
- canonical https://spaceshell.ru, og:title/description/image (тёмный OG 1200×630 со
скрином сетки панелей и каретки), lang=ru, theme-color #0A0D12, favicon из иконки app.
# Адаптив
- Desktop-first, но полностью отзывчиво. На мобильном hero-сетка сворачивается в
одну панель + краткий список возможностей; кнопка CTA закреплена снизу.
# Не делать
- Никаких стоковых «AI-градиентов», 3D-блобов, фейковых логотипов компаний.
- Не обещать фич из roadmap как готовых — секция «В планах» отдельно.
```
---
## 2. Текст лендинга (готовая копирайт-копия)
### Хедер
- Лого: `spacesh`
- Меню: Возможности · Как работает · CLI · GitHub
- Кнопка: **Скачать для macOS**
### Hero
- Надстрочник: `Терминал-воркспейс для AI-агентов · macOS`
- **H1:** Гоняй десяток AI-агентов параллельно. **Не теряй ни одного.**
- Подзаголовок: spacesh держит живые сессии Claude Code, Codex, Gemini и shell в
фоновом демоне. Закрыл окно, обновил приложение, словил краш — агенты продолжают работать.
- Кнопки: **Скачать для macOS** · Как это работает
- Микро-строка: macOS 13+ · Apple Silicon и Intel · открытый исходник · early access
### Лента агентов
Работает с: **Claude Code · Codex · Gemini · opencode · shell**
### Проблема → решение
**Обычный терминал привязывает агента к окну.** Закрыл вкладку, перезапустил приложение,
упал GUI — длинная сессия агента умирает вместе с ним.
**spacesh разрывает эту связь.** Сессиями владеет фоновый демон, а не окно. Интерфейс —
всего лишь вид поверх него.
### Возможности (6 карточек)
**`daemon` Демон — источник истины**
`spaceshd` владеет живыми PTY-сессиями. GUI и CLI — тонкие клиенты поверх одного
Unix-сокета. Убей интерфейс — агент жив. Открой заново — экран восстановится из
снапшота за доли секунды.
**`grid` Параллельные агенты в одной сетке**
Несколько агентов в раскладке-гриде: сплиты, зум панели, перетаскивание, пресеты
(2×2, 1+2, 2×3…), воркспейсы и избранное. Один клик в GUI и `spacesh focus` из
скрипта — одна и та же команда.
**`status` Статусы без догадок**
`work · wait · done · error · idle` приходят пушем — от хуков агентов, маркеров
OSC 133 и паттернов как запасной вариант. Кольца, бейджи, центр событий и нативные
уведомления macOS.
**`search` Гибридный терминал**
xterm.js рисует, грид alacritty в демоне анализирует. Отсюда — поиск по скроллбэку
(⌘F) с подсветкой, извлечение последней команды и мгновенные снапшоты для reattach.
**`cli` CLI как первый класс**
`spacesh status --json`, `focus`, `new-surface`, `notify` — те же команды, что и в
интерфейсе, плюс shell-completions. Встраивай spacesh в свои пайплайны.
**`theme` Под себя**
Тёмная и светлая темы, акцентные цвета, шрифт и размер терминала, дефолтный shell —
всё хранится демоном в `config.toml` и применяется на лету ко всем окнам.
### Как это работает (3 шага)
1. **Запуск.** Создаёшь воркспейс и панели — демон спавнит PTY-сессии под агентов.
2. **Демон владеет.** Байты летают GUI ↔ демон ↔ PTY по одному сокету. Интерфейс
состояния не хранит — только команды и события.
3. **Reattach.** Закрыл и открыл приложение — демон отдаёт снапшот экрана, окно
перерисовывается мгновенно, дальше идёт живой вывод.
### В планах
Внешние уведомления в Telegram и MAX · diff-просмотр изменений агента · удалённая
работа через SSH-туннель к демону.
### Tech
Rust · Tauri 2 · tokio · xterm.js · alacritty — нативно и быстро. Бюджет отклика:
меньше 16 мс на нажатие клавиши.
### Финальный CTA
**Готов гонять агентов пачками?**
Кнопка: **Скачать для macOS** · Исходники на GitHub
(опц.) Форма: «Оставь email — сообщим о релизе»
### Футер
spaceshell.ru · © 2026 spacesh · GitHub · Документация · Лицензия
«Сделано для тех, кто запускает агентов пачками.»
---
## 3. SEO-мета (готово к вставке)
```html
<html lang="ru">
<title>spacesh — терминал-воркспейс для AI-агентов на macOS</title>
<meta name="description" content="Запускай Claude Code, Codex, Gemini и shell параллельно. Фоновый демон держит сессии живыми: закрыл окно — агенты работают. Скачать для macOS.">
<link rel="canonical" href="https://spaceshell.ru">
<meta property="og:title" content="spacesh — терминал-воркспейс для AI-агентов">
<meta property="og:description" content="Десяток AI-агентов параллельно. Демон держит сессии живыми — закрой окно, агенты работают.">
<meta property="og:url" content="https://spaceshell.ru">
<meta property="og:image" content="https://spaceshell.ru/og.png">
<meta property="og:type" content="website">
<meta name="theme-color" content="#0A0D12">
```
+4
View File
@@ -0,0 +1,4 @@
Dockerfile
.dockerignore
VERSION
*.md
+9
View File
@@ -0,0 +1,9 @@
# Static landing for spaceshell.ru — served by nginx.
# Build context is ./landing (see .gitea/workflows/build.yaml).
FROM nginx:1.27-alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY index.html /usr/share/nginx/html/index.html
COPY pics/ /usr/share/nginx/html/pics/
EXPOSE 80
+1
View File
@@ -0,0 +1 @@
0.1.0
+1188
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
server {
listen 80;
listen [::]:80;
server_name _;
root /usr/share/nginx/html;
index index.html;
gzip on;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
# Long cache for fingerprint-free static assets; HTML stays revalidated.
location ~* \.(?:css|js|png|jpe?g|gif|svg|webp|woff2?)$ {
expires 7d;
add_header Cache-Control "public, immutable";
access_log off;
}
location = /index.html {
add_header Cache-Control "no-cache";
}
location / {
try_files $uri $uri/ /index.html;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 651 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB