# 홈 관리 시스템 DB 구축 계획서 ## 🎯 프로젝트 개요 ### Git 저장소 - **Repository**: https://git.hyungi.net/hyungi/myhome-server.git - **Gitea Server**: git.hyungi.net (DS1525+ 호스팅) ### 목표 - 홈 IoT 기기 모니터링 및 관리 - 전력 소비, 네트워크 트래픽 추적 - 시스템 리소스 모니터링 - 향후 개인 서비스 확장 기반 마련 ### 기술 스택 ``` Backend: Express.js (Node.js) Database: MariaDB 11.x Admin Tool: phpMyAdmin Architecture: MVC + Service Layer Code Style: 모듈형 구조 (500자 이하 함수) ``` --- ## 🗄️ 데이터베이스 설계 ### 데이터베이스 생성 ```sql CREATE DATABASE home_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'homeuser'@'localhost' IDENTIFIED BY 'secure_password'; GRANT ALL PRIVILEGES ON home_management.* TO 'homeuser'@'localhost'; FLUSH PRIVILEGES; ``` ### 테이블 설계 #### 1. 디바이스 관리 (devices) ```sql CREATE TABLE devices ( id INT AUTO_INCREMENT PRIMARY KEY, device_id VARCHAR(50) UNIQUE NOT NULL, name VARCHAR(100) NOT NULL, device_type ENUM('server', 'nas', 'router', 'smart_plug', 'other') NOT NULL, location VARCHAR(50), ip_address VARCHAR(45), mac_address VARCHAR(17), power_rating_watts INT, monitoring_enabled BOOLEAN DEFAULT TRUE, metadata JSON, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_device_id (device_id), INDEX idx_type_enabled (device_type, monitoring_enabled) ) ENGINE=InnoDB; ``` #### 2. 전력 소비 데이터 (power_consumption) ```sql CREATE TABLE power_consumption ( id BIGINT AUTO_INCREMENT PRIMARY KEY, device_id VARCHAR(50) NOT NULL, timestamp TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3), watts DECIMAL(8,2) NOT NULL, voltage DECIMAL(6,2), current DECIMAL(6,3), kwh_total DECIMAL(10,4), metadata JSON, INDEX idx_device_time (device_id, timestamp), INDEX idx_timestamp (timestamp), INDEX idx_watts (watts), FOREIGN KEY (device_id) REFERENCES devices(device_id) ON DELETE CASCADE ) ENGINE=InnoDB PARTITION BY RANGE (UNIX_TIMESTAMP(timestamp)) ( PARTITION p_2025_q1 VALUES LESS THAN (UNIX_TIMESTAMP('2025-04-01')), PARTITION p_2025_q2 VALUES LESS THAN (UNIX_TIMESTAMP('2025-07-01')), PARTITION p_2025_q3 VALUES LESS THAN (UNIX_TIMESTAMP('2025-10-01')), PARTITION p_2025_q4 VALUES LESS THAN (UNIX_TIMESTAMP('2026-01-01')), PARTITION p_future VALUES LESS THAN MAXVALUE ); ``` #### 3. 네트워크 트래픽 (network_traffic) ```sql CREATE TABLE network_traffic ( id BIGINT AUTO_INCREMENT PRIMARY KEY, device_mac VARCHAR(17) NOT NULL, device_name VARCHAR(100), timestamp TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3), bytes_in BIGINT UNSIGNED NOT NULL, bytes_out BIGINT UNSIGNED NOT NULL, packets_in INT UNSIGNED, packets_out INT UNSIGNED, connection_count INT UNSIGNED, metadata JSON, INDEX idx_mac_time (device_mac, timestamp), INDEX idx_timestamp (timestamp) ) ENGINE=InnoDB; ``` #### 4. 시스템 리소스 (system_resources) ```sql CREATE TABLE system_resources ( id BIGINT AUTO_INCREMENT PRIMARY KEY, server_name VARCHAR(50) NOT NULL, timestamp TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3), cpu_percent DECIMAL(5,2), memory_used_gb DECIMAL(6,2), memory_total_gb DECIMAL(6,2), disk_used_gb DECIMAL(8,2), disk_total_gb DECIMAL(8,2), network_io JSON, temperature DECIMAL(4,1), load_average JSON, processes_count INT, uptime_seconds BIGINT, INDEX idx_server_time (server_name, timestamp) ) ENGINE=InnoDB; ``` #### 5. 서비스 상태 (service_status) ```sql CREATE TABLE service_status ( id BIGINT AUTO_INCREMENT PRIMARY KEY, service_name VARCHAR(50) NOT NULL, server_name VARCHAR(50) NOT NULL, timestamp TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3), status ENUM('online', 'offline', 'degraded', 'maintenance') NOT NULL, response_time_ms INT UNSIGNED, cpu_usage DECIMAL(5,2), memory_usage_mb INT, error_message TEXT, metadata JSON, INDEX idx_service_time (service_name, timestamp), INDEX idx_server_service (server_name, service_name), INDEX idx_status (status, timestamp) ) ENGINE=InnoDB; ``` #### 6. 사용자 관리 (users) ```sql CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(100) UNIQUE, password_hash VARCHAR(255), full_name VARCHAR(100), role ENUM('admin', 'family', 'guest') DEFAULT 'family', preferences JSON, last_login TIMESTAMP NULL, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_username (username), INDEX idx_email (email), INDEX idx_role (role) ) ENGINE=InnoDB; ``` #### 7. 알림 규칙 (alert_rules) ```sql CREATE TABLE alert_rules ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, description TEXT, condition_type ENUM('threshold', 'change', 'pattern', 'custom') NOT NULL, target_table VARCHAR(50) NOT NULL, target_field VARCHAR(50) NOT NULL, operator ENUM('>', '<', '>=', '<=', '=', '!=', 'contains') NOT NULL, threshold_value DECIMAL(15,4), time_window_minutes INT DEFAULT 5, severity ENUM('info', 'warning', 'critical') DEFAULT 'warning', notification_methods JSON, is_active BOOLEAN DEFAULT TRUE, cooldown_minutes INT DEFAULT 60, last_triggered TIMESTAMP NULL, created_by INT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (created_by) REFERENCES users(id), INDEX idx_active (is_active), INDEX idx_target (target_table, target_field) ) ENGINE=InnoDB; ``` #### 8. 알림 로그 (alert_logs) ```sql CREATE TABLE alert_logs ( id BIGINT AUTO_INCREMENT PRIMARY KEY, alert_rule_id INT NOT NULL, triggered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, trigger_value DECIMAL(15,4), message TEXT, severity ENUM('info', 'warning', 'critical') NOT NULL, notification_sent BOOLEAN DEFAULT FALSE, resolved_at TIMESTAMP NULL, metadata JSON, FOREIGN KEY (alert_rule_id) REFERENCES alert_rules(id) ON DELETE CASCADE, INDEX idx_rule_time (alert_rule_id, triggered_at), INDEX idx_severity (severity, triggered_at) ) ENGINE=InnoDB; ``` --- ## 🚀 Express.js 프로젝트 구조 ### 폴더 구조 ``` home-management-api/ ├── src/ │ ├── config/ │ │ ├── database.js # DB 연결 설정 │ │ ├── redis.js # Redis 캐시 설정 │ │ └── environment.js # 환경변수 관리 │ ├── models/ │ │ ├── index.js # 모델 인덱스 │ │ ├── Device.js # 디바이스 모델 │ │ ├── PowerConsumption.js # 전력 소비 모델 │ │ ├── NetworkTraffic.js # 네트워크 트래픽 모델 │ │ ├── SystemResource.js # 시스템 리소스 모델 │ │ ├── ServiceStatus.js # 서비스 상태 모델 │ │ ├── User.js # 사용자 모델 │ │ ├── AlertRule.js # 알림 규칙 모델 │ │ └── AlertLog.js # 알림 로그 모델 │ ├── controllers/ │ │ ├── deviceController.js # 디바이스 CRUD │ │ ├── powerController.js # 전력 데이터 처리 │ │ ├── networkController.js # 네트워크 데이터 처리 │ │ ├── systemController.js # 시스템 모니터링 │ │ ├── dashboardController.js # 대시보드 데이터 │ │ ├── authController.js # 인증/권한 │ │ └── alertController.js # 알림 관리 │ ├── services/ │ │ ├── deviceService.js # 디바이스 비즈니스 로직 │ │ ├── powerService.js # 전력 분석 서비스 │ │ ├── networkService.js # 네트워크 분석 서비스 │ │ ├── statisticsService.js # 통계 계산 서비스 │ │ ├── alertService.js # 알림 처리 서비스 │ │ ├── cacheService.js # 캐시 관리 서비스 │ │ └── schedulerService.js # 스케줄 작업 서비스 │ ├── routes/ │ │ ├── index.js # 라우터 인덱스 │ │ ├── devices.js # 디바이스 라우터 │ │ ├── power.js # 전력 라우터 │ │ ├── network.js # 네트워크 라우터 │ │ ├── system.js # 시스템 라우터 │ │ ├── dashboard.js # 대시보드 라우터 │ │ ├── auth.js # 인증 라우터 │ │ └── alerts.js # 알림 라우터 │ ├── middleware/ │ │ ├── auth.js # 인증 미들웨어 │ │ ├── validation.js # 데이터 검증 │ │ ├── rateLimit.js # 요청 제한 │ │ ├── errorHandler.js # 에러 처리 │ │ ├── logger.js # 로깅 미들웨어 │ │ └── cors.js # CORS 설정 │ ├── utils/ │ │ ├── logger.js # 로깅 유틸 │ │ ├── validation.js # 검증 함수들 │ │ ├── dateUtils.js # 날짜 처리 유틸 │ │ ├── mathUtils.js # 수학 계산 유틸 │ │ ├── encryption.js # 암호화 유틸 │ │ └── responseUtils.js # 응답 포매팅 │ ├── collectors/ │ │ ├── powerCollector.js # 전력 데이터 수집 │ │ ├── networkCollector.js # 네트워크 데이터 수집 │ │ ├── systemCollector.js # 시스템 데이터 수집 │ │ └── serviceCollector.js # 서비스 상태 수집 │ └── app.js # Express 앱 설정 ├── tests/ │ ├── unit/ # 단위 테스트 │ ├── integration/ # 통합 테스트 │ └── fixtures/ # 테스트 데이터 ├── docs/ │ ├── api/ # API 문서 │ └── database/ # DB 스키마 문서 ├── scripts/ │ ├── setup-db.sql # 초기 DB 설정 │ ├── seed-data.sql # 샘플 데이터 │ └── migrate.js # 마이그레이션 스크립트 ├── docker-compose.yml # Docker 구성 ├── package.json ├── .env.example └── README.md ``` --- ## 📦 Package.json 구성 ### 주요 의존성 ```json { "name": "home-management-api", "version": "1.0.0", "description": "홈 관리 시스템 백엔드 API", "main": "src/app.js", "scripts": { "start": "node src/app.js", "dev": "nodemon src/app.js", "test": "jest", "test:watch": "jest --watch", "db:migrate": "node scripts/migrate.js", "db:seed": "node scripts/seed.js", "lint": "eslint src/", "lint:fix": "eslint src/ --fix" }, "dependencies": { "express": "^4.18.2", "mysql2": "^3.6.0", "sequelize": "^6.32.1", "redis": "^4.6.7", "bcryptjs": "^2.4.3", "jsonwebtoken": "^9.0.2", "joi": "^17.9.2", "helmet": "^7.0.0", "cors": "^2.8.5", "compression": "^1.7.4", "express-rate-limit": "^6.8.1", "winston": "^3.10.0", "node-cron": "^3.0.2", "dotenv": "^16.3.1", "express-validator": "^7.0.1", "moment": "^2.29.4" }, "devDependencies": { "nodemon": "^3.0.1", "jest": "^29.6.1", "supertest": "^6.3.3", "eslint": "^8.45.0", "prettier": "^3.0.0" } } ``` --- ## 🐳 Docker Compose 구성 ### docker-compose.yml ```yaml version: '3.8' services: mariadb: image: mariadb:11-jammy container_name: home_mariadb environment: MYSQL_ROOT_PASSWORD: root_password MYSQL_DATABASE: home_management MYSQL_USER: homeuser MYSQL_PASSWORD: home_password ports: - "3306:3306" volumes: - mariadb_data:/var/lib/mysql - ./scripts/setup-db.sql:/docker-entrypoint-initdb.d/setup.sql - ./config/mariadb.cnf:/etc/mysql/conf.d/custom.cnf command: > --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --innodb-buffer-pool-size=2G --innodb-log-file-size=256M --max-connections=200 --query-cache-size=256M restart: unless-stopped networks: - home_network phpmyadmin: image: phpmyadmin/phpmyadmin:latest container_name: home_phpmyadmin environment: PMA_HOST: mariadb PMA_PORT: 3306 PMA_USER: homeuser PMA_PASSWORD: home_password UPLOAD_LIMIT: 2G MEMORY_LIMIT: 512M ports: - "8080:80" depends_on: - mariadb restart: unless-stopped networks: - home_network redis: image: redis:7-alpine container_name: home_redis ports: - "6379:6379" volumes: - redis_data:/data - ./config/redis.conf:/usr/local/etc/redis/redis.conf command: redis-server /usr/local/etc/redis/redis.conf restart: unless-stopped networks: - home_network api: build: . container_name: home_api environment: NODE_ENV: development DB_HOST: mariadb DB_PORT: 3306 DB_NAME: home_management DB_USER: homeuser DB_PASSWORD: home_password REDIS_HOST: redis REDIS_PORT: 6379 JWT_SECRET: your-jwt-secret-key API_PORT: 3000 ports: - "3000:3000" volumes: - ./src:/app/src - ./logs:/app/logs depends_on: - mariadb - redis restart: unless-stopped networks: - home_network volumes: mariadb_data: redis_data: networks: home_network: driver: bridge ``` --- ## 🔧 환경 설정 ### .env.example ```env # 서버 설정 NODE_ENV=development API_PORT=3000 API_HOST=0.0.0.0 # 데이터베이스 설정 DB_HOST=localhost DB_PORT=3306 DB_NAME=home_management DB_USER=homeuser DB_PASSWORD=home_password DB_POOL_MIN=5 DB_POOL_MAX=20 DB_TIMEOUT=30000 # Redis 설정 REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASSWORD= REDIS_DB=0 REDIS_CACHE_TTL=3600 # JWT 설정 JWT_SECRET=your-super-secret-jwt-key JWT_EXPIRES_IN=24h JWT_REFRESH_EXPIRES_IN=7d # 로깅 설정 LOG_LEVEL=info LOG_FILE_PATH=./logs/app.log LOG_MAX_SIZE=10m LOG_MAX_FILES=5 # 알림 설정 SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_USER=your-email@gmail.com SMTP_PASSWORD=your-app-password EMAIL_FROM=Home Management System # 데이터 수집 설정 POWER_COLLECTION_INTERVAL=300000 # 5분 NETWORK_COLLECTION_INTERVAL=60000 # 1분 SYSTEM_COLLECTION_INTERVAL=30000 # 30초 # API 제한 설정 RATE_LIMIT_WINDOW=900000 # 15분 RATE_LIMIT_MAX_REQUESTS=100 # 보안 설정 BCRYPT_ROUNDS=12 CORS_ORIGIN=http://localhost:3001,https://yourdomain.com ``` --- ## 📊 MariaDB 최적화 설정 ### config/mariadb.cnf ```ini [mariadb] # 기본 설정 character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci init-connect = 'SET NAMES utf8mb4' # 메모리 최적화 (Mac Mini M4 Pro 64GB 기준) innodb_buffer_pool_size = 4G innodb_log_buffer_size = 64M innodb_log_file_size = 512M key_buffer_size = 256M sort_buffer_size = 4M read_buffer_size = 2M read_rnd_buffer_size = 8M thread_cache_size = 50 table_open_cache = 4000 # 연결 설정 max_connections = 200 max_user_connections = 180 wait_timeout = 600 interactive_timeout = 600 # 쿼리 캐시 query_cache_type = 1 query_cache_size = 256M query_cache_limit = 2M # InnoDB 최적화 innodb_flush_log_at_trx_commit = 2 innodb_flush_method = O_DIRECT innodb_file_per_table = 1 innodb_io_capacity = 2000 innodb_io_capacity_max = 4000 innodb_read_io_threads = 4 innodb_write_io_threads = 4 # 시계열 데이터 최적화 innodb_compression_default = ON innodb_page_compression = ON innodb_adaptive_hash_index = ON # 로깅 general_log = OFF slow_query_log = ON slow_query_log_file = /var/log/mysql/slow.log long_query_time = 2 # 바이너리 로그 (백업/복제용) log_bin = mysql-bin binlog_format = ROW expire_logs_days = 7 max_binlog_size = 100M ``` --- ## 🔍 초기 데이터 및 인덱스 ### scripts/setup-db.sql ```sql -- 기본 관리자 사용자 생성 INSERT INTO users (username, email, password_hash, full_name, role) VALUES ('admin', 'admin@home.local', '$2b$12$encrypted_password_hash', '시스템 관리자', 'admin'), ('family', 'family@home.local', '$2b$12$encrypted_password_hash', '가족 사용자', 'family'); -- 기본 디바이스 등록 INSERT INTO devices (device_id, name, device_type, location, monitoring_enabled) VALUES ('mac_mini_m4', 'Mac Mini M4 Pro', 'server', '서재', TRUE), ('ds1525plus', 'Synology DS1525+', 'nas', '서재', TRUE), ('rt6600ax', 'Synology RT6600ax', 'router', '거실', TRUE); -- 기본 알림 규칙 INSERT INTO alert_rules (name, description, condition_type, target_table, target_field, operator, threshold_value, severity, notification_methods, created_by) VALUES ('높은 CPU 사용률', 'CPU 사용률이 80% 이상일 때 알림', 'threshold', 'system_resources', 'cpu_percent', '>', 80.0, 'warning', '["email"]', 1), ('높은 전력 소비', '전력 소비가 평소보다 50% 이상 증가했을 때', 'change', 'power_consumption', 'watts', '>', 50.0, 'warning', '["email"]', 1), ('서비스 다운', '서비스가 오프라인 상태일 때', 'threshold', 'service_status', 'status', '=', 'offline', 'critical', '["email", "push"]', 1); -- 파티션 관리 프로시저 DELIMITER // CREATE PROCEDURE CreateMonthlyPartitions() BEGIN DECLARE partition_date DATE; DECLARE partition_name VARCHAR(20); DECLARE partition_value BIGINT; SET partition_date = DATE_ADD(NOW(), INTERVAL 3 MONTH); SET partition_name = CONCAT('p_', DATE_FORMAT(partition_date, '%Y_%m')); SET partition_value = UNIX_TIMESTAMP(DATE_ADD(partition_date, INTERVAL 1 MONTH)); SET @sql = CONCAT( 'ALTER TABLE power_consumption ADD PARTITION (', 'PARTITION ', partition_name, ' VALUES LESS THAN (', partition_value, '))' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END // DELIMITER ; -- 월별 파티션 자동 생성 이벤트 CREATE EVENT monthly_partition_maintenance ON SCHEDULE EVERY 1 MONTH STARTS CURRENT_TIMESTAMP DO CALL CreateMonthlyPartitions(); -- 성능 모니터링용 뷰 CREATE VIEW device_power_summary AS SELECT d.device_id, d.name, d.device_type, AVG(pc.watts) as avg_watts, MAX(pc.watts) as max_watts, COUNT(*) as reading_count, MAX(pc.timestamp) as last_reading FROM devices d LEFT JOIN power_consumption pc ON d.device_id = pc.device_id WHERE d.monitoring_enabled = TRUE GROUP BY d.device_id, d.name, d.device_type; CREATE VIEW system_health_summary AS SELECT server_name, AVG(cpu_percent) as avg_cpu, AVG(memory_used_gb / memory_total_gb * 100) as avg_memory_pct, AVG(disk_used_gb / disk_total_gb * 100) as avg_disk_pct, MAX(timestamp) as last_update FROM system_resources WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 1 HOUR) GROUP BY server_name; ``` --- ## 🔄 CI/CD 파이프라인 구성 ### 개발 환경 구성 ``` MacBook Pro M3 Pro (개발/빌드) Mac Mini M4 Pro (개발/빌드) ↓ git push Gitea Server (git.hyungi.net - DS1525+ Container) ↓ webhook 개발 머신 (MacBook Pro / Mac Mini) ↓ docker build & test Synology DS1525+ (프로덕션 배포) ``` #### 개발 환경 특징 - **MacBook Pro M3 Pro**: 이동성이 필요한 개발 작업 - **Mac Mini M4 Pro**: 고정된 개발 환경, 장시간 빌드/테스트 - **공통 환경**: 동일한 ARM64 아키텍처로 일관된 빌드 환경 - **유연한 작업**: 두 머신 모두에서 완전한 개발 사이클 가능 ### CI/CD 워크플로우 #### 1. Gitea Actions 설정 (.gitea/workflows/deploy.yml) ```yaml name: Home Management CI/CD on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: test: runs-on: self-hosted container: image: node:18-alpine options: --network host services: mariadb: image: mariadb:11-jammy env: MARIADB_ROOT_PASSWORD: test_password MARIADB_DATABASE: home_management_test MARIADB_USER: testuser MARIADB_PASSWORD: test_password ports: - 3307:3306 options: >- --health-cmd="mariadb-admin ping -h localhost" --health-interval=10s --health-timeout=5s --health-retries=5 steps: - name: Checkout code uses: actions/checkout@v4 - name: Cache Node modules uses: actions/cache@v3 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Install dependencies run: npm ci - name: Wait for MariaDB run: | for i in {1..30}; do if nc -z localhost 3307; then break; fi echo "Waiting for MariaDB... ($i/30)" sleep 2 done - name: Run database migrations run: npm run db:migrate env: NODE_ENV: test DB_HOST: localhost DB_PORT: 3307 DB_NAME: home_management_test DB_USER: testuser DB_PASSWORD: test_password - name: Run unit tests run: npm run test:unit env: NODE_ENV: test DB_HOST: localhost DB_PORT: 3307 - name: Run integration tests run: npm run test:integration env: NODE_ENV: test DB_HOST: localhost DB_PORT: 3307 - name: Generate coverage report run: npm run test:coverage - name: Lint check run: npm run lint - name: Security audit run: npm audit --audit-level high build: needs: test runs-on: self-hosted if: gitea.ref == 'refs/heads/main' steps: - name: Checkout code uses: actions/checkout@v4 - name: Set build info run: | echo "BUILD_TIME=$(date -Iseconds)" >> $GITHUB_ENV echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "BRANCH_NAME=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV - name: Build Docker image run: | docker build \ --build-arg BUILD_TIME=$BUILD_TIME \ --build-arg COMMIT_HASH=$COMMIT_HASH \ --build-arg BRANCH_NAME=$BRANCH_NAME \ -t home-management-api:$COMMIT_HASH \ -t home-management-api:latest . - name: Run container security scan run: | docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/trivy image home-management-api:latest - name: Save Docker image run: | docker save home-management-api:latest | gzip > home-management-api.tar.gz - name: Record deployment start run: | curl -X POST http://localhost:3000/api/deployments/start \ -H "Content-Type: application/json" \ -d '{ "deployment_id": "${{ gitea.run_id }}", "commit_hash": "${{ env.COMMIT_HASH }}", "branch": "${{ env.BRANCH_NAME }}", "stage": "build" }' - name: Deploy to NAS run: | scp -o StrictHostKeyChecking=no \ home-management-api.tar.gz \ admin@ds1525plus:/volume1/docker/images/ ssh -o StrictHostKeyChecking=no admin@ds1525plus \ "cd /volume1/docker && ./deploy-api.sh $COMMIT_HASH ${{ gitea.run_id }}" - name: Record deployment result if: always() run: | STATUS=${{ job.status == 'success' && 'success' || 'failed' }} curl -X POST http://localhost:3000/api/deployments/complete \ -H "Content-Type: application/json" \ -d '{ "deployment_id": "${{ gitea.run_id }}", "status": "'$STATUS'", "stage": "deploy" }' ``` #### 2. Gitea Webhook 설정 ```bash # Gitea 서버에서 webhook 설정 # git.hyungi.net > myhome-server > Settings > Webhooks > Add Webhook Payload URL: http://developer-machine:9000/webhook # MacBook Pro 또는 Mac Mini IP Content Type: application/json Secret: your-webhook-secret Events: Push events, Pull request events # 개발 머신에서 webhook 리스너 설정 (webhook.js) const express = require('express'); const crypto = require('crypto'); const { execSync } = require('child_process'); const app = express(); app.use(express.json()); const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; const GITEA_ACTIONS_PATH = '/Users/admin/gitea-actions'; function verifySignature(payload, signature) { const computedSignature = crypto .createHmac('sha256', WEBHOOK_SECRET) .update(payload) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(`sha256=${computedSignature}`), Buffer.from(signature) ); } app.post('/webhook', (req, res) => { const signature = req.headers['x-gitea-signature']; const payload = JSON.stringify(req.body); if (!verifySignature(payload, signature)) { return res.status(401).send('Unauthorized'); } const { repository, ref, commits } = req.body; if (ref === 'refs/heads/main' || ref === 'refs/heads/develop') { console.log(`Received push to ${ref} in ${repository.full_name}`); try { // Gitea Actions Runner 트리거 execSync(`cd ${GITEA_ACTIONS_PATH} && ./act_runner exec`, { stdio: 'inherit' }); res.status(200).send('Workflow triggered'); } catch (error) { console.error('Failed to trigger workflow:', error); res.status(500).send('Workflow trigger failed'); } } else { res.status(200).send('No action needed'); } }); app.listen(9000, () => { console.log('Webhook server listening on port 9000'); }); ``` #### 2. DS1525+ 배포 스크립트 (/volume1/docker/deploy-api.sh) ```bash #!/bin/bash # 배포 설정 IMAGE_NAME="home-management-api" CONTAINER_NAME="home-api" BACKUP_DIR="/volume1/backups/deployments" LOG_FILE="/volume1/logs/deploy.log" echo "$(date): Starting deployment..." >> $LOG_FILE # 1. 기존 컨테이너 백업 if [ "$(docker ps -q -f name=$CONTAINER_NAME)" ]; then echo "Creating backup of current deployment..." >> $LOG_FILE docker commit $CONTAINER_NAME $IMAGE_NAME:backup-$(date +%Y%m%d-%H%M%S) docker stop $CONTAINER_NAME docker rm $CONTAINER_NAME fi # 2. 새 이미지 로드 echo "Loading new Docker image..." >> $LOG_FILE gunzip -c /volume1/docker/images/home-management-api.tar.gz | docker load # 3. 데이터베이스 마이그레이션 체크 echo "Checking database migrations..." >> $LOG_FILE docker run --rm --network=host -e NODE_ENV=production $IMAGE_NAME:latest npm run db:migrate:check # 4. 새 컨테이너 시작 echo "Starting new container..." >> $LOG_FILE docker run -d \ --name $CONTAINER_NAME \ --network=host \ --restart=unless-stopped \ -v /volume1/docker/config/api.env:/app/.env \ -v /volume1/logs/api:/app/logs \ -v /volume1/data/uploads:/app/uploads \ $IMAGE_NAME:latest # 5. 헬스체크 echo "Performing health check..." >> $LOG_FILE sleep 10 for i in {1..30}; do if curl -f http://localhost:3000/health > /dev/null 2>&1; then echo "$(date): Deployment successful!" >> $LOG_FILE # 이전 백업 이미지 정리 (7일 이상 된 것) docker images | grep "$IMAGE_NAME:backup" | awk '{print $2}' | tail -n +8 | xargs -I {} docker rmi $IMAGE_NAME:{} exit 0 fi echo "Waiting for service to start... ($i/30)" >> $LOG_FILE sleep 2 done echo "$(date): Deployment failed! Rolling back..." >> $LOG_FILE # 롤백 로직 LATEST_BACKUP=$(docker images | grep "$IMAGE_NAME:backup" | head -1 | awk '{print $2}') if [ ! -z "$LATEST_BACKUP" ]; then docker stop $CONTAINER_NAME 2>/dev/null docker rm $CONTAINER_NAME 2>/dev/null docker run -d --name $CONTAINER_NAME --network=host --restart=unless-stopped $IMAGE_NAME:backup-$LATEST_BACKUP fi exit 1 ``` #### 3. Gitea Actions Runner 설정 (개발 머신) ```bash # Gitea Actions Runner 설치 및 설정 (MacBook Pro / Mac Mini 공통) cd /Users/$(whoami) wget https://gitea.com/gitea/act_runner/releases/download/v0.2.6/act_runner-0.2.6-darwin-arm64 chmod +x act_runner-0.2.6-darwin-arm64 sudo mv act_runner-0.2.6-darwin-arm64 /usr/local/bin/act_runner # Runner 등록 (Gitea에서 토큰 생성 후) act_runner register \ --instance https://git.hyungi.net \ --token YOUR_RUNNER_TOKEN \ --name $(hostname)-runner \ --labels self-hosted,macOS,ARM64 # 설정 파일 생성 (.runner 파일이 생성됨) # 데몬으로 실행하기 위한 LaunchDaemon 설정 sudo tee /Library/LaunchDaemons/com.gitea.act_runner.plist << EOF Label com.gitea.act_runner ProgramArguments /usr/local/bin/act_runner daemon --config /Users/$(whoami)/.runner RunAtLoad KeepAlive StandardOutPath /usr/local/var/log/act_runner.log StandardErrorPath /usr/local/var/log/act_runner.error.log WorkingDirectory /Users/$(whoami) UserName $(whoami) EOF # 서비스 시작 sudo launchctl load /Library/LaunchDaemons/com.gitea.act_runner.plist sudo launchctl start com.gitea.act_runner ``` #### 4. DS1525+ 배포 스크립트 업데이트 ```bash #!/bin/bash # /volume1/docker/deploy-api.sh # 배포 설정 IMAGE_NAME="home-management-api" CONTAINER_NAME="home-api" BACKUP_DIR="/volume1/backups/deployments" LOG_FILE="/volume1/logs/deploy.log" COMMIT_HASH=$1 DEPLOYMENT_ID=$2 echo "$(date): Starting deployment $DEPLOYMENT_ID (commit: $COMMIT_HASH)..." >> $LOG_FILE # 배포 상태를 API에 보고 function report_status() { local status=$1 local message=$2 curl -s -X POST http://localhost:3000/api/deployments/update \ -H "Content-Type: application/json" \ -d "{ \"deployment_id\": \"$DEPLOYMENT_ID\", \"status\": \"$status\", \"message\": \"$message\", \"timestamp\": \"$(date -Iseconds)\" }" || true } # 1. 현재 컨테이너 상태 확인 및 백업 if [ "$(docker ps -q -f name=$CONTAINER_NAME)" ]; then echo "Creating backup of current deployment..." >> $LOG_FILE BACKUP_TAG="backup-$(date +%Y%m%d-%H%M%S)" docker commit $CONTAINER_NAME $IMAGE_NAME:$BACKUP_TAG report_status "backing_up" "Creating backup: $BACKUP_TAG" # Graceful shutdown docker exec $CONTAINER_NAME npm run graceful-shutdown 2>/dev/null || true sleep 5 docker stop $CONTAINER_NAME docker rm $CONTAINER_NAME fi # 2. 새 이미지 로드 echo "Loading new Docker image..." >> $LOG_FILE report_status "loading_image" "Loading Docker image" if ! gunzip -c /volume1/docker/images/home-management-api.tar.gz | docker load; then echo "Failed to load Docker image" >> $LOG_FILE report_status "failed" "Failed to load Docker image" exit 1 fi # 3. 데이터베이스 마이그레이션 체크 echo "Checking database migrations..." >> $LOG_FILE report_status "migrating" "Running database migrations" if ! docker run --rm --network=host \ -v /volume1/docker/config/api.env:/app/.env \ $IMAGE_NAME:latest npm run db:migrate; then echo "Database migration failed" >> $LOG_FILE report_status "failed" "Database migration failed" exit 1 fi # 4. 새 컨테이너 시작 echo "Starting new container..." >> $LOG_FILE report_status "starting" "Starting new container" docker run -d \ --name $CONTAINER_NAME \ --network=host \ --restart=unless-stopped \ -v /volume1/docker/config/api.env:/app/.env \ -v /volume1/logs/api:/app/logs \ -v /volume1/data/uploads:/app/uploads \ -e COMMIT_HASH=$COMMIT_HASH \ -e DEPLOYMENT_ID=$DEPLOYMENT_ID \ $IMAGE_NAME:latest if [ $? -ne 0 ]; then echo "Failed to start container" >> $LOG_FILE report_status "failed" "Failed to start container" exit 1 fi # 5. 헬스체크 및 성능 테스트 echo "Performing health check..." >> $LOG_FILE report_status "health_check" "Performing health check" sleep 15 for i in {1..30}; do if curl -f -s http://localhost:3000/health > /dev/null 2>&1; then # 기본 기능 테스트 if curl -f -s http://localhost:3000/api/devices > /dev/null 2>&1; then echo "$(date): Deployment successful!" >> $LOG_FILE report_status "success" "Deployment completed successfully" # 성능 벤치마크 실행 /volume1/docker/scripts/performance-test.sh $DEPLOYMENT_ID & # 오래된 백업 이미지 정리 (7개 이상) docker images | grep "$IMAGE_NAME:backup" | tail -n +8 | \ awk '{print $1":"$2}' | xargs -r docker rmi exit 0 fi fi echo "Waiting for service to start... ($i/30)" >> $LOG_FILE sleep 2 done # 6. 실패시 롤백 echo "$(date): Deployment failed! Rolling back..." >> $LOG_FILE report_status "rolling_back" "Deployment failed, rolling back" docker stop $CONTAINER_NAME 2>/dev/null || true docker rm $CONTAINER_NAME 2>/dev/null || true # 최신 백업으로 롤백 LATEST_BACKUP=$(docker images | grep "$IMAGE_NAME:backup" | head -1 | awk '{print $2}') if [ ! -z "$LATEST_BACKUP" ]; then docker run -d \ --name $CONTAINER_NAME \ --network=host \ --restart=unless-stopped \ -v /volume1/docker/config/api.env:/app/.env \ -v /volume1/logs/api:/app/logs \ -v /volume1/data/uploads:/app/uploads \ $IMAGE_NAME:backup-$LATEST_BACKUP report_status "rolled_back" "Rolled back to: backup-$LATEST_BACKUP" else report_status "failed" "Rollback failed - no backup available" fi exit 1 ``` #### 4. Docker 컨테이너 모니터링 (DS1525+) ```bash # /volume1/docker/monitor-containers.sh #!/bin/bash SERVICES=("home-api" "home-mariadb" "home-redis" "home-phpmyadmin") WEBHOOK_URL="http://developer-machine:3000/api/alerts/webhook" for service in "${SERVICES[@]}"; do if ! docker ps --format "table {{.Names}}" | grep -q "^${service}$"; then # 서비스 다운 알림 curl -X POST $WEBHOOK_URL \ -H "Content-Type: application/json" \ -d "{ \"service\": \"$service\", \"status\": \"down\", \"timestamp\": \"$(date -Iseconds)\", \"server\": \"ds1525plus\" }" # 자동 재시작 시도 echo "$(date): Attempting to restart $service..." >> /volume1/logs/monitor.log docker start $service fi done ``` ### Gitea 연동 최적화 #### 1. Gitea 서버 설정 (DS1525+ 컨테이너) ```yaml # docker-compose.yml에 추가 gitea: image: gitea/gitea:1.21 container_name: home_gitea environment: - USER_UID=1000 - USER_GID=1000 - GITEA__database__DB_TYPE=mysql - GITEA__database__HOST=mariadb:3306 - GITEA__database__NAME=gitea - GITEA__database__USER=gitea - GITEA__database__PASSWD=gitea_password - GITEA__server__DOMAIN=git.hyungi.net - GITEA__server__HTTP_PORT=3000 - GITEA__server__ROOT_URL=https://git.hyungi.net - GITEA__actions__ENABLED=true - GITEA__actions__DEFAULT_ACTIONS_URL=https://github.com restart: unless-stopped networks: - home_network volumes: - gitea_data:/data - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro ports: - "3000:3000" - "222:22" depends_on: - mariadb ``` #### 2. 배포 상태 추적 API ```javascript // routes/deployments.js const express = require('express'); const router = express.Router(); const DeploymentService = require('../services/deploymentService'); // 배포 시작 기록 router.post('/start', async (req, res) => { try { const { deployment_id, commit_hash, branch, stage } = req.body; const logId = await DeploymentService.recordDeployment({ id: deployment_id, stage: stage || 'build', commit: commit_hash, branch: branch, user: req.user?.username || 'system' }); res.json({ log_id: logId, status: 'recorded' }); } catch (error) { res.status(500).json({ error: error.message }); } }); // 배포 상태 업데이트 router.post('/update', async (req, res) => { try { const { deployment_id, status, message } = req.body; await DeploymentService.updateDeploymentStatus( deployment_id, status, message ); // 실시간 알림 (WebSocket) req.app.get('io').emit('deployment_update', { deployment_id, status, message, timestamp: new Date() }); res.json({ status: 'updated' }); } catch (error) { res.status(500).json({ error: error.message }); } }); // 배포 완료 처리 router.post('/complete', async (req, res) => { try { const { deployment_id, status, stage } = req.body; await DeploymentService.completeDeployment( deployment_id, status, stage ); if (status === 'success') { // 성공시 현재 버전 업데이트 await DeploymentService.updateActiveVersion(deployment_id); } res.json({ status: 'completed' }); } catch (error) { res.status(500).json({ error: error.message }); } }); // 배포 히스토리 조회 router.get('/history', async (req, res) => { try { const { limit = 20, offset = 0 } = req.query; const history = await DeploymentService.getDeploymentHistory( parseInt(limit), parseInt(offset) ); res.json(history); } catch (error) { res.status(500).json({ error: error.message }); } }); module.exports = router; ``` #### 3. 브랜치별 배포 전략 ```yaml # .gitea/workflows/deploy.yml 추가 구성 on: push: branches: [ main, develop, feature/* ] pull_request: branches: [ main, develop ] jobs: test: runs-on: self-hosted # 모든 브랜치에서 테스트 실행 build-develop: needs: test runs-on: self-hosted if: gitea.ref == 'refs/heads/develop' steps: - name: Deploy to Staging run: | docker build -t home-management-api:develop . # 스테이징 환경 배포 (포트 3001) ssh admin@ds1525plus \ "cd /volume1/docker && ./deploy-staging.sh develop" build-main: needs: test runs-on: self-hosted if: gitea.ref == 'refs/heads/main' steps: - name: Deploy to Production run: | docker build -t home-management-api:latest . # 프로덕션 배포 (포트 3000) ssh admin@ds1525plus \ "cd /volume1/docker && ./deploy-api.sh latest ${{ gitea.run_id }}" feature-test: needs: test runs-on: self-hosted if: startsWith(gitea.ref, 'refs/heads/feature/') steps: - name: Feature Branch Tests run: | # 추가 테스트만 수행, 배포 안함 npm run test:e2e npm run test:security ``` #### 4. Gitea Actions 캐시 최적화 ```yaml # 캐시 설정으로 빌드 시간 단축 steps: - name: Cache Node modules uses: actions/cache@v3 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Cache Docker layers uses: actions/cache@v3 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ gitea.sha }} restore-keys: | ${{ runner.os }}-buildx- ``` --- ## 🔧 환경 설정 #### 1. 개발 환경 (MacBook Pro / Mac Mini) ```env # .env.development NODE_ENV=development DB_HOST=ds1525plus # NAS의 개발용 DB 연결 DB_PORT=3306 API_PORT=3000 CORS_ORIGIN=http://localhost:3001 LOG_LEVEL=debug REDIS_HOST=ds1525plus ``` #### 2. 로컬 개발 환경 (개발 머신) ```env # .env.local NODE_ENV=development DB_HOST=localhost # 로컬 Docker DB DB_PORT=3306 API_PORT=3000 CORS_ORIGIN=http://localhost:3001 LOG_LEVEL=debug REDIS_HOST=localhost ``` #### 3. 테스트 환경 (Mac Mini CI) ```env # .env.test NODE_ENV=test DB_HOST=localhost DB_PORT=3307 DB_NAME=home_management_test API_PORT=3001 REDIS_HOST=localhost REDIS_DB=1 ``` #### 4. 프로덕션 환경 (DS1525+) ```env # /volume1/docker/config/api.env NODE_ENV=production DB_HOST=localhost DB_PORT=3306 DB_NAME=home_management API_PORT=3000 CORS_ORIGIN=https://home.yourdomain.com LOG_LEVEL=info REDIS_HOST=localhost # 보안 설정 JWT_SECRET=production-jwt-secret-key BCRYPT_ROUNDS=14 # 프로덕션 최적화 NODE_OPTIONS=--max-old-space-size=2048 PM2_INSTANCES=2 ``` ### 데이터베이스 마이그레이션 관리 #### 1. 마이그레이션 스크립트 구조 ``` migrations/ ├── 001_initial_schema.sql ├── 002_add_alert_system.sql ├── 003_add_user_preferences.sql ├── 004_optimize_indexes.sql └── 005_add_ci_cd_logs.sql ``` #### 2. 마이그레이션 실행기 (scripts/migrate.js) ```javascript // 500자 이하로 간단하게 구성 const mysql = require('mysql2/promise'); const fs = require('fs').promises; const path = require('path'); async function runMigrations() { const connection = await mysql.createConnection({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, multipleStatements: true }); // 마이그레이션 테이블 생성 await connection.execute(` CREATE TABLE IF NOT EXISTS migrations ( id INT AUTO_INCREMENT PRIMARY KEY, filename VARCHAR(255) UNIQUE NOT NULL, executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); const migrationsDir = path.join(__dirname, '../migrations'); const files = await fs.readdir(migrationsDir); for (const file of files.sort()) { if (!file.endsWith('.sql')) continue; const [rows] = await connection.execute( 'SELECT id FROM migrations WHERE filename = ?', [file] ); if (rows.length === 0) { console.log(`Running migration: ${file}`); const sql = await fs.readFile(path.join(migrationsDir, file), 'utf8'); await connection.execute(sql); await connection.execute( 'INSERT INTO migrations (filename) VALUES (?)', [file] ); } } await connection.end(); } module.exports = { runMigrations }; ``` ### 로그 및 모니터링 통합 #### 1. 배포 로그 수집 테이블 ```sql -- 005_add_ci_cd_logs.sql CREATE TABLE deployment_logs ( id BIGINT AUTO_INCREMENT PRIMARY KEY, deployment_id VARCHAR(100) NOT NULL, stage ENUM('test', 'build', 'deploy', 'rollback') NOT NULL, status ENUM('started', 'success', 'failed') NOT NULL, commit_hash VARCHAR(40), branch_name VARCHAR(100), deployed_by VARCHAR(100), start_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, end_time TIMESTAMP NULL, duration_seconds INT GENERATED ALWAYS AS ( CASE WHEN end_time IS NOT NULL THEN TIMESTAMPDIFF(SECOND, start_time, end_time) ELSE NULL END ) STORED, error_message TEXT, metadata JSON, INDEX idx_deployment_stage (deployment_id, stage), INDEX idx_status_time (status, start_time) ) ENGINE=InnoDB; CREATE TABLE service_deployments ( id INT AUTO_INCREMENT PRIMARY KEY, service_name VARCHAR(50) NOT NULL, version VARCHAR(50) NOT NULL, commit_hash VARCHAR(40) NOT NULL, deployed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, deployed_by VARCHAR(100), rollback_version VARCHAR(50) NULL, is_active BOOLEAN DEFAULT TRUE, performance_baseline JSON, -- 배포 후 성능 지표 INDEX idx_service_version (service_name, version), INDEX idx_active (is_active, deployed_at) ) ENGINE=InnoDB; ``` #### 2. 배포 성능 모니터링 ```javascript // services/deploymentService.js class DeploymentService { async recordDeployment(deploymentData) { // 배포 기록 저장 (500자 이하) const deployment = await this.db.query(` INSERT INTO deployment_logs (deployment_id, stage, status, commit_hash, branch_name, deployed_by) VALUES (?, ?, ?, ?, ?, ?) `, [deploymentData.id, deploymentData.stage, 'started', deploymentData.commit, deploymentData.branch, deploymentData.user]); return deployment.insertId; } async updateDeploymentStatus(logId, status, errorMessage = null) { // 배포 상태 업데이트 await this.db.query(` UPDATE deployment_logs SET status = ?, end_time = CURRENT_TIMESTAMP, error_message = ? WHERE id = ? `, [status, errorMessage, logId]); } async getDeploymentHistory(limit = 50) { // 최근 배포 이력 조회 const [rows] = await this.db.query(` SELECT * FROM deployment_logs ORDER BY start_time DESC LIMIT ? `, [limit]); return rows; } } ``` ### 성능 벤치마크 자동화 #### 1. 배포 후 성능 테스트 ```bash # scripts/performance-test.sh #!/bin/bash API_URL="http://localhost:3000" RESULTS_FILE="/tmp/perf-results.json" echo "Running performance tests after deployment..." # API 응답 시간 테스트 ab -n 1000 -c 10 -g /tmp/ab-results.tsv $API_URL/api/devices/ > /tmp/ab-output.txt # 메모리 사용량 체크 MEMORY_USAGE=$(docker stats --no-stream --format "table {{.Container}}\t{{.MemUsage}}" | grep home-api) # 결과를 JSON으로 저장 echo "{ \"timestamp\": \"$(date -Iseconds)\", \"memory_usage\": \"$MEMORY_USAGE\", \"ab_results\": \"$(tail -5 /tmp/ab-output.txt)\" }" > $RESULTS_FILE # 성능 저하 감지시 알림 if [ $(awk '/Time per request/ {print $4}' /tmp/ab-output.txt | head -1 | cut -d. -f1) -gt 100 ]; then curl -X POST http://developer-machine:3000/api/alerts/webhook \ -d '{"type":"performance_degradation","details":"'$(cat $RESULTS_FILE)'"}' fi ``` --- ## 🚀 개발 시작 체크리스트 ### 1. 환경 준비 - [ ] Node.js 18+ 설치 - [ ] Docker & Docker Compose 설치 - [ ] Git 저장소 클론: `git clone https://git.hyungi.net/hyungi/myhome-server.git` - [ ] 프로젝트 폴더 구조 생성 ### 2. 데이터베이스 설정 - [ ] `docker-compose up -d mariadb` 실행 - [ ] phpMyAdmin 접속 확인 (localhost:8080) - [ ] 테이블 생성 및 초기 데이터 삽입 - [ ] 인덱스 및 파티션 설정 확인 ### 3. 백엔드 개발 순서 1. **기본 설정**: Express 앱, DB 연결, 미들웨어 설정 2. **모델 개발**: Sequelize 모델 정의 (devices → power_consumption → network_traffic) 3. **컨트롤러 개발**: 기본 CRUD 작업 (GET, POST, PUT, DELETE) 4. **서비스 계층**: 비즈니스 로직 분리 (통계, 분석, 집계) 5. **라우터 연결**: API 엔드포인트 구성 6. **데이터 수집기**: 실제 하드웨어 연동 모듈 7. **알림 시스템**: 실시간 모니터링 및 알림 8. **테스트 작성**: 단위 테스트 및 통합 테스트 ### 4. 성능 최적화 - [ ] Redis 캐싱 구현 - [ ] DB 쿼리 최적화 - [ ] API 응답 시간 모니터링 - [ ] 메모리 사용량 추적 ### 5. CI/CD 파이프라인 구축 - [ ] Gitea Actions 활성화 및 설정 - [ ] 개발 머신(MacBook Pro/Mac Mini)에 Gitea Actions Runner 설치 - [ ] DS1525+에 Docker 환경 및 Gitea 서버 구성 - [ ] 배포 스크립트 작성 및 테스트 (프로덕션/스테이징) - [ ] 데이터베이스 마이그레이션 자동화 - [ ] 브랜치별 배포 전략 설정 (main→production, develop→staging) - [ ] 성능 모니터링 및 배포 알림 설정 - [ ] Webhook 서버 구성 (개발 머신) ### 6. 보안 설정 - [ ] JWT 인증 구현 - [ ] API 속도 제한 설정 - [ ] HTTPS 인증서 구성 (Let's Encrypt) - [ ] 입력 데이터 검증 강화 - [ ] SSH Key 기반 서버 간 통신 설정 - [ ] Gitea 보안 설정 (2FA, 브랜치 보호) ### 7. 모니터링 및 알림 - [ ] 배포 성공/실패 실시간 알림 (WebSocket) - [ ] 서비스 상태 모니터링 대시보드 - [ ] 성능 저하 감지 및 자동 알림 - [ ] 로그 중앙화 (Mac Mini → DS1525+) - [ ] 배포 메트릭 수집 및 분석 ## 🎯 Gitea 기반 CI/CD 장점 ### 완전 프라이빗 환경 - **내부 네트워크**: 모든 CI/CD 프로세스가 홈 네트워크 내에서 실행 - **데이터 보안**: 소스코드와 빌드 아티팩트가 외부로 유출되지 않음 - **비용 효율**: 외부 CI/CD 서비스 비용 절약 ### 하드웨어 최적화 - **MacBook Pro**: 모바일 개발 환경, 외부 작업 가능 - **Mac Mini**: 고정 개발 환경, 안정적인 빌드 서버 역할 - **공통 ARM64**: 두 머신 모두 네이티브 빌드 환경 - **DS1525+**: 안정적인 Git 서버 및 프로덕션 환경 - **통합 관리**: 홈 관리 시스템과 개발 도구 통합 ### 확장성 - **멀티 브랜치**: feature/develop/main 브랜치별 배포 전략 - **스테이징**: develop 브랜치로 스테이징 환경 자동 배포 - **롤백**: 실패시 자동 롤백 및 알림 이제 완전히 프라이빗한 환경에서 기업급 CI/CD 파이프라인을 구축할 수 있습니다. Gitea 서버로 완전한 DevOps 환경을 홈에서 운영하는 것이 가능하겠네요! 이 계획서를 따라 단계별로 구현하면 확장성과 유지보수성을 갖춘 견고한 홈 관리 시스템과 함께 전문적인 CI/CD 파이프라인을 구축할 수 있습니다.