Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .kamal/hooks/post-deploy
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/bash
# Post-deploy hook to setup cron job for backups on the server

set -e

echo "Setting up backup cron job on server..."

CRON_JOB="0 2 * * * /usr/bin/docker exec stackoverflow_clone-web-1 /rails/bin/backup-sqlite.sh >> /var/log/sqlite-backup.log 2>&1"

# Setup cron on the server via SSH
ssh root@90.156.228.95 << 'EOF'
# Check if cron job already exists
if ! crontab -l 2>/dev/null | grep -q "backup-sqlite.sh"; then
(crontab -l 2>/dev/null; echo "0 2 * * * /usr/bin/docker exec stackoverflow_clone-web-1 /rails/bin/backup-sqlite.sh >> /var/log/sqlite-backup.log 2>&1") | crontab -
echo "✓ Backup cron job installed"
else
echo "✓ Backup cron job already exists"
fi

# Create log file
touch /var/log/sqlite-backup.log
chmod 644 /var/log/sqlite-backup.log
EOF

echo "✓ Post-deploy setup completed"
139 changes: 139 additions & 0 deletions BACKUP_INSTALLATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Установка системы бэкапа с Backup gem

## Что было добавлено

### 1. Гем backup
- Добавлен в `Gemfile`: `gem "backup", "~> 5.0"`

### 2. Конфигурационные файлы
- `config/backup.rb` - основная конфигурация Backup gem
- `config/sidekiq.yml` - обновлен с расписанием бэкапа

### 3. Код приложения
- `app/jobs/database_backup_job.rb` - Sidekiq job для автоматического бэкапа
- `lib/tasks/backup.rake` - Rake tasks для управления бэкапами

### 4. Документация
- `docs/backup/README.md` - обзор системы бэкапа
- `docs/backup/QUICK_START.md` - быстрый старт
- `docs/backup/BACKUP_GEM_SETUP.md` - полная документация

## Шаги для установки

### 1. Установите зависимости локально

```bash
bundle install
```

### 2. Проверьте конфигурацию

Убедитесь, что в `Dockerfile` установлен `sqlite3` (уже есть на строке 16):
```dockerfile
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
```

### 3. Задеплойте приложение

```bash
# Полный деплой с пересборкой образа
kamal deploy

# Или только перезапуск если образ уже собран
kamal app boot
```

### 4. Проверьте работу

```bash
# Запустите тестовый бэкап
kamal app exec 'bundle exec rake backup:run'

# Проверьте созданные бэкапы
kamal app exec 'bundle exec rake backup:list'
```

Вы должны увидеть что-то вроде:
```
Available backups in /backups:
stackoverflow_clone_db.tar.gz (2.45 MB) - 2025-01-20 14:30:15
```

### 5. Проверьте автоматическое расписание

Откройте Sidekiq Web UI:
```
http://your-server-ip:3000/sidekiq/cron
```

Вы должны увидеть задачу `database_backup` с расписанием `0 2 * * *` (каждый день в 2:00).

## Что дальше?

1. **Прочитайте документацию**: `docs/backup/README.md`
2. **Настройте email уведомления** (опционально)
3. **Настройте хранение на S3** (рекомендуется для production)
4. **Протестируйте восстановление** из бэкапа

## Основные команды

```bash
# Ручной запуск бэкапа
kamal app exec 'bundle exec rake backup:run'

# Список бэкапов
kamal app exec 'bundle exec rake backup:list'

# Очистка старых бэкапов
kamal app exec 'bundle exec rake backup:clean'

# Просмотр логов
kamal app logs | grep DatabaseBackupJob
```

## Параметры по умолчанию

- **Расписание**: Каждый день в 2:00 ночи
- **Хранение**: Последние 7 бэкапов
- **Место**: Docker volume `stackoverflow_clone_backups` → `/backups`
- **Сжатие**: Gzip (уровень 6)
- **Разбивка**: Файлы > 250 MB разбиваются на части

## Troubleshooting

### Ошибка при bundle install

Если возникает ошибка при установке гема `backup`, попробуйте:
```bash
bundle update backup
```

### Бэкап не создается

1. Проверьте, что Sidekiq запущен:
```bash
docker ps | grep sidekiq
```

2. Проверьте логи:
```bash
kamal app logs | grep -i backup
```

3. Запустите вручную для диагностики:
```bash
kamal app exec 'bundle exec rake backup:run'
```

### Нет доступа к /backups

Проверьте права доступа:
```bash
kamal app exec 'ls -la /backups'
```

## Дополнительная информация

Полная документация находится в `docs/backup/BACKUP_GEM_SETUP.md`.
3 changes: 0 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# syntax=docker/dockerfile:1
# check=error=true

# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
# docker build -t stackoverflow_clone .
# docker run -d -p 80:80 -e RAILS_MASTER_KEY=<value from config/master.key> --name stackoverflow_clone stackoverflow_clone
Expand Down
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ gem "sidekiq"
gem "redis"
gem "sidekiq-cron"

# Prometheus metrics for monitoring
gem "prometheus_exporter"

# Database backup solution
gem "backup", "~> 3.4.0"

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[ windows jruby ]

