Add 5 more runbooks
- aws-ses-setup.md: AWS SES email configuration - anvil-ses-final-setup.md: Anvil SES final setup - n8n-setup-guide.md: n8n workflow automation - gitea-setup.md: Gitea server installation - cloudflare-vault-integration.md: Cloudflare + Vault integration Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
14
README.md
14
README.md
@@ -14,6 +14,11 @@ DevOps 운영 매뉴얼 및 기술 가이드 모음
|
|||||||
| [kitty-setup-guide.md](kitty-setup-guide.md) | Kitty 터미널 설정 가이드 |
|
| [kitty-setup-guide.md](kitty-setup-guide.md) | Kitty 터미널 설정 가이드 |
|
||||||
| [claude_communication_flow.md](claude_communication_flow.md) | Claude 통신 흐름 문서 |
|
| [claude_communication_flow.md](claude_communication_flow.md) | Claude 통신 흐름 문서 |
|
||||||
| [anvil-load-test-report.md](anvil-load-test-report.md) | Anvil 부하 테스트 리포트 |
|
| [anvil-load-test-report.md](anvil-load-test-report.md) | Anvil 부하 테스트 리포트 |
|
||||||
|
| [aws-ses-setup.md](aws-ses-setup.md) | AWS SES 이메일 설정 |
|
||||||
|
| [anvil-ses-final-setup.md](anvil-ses-final-setup.md) | Anvil SES 최종 설정 |
|
||||||
|
| [n8n-setup-guide.md](n8n-setup-guide.md) | n8n 워크플로우 자동화 설정 |
|
||||||
|
| [gitea-setup.md](gitea-setup.md) | Gitea Git 서버 설치 및 설정 |
|
||||||
|
| [cloudflare-vault-integration.md](cloudflare-vault-integration.md) | Cloudflare + Vault 연동 |
|
||||||
|
|
||||||
## 카테고리
|
## 카테고리
|
||||||
|
|
||||||
@@ -26,6 +31,15 @@ DevOps 운영 매뉴얼 및 기술 가이드 모음
|
|||||||
### 인프라
|
### 인프라
|
||||||
- incus-meilisearch-manual.md
|
- incus-meilisearch-manual.md
|
||||||
- anvil-load-test-report.md
|
- anvil-load-test-report.md
|
||||||
|
- gitea-setup.md
|
||||||
|
|
||||||
|
### 이메일
|
||||||
|
- aws-ses-setup.md
|
||||||
|
- anvil-ses-final-setup.md
|
||||||
|
|
||||||
|
### 자동화
|
||||||
|
- n8n-setup-guide.md
|
||||||
|
- cloudflare-vault-integration.md
|
||||||
|
|
||||||
### 개발 환경
|
### 개발 환경
|
||||||
- kitty-setup-guide.md
|
- kitty-setup-guide.md
|
||||||
|
|||||||
195
anvil-ses-final-setup.md
Normal file
195
anvil-ses-final-setup.md
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
# AWS SES 완벽 설정 완료 - anvil.it.com
|
||||||
|
|
||||||
|
## 🎉 설정 완료 상태
|
||||||
|
**Mail-Tester 점수: 10/10 (만점)** ✅
|
||||||
|
|
||||||
|
설정 완료일: 2025-09-06
|
||||||
|
도메인: anvil.it.com
|
||||||
|
리전: ap-northeast-2 (Seoul)
|
||||||
|
|
||||||
|
## 📧 SMTP 설정 정보
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
SMTP_HOST: email-smtp.ap-northeast-2.amazonaws.com
|
||||||
|
SMTP_PORT: 587
|
||||||
|
SMTP_TLS: true
|
||||||
|
SMTP_USERNAME: [Vault: secret/aws/ses/smtp -> smtp_username]
|
||||||
|
SMTP_PASSWORD: [Vault: secret/aws/ses/smtp -> smtp_password]
|
||||||
|
SMTP_FROM: noreply@anvil.it.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vault에서 인증 정보 조회
|
||||||
|
```bash
|
||||||
|
# 전체 설정 조회
|
||||||
|
vault kv get secret/aws/ses/smtp
|
||||||
|
|
||||||
|
# SMTP Password만 조회
|
||||||
|
vault kv get -field=smtp_password secret/aws/ses/smtp
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔐 DNS 레코드 설정
|
||||||
|
|
||||||
|
### 1. SPF 레코드 (TXT)
|
||||||
|
```
|
||||||
|
anvil.it.com TXT "v=spf1 include:_spf.mx.cloudflare.net include:amazonses.com ~all"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. DKIM 레코드 (CNAME × 3)
|
||||||
|
```
|
||||||
|
dgcehnldehfmfgpvrrbc6drwasiibhnp._domainkey.anvil.it.com CNAME dgcehnldehfmfgpvrrbc6drwasiibhnp.dkim.amazonses.com
|
||||||
|
spopdscdt2sxngqzl5ir66k3ed6og7ut._domainkey.anvil.it.com CNAME spopdscdt2sxngqzl5ir66k3ed6og7ut.dkim.amazonses.com
|
||||||
|
55l5wnmktvacgyfpt6sovcgb2rqexrpy._domainkey.anvil.it.com CNAME 55l5wnmktvacgyfpt6sovcgb2rqexrpy.dkim.amazonses.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. DMARC 레코드 (TXT)
|
||||||
|
```
|
||||||
|
_dmarc.anvil.it.com TXT "v=DMARC1;p=quarantine;pct=25;rua=mailto:908761dcafa547a981e283a21768d69f@dmarc-reports.cloudflare.net,mailto:dmarc-reports@anvil.it.com;ruf=mailto:dmarc-failures@anvil.it.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. MAIL FROM 도메인 설정
|
||||||
|
```
|
||||||
|
# MX 레코드
|
||||||
|
bounce.anvil.it.com MX 10 feedback-smtp.ap-northeast-2.amazonses.com
|
||||||
|
|
||||||
|
# SPF 레코드
|
||||||
|
bounce.anvil.it.com TXT "v=spf1 include:amazonses.com ~all"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 도메인 검증 (TXT)
|
||||||
|
```
|
||||||
|
_amazonses.anvil.it.com TXT "0cuw9v32N+aeFiNlTh2Poxglgzf3BlmFRjVOjeLEdy4="
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ AWS SES 상태
|
||||||
|
|
||||||
|
### 도메인 검증
|
||||||
|
- **Status**: Success ✅
|
||||||
|
- **DKIM**: Success & Enabled ✅
|
||||||
|
- **MAIL FROM**: bounce.anvil.it.com (Success) ✅
|
||||||
|
|
||||||
|
### 발송 한도
|
||||||
|
- **일일 최대**: 50,000통
|
||||||
|
- **초당 최대**: 14통
|
||||||
|
- **현재 환경**: Production Ready
|
||||||
|
|
||||||
|
## 🎯 컴플라이언스 달성 상태
|
||||||
|
|
||||||
|
| 요구사항 | 상태 | 점수 |
|
||||||
|
|---------|------|------|
|
||||||
|
| SPF Authentication | ✅ Compliant | Pass |
|
||||||
|
| DKIM Authentication | ✅ Compliant | Pass |
|
||||||
|
| DMARC Authentication | ✅ Quarantine (25%) | Pass |
|
||||||
|
| From Header Alignment | ✅ Compliant | Pass |
|
||||||
|
| DNS Records | ✅ Compliant | Pass |
|
||||||
|
| Encryption | ✅ TLS Required | Pass |
|
||||||
|
| One-click Unsubscribe | ✅ RFC 8058 | Pass |
|
||||||
|
| Honor Unsubscribe | ✅ Compliant | Pass |
|
||||||
|
|
||||||
|
**최종 점수: 8/8 (완전 준수)** 🏆
|
||||||
|
|
||||||
|
## 📱 애플리케이션 통합 예시
|
||||||
|
|
||||||
|
### Node.js (nodemailer)
|
||||||
|
```javascript
|
||||||
|
const nodemailer = require('nodemailer');
|
||||||
|
|
||||||
|
const transporter = nodemailer.createTransporter({
|
||||||
|
host: 'email-smtp.ap-northeast-2.amazonaws.com',
|
||||||
|
port: 587,
|
||||||
|
secure: false,
|
||||||
|
auth: {
|
||||||
|
user: process.env.SMTP_USERNAME, // SMTP Username
|
||||||
|
pass: process.env.SMTP_PASSWORD // SMTP Password (변환된 값)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// RFC 8058 호환 메일 발송
|
||||||
|
const mailOptions = {
|
||||||
|
from: 'noreply@anvil.it.com',
|
||||||
|
to: 'user@example.com',
|
||||||
|
subject: 'Welcome!',
|
||||||
|
html: '<h1>Welcome!</h1>',
|
||||||
|
headers: {
|
||||||
|
'List-Unsubscribe': '<mailto:unsubscribe@anvil.it.com>, <https://anvil.it.com/unsubscribe>',
|
||||||
|
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Python (smtplib)
|
||||||
|
```python
|
||||||
|
import smtplib
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
|
||||||
|
msg = MIMEMultipart()
|
||||||
|
msg['From'] = 'noreply@anvil.it.com'
|
||||||
|
msg['To'] = 'user@example.com'
|
||||||
|
msg['Subject'] = 'Welcome!'
|
||||||
|
msg['List-Unsubscribe'] = '<mailto:unsubscribe@anvil.it.com>, <https://anvil.it.com/unsubscribe>'
|
||||||
|
msg['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click'
|
||||||
|
|
||||||
|
server = smtplib.SMTP('email-smtp.ap-northeast-2.amazonaws.com', 587)
|
||||||
|
server.starttls()
|
||||||
|
server.login(smtp_username, smtp_password)
|
||||||
|
server.send_message(msg)
|
||||||
|
server.quit()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 성과 요약
|
||||||
|
|
||||||
|
1. **완벽한 인증 체계**: SPF, DKIM, DMARC 모두 통과
|
||||||
|
2. **최고 수준 컴플라이언스**: RFC 8058 One-click Unsubscribe 구현
|
||||||
|
3. **Gmail 최적화**: Postmaster Tools 준비 완료
|
||||||
|
4. **확장 가능한 구조**: 일일 50K 발송 지원
|
||||||
|
5. **보안 강화**: 단계적 DMARC 정책 (quarantine 25%)
|
||||||
|
|
||||||
|
## 📊 모니터링 및 관리
|
||||||
|
|
||||||
|
### Gmail Postmaster Tools
|
||||||
|
- URL: https://postmaster.google.com
|
||||||
|
- 도메인 등록: anvil.it.com
|
||||||
|
- 데이터 확인: 1-2일 후부터
|
||||||
|
|
||||||
|
### DMARC 리포트
|
||||||
|
- **집계 리포트**: dmarc-reports@anvil.it.com
|
||||||
|
- **실패 리포트**: dmarc-failures@anvil.it.com
|
||||||
|
- **Cloudflare**: 자동 대시보드 제공
|
||||||
|
|
||||||
|
### AWS SES 모니터링
|
||||||
|
```bash
|
||||||
|
# 발송 통계
|
||||||
|
aws ses get-send-statistics --region ap-northeast-2
|
||||||
|
|
||||||
|
# 발송 한도 확인
|
||||||
|
aws ses get-send-quota --region ap-northeast-2
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 향후 개선 계획
|
||||||
|
|
||||||
|
### 1단계 (완료): 기본 설정
|
||||||
|
- ✅ 도메인 검증
|
||||||
|
- ✅ DKIM 활성화
|
||||||
|
- ✅ SPF 설정
|
||||||
|
- ✅ DMARC 모니터링
|
||||||
|
|
||||||
|
### 2단계 (완료): 컴플라이언스
|
||||||
|
- ✅ DMARC quarantine (25%)
|
||||||
|
- ✅ One-click Unsubscribe
|
||||||
|
- ✅ RFC 8058 준수
|
||||||
|
|
||||||
|
### 3단계 (2주 후): 정책 강화
|
||||||
|
- DMARC: p=quarantine;pct=100
|
||||||
|
- 평판 안정화 후 p=reject 고려
|
||||||
|
- Gmail Postmaster 데이터 분석
|
||||||
|
|
||||||
|
### 4단계 (1개월 후): 고도화
|
||||||
|
- 발송량 점진적 증가 (Warm-up)
|
||||||
|
- A/B 테스트 기반 최적화
|
||||||
|
- 고급 모니터링 구축
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**🎯 결과: anvil.it.com 메일 시스템은 업계 최고 수준으로 설정되었습니다!**
|
||||||
|
|
||||||
|
Mail-Tester 10/10 점수는 모든 주요 메일 서비스에서 anvil.it.com을 신뢰할 수 있는 발신자로 인식한다는 의미입니다.
|
||||||
271
aws-ses-setup.md
Normal file
271
aws-ses-setup.md
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
# AWS SES 메일 발송 설정 가이드
|
||||||
|
|
||||||
|
> 작성일: 2026-01-12
|
||||||
|
> 리전: ap-northeast-2 (서울)
|
||||||
|
|
||||||
|
## 개요
|
||||||
|
|
||||||
|
AWS SES(Simple Email Service)를 사용하여 서버에서 메일을 발송하기 위한 설정 가이드입니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 계정 상태
|
||||||
|
|
||||||
|
| 항목 | 값 |
|
||||||
|
|------|-----|
|
||||||
|
| 리전 | ap-northeast-2 (서울) |
|
||||||
|
| 모드 | **프로덕션** (샌드박스 해제됨) |
|
||||||
|
| 24시간 발송 한도 | 50,000통 |
|
||||||
|
| 초당 발송률 | 14통/초 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 인증된 도메인
|
||||||
|
|
||||||
|
| 도메인 | 도메인 인증 | DKIM |
|
||||||
|
|--------|------------|------|
|
||||||
|
| `anvil.it.com` | ✅ Success | ✅ Success |
|
||||||
|
| `ironclad.it.com` | ✅ Success | ✅ Success |
|
||||||
|
|
||||||
|
### 사용 가능한 발신자 주소
|
||||||
|
|
||||||
|
```
|
||||||
|
*@anvil.it.com (예: noreply@anvil.it.com, support@anvil.it.com)
|
||||||
|
*@ironclad.it.com (예: noreply@ironclad.it.com, admin@ironclad.it.com)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## DNS 설정 (Cloudflare)
|
||||||
|
|
||||||
|
### anvil.it.com
|
||||||
|
|
||||||
|
**TXT 레코드 (도메인 소유권 확인)**
|
||||||
|
| Type | Name | Content |
|
||||||
|
|------|------|---------|
|
||||||
|
| TXT | `_amazonses.anvil.it.com` | `0cuw9v32N+aeFiNlTh2Poxglgzf3BlmFRjVOjeLEdy4=` |
|
||||||
|
|
||||||
|
**CNAME 레코드 (DKIM)**
|
||||||
|
| Type | Name | Target |
|
||||||
|
|------|------|--------|
|
||||||
|
| CNAME | `dgcehnldehfmfgpvrrbc6drwasiibhnp._domainkey` | `dgcehnldehfmfgpvrrbc6drwasiibhnp.dkim.amazonses.com` |
|
||||||
|
| CNAME | `spopdscdt2sxngqzl5ir66k3ed6og7ut._domainkey` | `spopdscdt2sxngqzl5ir66k3ed6og7ut.dkim.amazonses.com` |
|
||||||
|
| CNAME | `55l5wnmktvacgyfpt6sovcgb2rqexrpy._domainkey` | `55l5wnmktvacgyfpt6sovcgb2rqexrpy.dkim.amazonses.com` |
|
||||||
|
|
||||||
|
### ironclad.it.com
|
||||||
|
|
||||||
|
**TXT 레코드 (도메인 소유권 확인)**
|
||||||
|
| Type | Name | Content |
|
||||||
|
|------|------|---------|
|
||||||
|
| TXT | `_amazonses.ironclad.it.com` | `C+RMHyLd/U2H5WSCu8L2avRn8NmuwEll0xxYjTyvoEY=` |
|
||||||
|
|
||||||
|
**CNAME 레코드 (DKIM)**
|
||||||
|
| Type | Name | Target |
|
||||||
|
|------|------|--------|
|
||||||
|
| CNAME | `5sycmzu364y2rgefnqaloptgodymasct._domainkey` | `5sycmzu364y2rgefnqaloptgodymasct.dkim.amazonses.com` |
|
||||||
|
| CNAME | `qm7d7qgkdikpcbrbgo7bmgqfmuulrbah._domainkey` | `qm7d7qgkdikpcbrbgo7bmgqfmuulrbah.dkim.amazonses.com` |
|
||||||
|
| CNAME | `tu6oey5tktoqub753cxpayhhwrlzcskk._domainkey` | `tu6oey5tktoqub753cxpayhhwrlzcskk.dkim.amazonses.com` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## IAM 권한 설정
|
||||||
|
|
||||||
|
### 최소 권한 정책 (권장)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Sid": "SESSendEmail",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"ses:SendEmail",
|
||||||
|
"ses:SendRawEmail"
|
||||||
|
],
|
||||||
|
"Resource": "*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 특정 발신자 제한 정책
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Sid": "SESSendFromVerified",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"ses:SendEmail",
|
||||||
|
"ses:SendRawEmail"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:ses:ap-northeast-2:*:identity/anvil.it.com",
|
||||||
|
"arn:aws:ses:ap-northeast-2:*:identity/ironclad.it.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 사용 방법
|
||||||
|
|
||||||
|
### AWS CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
aws ses send-email \
|
||||||
|
--from "noreply@anvil.it.com" \
|
||||||
|
--to "recipient@example.com" \
|
||||||
|
--subject "메일 제목" \
|
||||||
|
--text "메일 본문" \
|
||||||
|
--region ap-northeast-2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Python (boto3)
|
||||||
|
|
||||||
|
```python
|
||||||
|
import boto3
|
||||||
|
from botocore.exceptions import ClientError
|
||||||
|
|
||||||
|
def send_email(to_email: str, subject: str, body: str, sender: str = "noreply@anvil.it.com"):
|
||||||
|
client = boto3.client('ses', region_name='ap-northeast-2')
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = client.send_email(
|
||||||
|
Source=sender,
|
||||||
|
Destination={'ToAddresses': [to_email]},
|
||||||
|
Message={
|
||||||
|
'Subject': {'Data': subject, 'Charset': 'UTF-8'},
|
||||||
|
'Body': {
|
||||||
|
'Text': {'Data': body, 'Charset': 'UTF-8'},
|
||||||
|
# HTML 본문 사용 시:
|
||||||
|
# 'Html': {'Data': '<h1>Hello</h1>', 'Charset': 'UTF-8'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
print(f"발송 완료: {response['MessageId']}")
|
||||||
|
return response
|
||||||
|
except ClientError as e:
|
||||||
|
print(f"발송 실패: {e.response['Error']['Message']}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# 사용 예시
|
||||||
|
send_email(
|
||||||
|
to_email='recipient@example.com',
|
||||||
|
subject='테스트 메일',
|
||||||
|
body='안녕하세요!'
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Python (HTML 메일)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def send_html_email(to_email: str, subject: str, html_body: str, text_body: str = None):
|
||||||
|
client = boto3.client('ses', region_name='ap-northeast-2')
|
||||||
|
|
||||||
|
body = {'Html': {'Data': html_body, 'Charset': 'UTF-8'}}
|
||||||
|
if text_body:
|
||||||
|
body['Text'] = {'Data': text_body, 'Charset': 'UTF-8'}
|
||||||
|
|
||||||
|
response = client.send_email(
|
||||||
|
Source='noreply@anvil.it.com',
|
||||||
|
Destination={'ToAddresses': [to_email]},
|
||||||
|
Message={
|
||||||
|
'Subject': {'Data': subject, 'Charset': 'UTF-8'},
|
||||||
|
'Body': body
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
# 사용 예시
|
||||||
|
html = """
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>안녕하세요!</h1>
|
||||||
|
<p>AWS SES로 발송된 <strong>HTML 메일</strong>입니다.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
send_html_email('recipient@example.com', '테스트', html)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 환경변수 설정 (컨테이너용)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export AWS_ACCESS_KEY_ID="your_access_key"
|
||||||
|
export AWS_SECRET_ACCESS_KEY="your_secret_key"
|
||||||
|
export AWS_DEFAULT_REGION="ap-northeast-2"
|
||||||
|
```
|
||||||
|
|
||||||
|
환경변수 설정 시 코드에서 자격증명 생략 가능:
|
||||||
|
|
||||||
|
```python
|
||||||
|
client = boto3.client('ses') # 환경변수에서 자동 로드
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 상태 확인 명령어
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 발송 활성화 상태
|
||||||
|
aws ses get-account-sending-enabled
|
||||||
|
|
||||||
|
# 발송 한도 확인
|
||||||
|
aws ses get-send-quota
|
||||||
|
|
||||||
|
# 등록된 Identity 목록
|
||||||
|
aws ses list-identities
|
||||||
|
|
||||||
|
# 도메인 인증 상태
|
||||||
|
aws ses get-identity-verification-attributes --identities anvil.it.com ironclad.it.com
|
||||||
|
|
||||||
|
# DKIM 상태
|
||||||
|
aws ses get-identity-dkim-attributes --identities anvil.it.com ironclad.it.com
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 새 도메인 추가 방법
|
||||||
|
|
||||||
|
### 1. SES 도메인 등록
|
||||||
|
|
||||||
|
```bash
|
||||||
|
aws ses verify-domain-identity --domain newdomain.com
|
||||||
|
aws ses verify-domain-dkim --domain newdomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Cloudflare DNS 레코드 추가
|
||||||
|
|
||||||
|
출력된 토큰으로 TXT, CNAME 레코드 추가 (Cloudflare API 또는 대시보드)
|
||||||
|
|
||||||
|
### 3. 인증 확인
|
||||||
|
|
||||||
|
```bash
|
||||||
|
aws ses get-identity-verification-attributes --identities newdomain.com
|
||||||
|
aws ses get-identity-dkim-attributes --identities newdomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 참고 사항
|
||||||
|
|
||||||
|
- **프로덕션 모드**: 수신자 제한 없음
|
||||||
|
- **DKIM**: 메일 신뢰도 향상 (스팸 방지)
|
||||||
|
- **발송 한도**: 필요시 AWS에 증가 요청 가능
|
||||||
|
- **비용**: $0.10 / 1,000통
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 관련 링크
|
||||||
|
|
||||||
|
- [AWS SES 콘솔](https://ap-northeast-2.console.aws.amazon.com/ses/)
|
||||||
|
- [AWS SES 개발자 가이드](https://docs.aws.amazon.com/ses/latest/dg/)
|
||||||
|
- [Cloudflare DNS](https://dash.cloudflare.com/)
|
||||||
69
cloudflare-vault-integration.md
Normal file
69
cloudflare-vault-integration.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Cloudflare Vault 통합 가이드
|
||||||
|
|
||||||
|
## 저장 방식 권장사항
|
||||||
|
|
||||||
|
### 1. **API Token 방식 (권장)**
|
||||||
|
```bash
|
||||||
|
# Vault에 저장
|
||||||
|
vault kv put secret/cloudflare/tokens \
|
||||||
|
api_token="your-api-token" \
|
||||||
|
account_id="your-account-id"
|
||||||
|
|
||||||
|
# 사용
|
||||||
|
export CLOUDFLARE_API_TOKEN=$(vault kv get -field=api_token secret/cloudflare/tokens)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Global API Key 방식 (현재)**
|
||||||
|
```bash
|
||||||
|
# 현재 Vault 구조
|
||||||
|
secret/cloudflare/
|
||||||
|
├── email
|
||||||
|
└── api_key
|
||||||
|
|
||||||
|
# 사용
|
||||||
|
export CF_API_EMAIL=$(vault kv get -field=email secret/cloudflare)
|
||||||
|
export CF_API_KEY=$(vault kv get -field=api_key secret/cloudflare)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. **Tunnel 별 크레덴셜**
|
||||||
|
```bash
|
||||||
|
# Vault에 터널별로 저장
|
||||||
|
vault kv put secret/cloudflare/tunnels/production \
|
||||||
|
tunnel_id="uuid" \
|
||||||
|
tunnel_secret="base64-secret" \
|
||||||
|
account_id="account-id"
|
||||||
|
|
||||||
|
# JSON 파일로 추출
|
||||||
|
vault kv get -format=json secret/cloudflare/tunnels/production | \
|
||||||
|
jq '.data.data' > ~/.cloudflared/production.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## 자동화 스크립트
|
||||||
|
|
||||||
|
### Fish Shell 함수
|
||||||
|
```fish
|
||||||
|
function cf-auth
|
||||||
|
set -gx CF_API_EMAIL (vault kv get -field=email secret/cloudflare)
|
||||||
|
set -gx CF_API_KEY (vault kv get -field=api_key secret/cloudflare)
|
||||||
|
echo "Cloudflare 인증 설정 완료"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bash 별칭
|
||||||
|
```bash
|
||||||
|
alias cf-auth='eval $(vault kv get -format=json secret/cloudflare | jq -r ".data.data | to_entries[] | \"export CF_\" + (.key | ascii_upcase) + \"=\" + .value")'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 보안 고려사항
|
||||||
|
|
||||||
|
1. **최소 권한 원칙**: API Token 사용 시 필요한 권한만 부여
|
||||||
|
2. **임시 인증**: 환경변수는 세션 종료 시 자동 삭제
|
||||||
|
3. **파일 권한**: `chmod 600 ~/.cloudflared/*`
|
||||||
|
4. **Vault 통합**: 모든 크레덴셜은 Vault에서 중앙 관리
|
||||||
|
|
||||||
|
## 추천 워크플로우
|
||||||
|
|
||||||
|
1. Vault에 API Token 저장 (Global Key 대신)
|
||||||
|
2. 필요시 환경변수로 로드
|
||||||
|
3. 작업 완료 후 환경변수 정리
|
||||||
|
4. CI/CD에서는 Vault Agent 사용
|
||||||
552
gitea-setup.md
Normal file
552
gitea-setup.md
Normal file
@@ -0,0 +1,552 @@
|
|||||||
|
# Gitea Docker Compose Setup
|
||||||
|
|
||||||
|
A production-ready Gitea deployment using Docker Compose with PostgreSQL, optimized for NAS systems and self-hosted environments.
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
1. **Clone or download this repository**
|
||||||
|
2. **Run the setup script:**
|
||||||
|
```bash
|
||||||
|
./setup-gitea.sh
|
||||||
|
```
|
||||||
|
3. **Start Gitea:**
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
4. **Access Gitea at:** `http://localhost:3000`
|
||||||
|
|
||||||
|
## 📋 Prerequisites
|
||||||
|
|
||||||
|
### Required Software
|
||||||
|
- **Docker Engine** (20.10+)
|
||||||
|
- **Docker Compose** (2.0+)
|
||||||
|
- **OpenSSL** (for generating secure keys)
|
||||||
|
- **Bash** (for setup scripts)
|
||||||
|
|
||||||
|
### System Requirements
|
||||||
|
- **RAM:** Minimum 2GB, recommended 4GB+
|
||||||
|
- **Storage:** Minimum 10GB free space
|
||||||
|
- **Network:** Ports 3000 (HTTP) and 2222 (SSH) available
|
||||||
|
|
||||||
|
### Installation Commands
|
||||||
|
|
||||||
|
**Ubuntu/Debian:**
|
||||||
|
```bash
|
||||||
|
# Install Docker
|
||||||
|
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||||
|
sudo sh get-docker.sh
|
||||||
|
sudo usermod -aG docker $USER
|
||||||
|
|
||||||
|
# Install Docker Compose
|
||||||
|
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||||
|
sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
|
||||||
|
# Log out and back in to apply group changes
|
||||||
|
```
|
||||||
|
|
||||||
|
**macOS:**
|
||||||
|
```bash
|
||||||
|
# Install Docker Desktop from https://docker.com/products/docker-desktop
|
||||||
|
# Or via Homebrew:
|
||||||
|
brew install --cask docker
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🏗️ Architecture
|
||||||
|
|
||||||
|
### Services
|
||||||
|
- **gitea**: Main Gitea application (rootless container)
|
||||||
|
- **gitea-db**: PostgreSQL 15 database
|
||||||
|
- **gitea-runner**: Optional Gitea Actions runner
|
||||||
|
|
||||||
|
### Volumes
|
||||||
|
- **gitea-data**: Application data and repositories
|
||||||
|
- **gitea-config**: Configuration files
|
||||||
|
- **gitea-db-data**: PostgreSQL data
|
||||||
|
- **gitea-runner-data**: Actions runner data
|
||||||
|
|
||||||
|
### Network
|
||||||
|
- **gitea-network**: Isolated bridge network with custom subnet
|
||||||
|
|
||||||
|
## ⚙️ Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
The setup is configured through a `.env` file. Key settings include:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Domain Configuration
|
||||||
|
GITEA_DOMAIN=your-domain.com
|
||||||
|
GITEA_ROOT_URL=https://your-domain.com
|
||||||
|
|
||||||
|
# Port Configuration
|
||||||
|
GITEA_HTTP_PORT=3000
|
||||||
|
GITEA_SSH_PORT=2222
|
||||||
|
|
||||||
|
# Security (automatically generated)
|
||||||
|
GITEA_SECRET_KEY=your_secret_key
|
||||||
|
GITEA_INTERNAL_TOKEN=your_internal_token
|
||||||
|
|
||||||
|
# Database
|
||||||
|
POSTGRES_PASSWORD=secure_password
|
||||||
|
|
||||||
|
# Admin Account
|
||||||
|
GITEA_ADMIN_USER=admin
|
||||||
|
GITEA_ADMIN_EMAIL=admin@your-domain.com
|
||||||
|
GITEA_ADMIN_PASSWORD=secure_password
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Configuration
|
||||||
|
|
||||||
|
For advanced settings, modify:
|
||||||
|
- **docker-compose.yml**: Service configuration, resource limits, environment variables
|
||||||
|
- **gitea-app.ini.template**: Detailed Gitea configuration reference
|
||||||
|
- **.env**: Environment-specific settings
|
||||||
|
|
||||||
|
## 🚀 Installation Guide
|
||||||
|
|
||||||
|
### Method 1: Automated Setup (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Download the setup files
|
||||||
|
git clone <repository-url> gitea-setup
|
||||||
|
cd gitea-setup
|
||||||
|
|
||||||
|
# 2. Run interactive setup
|
||||||
|
./setup-gitea.sh
|
||||||
|
|
||||||
|
# 3. Start services
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 4. Check status
|
||||||
|
docker-compose ps
|
||||||
|
docker-compose logs -f gitea
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 2: Manual Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Create directories
|
||||||
|
mkdir -p gitea-{data,config,db-data,runner-data} backups
|
||||||
|
|
||||||
|
# 2. Copy environment file
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# 3. Edit configuration
|
||||||
|
nano .env # Update all required values
|
||||||
|
|
||||||
|
# 4. Generate secure keys
|
||||||
|
openssl rand -base64 32 # Use for GITEA_SECRET_KEY
|
||||||
|
openssl rand -base64 32 # Use for GITEA_INTERNAL_TOKEN
|
||||||
|
|
||||||
|
# 5. Start services
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔐 Security Configuration
|
||||||
|
|
||||||
|
### SSL/TLS Setup with Reverse Proxy
|
||||||
|
|
||||||
|
**Nginx Configuration:**
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
ssl_certificate /path/to/certificate.crt;
|
||||||
|
ssl_certificate_key /path/to/private.key;
|
||||||
|
|
||||||
|
client_max_body_size 512M;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traefik Configuration:**
|
||||||
|
```yaml
|
||||||
|
# docker-compose.override.yml
|
||||||
|
services:
|
||||||
|
gitea:
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.gitea.rule=Host(`your-domain.com`)"
|
||||||
|
- "traefik.http.routers.gitea.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
|
||||||
|
```
|
||||||
|
|
||||||
|
### SSH Configuration
|
||||||
|
|
||||||
|
**Host SSH Configuration (recommended):**
|
||||||
|
```bash
|
||||||
|
# Add to /etc/ssh/sshd_config
|
||||||
|
Match User git
|
||||||
|
AllowTcpForwarding no
|
||||||
|
AllowAgentForwarding no
|
||||||
|
PermitTTY no
|
||||||
|
X11Forwarding no
|
||||||
|
```
|
||||||
|
|
||||||
|
**Container SSH (current setup):**
|
||||||
|
- SSH server runs inside Gitea container
|
||||||
|
- Exposed on port 2222
|
||||||
|
- User authentication via Gitea SSH keys
|
||||||
|
|
||||||
|
### Firewall Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ubuntu/Debian (ufw)
|
||||||
|
sudo ufw allow 3000/tcp comment 'Gitea HTTP'
|
||||||
|
sudo ufw allow 2222/tcp comment 'Gitea SSH'
|
||||||
|
|
||||||
|
# CentOS/RHEL (firewalld)
|
||||||
|
sudo firewall-cmd --permanent --add-port=3000/tcp
|
||||||
|
sudo firewall-cmd --permanent --add-port=2222/tcp
|
||||||
|
sudo firewall-cmd --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🗄️ Backup & Restore
|
||||||
|
|
||||||
|
### Automated Backup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Full backup (recommended)
|
||||||
|
./backup-gitea.sh --full
|
||||||
|
|
||||||
|
# Database only
|
||||||
|
./backup-gitea.sh --database-only
|
||||||
|
|
||||||
|
# Custom retention and compression
|
||||||
|
./backup-gitea.sh --full --retention 90 --compress 9
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backup Schedule
|
||||||
|
|
||||||
|
**Crontab example:**
|
||||||
|
```bash
|
||||||
|
# Daily backup at 2 AM, keep for 30 days
|
||||||
|
0 2 * * * /path/to/gitea-setup/backup-gitea.sh --full --retention 30
|
||||||
|
|
||||||
|
# Weekly full backup, keep for 1 year
|
||||||
|
0 2 * * 0 /path/to/gitea-setup/backup-gitea.sh --full --retention 365
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restore from Backup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restore from full backup
|
||||||
|
./restore-gitea.sh backups/gitea_backup_20240101_120000.tar.gz
|
||||||
|
|
||||||
|
# Restore database only
|
||||||
|
./restore-gitea.sh --database-only backup_directory/
|
||||||
|
|
||||||
|
# Restore with current data backup
|
||||||
|
./restore-gitea.sh --backup-current latest_backup.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Maintenance
|
||||||
|
|
||||||
|
### Update Gitea
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check for updates
|
||||||
|
./update-gitea.sh --check-only
|
||||||
|
|
||||||
|
# Update to latest version
|
||||||
|
./update-gitea.sh
|
||||||
|
|
||||||
|
# Update to specific version
|
||||||
|
./update-gitea.sh 1.21.5
|
||||||
|
```
|
||||||
|
|
||||||
|
### Monitor Services
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check service status
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker-compose logs -f gitea
|
||||||
|
docker-compose logs -f gitea-db
|
||||||
|
|
||||||
|
# Monitor resources
|
||||||
|
docker-compose top
|
||||||
|
docker stats
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Maintenance
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Access database
|
||||||
|
docker-compose exec gitea-db psql -U gitea -d gitea
|
||||||
|
|
||||||
|
# Database backup
|
||||||
|
docker-compose exec gitea-db pg_dump -U gitea -d gitea > backup.sql
|
||||||
|
|
||||||
|
# Database restore
|
||||||
|
docker-compose exec -T gitea-db psql -U gitea -d gitea < backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**1. Permission Errors**
|
||||||
|
```bash
|
||||||
|
# Fix directory permissions
|
||||||
|
sudo chown -R 1000:1000 gitea-data gitea-config
|
||||||
|
sudo chmod -R 755 gitea-data gitea-config
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Database Connection Issues**
|
||||||
|
```bash
|
||||||
|
# Check database logs
|
||||||
|
docker-compose logs gitea-db
|
||||||
|
|
||||||
|
# Test database connection
|
||||||
|
docker-compose exec gitea-db pg_isready -U gitea -d gitea
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. SSH Access Issues**
|
||||||
|
```bash
|
||||||
|
# Check SSH configuration
|
||||||
|
docker-compose exec gitea cat /etc/gitea/app.ini | grep -A 5 "\[server\]"
|
||||||
|
|
||||||
|
# Test SSH connection
|
||||||
|
ssh -T git@localhost -p 2222
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. Memory/Resource Issues**
|
||||||
|
```bash
|
||||||
|
# Check resource usage
|
||||||
|
docker stats
|
||||||
|
|
||||||
|
# Adjust resource limits in docker-compose.yml
|
||||||
|
services:
|
||||||
|
gitea:
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 2G
|
||||||
|
cpus: '1.0'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Log Analysis
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Application logs
|
||||||
|
docker-compose logs --tail=100 -f gitea
|
||||||
|
|
||||||
|
# Database logs
|
||||||
|
docker-compose logs --tail=100 -f gitea-db
|
||||||
|
|
||||||
|
# System logs (Ubuntu/Debian)
|
||||||
|
sudo journalctl -u docker --tail=100 -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health Checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Service health
|
||||||
|
docker-compose exec gitea curl -f http://localhost:3000/api/healthz
|
||||||
|
|
||||||
|
# Database health
|
||||||
|
docker-compose exec gitea-db pg_isready -U gitea -d gitea
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Performance Optimization
|
||||||
|
|
||||||
|
### NAS-Specific Optimizations
|
||||||
|
|
||||||
|
**1. Storage Configuration:**
|
||||||
|
```yaml
|
||||||
|
# Use external SSD for better performance
|
||||||
|
volumes:
|
||||||
|
gitea-data:
|
||||||
|
driver: local
|
||||||
|
driver_opts:
|
||||||
|
type: none
|
||||||
|
o: bind
|
||||||
|
device: /mnt/ssd/gitea-data
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Resource Limits:**
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
gitea:
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 1G
|
||||||
|
cpus: '1.0'
|
||||||
|
reservations:
|
||||||
|
memory: 512M
|
||||||
|
cpus: '0.5'
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Database Tuning:**
|
||||||
|
```bash
|
||||||
|
# Add to docker-compose.yml under gitea-db environment
|
||||||
|
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Network Optimization
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Custom network configuration
|
||||||
|
networks:
|
||||||
|
gitea-network:
|
||||||
|
driver: bridge
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: 172.20.0.0/16
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Monitoring
|
||||||
|
|
||||||
|
### Basic Monitoring
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Resource usage
|
||||||
|
docker stats --no-stream
|
||||||
|
|
||||||
|
# Disk usage
|
||||||
|
du -sh gitea-data/ gitea-db-data/
|
||||||
|
|
||||||
|
# Service health
|
||||||
|
curl -f http://localhost:3000/api/healthz
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Monitoring with Prometheus
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Add to docker-compose.yml
|
||||||
|
prometheus:
|
||||||
|
image: prom/prometheus:latest
|
||||||
|
ports:
|
||||||
|
- "9090:9090"
|
||||||
|
volumes:
|
||||||
|
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||||
|
|
||||||
|
grafana:
|
||||||
|
image: grafana/grafana:latest
|
||||||
|
ports:
|
||||||
|
- "3001:3000"
|
||||||
|
environment:
|
||||||
|
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔌 Gitea Actions (CI/CD)
|
||||||
|
|
||||||
|
### Enable Actions Runner
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start with actions profile
|
||||||
|
docker-compose --profile actions up -d
|
||||||
|
|
||||||
|
# Or enable in existing deployment
|
||||||
|
docker-compose up -d gitea-runner
|
||||||
|
```
|
||||||
|
|
||||||
|
### Runner Configuration
|
||||||
|
|
||||||
|
1. **Generate Registration Token:**
|
||||||
|
- Go to Gitea Admin → Site Administration → Actions → Runners
|
||||||
|
- Click "Create new Runner"
|
||||||
|
- Copy the registration token
|
||||||
|
|
||||||
|
2. **Add Token to Environment:**
|
||||||
|
```bash
|
||||||
|
echo "GITEA_RUNNER_TOKEN=your_token_here" >> .env
|
||||||
|
docker-compose restart gitea-runner
|
||||||
|
```
|
||||||
|
|
||||||
|
### Action Examples
|
||||||
|
|
||||||
|
**.gitea/workflows/ci.yml:**
|
||||||
|
```yaml
|
||||||
|
name: CI
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
echo "Running tests..."
|
||||||
|
# Add your test commands here
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Additional Resources
|
||||||
|
|
||||||
|
### Official Documentation
|
||||||
|
- [Gitea Documentation](https://docs.gitea.com/)
|
||||||
|
- [Docker Compose Reference](https://docs.docker.com/compose/)
|
||||||
|
- [PostgreSQL Documentation](https://www.postgresql.org/docs/)
|
||||||
|
|
||||||
|
### Community Resources
|
||||||
|
- [Gitea Community](https://github.com/go-gitea/gitea/discussions)
|
||||||
|
- [Docker Community](https://forums.docker.com/)
|
||||||
|
|
||||||
|
### Migration Guides
|
||||||
|
- [GitHub to Gitea Migration](https://docs.gitea.com/usage/migrate-from-github/)
|
||||||
|
- [GitLab to Gitea Migration](https://docs.gitea.com/usage/migrate-from-gitlab/)
|
||||||
|
|
||||||
|
## 🆘 Support
|
||||||
|
|
||||||
|
### Getting Help
|
||||||
|
|
||||||
|
1. **Check logs first:**
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f gitea
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Review common issues** in this README
|
||||||
|
|
||||||
|
3. **Search existing issues:**
|
||||||
|
- [Gitea Issues](https://github.com/go-gitea/gitea/issues)
|
||||||
|
- [Community Discussions](https://github.com/go-gitea/gitea/discussions)
|
||||||
|
|
||||||
|
4. **Create detailed bug report** with:
|
||||||
|
- Gitea version
|
||||||
|
- Docker version
|
||||||
|
- Operating system
|
||||||
|
- Error logs
|
||||||
|
- Steps to reproduce
|
||||||
|
|
||||||
|
### Script Help
|
||||||
|
|
||||||
|
All scripts include built-in help:
|
||||||
|
```bash
|
||||||
|
./setup-gitea.sh --help
|
||||||
|
./backup-gitea.sh --help
|
||||||
|
./restore-gitea.sh --help
|
||||||
|
./update-gitea.sh --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
This setup configuration is provided under the MIT License. Gitea itself is licensed under the MIT License.
|
||||||
|
|
||||||
|
## 🙏 Acknowledgments
|
||||||
|
|
||||||
|
- [Gitea Team](https://gitea.io/) for creating an excellent Git service
|
||||||
|
- [Docker Community](https://docker.com/) for containerization platform
|
||||||
|
- [PostgreSQL Team](https://postgresql.org/) for the reliable database
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Happy Self-Hosting!** 🎉
|
||||||
|
|
||||||
|
For questions or improvements, please open an issue or pull request.
|
||||||
273
n8n-setup-guide.md
Normal file
273
n8n-setup-guide.md
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
# 🤖 n8n AI Agent: Cloudflare Tunnel 자동화 가이드
|
||||||
|
|
||||||
|
n8n에서 Cloudflare Tunnel과 Nginx Proxy Manager를 자동으로 설정하는 AI 에이전트 워크플로우입니다.
|
||||||
|
|
||||||
|
## 🏗️ 아키텍처
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[HTTP Request] --> B[n8n Webhook]
|
||||||
|
B --> C[AI Agent 검증]
|
||||||
|
C --> D[Cloudflare API]
|
||||||
|
C --> E[NPM API]
|
||||||
|
D --> F[DNS CNAME 설정]
|
||||||
|
E --> G[프록시 호스트 생성]
|
||||||
|
F --> H[완료 응답]
|
||||||
|
G --> H
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 사전 요구사항
|
||||||
|
|
||||||
|
### 1. n8n 설치 및 실행
|
||||||
|
```bash
|
||||||
|
# Docker Compose로 실행 (권장)
|
||||||
|
docker run -it --rm --name n8n -p 5678:5678 n8nio/n8n
|
||||||
|
|
||||||
|
# 또는 npm으로 설치
|
||||||
|
npm install n8n -g
|
||||||
|
n8n start
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Credentials 설정
|
||||||
|
n8n에서 다음 Credentials를 생성해야 합니다:
|
||||||
|
|
||||||
|
#### Cloudflare Credential (`cloudflare`)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email": "your-email@example.com",
|
||||||
|
"api_key": "your-global-api-key"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### NPM Credential (`npm`) - 선택사항
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"url": "http://localhost:81",
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"password": "changeme"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 워크플로우 설치
|
||||||
|
|
||||||
|
### 1. 메인 AI Agent 워크플로우 임포트
|
||||||
|
1. n8n 대시보드에서 **"Import from file"** 선택
|
||||||
|
2. `n8n-cf-tunnel-workflow.json` 파일 업로드
|
||||||
|
3. Credentials 연결:
|
||||||
|
- **cloudflare**: Cloudflare credential 선택
|
||||||
|
- **npm**: NPM credential 선택 (있는 경우)
|
||||||
|
|
||||||
|
### 2. Webhook API 워크플로우 임포트
|
||||||
|
1. `n8n-webhook-workflow.json` 파일 임포트
|
||||||
|
2. **Execute CF Agent** 노드에서:
|
||||||
|
- `CF-TUNNEL-AI-AGENT-WORKFLOW-ID`를 실제 워크플로우 ID로 변경
|
||||||
|
|
||||||
|
## 📡 API 사용법
|
||||||
|
|
||||||
|
### HTTP POST 요청
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:5678/webhook/cf-tunnel" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"domain": "example.com",
|
||||||
|
"subdomain": "app",
|
||||||
|
"service_ip": "192.168.1.100",
|
||||||
|
"service_port": "8080"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 성공 응답 (200)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"domain": "app.example.com",
|
||||||
|
"tunnel_id": "0adb287c-10e2-4f1d-af4c-8e083ed878d4",
|
||||||
|
"tunnel_domain": "0adb287c-10e2-4f1d-af4c-8e083ed878d4.cfargotunnel.com",
|
||||||
|
"service": "192.168.1.100:8080",
|
||||||
|
"dns_configured": true,
|
||||||
|
"npm_configured": true,
|
||||||
|
"npm_host_id": "123",
|
||||||
|
"timestamp": "2024-09-07T01:00:00.000Z",
|
||||||
|
"message": "🚀 AI Agent 완료: app.example.com → 192.168.1.100:8080"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 에러 응답 (400/500)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": true,
|
||||||
|
"message": "Missing required fields: domain, subdomain",
|
||||||
|
"required_fields": ["domain", "subdomain", "service_ip", "service_port"],
|
||||||
|
"example": {
|
||||||
|
"domain": "example.com",
|
||||||
|
"subdomain": "app",
|
||||||
|
"service_ip": "192.168.1.100",
|
||||||
|
"service_port": "8080"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 커스터마이징
|
||||||
|
|
||||||
|
### 1. 터널 ID 변경
|
||||||
|
각 워크플로우의 **Initialize Config** 노드에서:
|
||||||
|
```javascript
|
||||||
|
tunnel_id: 'YOUR-TUNNEL-ID-HERE',
|
||||||
|
tunnel_domain: 'YOUR-TUNNEL-ID-HERE.cfargotunnel.com',
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 계정 ID 변경
|
||||||
|
```javascript
|
||||||
|
account_id: 'YOUR-ACCOUNT-ID-HERE'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. NPM 설정 수정
|
||||||
|
**Create NPM Proxy** 노드에서 SSL, 캐싱 등 설정 변경 가능:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ssl_forced": true,
|
||||||
|
"caching_enabled": true,
|
||||||
|
"certificate_id": 1,
|
||||||
|
"meta": {
|
||||||
|
"letsencrypt_agree": true,
|
||||||
|
"dns_challenge": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔒 보안 고려사항
|
||||||
|
|
||||||
|
### 1. API 키 보호
|
||||||
|
- Vault 연동 사용 권장
|
||||||
|
- n8n Credentials에 직접 저장 시 암호화 확인
|
||||||
|
- 최소 권한 원칙 적용
|
||||||
|
|
||||||
|
### 2. 웹훅 보안
|
||||||
|
```javascript
|
||||||
|
// Webhook 노드에 인증 추가
|
||||||
|
const authHeader = $input.all()[0].headers.authorization;
|
||||||
|
if (!authHeader || authHeader !== 'Bearer YOUR-SECRET-TOKEN') {
|
||||||
|
return [{ json: { error: true, message: 'Unauthorized' } }];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Rate Limiting
|
||||||
|
n8n에서 실행 제한 설정:
|
||||||
|
- **Execution Timeout**: 300초
|
||||||
|
- **Max Executions**: 시간당 제한 설정
|
||||||
|
|
||||||
|
## 📊 모니터링 및 로깅
|
||||||
|
|
||||||
|
### 1. 실행 로그 확인
|
||||||
|
n8n 대시보드 → **Executions** → 워크플로우 실행 기록 확인
|
||||||
|
|
||||||
|
### 2. 에러 알림 설정
|
||||||
|
Error Workflow 생성하여 실패 시 알림 발송:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "CF-Tunnel-Error-Handler",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"name": "Send Slack Alert",
|
||||||
|
"type": "n8n-nodes-base.slack"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 성능 메트릭
|
||||||
|
- 평균 실행 시간: ~30-60초
|
||||||
|
- 성공률: >95% 목표
|
||||||
|
- API 응답 시간 모니터링
|
||||||
|
|
||||||
|
## 🧪 테스트 시나리오
|
||||||
|
|
||||||
|
### 1. 기본 테스트
|
||||||
|
```bash
|
||||||
|
# 새 도메인 생성
|
||||||
|
curl -X POST "http://localhost:5678/webhook/cf-tunnel" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"domain": "test.com",
|
||||||
|
"subdomain": "api",
|
||||||
|
"service_ip": "127.0.0.1",
|
||||||
|
"service_port": "3000"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 기존 도메인 업데이트
|
||||||
|
```bash
|
||||||
|
# 같은 subdomain.domain으로 다른 서비스로 업데이트
|
||||||
|
curl -X POST "http://localhost:5678/webhook/cf-tunnel" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"domain": "test.com",
|
||||||
|
"subdomain": "api",
|
||||||
|
"service_ip": "127.0.0.1",
|
||||||
|
"service_port": "4000"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 에러 테스트
|
||||||
|
```bash
|
||||||
|
# 잘못된 IP 주소
|
||||||
|
curl -X POST "http://localhost:5678/webhook/cf-tunnel" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"domain": "test.com",
|
||||||
|
"subdomain": "api",
|
||||||
|
"service_ip": "999.999.999.999",
|
||||||
|
"service_port": "3000"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 프로덕션 배포
|
||||||
|
|
||||||
|
### 1. Docker Compose 설정
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
n8n:
|
||||||
|
image: n8nio/n8n
|
||||||
|
ports:
|
||||||
|
- "5678:5678"
|
||||||
|
environment:
|
||||||
|
- N8N_HOST=0.0.0.0
|
||||||
|
- N8N_PORT=5678
|
||||||
|
- N8N_PROTOCOL=https
|
||||||
|
- N8N_BASIC_AUTH_ACTIVE=true
|
||||||
|
- N8N_BASIC_AUTH_USER=admin
|
||||||
|
- N8N_BASIC_AUTH_PASSWORD=secure-password
|
||||||
|
volumes:
|
||||||
|
- n8n_data:/home/node/.n8n
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
n8n_data:
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 리버스 프록시 설정
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name n8n.yourdomain.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:5678;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 추가 기능 아이디어
|
||||||
|
|
||||||
|
1. **Bulk API**: 여러 도메인 한번에 설정
|
||||||
|
2. **Status Check**: 터널 상태 모니터링 API
|
||||||
|
3. **SSL Certificate**: Let's Encrypt 자동 설정
|
||||||
|
4. **Health Check**: 서비스 상태 확인 및 알림
|
||||||
|
5. **Rollback**: 설정 되돌리기 기능
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
이제 n8n에서 Cloudflare 터널을 자동화하는 AI 에이전트가 준비되었습니다! 🤖✨
|
||||||
Reference in New Issue
Block a user