first commit

This commit is contained in:
Илья Братских 2026-06-25 13:01:28 +03:00
commit cd9f0b8e72
22 changed files with 1716 additions and 0 deletions

22
.env.example Normal file
View File

@ -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

14
.gitattributes vendored Normal file
View File

@ -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

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.env
backups/

314
README.md Normal file
View File

@ -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 <url-репозитория> 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. Данный репозиторий содержит только конфигурацию развёртывания.

206
deploy.ps1 Normal file
View File

@ -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
}

202
deploy.sh Normal file
View File

@ -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

57
docker-compose.yml Normal file
View File

@ -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

71
install-service.ps1 Normal file
View File

@ -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
}

60
install-service.sh Normal file
View File

@ -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} установлена и включена в автозагрузку."

207
lib/common.ps1 Normal file
View File

@ -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")
}

199
lib/common.sh Normal file
View File

@ -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/urandom 2>/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 <<EOF
# Сгенерировано $(date -Iseconds 2>/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"
}

55
reinstall.ps1 Normal file
View File

@ -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
}

57
reinstall.sh Normal file
View File

@ -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}"

32
remove-service.ps1 Normal file
View File

@ -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
}

37
remove-service.sh Normal file
View File

@ -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} удалена. Контейнеры и данные не затронуты."

20
start.ps1 Normal file
View File

@ -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
}

11
start.sh Normal file
View File

@ -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

19
stop.ps1 Normal file
View File

@ -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
}

10
stop.sh Normal file
View File

@ -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 "Контейнеры остановлены."

17
systemd/gitea.service Normal file
View File

@ -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

51
uninstall.ps1 Normal file
View File

@ -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
}

53
uninstall.sh Normal file
View File

@ -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