# Wishpad: repository and deployment specification
Документ фиксирует решения по лицензии исходного кода, хранению закрытого репозитория и deploy pipeline.
## 1. Статус Документа
Документ является частью технической спецификации Wishpad.
Связанные документы:
- `docs/SPEC.md` - основная продуктово-техническая спецификация;
- `docs/DOCUMENTATION_WORKFLOW.md` - правила ведения документации;
- `docs/CHANGELOG.md` - release notes.
## 2. Лицензия Исходного Кода
Исходный код Wishpad является закрытым.
Для проекта используется модель:
- proprietary software;
- all rights reserved;
- без open-source лицензии для исходного кода приложения.
В репозитории не создаётся `LICENSE` с open-source лицензией.
Для явной фиксации закрытого статуса проекта в корне репозитория используется `LICENSE.md` с proprietary license notice.
В `README.md` нужно добавить короткий license notice:
```md
## License
This project is proprietary software. All rights reserved.
You may not copy, modify, distribute, sublicense, or use this source code without explicit written permission.
```
Лицензии сторонних зависимостей должны проверяться отдельно перед production release.
## 3. Хранилище Кода
Выбранное решение для MVP:
- self-hosted Gitea;
- приватные репозитории;
- репозитории размещаются на инфраструктуре, контролируемой владельцем проекта;
- внешний GitHub/GitLab/Bitbucket не используется как основной источник кода.
Причины выбора Gitea:
- бесплатная и open-source;
- поддерживает приватные репозитории;
- поддерживает pull requests, issues, packages и CI/CD через Gitea Actions;
- легче self-hosted GitLab;
- снижает риск блокировок внешних SaaS-платформ;
- достаточно проста для solo/small-team разработки.
Canonical repositories:
| Repository | Назначение |
| --- | --- |
| `wishpad-docs` | product, UI, API, DB, deployment documentation, OpenAPI contract, brand assets |
| `wishpad-backend` | Laravel API backend, migrations, queue jobs, mail templates, backend/admin integration and backend tests |
| `wishpad-frontend` | Vue 3 + Vite frontend, admin frontend, Capacitor mobile layer, frontend tests and NPM tooling |
Legacy/bootstrap repository:
| Repository | Статус |
| --- | --- |
| `wishpad` | initial bootstrap repository; kept for history until docs/code repositories fully replace it |
Рекомендуемые настройки code repositories:
- репозиторий приватный;
- основная ветка: `main`;
- интеграционная ветка: `dev`;
- прямой push в `main` можно разрешить на раннем solo-этапе;
- при появлении команды включить protected branch и merge через pull request;
- доступ выдавать только нужным пользователям;
- включить 2FA для пользователей Gitea, если доступно в выбранной конфигурации;
- секреты не хранить в git.
### 3.1. Доступ К Репозиторию
Canonical Gitea URL:
```text
https://gitea.wishpad.me/
```
Repository:
```text
wishpad-admin/wishpad-docs
wishpad-admin/wishpad-backend
wishpad-admin/wishpad-frontend
```
HTTPS remote:
```text
https://gitea.wishpad.me/wishpad-admin/wishpad-docs.git
https://gitea.wishpad.me/wishpad-admin/wishpad-backend.git
https://gitea.wishpad.me/wishpad-admin/wishpad-frontend.git
```
SSH remote:
```text
ssh://git@gitea.wishpad.me:2222/wishpad-admin/wishpad-docs.git
ssh://git@gitea.wishpad.me:2222/wishpad-admin/wishpad-backend.git
ssh://git@gitea.wishpad.me:2222/wishpad-admin/wishpad-frontend.git
```
Для локальной разработки предпочтителен SSH remote после добавления личного public key в Gitea account settings.
HTTPS remote допустим для серверных maintenance scripts и первичной настройки, но пароль или token не должны попадать в shell history, git remote URL или logs.
Локальные административные доступы для текущего рабочего места хранятся вне git:
```text
.secrets/deploy.env
.secrets/gitea.env
.secrets/ssh/
```
Server-side секреты хранятся на VPS:
```text
/etc/wishpad/secrets/
```
Эти файлы нельзя копировать в репозиторий, issue, pull request, workflow logs или frontend bundle.
### 3.2. Работа С Ветками
Базовый цикл разработки:
```bash
git clone ssh://git@gitea.wishpad.me:2222/wishpad-admin/wishpad-backend.git
git clone ssh://git@gitea.wishpad.me:2222/wishpad-admin/wishpad-frontend.git
git clone ssh://git@gitea.wishpad.me:2222/wishpad-admin/wishpad-docs.git
cd wishpad-backend
git switch dev
```
Правила веток:
- `dev` - основная ветка разработки и интеграционной проверки на `https://dev.wishpad.me`;
- `main` - production branch для `https://wishpad.me`;
- обычные изменения сначала попадают в `dev`;
- перенос в production делается merge из `dev` в `main`;
- hotfix можно делать от `main`, затем обязательно переносить обратно в `dev`;
- секреты и machine-specific файлы не коммитятся.
Рекомендуемый promote flow:
```bash
git switch dev
git pull
# implement and commit changes
git push origin dev
# после проверки dev окружения:
git switch main
git pull
git merge --no-ff dev
git push origin main
```
На раннем solo-этапе допускается прямой push в `main`, но он сразу запускает production deploy workflow.
Перед production push нужно убедиться, что dev environment уже проверен.
### 3.3. Локальные И Серверные Remotes
В developer checkout можно использовать SSH remote:
```bash
git remote set-url origin ssh://git@gitea.wishpad.me:2222/wishpad-admin/wishpad-backend.git
```
В server-side checkout допускается internal HTTP remote:
```text
http://127.0.0.1:3000/wishpad-admin/wishpad-backend.git
http://127.0.0.1:3000/wishpad-admin/wishpad-frontend.git
http://127.0.0.1:3000/wishpad-admin/wishpad-docs.git
```
Внутри Gitea runner containers используется Docker-network remote:
```text
http://wishpad-gitea:3000/wishpad-admin/wishpad-backend.git
http://wishpad-gitea:3000/wishpad-admin/wishpad-frontend.git
http://wishpad-gitea:3000/wishpad-admin/wishpad-docs.git
```
Deploy scripts должны выбирать правильный remote по контексту выполнения и не сохранять credentials в remote URL.
## 4. CI/CD
Для MVP используется встроенный CI/CD Gitea:
- Gitea Actions;
- Gitea Runner;
- workflow files в репозитории;
- Docker-based job execution там, где это возможно.
Отдельная deploy-платформа для MVP не обязательна.
Coolify и Dokploy остаются future option, если позже потребуется:
- dashboard для deploy;
- удобное управление env-переменными;
- управление несколькими окружениями;
- визуальные логи деплоев;
- rollback из UI;
- более удобное управление несколькими приложениями и базами.
## 5. MVP Deploy Flow
Целевая схема:
```text
Developer
-> git push
-> self-hosted Gitea
-> Gitea Actions workflow
-> Gitea Runner
-> VPS
-> Docker Compose
-> Wishpad production services
```
Ожидаемый production flow:
1. Разработчик делает push в `main`.
2. Gitea Actions запускает workflow.
3. Workflow собирает frontend assets.
4. Workflow проверяет backend/frontend, если соответствующие проверки уже настроены.
5. Workflow доставляет изменения на VPS.
6. На VPS выполняется `docker compose` deploy.
7. Laravel migrations выполняются после доставки новой версии.
8. Queue worker и scheduler перезапускаются или обновляются вместе с Docker services.
9. Nginx отдаёт frontend static assets и проксирует API на Laravel backend.
Точный способ доставки на VPS фиксируется на этапе реализации:
- через SSH-команды из Gitea Actions;
- или через pull на VPS;
- или через сборку и публикацию Docker images в Gitea package/container registry.
Для MVP предпочтителен самый простой вариант:
- Gitea Actions подключается к VPS по SSH;
- на VPS выполняется обновление исходников или образов;
- затем запускаются `docker compose` команды.
## 5.1. Домены Инфраструктуры
DNS zone для `wishpad.me` хранится в `docs/wishpad.me.zone.txt`.
Для MVP используются следующие публичные имена:
| Host | Назначение | Целевой VPS |
| --- | --- | --- |
| `wishpad.me` | production web/API | `168.222.202.29` |
| `www.wishpad.me` | алиас production, редирект на `wishpad.me` | `168.222.202.29` |
| `dev.wishpad.me` | development environment для ветки `dev` | `168.222.202.29` |
| `docs.wishpad.me` | documentation engine / Scalar OpenAPI viewer | `168.222.202.29` |
| `gitea.wishpad.me` | self-hosted Gitea UI и Git HTTP remote | `168.222.202.29` |
Gitea не должен жить в подпути production-домена. После появления `gitea.wishpad.me` canonical Gitea URL:
```text
https://gitea.wishpad.me/
```
Git over SSH для Gitea использует отдельный порт:
```text
ssh://git@gitea.wishpad.me:2222/wishpad-admin/wishpad-backend.git
ssh://git@gitea.wishpad.me:2222/wishpad-admin/wishpad-frontend.git
ssh://git@gitea.wishpad.me:2222/wishpad-admin/wishpad-docs.git
```
Production и dev окружения обслуживаются тем же VPS, но должны быть разделены на уровне:
- Git branch;
- Gitea Actions runner label;
- application checkout directory;
- Docker Compose project name;
- Docker network;
- database volume;
- environment file;
- public domain;
- backup namespace.
## 5.2. Branch-To-Environment Mapping
Для MVP фиксируется два server-side окружения:
| Branch | Environment | Public URL | Deploy runner label | VPS checkout |
| --- | --- | --- | --- | --- |
| `dev` in `wishpad-backend` / `wishpad-frontend` | development | `https://dev.wishpad.me` | `wishpad-dev` | `/var/www/wishpad-dev/backend`, `/var/www/wishpad-dev/frontend` |
| `main` in `wishpad-backend` / `wishpad-frontend` | production | `https://wishpad.me` | `wishpad-production` | `/var/www/wishpad-production/backend`, `/var/www/wishpad-production/frontend` |
| `main` in `wishpad-docs` | documentation | `http://docs.wishpad.me` until TLS propagates, then `https://docs.wishpad.me` | `wishpad-docs` | `/var/www/wishpad-docs` |
Правила:
- push в `dev` запускает deploy только development environment;
- push в `main` запускает deploy только production environment;
- production deploy не должен использовать dev `.env`, dev database volume или dev compose project;
- development deploy не должен использовать production `.env`, production database volume или production compose project;
- ручной deploy допускается только как emergency/maintenance operation и должен повторять те же команды, что workflow;
- `main` считается production branch;
- `dev` считается интеграционной веткой для разработки и проверки изменений до production.
На раннем solo-этапе допустим прямой push в `main` и `dev`.
Когда появится команда или внешний доступ, `main` нужно защитить и переводить изменения из `dev` через pull request.
## 5.3. Gitea Actions Runners
Для окружений используются отдельные Gitea Actions runners или отдельные runner containers с разными labels.
Минимальные runner labels:
```text
wishpad-dev
wishpad-production
wishpad-docs
```
Рекомендуемая структура runner containers на VPS:
```text
/opt/wishpad/gitea-runner-dev/docker-compose.yml
/opt/wishpad/gitea-runner-production/docker-compose.yml
/opt/wishpad/gitea-runner-docs/docker-compose.yml
```
Каждый runner:
- регистрируется в той же Gitea instance;
- использует Docker socket только если workflow реально собирает Docker images или запускает Docker-based jobs;
- имеет отдельный container name;
- имеет отдельный data volume;
- объявляет только свой environment label;
- не должен выполнять jobs другого окружения.
Пример labels:
```text
GITEA_RUNNER_LABELS=wishpad-dev:host
GITEA_RUNNER_LABELS=wishpad-production:host
```
Если workflow использует Docker job images, labels можно расширить, например:
```text
wishpad-dev:host,ubuntu-latest:docker://node:20-bookworm
wishpad-production:host,ubuntu-latest:docker://node:20-bookworm
```
Workflow для production должен явно использовать `runs-on: wishpad-production`.
Workflow для dev должен явно использовать `runs-on: wishpad-dev`.
Workflow для documentation должен явно использовать `runs-on: wishpad-docs`.
Текущие workflow files:
```text
wishpad-backend/.gitea/workflows/deploy-dev.yml
wishpad-backend/.gitea/workflows/deploy-production.yml
wishpad-frontend/.gitea/workflows/deploy-dev.yml
wishpad-frontend/.gitea/workflows/deploy-production.yml
wishpad-docs/.gitea/workflows/deploy-docs.yml
```
Smoke-test workflow runs подтверждают, что runners разделены корректно:
```text
wishpad-backend:dev 305af68 Add environment deploy workflows
wishpad-backend:main 11fa942 Add environment deploy workflows
wishpad-frontend:dev eab6a7b Add environment deploy workflows
wishpad-frontend:main ecf49ae Add environment deploy workflows
wishpad-docs:main 10ac6df Add documentation deploy workflow
```
Первые failed runs после создания workflows допустимы как исторический след настройки:
они падали из-за `127.0.0.1` remote внутри runner container.
Актуальные deploy scripts используют context-aware remote и smoke runs завершаются успешно.
## 5.4. VPS Directory Layout
Базовая структура инфраструктуры на VPS:
```text
/opt/wishpad/
gitea/
gitea-runner-dev/
gitea-runner-production/
gitea-runner-docs/
deploy/
dev/
production/
docs/
/var/www/
wishpad-dev/
backend/
frontend/
wishpad-production/
backend/
frontend/
wishpad-docs/
wishpad-docs-site/
/var/lib/wishpad/
gitea/
gitea-runner-dev/
gitea-runner-production/
gitea-runner-docs/
dev/
production/
/var/backups/wishpad/
gitea/
dev/
production/
/etc/wishpad/
secrets/
gitea.env
gitea-runner-dev.env
gitea-runner-production.env
dev.env
production.env
```
Назначение:
- `/opt/wishpad/*` - infrastructure compose files, deploy scripts and operational config;
- `/var/www/wishpad-dev/backend` - checkout `wishpad-backend` ветки `dev`;
- `/var/www/wishpad-dev/frontend` - checkout `wishpad-frontend` ветки `dev`;
- `/var/www/wishpad-production/backend` - checkout `wishpad-backend` ветки `main`;
- `/var/www/wishpad-production/frontend` - checkout `wishpad-frontend` ветки `main`;
- `/var/www/wishpad-docs` - checkout `wishpad-docs` ветки `main`;
- `/var/www/wishpad-docs-site` - generated static documentation site served by Nginx;
- `/var/lib/wishpad/*` - persistent Docker volumes and runtime state;
- `/var/backups/wishpad/*` - environment-specific backups;
- `/etc/wishpad/secrets/*` - server-side secrets, never committed to git.
## 5.5. Application Docker Compose Environments
Wishpad application deploy uses Docker Compose.
Dev и production должны использовать разные compose project names:
```text
COMPOSE_PROJECT_NAME=wishpad_dev
COMPOSE_PROJECT_NAME=wishpad_production
```
Ожидаемые production services описаны в `docs/SPEC.md`:
- `backend`;
- `postgres`;
- `nginx`;
- `queue`;
- `scheduler`.
`frontend` в production не обязан быть постоянно запущенным, потому что Vue/Vite frontend собирается в static assets.
Для development environment на VPS допускается тот же production-like режим:
- frontend собирается в static assets;
- Nginx отдаёт frontend и проксирует `/api`;
- backend, queue, scheduler и PostgreSQL живут в отдельных containers.
Это не заменяет local development environment.
`dev.wishpad.me` нужен для интеграционной проверки server-side deploy.
Окружения должны иметь отдельные PostgreSQL volumes.
Общий PostgreSQL container для dev и production в MVP не используется, чтобы снизить риск пересечения данных.
## 5.6. Deploy Workflow Responsibilities
Dev workflow:
1. Trigger: push в `dev` in `wishpad-backend` or `wishpad-frontend`.
2. Runner: `wishpad-dev`.
3. Checkout branch: `dev`.
4. Target directory: `/var/www/wishpad-dev`.
5. Env file: `/etc/wishpad/secrets/dev.env`.
6. Compose project: `wishpad_dev`.
7. Public URL: `https://dev.wishpad.me`.
Production workflow:
1. Trigger: push в `main` in `wishpad-backend` or `wishpad-frontend`.
2. Runner: `wishpad-production`.
3. Checkout branch: `main`.
4. Target directory: `/var/www/wishpad-production`.
5. Env file: `/etc/wishpad/secrets/production.env`.
6. Compose project: `wishpad_production`.
7. Public URL: `https://wishpad.me`.
Both workflows:
- fetch or pull both backend and frontend target branches;
- install/build only what is required by the selected deploy strategy;
- run Docker Compose from the target checkout or from `/opt/wishpad/deploy/<env>`;
- run Laravel migrations after services are updated;
- restart queue and scheduler containers;
- fail fast if required env files are missing;
- avoid printing secrets;
- leave enough logs for diagnosis.
Documentation workflow:
1. Trigger: push в `main` in `wishpad-docs`.
2. Runner: `wishpad-docs`.
3. Checkout branch: `main`.
4. Target repository checkout: `/var/www/wishpad-docs`.
5. Generated static site: `/var/www/wishpad-docs-site`.
6. Public URL: `http://docs.wishpad.me` until Let's Encrypt sees DNS propagation, then `https://docs.wishpad.me`.
The documentation site uses Scalar API Reference and serves `docs/OPENAPI.yaml` as `/openapi.yaml`.
## 5.7. Nginx Routing
Nginx on the host terminates TLS and routes public domains.
Required host-level virtual hosts:
| Host | Upstream |
| --- | --- |
| `gitea.wishpad.me` | `127.0.0.1:3000` |
| `dev.wishpad.me` | dev Wishpad Nginx/app upstream |
| `docs.wishpad.me` | static Scalar documentation site in `/var/www/wishpad-docs-site` |
| `wishpad.me` | production Wishpad Nginx/app upstream |
| `www.wishpad.me` | redirect to `https://wishpad.me` |
TLS certificates are managed by Certbot on the VPS.
Gitea uses host-level Nginx and is not part of application deploy.
Wishpad app Nginx can run inside each environment compose project; host Nginx proxies to its exposed localhost port.
Recommended local host ports:
```text
dev app nginx: 127.0.0.1:8081
production app nginx: 127.0.0.1:8080
gitea: 127.0.0.1:3000
```
These ports are implementation details and must not be exposed publicly except through host Nginx.
## 6. Секреты И Доступы
Секреты хранятся вне git.
Нужно предусмотреть:
- production `.env` на VPS;
- SSH key для deploy;
- Gitea Actions secrets для доступа к VPS;
- отдельного deploy-пользователя на VPS;
- ограниченные права deploy-пользователя;
- запрет вывода секретов в CI logs.
Текущая локальная структура секретов:
```text
.secrets/
deploy.env # SSH/VPS connection metadata
gitea.env # local Gitea admin metadata and generated secrets
ssh/ # private/public SSH keys for VPS access
```
Текущая server-side структура секретов:
```text
/etc/wishpad/secrets/
gitea.env
gitea-runner-dev.env
gitea-runner-production.env
dev.env
production.env
```
Назначение:
- `gitea.env` - Gitea database password, internal tokens, admin bootstrap credentials;
- `gitea-runner-*.env` - one-time runner registration token snapshots;
- `dev.env` - application env для `dev.wishpad.me`;
- `production.env` - application env для `wishpad.me`.
Application env files уже созданы как placeholders для будущего Laravel/Vue deploy.
SMTP credentials пока должны быть заполнены вручную перед включением email sending.
Секреты SMTP.bz, PostgreSQL, `APP_KEY`, deploy SSH key и любые production tokens не должны попадать:
- в git;
- в frontend bundle;
- в публичные логи;
- в OpenAPI examples;
- в issue/PR тексты.
При ротации Gitea admin password нужно обновить:
- `.secrets/gitea.env` на рабочей машине;
- `/etc/wishpad/secrets/gitea.env` на VPS;
- deploy scripts или automation, если они читают этот пароль для server-side fetch.
При ротации VPS SSH key нужно обновить:
- public key в `/root/.ssh/authorized_keys` или у будущего deploy user;
- `VPS_SSH_KEY` в `.secrets/deploy.env`;
- локальную памятку в `.secrets/README.md`, если путь ключа изменился.
## 7. Среды
Для MVP обязательны:
- local development;
- production.
Staging не обязателен для MVP, но архитектурно не должен блокироваться.
Future option:
- staging environment на отдельном домене или поддомене;
- отдельная БД staging;
- отдельные env secrets;
- ручной promote flow из staging в production.
## 8. Backup
Backup относится к production-инфраструктуре и описан в `docs/SPEC.md`.
Для Gitea дополнительно нужно предусмотреть backup:
- Git repositories;
- Gitea database;
- Gitea config;
- uploaded attachments/packages, если они будут использоваться.
Gitea backup должен быть отделён от backup PostgreSQL приложения Wishpad.
## 9. Future Options
В будущем можно рассмотреть:
- Coolify как self-hosted deploy dashboard;
- Dokploy как self-hosted deploy dashboard;
- Gitea package/container registry для Docker images;
- отдельный staging server;
- protected branches;
- обязательные pull requests;
- обязательные CI checks перед merge;
- автоматический backup Gitea;
- зеркалирование приватного репозитория на второй self-hosted Git server.