Expand Down
9 changes: 9 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ GEM
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
ast (2.4.3)
backup (3.4.0)
open4 (~> 1.3.0)
thor (>= 0.15.4, < 2)
base64 (0.3.0)
bcrypt (3.1.20)
bcrypt_pbkdf (1.1.1)
Expand Down Expand Up @@ -276,6 +279,7 @@ GEM
omniauth (~> 2.0)
omniauth-telegram (0.2.1)
omniauth (>= 1.0)
open4 (1.3.4)
orm_adapter (0.5.0)
ostruct (0.6.2)
parallel (1.27.0)
Expand All @@ -286,6 +290,8 @@ GEM
prettyprint
prettyprint (0.2.0)
prism (1.4.0)
prometheus_exporter (2.3.0)
webrick
propshaft (1.2.1)
actionpack (>= 7.0.0)
activesupport (>= 7.0.0)
Expand Down Expand Up @@ -495,6 +501,7 @@ GEM
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webrick (1.9.1)
websocket (1.2.11)
websocket-driver (0.8.0)
base64
Expand All @@ -516,6 +523,7 @@ PLATFORMS

DEPENDENCIES
active_model_serializers (~> 0.10.0)
backup (~> 3.4.0)
bcrypt (~> 3.1.7)
bootsnap
brakeman
Expand All @@ -537,6 +545,7 @@ DEPENDENCIES
omniauth-google-oauth2
omniauth-rails_csrf_protection
omniauth-telegram
prometheus_exporter
propshaft
puma (>= 5.0)
pundit
Expand Down
92 changes: 60 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,83 @@
# StackOverflow Clone

A functional clone of StackOverflow built with Ruby on Rails 8. This application allows users to ask questions, provide answers, vote on content, and earn reputation points.
> Современный клон StackOverflow на Rails 8 с OAuth, поиском, API и production-ready инфраструктурой

## Features
## ✨ Возможности

* User authentication and profiles
* Question asking and answering
* Voting system for questions and answers
* Comment functionality
* Tags and categories
* User reputation system
* Search functionality
- 🔐 **Аутентификация** - Devise + OAuth (Google, Telegram)
- 💬 **Q&A система** - вопросы, ответы, комментарии
- 👍 **Голосование** - upvote/downvote с репутацией
- 🏆 **Награды** - система достижений за лучшие ответы
- 🔍 **Поиск** - полнотекстовый поиск через Elasticsearch
- 📡 **Real-time** - WebSocket обновления через Action Cable
- 🔌 **API** - OAuth2 provider + JSON API
- 📊 **Мониторинг** - Prometheus + Grafana
- 💾 **Бэкапы** - автоматические резервные копии

## Technical Stack
## 🛠 Технологии

* Ruby on Rails 8
* Ruby version: 3.x
* Database: PostgreSQL
* Frontend: ERB templates, JavaScript, CSS
**Backend**
- Rails 8.0.2 + Ruby 3.2.6
- SQLite3 (Solid Cache/Queue/Cable)
- Sidekiq + Redis (фоновые задачи)
- Elasticsearch (поиск)

## Setup and Installation
**Frontend**
- Hotwire (Turbo + Stimulus)
- TailwindCSS (Flowbite)
- ERB templates

### Prerequisites
**Infrastructure**
- Kamal (deployment)
- Prometheus + Grafana (мониторинг)
- Docker

* Ruby 3.x
* Rails 8.1
* PostgreSQL

### Installation Steps
## 🚀 Быстрый старт

```bash
# Clone the repository
# Клонировать репозиторий
git clone https://github.com/amsak1983/stackoverflow_clone
cd stackoverflow_clone

# Install dependencies
# Установить зависимости
bundle install
npm install

# Setup database
rails db:create
rails db:migrate
rails db:seed # Optional: adds sample data
# Настроить базу данных
bin/rails db:setup

# Start the server
rails server
# Запустить dev сервер (Rails + Sidekiq + TailwindCSS)
bin/dev
```

Visit `http://localhost:3000` in your browser to access the application.
Откройте http://localhost:3000

## Testing
## 🧪 Тестирование

```bash
rails test
# RSpec тесты
bundle exec rspec

# Проверка безопасности
bundle exec brakeman

# Линтер
bundle exec rubocop
```

## 📦 Production

```bash
# Деплой через Kamal
kamal setup
kamal deploy

# Мониторинг
kamal accessory boot prometheus grafana
```

**Документация:** [docs/](docs/README.md)

## 📝 Лицензия

MIT
6 changes: 6 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,10 @@ def formatted_date(date)
def time_ago(date)
time_ago_in_words(date) + " ago"
end

def cacheable_user_id
current_user&.id
rescue Devise::MissingWarden
nil
end
end
28 changes: 28 additions & 0 deletions app/jobs/database_backup_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class DatabaseBackupJob < ApplicationJob
queue_as :default

def perform
Rails.logger.info "Starting database backup job..."

config_file = Rails.root.join("config", "backup.rb")

unless File.exist?(config_file)
Rails.logger.error "Backup configuration file not found: #{config_file}"
raise "Backup configuration file not found"
end

# Run backup command with safe argument passing to prevent command injection
result = system("backup", "perform", "--trigger", "stackoverflow_clone_db", "--config-file", config_file.to_s)

if result
Rails.logger.info "Database backup completed successfully"
else
Rails.logger.error "Database backup failed with exit code: #{$?.exitstatus}"
raise "Database backup failed"
end
rescue => e
Rails.logger.error "Database backup job error: #{e.message}"
Rails.logger.error e.backtrace.join("\n")
raise e
end
end
Loading