commit cd9f0b8e72f5f58ab3836fd14ddf3efe0b1ae719 Author: Илья Братских Date: Thu Jun 25 13:01:28 2026 +0300 first commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..925c2b3 --- /dev/null +++ b/.env.example @@ -0,0 +1,22 @@ +# Скопируйте в .env и измените значения перед развертыванием +# cp .env.example .env + +# Домен или IP сервера (без http/https) +GITEA_DOMAIN=localhost + +# Порт веб-интерфейса Gitea +GITEA_HTTP_PORT=3000 + +# Порт SSH для git clone/push +GITEA_SSH_PORT=2222 + +# Версия образа Gitea +GITEA_VERSION=1.22.6 + +# База данных PostgreSQL +POSTGRES_USER=gitea +POSTGRES_PASSWORD=change_me_strong_password +POSTGRES_DB=gitea + +# Часовой пояс +TZ=Europe/Moscow diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8b7c9ff --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +# Enforce LF line endings +* text=auto eol=lf + +# Shell scripts must be executable with LF +*.sh text eol=lf +*.ps1 text eol=lf + +# Config and docs +*.yml text eol=lf +*.yaml text eol=lf +*.md text eol=lf +*.env* text eol=lf +*.service text eol=lf +.gitignore text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4128e00 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +backups/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..bc3688c --- /dev/null +++ b/README.md @@ -0,0 +1,314 @@ +# Развёртывание Gitea + +Docker Compose-стек для запуска [Gitea](https://about.gitea.com/) с PostgreSQL. Включает скрипты управления для Linux и Windows. + +## Состав + +| Компонент | Образ | Назначение | +|-----------|-------|------------| +| Gitea | `gitea/gitea` | Git-сервер, веб-интерфейс, SSH | +| PostgreSQL | `postgres:16-alpine` | База данных | + +Данные хранятся в Docker-томах `gitea_gitea_data` и `gitea_postgres_data` и сохраняются при перезапуске контейнеров. + +## Требования + +- **Docker** с плагином **Compose v2** (`docker compose`) +- Windows: [Docker Desktop](https://www.docker.com/products/docker-desktop/) +- Linux: Docker Engine + `docker-compose-plugin` +- Свободные порты (по умолчанию `3000` — HTTP, `2222` — SSH) + +## Быстрый старт + +### Linux + +```bash +git clone gitea +cd gitea + +chmod +x deploy.sh +./deploy.sh install +``` + +Скрипт задаст вопросы по настройке (домен, порты, пароль БД и др.). Enter — принять значение по умолчанию в скобках. Для пароля PostgreSQL Enter — автогенерация. + +Без интерактивного режима (CI, автоматизация): + +```bash +./deploy.sh install -y +``` + +### Windows (PowerShell) + +```powershell +cd D:\shared\Проекты\gitea + +.\deploy.ps1 install +``` + +Без интерактивного режима: + +```powershell +.\deploy.ps1 install -NonInteractive +``` + +После запуска откройте в браузере: + +- **Web UI:** `http://localhost:3000` (или ваш `GITEA_DOMAIN:GITEA_HTTP_PORT`) +- **Git SSH:** `ssh://git@localhost:2222` + +## Первоначальная настройка + +При первом входе в веб-интерфейс Gitea откроется мастер установки: + +1. Убедитесь, что тип БД — **PostgreSQL** (уже настроено через переменные окружения). +2. Создайте учётную запись администратора. +3. Задайте название сайта и прочие параметры по необходимости. + +> База данных и подключение к ней уже сконфигурированы в `docker-compose.yml`. На шаге настройки БД можно оставить значения по умолчанию или проверить, что они совпадают с `.env`. + +## Конфигурация + +При `./deploy.sh install` или `.\deploy.ps1 install` создаётся файл `.env` на основе ваших ответов. Можно также создать его вручную из `.env.example`. + +Интерактивно запрашиваются: + +| Переменная | По умолчанию | Описание | +|------------|--------------|----------| +| `GITEA_DOMAIN` | `localhost` | Домен или IP сервера (без `http://`) | +| `GITEA_HTTP_PORT` | `3000` | Порт веб-интерфейса | +| `GITEA_SSH_PORT` | `2222` | Внешний порт SSH для Git | +| `GITEA_VERSION` | `1.22.6` | Версия образа Gitea | +| `POSTGRES_USER` | `gitea` | Пользователь PostgreSQL | +| `POSTGRES_PASSWORD` | автогенерация | Пароль PostgreSQL (скрытый ввод) | +| `POSTGRES_DB` | `gitea` | Имя базы данных | +| `TZ` | `Europe/Moscow` | Часовой пояс | + +Если `.env` уже существует, скрипт спросит, пересоздать ли конфигурацию. + +Пример для продакшена: + +```env +GITEA_DOMAIN=git.example.com +GITEA_HTTP_PORT=3000 +GITEA_SSH_PORT=2222 +POSTGRES_PASSWORD=your_strong_secret_password +TZ=Europe/Moscow +``` + +## Управление + +Помимо универсального `deploy.sh` / `deploy.ps1` доступны отдельные скрипты: + +| Скрипт (Linux) | Скрипт (Windows) | Описание | +|----------------|------------------|----------| +| `start.sh` | `start.ps1` | Запуск контейнеров | +| `stop.sh` | `stop.ps1` | Остановка контейнеров | +| `install-service.sh` | `install-service.ps1` | Автозапуск при загрузке системы | +| `remove-service.sh` | `remove-service.ps1` | Удаление из автозагрузки | +| `uninstall.sh` | `uninstall.ps1` | Удаление контейнеров | +| `reinstall.sh` | `reinstall.ps1` | Переустановка (обновление образов) | + +### Linux + +```bash +chmod +x *.sh + +./start.sh +./stop.sh + +sudo ./install-service.sh # systemd: автозапуск +sudo ./remove-service.sh # удалить из systemd + +./uninstall.sh # удалить контейнеры (данные сохраняются) +./uninstall.sh --purge # удалить контейнеры и данные + +./reinstall.sh # пересоздать контейнеры, данные сохраняются +./reinstall.sh --purge # чистая переустановка +``` + +После `install-service.sh` управление через systemd: + +```bash +sudo systemctl start gitea +sudo systemctl stop gitea +sudo systemctl status gitea +sudo systemctl enable gitea +sudo systemctl disable gitea +``` + +### Windows + +```powershell +.\start.ps1 +.\stop.ps1 + +# Запуск от администратора: +.\install-service.ps1 # задача в планировщике (автозапуск) +.\remove-service.ps1 # удалить задачу + +.\uninstall.ps1 # удалить контейнеры (данные сохраняются) +.\uninstall.ps1 -Purge # удалить контейнеры и данные + +.\reinstall.ps1 # пересоздать контейнеры +.\reinstall.ps1 -Purge # чистая переустановка +``` + +### Универсальный deploy (все команды) + +#### Linux + +```bash +./deploy.sh install # Первичное развёртывание +./deploy.sh start # Запуск +./deploy.sh stop # Остановка +./deploy.sh restart # Перезапуск +./deploy.sh update # Обновление образов +./deploy.sh status # Статус контейнеров +./deploy.sh logs # Логи Gitea (follow) +./deploy.sh backup # Резервная копия +./deploy.sh restore backups/gitea-backup-20260101-120000.tar.gz +./deploy.sh uninstall # Удаление контейнеров (данные сохраняются) +``` + +#### Windows + +```powershell +.\deploy.ps1 install +.\deploy.ps1 start +.\deploy.ps1 stop +.\deploy.ps1 restart +.\deploy.ps1 update +.\deploy.ps1 status +.\deploy.ps1 logs +.\deploy.ps1 backup +.\deploy.ps1 restore -BackupFile backups\gitea-backup-20260101-120000.tar.gz +.\deploy.ps1 uninstall +``` + +## Резервное копирование и восстановление + +### Создание бэкапа + +```bash +./deploy.sh backup +# или +.\deploy.ps1 backup +``` + +Архив сохраняется в каталог `backups/` с именем `gitea-backup-YYYYMMDD-HHMMSS.tar.gz`. В архив входят Docker-тома с данными Gitea и PostgreSQL. + +### Восстановление + +```bash +./deploy.sh restore backups/gitea-backup-20260101-120000.tar.gz +``` + +```powershell +.\deploy.ps1 restore -BackupFile backups\gitea-backup-20260101-120000.tar.gz +``` + +> Восстановление останавливает контейнеры и **перезаписывает** текущие данные. Перед выполнением скрипт запросит подтверждение. + +## Обновление Gitea + +1. При необходимости измените `GITEA_VERSION` в `.env`. +2. Выполните обновление: + +```bash +./deploy.sh update +``` + +Скрипт загрузит новые образы и перезапустит контейнеры. Данные в томах сохраняются. + +## Структура проекта + +``` +. +├── docker-compose.yml # Описание сервисов +├── .env.example # Шаблон переменных окружения +├── deploy.sh / deploy.ps1 # Универсальный скрипт управления +├── start.sh / start.ps1 # Запуск +├── stop.sh / stop.ps1 # Остановка +├── install-service.sh / install-service.ps1 # Автозапуск +├── remove-service.sh / remove-service.ps1 # Удаление автозапуска +├── uninstall.sh / uninstall.ps1 # Удаление +├── reinstall.sh / reinstall.ps1 # Переустановка +├── lib/ # Общие функции +├── systemd/ # Шаблон unit-файла для Linux +├── backups/ # Резервные копии (создаётся автоматически) +└── README.md +``` + +## Продакшен + +### HTTPS + +Для продакшена рекомендуется разместить Gitea за reverse proxy (Nginx, Caddy, Traefik) с TLS-сертификатом. После настройки прокси обновите в `.env`: + +```env +GITEA_DOMAIN=git.example.com +GITEA_HTTP_PORT=3000 +``` + +И задайте корректный `ROOT_URL` — при необходимости добавьте в `docker-compose.yml`: + +```yaml +GITEA__server__ROOT_URL: https://git.example.com/ +``` + +### Firewall + +Откройте только нужные порты: + +- `GITEA_HTTP_PORT` — веб-интерфейс (или только через reverse proxy на 443) +- `GITEA_SSH_PORT` — Git по SSH + +### Полное удаление данных + +Контейнеры удаляются командой `uninstall`, но тома остаются. Для полной очистки: + +```bash +docker compose down +docker volume rm gitea_gitea_data gitea_postgres_data +``` + +## Устранение неполадок + +### Контейнер Gitea не стартует + +```bash +docker compose logs gitea +docker compose logs db +``` + +Убедитесь, что `POSTGRES_PASSWORD` задан в `.env` и PostgreSQL прошёл healthcheck. + +### Порт занят + +Измените `GITEA_HTTP_PORT` или `GITEA_SSH_PORT` в `.env` и перезапустите: + +```bash +./deploy.sh restart +``` + +### SSH clone не работает + +Проверьте, что порт `GITEA_SSH_PORT` проброшен и доступен с клиента: + +```bash +ssh -p 2222 git@your-server +``` + +В Gitea SSH-домен должен совпадать с `GITEA_DOMAIN` из `.env`. + +### Сброс пароля администратора + +```bash +docker compose exec gitea gitea admin user list +docker compose exec gitea gitea admin user change-password --username admin --password newpassword +``` + +## Лицензия + +Gitea распространяется под лицензией MIT. Данный репозиторий содержит только конфигурацию развёртывания. diff --git a/deploy.ps1 b/deploy.ps1 new file mode 100644 index 0000000..f8e01f1 --- /dev/null +++ b/deploy.ps1 @@ -0,0 +1,206 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Скрипт развертывания Gitea через Docker Compose (Windows) + +.EXAMPLE + .\deploy.ps1 install + .\deploy.ps1 install -NonInteractive + .\deploy.ps1 update + .\deploy.ps1 backup +#> + +param( + [Parameter(Position = 0)] + [ValidateSet("install", "start", "stop", "restart", "update", "status", "logs", "backup", "restore", "uninstall", "help")] + [string]$Command = "install", + + [Parameter(Position = 1)] + [string]$BackupFile, + + [switch]$NonInteractive +) + +$ErrorActionPreference = "Stop" +$script:ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $script:ScriptDir +. "$script:ScriptDir\lib\common.ps1" + +function Show-Usage { + @" +Использование: .\deploy.ps1 [команда] [опции] + +Команды: + install — первичное развертывание (по умолчанию) + start — запуск контейнеров + stop — остановка контейнеров + restart — перезапуск + update — обновление образов и перезапуск + status — статус контейнеров + logs — просмотр логов Gitea + backup — резервная копия данных + restore — восстановление из backup + uninstall — остановка и удаление контейнеров (данные сохраняются) + +Опции install: + -NonInteractive Без интерактивных вопросов (значения из .env.example, пароль — автогенерация) + +Примеры: + .\deploy.ps1 install + .\deploy.ps1 install -NonInteractive + .\deploy.ps1 update + .\deploy.ps1 backup + .\deploy.ps1 restore -BackupFile backups\gitea-backup-20260101.tar.gz +"@ +} + +function Invoke-Install { + Test-Dependencies + Invoke-SetupEnvInteractive -NonInteractive:$NonInteractive + + Write-Info "Загрузка образов..." + docker compose pull + + Write-Info "Запуск Gitea..." + docker compose up -d + + Start-Sleep -Seconds 5 + docker compose ps + + $domain = if ($env:GITEA_DOMAIN) { $env:GITEA_DOMAIN } else { "localhost" } + $httpPort = if ($env:GITEA_HTTP_PORT) { $env:GITEA_HTTP_PORT } else { "3000" } + $sshPort = if ($env:GITEA_SSH_PORT) { $env:GITEA_SSH_PORT } else { "2222" } + + Write-Info "Gitea развернута!" + Write-Info " Web: http://${domain}:${httpPort}" + Write-Info " SSH: ssh://git@${domain}:${sshPort}" + Write-Info "При первом входе откройте Web UI и завершите мастер настройки." +} + +function Invoke-Start { + Test-Dependencies + Import-EnvFile + docker compose up -d + docker compose ps +} + +function Invoke-Stop { + Test-Dependencies + docker compose down + Write-Info "Контейнеры остановлены." +} + +function Invoke-Restart { + Test-Dependencies + Import-EnvFile + docker compose restart + docker compose ps +} + +function Invoke-Update { + Test-Dependencies + Import-EnvFile + Write-Info "Обновление образов..." + docker compose pull + docker compose up -d + docker compose ps + Write-Info "Обновление завершено." +} + +function Invoke-Status { + Test-Dependencies + docker compose ps +} + +function Invoke-Logs { + Test-Dependencies + docker compose logs -f --tail=100 gitea +} + +function Invoke-Backup { + Test-Dependencies + Import-EnvFile + + $backupDir = Join-Path $script:ScriptDir "backups" + $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" + $backupFile = Join-Path $backupDir "gitea-backup-$timestamp.tar.gz" + + New-Item -ItemType Directory -Force -Path $backupDir | Out-Null + + Write-Info "Создание резервной копии: $backupFile" + + docker run --rm ` + -v gitea_gitea_data:/gitea_data:ro ` + -v gitea_postgres_data:/postgres_data:ro ` + -v "${backupDir}:/backup" ` + alpine:3.20 ` + sh -c "tar czf /backup/gitea-backup-$timestamp.tar.gz -C / gitea_data postgres_data" + + Write-Info "Резервная копия сохранена: $backupFile" +} + +function Invoke-Restore { + if (-not $BackupFile -or -not (Test-Path $BackupFile)) { + throw "Укажите существующий файл: .\deploy.ps1 restore -BackupFile backups\gitea-backup-....tar.gz" + } + + Test-Dependencies + Import-EnvFile + + Write-Warn "Восстановление перезапишет текущие данные!" + $confirm = Read-Host "Продолжить? [y/N]" + if ($confirm -notmatch '^[Yy]$') { + Write-Info "Отменено." + return + } + + Write-Info "Остановка контейнеров..." + docker compose down + + $backupDir = (Resolve-Path (Split-Path $BackupFile -Parent)).Path + $backupName = Split-Path $BackupFile -Leaf + + Write-Info "Восстановление из $BackupFile..." + docker run --rm ` + -v gitea_gitea_data:/gitea_data ` + -v gitea_postgres_data:/postgres_data ` + -v "${backupDir}:/backup:ro" ` + alpine:3.20 ` + sh -c "cd / && tar xzf /backup/$backupName" + + Write-Info "Запуск контейнеров..." + docker compose up -d + Write-Info "Восстановление завершено." +} + +function Invoke-Uninstall { + Test-Dependencies + Write-Warn "Контейнеры будут удалены. Тома с данными сохранятся." + $confirm = Read-Host "Продолжить? [y/N]" + if ($confirm -notmatch '^[Yy]$') { + Write-Info "Отменено." + return + } + docker compose down + Write-Info "Контейнеры удалены. Для полного удаления данных: docker volume rm gitea_gitea_data gitea_postgres_data" +} + +try { + switch ($Command) { + "install" { Invoke-Install } + "start" { Invoke-Start } + "stop" { Invoke-Stop } + "restart" { Invoke-Restart } + "update" { Invoke-Update } + "status" { Invoke-Status } + "logs" { Invoke-Logs } + "backup" { Invoke-Backup } + "restore" { Invoke-Restore } + "uninstall" { Invoke-Uninstall } + "help" { Show-Usage } + } +} +catch { + Write-Err $_.Exception.Message + exit 1 +} diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..025c850 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,202 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" +# shellcheck disable=SC1091 +source "${SCRIPT_DIR}/lib/common.sh" + +NON_INTERACTIVE=false + +usage() { + cat <<'EOF' +Использование: ./deploy.sh [команда] [опции] + +Команды: + install — первичное развертывание (по умолчанию) + start — запуск контейнеров + stop — остановка контейнеров + restart — перезапуск + update — обновление образов и перезапуск + status — статус контейнеров + logs — просмотр логов Gitea + backup — резервная копия данных + restore — восстановление из backup (./deploy.sh restore <файл.tar.gz>) + uninstall — остановка и удаление контейнеров (данные сохраняются) + +Опции install: + -y, --yes Без интерактивных вопросов (значения из .env.example, пароль — автогенерация) + +Примеры: + ./deploy.sh install + ./deploy.sh install -y + ./deploy.sh update + ./deploy.sh backup + ./deploy.sh restore backups/gitea-backup-20260101.tar.gz +EOF +} + +cmd_install() { + log "Проверка зависимостей..." + check_dependencies + setup_env_interactive "$NON_INTERACTIVE" + + log "Загрузка образов..." + docker compose pull + + log "Запуск Gitea..." + docker compose up -d + + log "Ожидание готовности сервисов..." + sleep 5 + docker compose ps + + log "Gitea развернута!" + log " Web: http://${GITEA_DOMAIN:-localhost}:${GITEA_HTTP_PORT:-3000}" + log " SSH: ssh://git@${GITEA_DOMAIN:-localhost}:${GITEA_SSH_PORT:-2222}" + log "При первом входе откройте Web UI и завершите мастер настройки." +} + +cmd_start() { + check_dependencies + ensure_env + docker compose up -d + docker compose ps +} + +cmd_stop() { + check_dependencies + docker compose down + log "Контейнеры остановлены." +} + +cmd_restart() { + check_dependencies + ensure_env + docker compose restart + docker compose ps +} + +cmd_update() { + check_dependencies + ensure_env + log "Обновление образов..." + docker compose pull + docker compose up -d + docker compose ps + log "Обновление завершено." +} + +cmd_status() { + check_dependencies + docker compose ps +} + +cmd_logs() { + check_dependencies + docker compose logs -f --tail=100 gitea +} + +cmd_backup() { + check_dependencies + ensure_env + + BACKUP_DIR="${SCRIPT_DIR}/backups" + TIMESTAMP="$(date +%Y%m%d-%H%M%S)" + BACKUP_FILE="${BACKUP_DIR}/gitea-backup-${TIMESTAMP}.tar.gz" + + mkdir -p "$BACKUP_DIR" + + log "Создание резервной копии: $BACKUP_FILE" + + docker compose exec -T db pg_dump -U "${POSTGRES_USER:-gitea}" "${POSTGRES_DB:-gitea}" \ + > "${BACKUP_DIR}/gitea-db-${TIMESTAMP}.sql" + + docker run --rm \ + -v gitea_gitea_data:/gitea_data:ro \ + -v gitea_postgres_data:/postgres_data:ro \ + -v "${BACKUP_DIR}:/backup" \ + alpine:3.20 \ + sh -c "tar czf /backup/gitea-backup-${TIMESTAMP}.tar.gz -C / gitea_data postgres_data && rm -f /backup/gitea-db-${TIMESTAMP}.sql" + + log "Резервная копия сохранена: $BACKUP_FILE" +} + +cmd_restore() { + local backup_file="${1:-}" + + if [[ -z "$backup_file" || ! -f "$backup_file" ]]; then + err "Укажите существующий файл резервной копии." + err "Пример: ./deploy.sh restore backups/gitea-backup-20260101.tar.gz" + exit 1 + fi + + check_dependencies + ensure_env + + warn "Восстановление перезапишет текущие данные!" + read -r -p "Продолжить? [y/N] " confirm + if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + log "Отменено." + exit 0 + fi + + log "Остановка контейнеров..." + docker compose down + + log "Восстановление из $backup_file..." + docker run --rm \ + -v gitea_gitea_data:/gitea_data \ + -v gitea_postgres_data:/postgres_data \ + -v "$(dirname "$(realpath "$backup_file")"):/backup:ro" \ + alpine:3.20 \ + sh -c "cd / && tar xzf /backup/$(basename "$backup_file")" + + log "Запуск контейнеров..." + docker compose up -d + log "Восстановление завершено." +} + +cmd_uninstall() { + check_dependencies + warn "Контейнеры будут удалены. Тома с данными сохранятся." + read -r -p "Продолжить? [y/N] " confirm + if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + log "Отменено." + exit 0 + fi + docker compose down + log "Контейнеры удалены. Для полного удаления данных: docker volume rm gitea_gitea_data gitea_postgres_data" +} + +COMMAND="${1:-install}" +shift $(( $# > 0 ? 1 : 0 )) || true + +if [[ "$COMMAND" == "install" ]]; then + while [[ $# -gt 0 ]]; do + case "$1" in + -y|--yes) NON_INTERACTIVE=true; shift ;; + -h|--help|help) usage; exit 0 ;; + *) err "Неизвестная опция: $1"; usage; exit 1 ;; + esac + done +fi + +case "$COMMAND" in + install) cmd_install ;; + start) cmd_start ;; + stop) cmd_stop ;; + restart) cmd_restart ;; + update) cmd_update ;; + status) cmd_status ;; + logs) cmd_logs ;; + backup) cmd_backup ;; + restore) cmd_restore "${1:-}" ;; + uninstall) cmd_uninstall ;; + -h|--help|help) usage ;; + *) + err "Неизвестная команда: $COMMAND" + usage + exit 1 + ;; +esac diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..eecd8b2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,57 @@ +services: + db: + image: postgres:16-alpine + container_name: gitea-db + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:-gitea} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required} + POSTGRES_DB: ${POSTGRES_DB:-gitea} + TZ: ${TZ:-UTC} + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - gitea + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-gitea} -d ${POSTGRES_DB:-gitea}"] + interval: 10s + timeout: 5s + retries: 5 + + gitea: + image: gitea/gitea:${GITEA_VERSION:-1.22.6} + container_name: gitea + restart: unless-stopped + depends_on: + db: + condition: service_healthy + environment: + USER_UID: 1000 + USER_GID: 1000 + TZ: ${TZ:-UTC} + GITEA__database__DB_TYPE: postgres + GITEA__database__HOST: db:5432 + GITEA__database__NAME: ${POSTGRES_DB:-gitea} + GITEA__database__USER: ${POSTGRES_USER:-gitea} + GITEA__database__PASSWD: ${POSTGRES_PASSWORD} + GITEA__server__DOMAIN: ${GITEA_DOMAIN:-localhost} + GITEA__server__ROOT_URL: http://${GITEA_DOMAIN:-localhost}:${GITEA_HTTP_PORT:-3000}/ + GITEA__server__SSH_DOMAIN: ${GITEA_DOMAIN:-localhost} + GITEA__server__SSH_PORT: ${GITEA_SSH_PORT:-2222} + GITEA__server__SSH_LISTEN_PORT: 22 + GITEA__server__LFS_START_SERVER: "true" + ports: + - "${GITEA_HTTP_PORT:-3000}:3000" + - "${GITEA_SSH_PORT:-2222}:22" + volumes: + - gitea_data:/data + networks: + - gitea + +volumes: + postgres_data: + gitea_data: + +networks: + gitea: + driver: bridge diff --git a/install-service.ps1 b/install-service.ps1 new file mode 100644 index 0000000..0943030 --- /dev/null +++ b/install-service.ps1 @@ -0,0 +1,71 @@ +#Requires -Version 5.1 +#Requires -RunAsAdministrator + +$script:ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $script:ScriptDir +. "$script:ScriptDir\lib\common.ps1" + +$ErrorActionPreference = "Stop" +$TaskName = "Gitea" + +try { + Test-Dependencies + + if (-not (Test-Path ".env")) { + throw "Файл .env не найден. Сначала выполните .\deploy.ps1 install" + } + + $startScript = Join-Path $script:ScriptDir "start.ps1" + $stopScript = Join-Path $script:ScriptDir "stop.ps1" + + $existing = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue + if ($existing) { + Write-Warn "Задача '$TaskName' уже существует. Перерегистрация..." + Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false + } + + Write-Info "Регистрация задачи автозапуска: $TaskName" + + $startAction = New-ScheduledTaskAction ` + -Execute "powershell.exe" ` + -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$startScript`"" + + $stopAction = New-ScheduledTaskAction ` + -Execute "powershell.exe" ` + -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$stopScript`"" + + $startupTrigger = New-ScheduledTaskTrigger -AtStartup + $startupTrigger.Delay = "PT1M" + + $principal = New-ScheduledTaskPrincipal ` + -UserId "SYSTEM" ` + -LogonType ServiceAccount ` + -RunLevel Highest + + $settings = New-ScheduledTaskSettingsSet ` + -AllowStartIfOnBatteries ` + -DontStopIfGoingOnBatteries ` + -StartWhenAvailable ` + -ExecutionTimeLimit ([TimeSpan]::Zero) + + Register-ScheduledTask ` + -TaskName $TaskName ` + -Action $startAction ` + -Trigger $startupTrigger ` + -Principal $principal ` + -Settings $settings ` + -Description "Автозапуск Gitea (Docker Compose)" | Out-Null + + Write-Info "Запуск Gitea..." + & $startScript + + Write-Info "Задача '$TaskName' установлена. Gitea будет запускаться при загрузке Windows." + Write-Info "Управление:" + Write-Info " Start-ScheduledTask -TaskName $TaskName" + Write-Info " Stop-ScheduledTask -TaskName $TaskName" + Write-Info " Get-ScheduledTask -TaskName $TaskName" +} +catch { + Write-Err $_.Exception.Message + exit 1 +} diff --git a/install-service.sh b/install-service.sh new file mode 100644 index 0000000..519370c --- /dev/null +++ b/install-service.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(dirname "$0")/lib/common.sh" +init_script_dir + +SERVICE_NAME="gitea" +UNIT_FILE="/etc/systemd/system/${SERVICE_NAME}.service" +TEMPLATE_FILE="${SCRIPT_DIR}/systemd/gitea.service" + +usage() { + cat <<'EOF' +Использование: sudo ./install-service.sh + +Регистрирует systemd-службу для автозапуска Gitea при загрузке системы. + +Управление после установки: + sudo systemctl start gitea + sudo systemctl stop gitea + sudo systemctl status gitea + sudo systemctl enable gitea + sudo systemctl disable gitea +EOF +} + +if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then + err "Запустите скрипт от root: sudo ./install-service.sh" + exit 1 +fi + +if [[ ! -f "$TEMPLATE_FILE" ]]; then + err "Не найден шаблон: $TEMPLATE_FILE" + exit 1 +fi + +check_dependencies + +if [[ ! -f "${SCRIPT_DIR}/.env" ]]; then + err "Файл .env не найден. Сначала выполните ./deploy.sh install" + exit 1 +fi + +log "Создание unit-файла: $UNIT_FILE" +sed "s|__GITEA_DIR__|${SCRIPT_DIR}|g" "$TEMPLATE_FILE" > "$UNIT_FILE" + +log "Перезагрузка systemd..." +systemctl daemon-reload + +log "Включение автозапуска..." +systemctl enable "$SERVICE_NAME" + +if docker compose ps --status running -q 2>/dev/null | grep -q .; then + log "Контейнеры уже запущены." +else + log "Запуск службы..." + systemctl start "$SERVICE_NAME" +fi + +systemctl status "$SERVICE_NAME" --no-pager || true +log "Служба ${SERVICE_NAME} установлена и включена в автозагрузку." diff --git a/lib/common.ps1 b/lib/common.ps1 new file mode 100644 index 0000000..bcdd7be --- /dev/null +++ b/lib/common.ps1 @@ -0,0 +1,207 @@ +function Write-Info($Message) { Write-Host "[INFO] $Message" -ForegroundColor Green } +function Write-Warn($Message) { Write-Host "[WARN] $Message" -ForegroundColor Yellow } +function Write-Err($Message) { Write-Host "[ERROR] $Message" -ForegroundColor Red } + +function Test-Dependencies { + if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { + throw "Docker не найден. Установите Docker Desktop." + } + + docker compose version 2>$null | Out-Null + if ($LASTEXITCODE -ne 0) { + throw "Docker Compose v2 не найден." + } +} + +function Import-EnvFromFile { + param([string]$Path) + + if (-not (Test-Path $Path)) { return } + + Get-Content $Path | ForEach-Object { + if ($_ -match '^\s*#' -or $_ -match '^\s*$') { return } + $parts = $_ -split '=', 2 + if ($parts.Count -eq 2) { + $name = $parts[0].Trim() + $value = $parts[1].Trim() + Set-Item -Path "env:$name" -Value $value -Force + } + } +} + +function New-RandomPassword { + param([int]$Length = 20) + $bytes = New-Object byte[] 32 + [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes) + $token = [Convert]::ToBase64String($bytes) -replace '[^A-Za-z0-9]', '' + if ($token.Length -ge $Length) { + return $token.Substring(0, $Length) + } + return ($token + ('A' * $Length)).Substring(0, $Length) +} + +function Read-PromptValue { + param( + [string]$Prompt, + [string]$Default, + [switch]$Secret + ) + + if ($Secret) { + Write-Host "$Prompt (Enter — автогенерация): " -NoNewline + $secure = Read-Host -AsSecureString + $input = [Runtime.InteropServices.Marshal]::PtrToStringAuto( + [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure) + ) + if ([string]::IsNullOrWhiteSpace($input)) { + $input = New-RandomPassword + Write-Warn "Сгенерирован пароль PostgreSQL: $input" + } + return $input + } + + $input = Read-Host "$Prompt [$Default]" + if ([string]::IsNullOrWhiteSpace($input)) { + return $Default + } + return $input +} + +function Write-EnvFile { + $content = @" +# Сгенерировано $(Get-Date -Format "yyyy-MM-ddTHH:mm:ss") + +# Домен или IP сервера (без http/https) +GITEA_DOMAIN=$($env:GITEA_DOMAIN) + +# Порт веб-интерфейса Gitea +GITEA_HTTP_PORT=$($env:GITEA_HTTP_PORT) + +# Порт SSH для git clone/push +GITEA_SSH_PORT=$($env:GITEA_SSH_PORT) + +# Версия образа Gitea +GITEA_VERSION=$($env:GITEA_VERSION) + +# База данных PostgreSQL +POSTGRES_USER=$($env:POSTGRES_USER) +POSTGRES_PASSWORD=$($env:POSTGRES_PASSWORD) +POSTGRES_DB=$($env:POSTGRES_DB) + +# Часовой пояс +TZ=$($env:TZ) +"@ + + $path = Join-Path $script:ScriptDir ".env" + $utf8NoBom = New-Object System.Text.UTF8Encoding $false + [System.IO.File]::WriteAllText($path, $content + "`n", $utf8NoBom) +} + +function Initialize-EnvDefaults { + param([string]$DefaultsFile) + + Import-EnvFromFile $DefaultsFile + + if (-not $env:GITEA_DOMAIN) { $env:GITEA_DOMAIN = "localhost" } + if (-not $env:GITEA_HTTP_PORT) { $env:GITEA_HTTP_PORT = "3000" } + if (-not $env:GITEA_SSH_PORT) { $env:GITEA_SSH_PORT = "2222" } + if (-not $env:GITEA_VERSION) { $env:GITEA_VERSION = "1.22.6" } + if (-not $env:POSTGRES_USER) { $env:POSTGRES_USER = "gitea" } + if (-not $env:POSTGRES_DB) { $env:POSTGRES_DB = "gitea" } + if (-not $env:TZ) { $env:TZ = "Europe/Moscow" } +} + +function Invoke-SetupEnvInteractive { + param([switch]$NonInteractive) + + $defaultsFile = ".env.example" + + if (Test-Path ".env") { + $defaultsFile = ".env" + if (-not $NonInteractive) { + Write-Warn "Файл .env уже существует." + $confirm = Read-Host "Пересоздать конфигурацию? [y/N]" + if ($confirm -notmatch '^[Yy]$') { + Import-EnvFromFile ".env" + return + } + } + } + + if (-not (Test-Path $defaultsFile) -and -not (Test-Path ".env.example")) { + throw "Не найден .env.example" + } + + if (-not (Test-Path $defaultsFile)) { + $defaultsFile = ".env.example" + } + + Remove-Item Env:GITEA_DOMAIN, Env:GITEA_HTTP_PORT, Env:GITEA_SSH_PORT, Env:GITEA_VERSION -ErrorAction SilentlyContinue + Remove-Item Env:POSTGRES_USER, Env:POSTGRES_PASSWORD, Env:POSTGRES_DB, Env:TZ -ErrorAction SilentlyContinue + + Initialize-EnvDefaults $defaultsFile + + if ($NonInteractive) { + if ([string]::IsNullOrWhiteSpace($env:POSTGRES_PASSWORD) -or $env:POSTGRES_PASSWORD -eq "change_me_strong_password") { + $env:POSTGRES_PASSWORD = New-RandomPassword + Write-Warn "Сгенерирован пароль PostgreSQL: $($env:POSTGRES_PASSWORD)" + } + Write-EnvFile + Import-EnvFromFile ".env" + return + } + + Write-Host "" + Write-Host "=== Настройка Gitea ===" -ForegroundColor Cyan + Write-Host "Enter — оставить значение в скобках." + Write-Host "" + + $env:GITEA_DOMAIN = Read-PromptValue "Домен или IP сервера" $env:GITEA_DOMAIN + $env:GITEA_HTTP_PORT = Read-PromptValue "Порт веб-интерфейса" $env:GITEA_HTTP_PORT + $env:GITEA_SSH_PORT = Read-PromptValue "Порт SSH для Git" $env:GITEA_SSH_PORT + $env:GITEA_VERSION = Read-PromptValue "Версия образа Gitea" $env:GITEA_VERSION + $env:POSTGRES_USER = Read-PromptValue "Пользователь PostgreSQL" $env:POSTGRES_USER + $env:POSTGRES_DB = Read-PromptValue "Имя базы данных" $env:POSTGRES_DB + $env:TZ = Read-PromptValue "Часовой пояс (TZ)" $env:TZ + + Write-Host "" + $env:POSTGRES_PASSWORD = Read-PromptValue "Пароль PostgreSQL" "" -Secret + + Write-EnvFile + Import-EnvFromFile ".env" + Write-Info "Конфигурация сохранена в .env" +} + +function Import-EnvFile { + if (-not (Test-Path ".env")) { + throw "Файл .env не найден. Выполните: .\deploy.ps1 install" + } + + Import-EnvFromFile ".env" + + if ($env:POSTGRES_PASSWORD -eq "change_me_strong_password") { + Write-Warn "Используется пароль по умолчанию! Измените POSTGRES_PASSWORD в .env" + } +} + +function Confirm-OrExit { + param([string]$Message = "Продолжить?") + $confirm = Read-Host "$Message [y/N]" + if ($confirm -notmatch '^[Yy]$') { + Write-Info "Отменено." + exit 0 + } +} + +function Get-ComposeProjectName { + if ($env:COMPOSE_PROJECT_NAME) { + return $env:COMPOSE_PROJECT_NAME + } + $name = Split-Path $script:ScriptDir -Leaf + return ($name.ToLower() -replace '[^a-z0-9_-]', '_') +} + +function Get-VolumeNames { + $prefix = Get-ComposeProjectName + return @("${prefix}_gitea_data", "${prefix}_postgres_data") +} diff --git a/lib/common.sh b/lib/common.sh new file mode 100644 index 0000000..a06d98e --- /dev/null +++ b/lib/common.sh @@ -0,0 +1,199 @@ +#!/usr/bin/env bash + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +log() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +err() { echo -e "${RED}[ERROR]${NC} $*" >&2; } + +init_script_dir() { + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[1]}")" && pwd)" + cd "$SCRIPT_DIR" +} + +check_dependencies() { + if ! command -v docker >/dev/null 2>&1; then + err "Не найдена команда: docker" + exit 1 + fi + + if ! docker compose version >/dev/null 2>&1; then + err "Docker Compose v2 не найден. Установите docker compose plugin." + exit 1 + fi +} + +load_env_from_file() { + local file="$1" + [[ -f "$file" ]] || return 0 + + while IFS= read -r line || [[ -n "$line" ]]; do + [[ "$line" =~ ^[[:space:]]*# ]] && continue + [[ -z "${line// }" ]] && continue + if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then + export "${BASH_REMATCH[1]}=${BASH_REMATCH[2]}" + fi + done < "$file" +} + +generate_password() { + if command -v openssl >/dev/null 2>&1; then + openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 20 + else + tr -dc 'A-Za-z0-9' /dev/null | head -c 20 + fi +} + +prompt_value() { + local prompt_text="$1" + local default_value="$2" + local secret="${3:-false}" + local input="" + + if [[ "$secret" == true ]]; then + read -rs -p "$prompt_text (Enter — автогенерация): " input + echo + if [[ -z "$input" ]]; then + input="$(generate_password)" + warn "Сгенерирован пароль PostgreSQL: $input" + fi + else + read -r -p "$prompt_text [$default_value]: " input + if [[ -z "$input" ]]; then + input="$default_value" + fi + fi + + printf '%s' "$input" +} + +write_env_file() { + cat > .env </dev/null || date) + +# Домен или IP сервера (без http/https) +GITEA_DOMAIN=${GITEA_DOMAIN} + +# Порт веб-интерфейса Gitea +GITEA_HTTP_PORT=${GITEA_HTTP_PORT} + +# Порт SSH для git clone/push +GITEA_SSH_PORT=${GITEA_SSH_PORT} + +# Версия образа Gitea +GITEA_VERSION=${GITEA_VERSION} + +# База данных PostgreSQL +POSTGRES_USER=${POSTGRES_USER} +POSTGRES_PASSWORD=${POSTGRES_PASSWORD} +POSTGRES_DB=${POSTGRES_DB} + +# Часовой пояс +TZ=${TZ} +EOF +} + +setup_env_interactive() { + local non_interactive="${1:-false}" + local defaults_file=".env.example" + + if [[ -f .env ]]; then + defaults_file=".env" + if [[ "$non_interactive" != true ]]; then + warn "Файл .env уже существует." + read -r -p "Пересоздать конфигурацию? [y/N] " confirm + if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + load_env_from_file .env + return 0 + fi + fi + fi + + if [[ ! -f "$defaults_file" && ! -f .env.example ]]; then + err "Не найден .env.example" + exit 1 + fi + + [[ -f "$defaults_file" ]] || defaults_file=".env.example" + + unset GITEA_DOMAIN GITEA_HTTP_PORT GITEA_SSH_PORT GITEA_VERSION + unset POSTGRES_USER POSTGRES_PASSWORD POSTGRES_DB TZ + load_env_from_file "$defaults_file" + + GITEA_DOMAIN="${GITEA_DOMAIN:-localhost}" + GITEA_HTTP_PORT="${GITEA_HTTP_PORT:-3000}" + GITEA_SSH_PORT="${GITEA_SSH_PORT:-2222}" + GITEA_VERSION="${GITEA_VERSION:-1.22.6}" + POSTGRES_USER="${POSTGRES_USER:-gitea}" + POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-}" + POSTGRES_DB="${POSTGRES_DB:-gitea}" + TZ="${TZ:-Europe/Moscow}" + + if [[ "$non_interactive" == true ]]; then + if [[ -z "$POSTGRES_PASSWORD" || "$POSTGRES_PASSWORD" == "change_me_strong_password" ]]; then + POSTGRES_PASSWORD="$(generate_password)" + warn "Сгенерирован пароль PostgreSQL: $POSTGRES_PASSWORD" + fi + write_env_file + load_env_from_file .env + return 0 + fi + + echo + echo -e "${CYAN}=== Настройка Gitea ===${NC}" + echo "Enter — оставить значение в скобках." + echo + + GITEA_DOMAIN="$(prompt_value "Домен или IP сервера" "$GITEA_DOMAIN")" + GITEA_HTTP_PORT="$(prompt_value "Порт веб-интерфейса" "$GITEA_HTTP_PORT")" + GITEA_SSH_PORT="$(prompt_value "Порт SSH для Git" "$GITEA_SSH_PORT")" + GITEA_VERSION="$(prompt_value "Версия образа Gitea" "$GITEA_VERSION")" + POSTGRES_USER="$(prompt_value "Пользователь PostgreSQL" "$POSTGRES_USER")" + POSTGRES_DB="$(prompt_value "Имя базы данных" "$POSTGRES_DB")" + TZ="$(prompt_value "Часовой пояс (TZ)" "$TZ")" + + echo + POSTGRES_PASSWORD="$(prompt_value "Пароль PostgreSQL" "" true)" + + write_env_file + load_env_from_file .env + log "Конфигурация сохранена в .env" +} + +ensure_env() { + if [[ ! -f .env ]]; then + err "Файл .env не найден. Выполните: ./deploy.sh install" + exit 1 + fi + + load_env_from_file .env + + if [[ "${POSTGRES_PASSWORD:-}" == "change_me_strong_password" ]]; then + warn "Используется пароль по умолчанию! Измените POSTGRES_PASSWORD в .env" + fi +} + +confirm_or_exit() { + local message="${1:-Продолжить?}" + read -r -p "$message [y/N] " confirm + if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + log "Отменено." + exit 0 + fi +} + +get_compose_project_name() { + local project_name + project_name="$(basename "$SCRIPT_DIR" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_-]/_/g')" + echo "${COMPOSE_PROJECT_NAME:-$project_name}" +} + +get_volume_names() { + local prefix + prefix="$(get_compose_project_name)" + echo "${prefix}_gitea_data ${prefix}_postgres_data" +} diff --git a/reinstall.ps1 b/reinstall.ps1 new file mode 100644 index 0000000..917dfaa --- /dev/null +++ b/reinstall.ps1 @@ -0,0 +1,55 @@ +#Requires -Version 5.1 + +param( + [switch]$Purge +) + +$script:ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $script:ScriptDir +. "$script:ScriptDir\lib\common.ps1" + +$ErrorActionPreference = "Stop" + +try { + Test-Dependencies + + if ($Purge) { + Write-Warn "Данные будут удалены. Gitea будет установлена заново." + Confirm-OrExit "Продолжить?" + + docker compose down -v 2>$null + if ($LASTEXITCODE -ne 0) { docker compose down } + + foreach ($vol in Get-VolumeNames) { + docker volume rm $vol 2>$null + } + } + else { + Write-Warn "Контейнеры будут пересозданы. Данные сохранятся." + Confirm-OrExit "Продолжить?" + docker compose down + } + + Import-EnvFile + + Write-Info "Загрузка образов..." + docker compose pull + + Write-Info "Запуск Gitea..." + docker compose up -d --force-recreate + + Start-Sleep -Seconds 5 + docker compose ps + + $domain = if ($env:GITEA_DOMAIN) { $env:GITEA_DOMAIN } else { "localhost" } + $httpPort = if ($env:GITEA_HTTP_PORT) { $env:GITEA_HTTP_PORT } else { "3000" } + $sshPort = if ($env:GITEA_SSH_PORT) { $env:GITEA_SSH_PORT } else { "2222" } + + Write-Info "Переустановка завершена." + Write-Info " Web: http://${domain}:${httpPort}" + Write-Info " SSH: ssh://git@${domain}:${sshPort}" +} +catch { + Write-Err $_.Exception.Message + exit 1 +} diff --git a/reinstall.sh b/reinstall.sh new file mode 100644 index 0000000..033ccea --- /dev/null +++ b/reinstall.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(dirname "$0")/lib/common.sh" +init_script_dir +check_dependencies + +PURGE=false + +usage() { + cat <<'EOF' +Использование: ./reinstall.sh [--purge] + +Переустановка: остановка, обновление образов, повторный запуск. + + --purge Удалить данные перед переустановкой (чистая установка) +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --purge) PURGE=true; shift ;; + -h|--help) usage; exit 0 ;; + *) err "Неизвестный аргумент: $1"; usage; exit 1 ;; + esac +done + +if [[ "$PURGE" == true ]]; then + warn "Данные будут удалены. Gitea будет установлена заново." + confirm_or_exit "Продолжить?" + docker compose down -v 2>/dev/null || docker compose down + read -r -a volumes <<< "$(get_volume_names)" + for vol in "${volumes[@]}"; do + docker volume rm "$vol" 2>/dev/null || true + done +else + warn "Контейнеры будут пересозданы. Данные сохранятся." + confirm_or_exit "Продолжить?" + docker compose down +fi + +ensure_env + +log "Загрузка образов..." +docker compose pull + +log "Запуск Gitea..." +docker compose up -d --force-recreate + +sleep 5 +docker compose ps + +# shellcheck disable=SC1091 +source .env +log "Переустановка завершена." +log " Web: http://${GITEA_DOMAIN:-localhost}:${GITEA_HTTP_PORT:-3000}" +log " SSH: ssh://git@${GITEA_DOMAIN:-localhost}:${GITEA_SSH_PORT:-2222}" diff --git a/remove-service.ps1 b/remove-service.ps1 new file mode 100644 index 0000000..366b91c --- /dev/null +++ b/remove-service.ps1 @@ -0,0 +1,32 @@ +#Requires -Version 5.1 +#Requires -RunAsAdministrator + +$script:ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $script:ScriptDir +. "$script:ScriptDir\lib\common.ps1" + +$ErrorActionPreference = "Stop" +$TaskName = "Gitea" + +try { + $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue + if (-not $task) { + Write-Warn "Задача '$TaskName' не найдена." + exit 0 + } + + Write-Warn "Задача '$TaskName' будет удалена из планировщика." + Confirm-OrExit "Продолжить?" + + if ((Get-ScheduledTask -TaskName $TaskName).State -eq "Running") { + Write-Info "Остановка задачи..." + Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue + } + + Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false + Write-Info "Задача '$TaskName' удалена. Контейнеры и данные не затронуты." +} +catch { + Write-Err $_.Exception.Message + exit 1 +} diff --git a/remove-service.sh b/remove-service.sh new file mode 100644 index 0000000..16300a7 --- /dev/null +++ b/remove-service.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(dirname "$0")/lib/common.sh" +init_script_dir + +SERVICE_NAME="gitea" +UNIT_FILE="/etc/systemd/system/${SERVICE_NAME}.service" + +if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then + err "Запустите скрипт от root: sudo ./remove-service.sh" + exit 1 +fi + +if [[ ! -f "$UNIT_FILE" ]]; then + warn "Служба ${SERVICE_NAME} не установлена." + exit 0 +fi + +warn "Служба ${SERVICE_NAME} будет остановлена и удалена из автозагрузки." +confirm_or_exit "Продолжить?" + +if systemctl is-active --quiet "$SERVICE_NAME"; then + log "Остановка службы..." + systemctl stop "$SERVICE_NAME" +fi + +if systemctl is-enabled --quiet "$SERVICE_NAME" 2>/dev/null; then + log "Отключение автозапуска..." + systemctl disable "$SERVICE_NAME" +fi + +log "Удаление unit-файла..." +rm -f "$UNIT_FILE" +systemctl daemon-reload + +log "Служба ${SERVICE_NAME} удалена. Контейнеры и данные не затронуты." diff --git a/start.ps1 b/start.ps1 new file mode 100644 index 0000000..f04130d --- /dev/null +++ b/start.ps1 @@ -0,0 +1,20 @@ +#Requires -Version 5.1 + +$script:ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $script:ScriptDir +. "$script:ScriptDir\lib\common.ps1" + +$ErrorActionPreference = "Stop" + +try { + Test-Dependencies + Import-EnvFile + + Write-Info "Запуск Gitea..." + docker compose up -d + docker compose ps +} +catch { + Write-Err $_.Exception.Message + exit 1 +} diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..239c113 --- /dev/null +++ b/start.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(dirname "$0")/lib/common.sh" +init_script_dir +check_dependencies +ensure_env + +log "Запуск Gitea..." +docker compose up -d +docker compose ps diff --git a/stop.ps1 b/stop.ps1 new file mode 100644 index 0000000..d103dde --- /dev/null +++ b/stop.ps1 @@ -0,0 +1,19 @@ +#Requires -Version 5.1 + +$script:ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $script:ScriptDir +. "$script:ScriptDir\lib\common.ps1" + +$ErrorActionPreference = "Stop" + +try { + Test-Dependencies + + Write-Info "Остановка Gitea..." + docker compose down + Write-Info "Контейнеры остановлены." +} +catch { + Write-Err $_.Exception.Message + exit 1 +} diff --git a/stop.sh b/stop.sh new file mode 100644 index 0000000..d98800e --- /dev/null +++ b/stop.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(dirname "$0")/lib/common.sh" +init_script_dir +check_dependencies + +log "Остановка Gitea..." +docker compose down +log "Контейнеры остановлены." diff --git a/systemd/gitea.service b/systemd/gitea.service new file mode 100644 index 0000000..1b4623b --- /dev/null +++ b/systemd/gitea.service @@ -0,0 +1,17 @@ +[Unit] +Description=Gitea (Docker Compose) +Documentation=https://about.gitea.com/ +Requires=docker.service +After=docker.service network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +RemainAfterExit=yes +WorkingDirectory=__GITEA_DIR__ +ExecStart=/usr/bin/docker compose up -d --remove-orphans +ExecStop=/usr/bin/docker compose down +TimeoutStartSec=300 + +[Install] +WantedBy=multi-user.target diff --git a/uninstall.ps1 b/uninstall.ps1 new file mode 100644 index 0000000..0bbb678 --- /dev/null +++ b/uninstall.ps1 @@ -0,0 +1,51 @@ +#Requires -Version 5.1 + +param( + [switch]$Purge +) + +$script:ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $script:ScriptDir +. "$script:ScriptDir\lib\common.ps1" + +$ErrorActionPreference = "Stop" + +try { + Test-Dependencies + + if ($Purge) { + Write-Warn "Будут удалены контейнеры и все данные (тома Docker)!" + } + else { + Write-Warn "Контейнеры будут удалены. Тома с данными сохранятся." + } + + Confirm-OrExit "Продолжить?" + + $task = Get-ScheduledTask -TaskName "Gitea" -ErrorAction SilentlyContinue + if ($task) { + throw "Задача Gitea зарегистрирована в планировщике. Сначала выполните: .\remove-service.ps1" + } + + docker compose down + + if ($Purge) { + foreach ($vol in Get-VolumeNames) { + $exists = docker volume inspect $vol 2>$null + if ($LASTEXITCODE -eq 0) { + Write-Info "Удаление тома: $vol" + docker volume rm $vol + } + } + Write-Info "Полное удаление завершено." + } + else { + $volumes = (Get-VolumeNames) -join ", " + Write-Info "Контейнеры удалены. Данные сохранены в томах: $volumes" + Write-Info "Для полного удаления данных: .\uninstall.ps1 -Purge" + } +} +catch { + Write-Err $_.Exception.Message + exit 1 +} diff --git a/uninstall.sh b/uninstall.sh new file mode 100644 index 0000000..8df9f90 --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(dirname "$0")/lib/common.sh" +init_script_dir +check_dependencies + +PURGE=false + +usage() { + cat <<'EOF' +Использование: ./uninstall.sh [--purge] + + --purge Удалить контейнеры и Docker-тома с данными (необратимо) +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --purge) PURGE=true; shift ;; + -h|--help) usage; exit 0 ;; + *) err "Неизвестный аргумент: $1"; usage; exit 1 ;; + esac +done + +if [[ "$PURGE" == true ]]; then + warn "Будут удалены контейнеры и все данные (тома Docker)!" +else + warn "Контейнеры будут удалены. Тома с данными сохранятся." +fi + +confirm_or_exit "Продолжить?" + +if systemctl is-enabled gitea.service >/dev/null 2>&1; then + warn "Служба gitea.service включена. Сначала выполните: sudo ./remove-service.sh" + exit 1 +fi + +docker compose down + +if [[ "$PURGE" == true ]]; then + read -r -a volumes <<< "$(get_volume_names)" + for vol in "${volumes[@]}"; do + if docker volume inspect "$vol" >/dev/null 2>&1; then + log "Удаление тома: $vol" + docker volume rm "$vol" + fi + done + log "Полное удаление завершено." +else + log "Контейнеры удалены. Данные сохранены в томах: $(get_volume_names)" + log "Для полного удаления данных: ./uninstall.sh --purge" +fi