๐ง ์ด์ ๊ฐ์ด๋ (์ฑ ์์์ฉ)
์๋ฒ ๋์ฐ๊ธฐ, ์ธ๋ถ ๋ ธ์ถ, ๋ฐฑ์ , ์ธ์ฆ, ๋ณด์. ์ฌ์ฉ์(๋งค๋ฌผ ๊ด๋ฆฌ)๋ ์น ๊ฐ์ด๋ ์ฐธ๊ณ .
1. ์๋ฒ ๋์ฐ๊ธฐ
๋งค์ผ ์ฌ์ฉ
cd ~/easyseop/meetcute
./dev.sh
dev.sh ๊ฐ ์๋์ผ๋ก:
- .env ํ๊ฒฝ๋ณ์ ๋ก๋
- .secret ํ์ผ ์์ฑ/๋ก๋ (์ธ์
์ฟ ํค ํค)
- pip install -e . (์์กด์ฑ ๋๊ธฐํ)
- uvicorn --reload ์์
- 15์ด๋ง๋ค git ํด๋ง โ ์ ์ปค๋ฐ ์๋ pull โ ์๋ ๋ฆฌ๋ก๋
- pyproject ๋ณ๊ฒฝ ์ ์๋ ์ฌ์ค์น + ์ฌ์์
- uvicorn ์ฃฝ์ผ๋ฉด ์๋ ์ฌ๊ธฐ๋
.env ์ต์
PORT=8765 # ํฌํธ (๊ธฐ๋ณธ 8765)
HOST=127.0.0.1 # 0.0.0.0 ์ผ๋ก ํ๋ฉด ๊ฐ์ ์์ดํ์ด ์ ๊ทผ
MEETCUTE_AUTH=on # ์ธ์ฆ ์ผ๊ธฐ (๊ธฐ๋ณธ off)
MEETCUTE_PUBLIC=on # ์ธ๋ถ ๋
ธ์ถ ๋ชจ๋ (HTTPS ์ฟ ํค + AUTH ๊ฐ์ )
MEETCUTE_PUBLIC_URL=https://meetcute.xxx.com # ํ
๋ ๊ทธ๋จ ๋ฉ์์ง์ ๋งํฌ prefix
MEETCUTE_TELEGRAM_BOT_TOKEN=123:abc... # ๋ด ํ ํฐ (BotFather ์์)
MEETCUTE_DB_URL=sqlite:///./data/meetcute.db # DB URL (๊ธฐ๋ณธ SQLite)
MEETCUTE_TUNNEL_NAME=meetcute # cloudflared ์๊ตฌ ํฐ๋ ์ด๋ฆ
MEETCUTE_TUNNEL_HOSTNAME=meetcute.xxx.com # ์๊ตฌ ๋๋ฉ์ธ
2. ์ธ๋ถ ๋ ธ์ถ (cloudflared)
๋ณ๋ ํฐ๋ฏธ๋์์:
./tunnel.sh
.env์MEETCUTE_TUNNEL_NAME+MEETCUTE_TUNNEL_HOSTNAME๋ ๋ค ์์ผ๋ฉด โ named ๋ชจ๋ (์๊ตฌ URL)- ์์ผ๋ฉด โ quick ๋ชจ๋ (
*.trycloudflare.com๋๋ค URL, ์ฌ์์๋ง๋ค ๋ฐ๋)
์๊ตฌ URL ์ ์ (1ํ)
brew install cloudflared
cloudflared tunnel login # ๋ธ๋ผ์ฐ์ ๋ก cloudflare ๊ณ์ ์ธ์ฆ (๋๋ฉ์ธ ํ์)
# .env ์ MEETCUTE_TUNNEL_NAME / MEETCUTE_TUNNEL_HOSTNAME ์ถ๊ฐ
./tunnel.sh # ์๋์ผ๋ก tunnel ์์ฑ + DNS ๋ผ์ฐํ
URL ๋ณ๊ฒฝ ์๋ ์๋ฆผ
tunnel.sh ๊ฐ ํ์ฌ URL ์ .public_url ํ์ผ์ ๊ธฐ๋ก. ์ฑ์ด ๋งค๋ถ ํ์ธ โ ๋ณ๊ฒฝ ๊ฐ์ง ์ ๋ฑ๋ก๋ ๋ชจ๋ ๋ง๋ด๋ ์๊ฒ ํ
๋ ๊ทธ๋จ์ผ๋ก ์ URL ํธ์.
3. ์ธ์ฆ ํ ๊ธ
| ๋ชจ๋ | env | ๋์ |
|---|---|---|
| ๋ก์ปฌ (๊ธฐ๋ณธ) | MEETCUTE_AUTH ์์ |
๋ก๊ทธ์ธ X ยท ๋ชจ๋ ํ์ด์ง ๋ฐ๋ก ์ ๊ทผ |
| ์ธ์ฆ | MEETCUTE_AUTH=on |
๋ก๊ทธ์ธ ํ์ ยท ๋ค์ค admin ๊ฐ๋ฅ |
| ๊ณต๊ฐ | MEETCUTE_PUBLIC=on |
์ธ์ฆ ์๋ ๊ฐ์ + HTTPS ์ฟ ํค + ๋ถํ ๋ฐฐ๋ |
์ฒซ ๊ฐ์ ์ = ์ฑ ์์
- DB ์ ์ ์ ์์ ๋ ์ฒซ ๊ฐ์
์๊ฐ ์๋์ผ๋ก
is_admin=True, is_owner=True - ์ดํ ๊ฐ์
์๋ ์ฑ
์์ ์น์ธ ๋๊ธฐ (
/auth/pending) - ์ฑ
์์๊ฐ
/users์์ ๋ง๋ด๋๋ก ๋ฒํผ ํด๋ฆญํ๋ฉด ํ ๊ถํ
์ฑ ์์ (๐) ํน๊ถ
- ๋ชจ๋ ์ฌ๋ ํ ๋ ๊ทธ๋จ chat_id ์ด๋ (๋ค๋ฅธ ๋ง๋ด๋ํํ ๋ ์ ๋ณด์)
- ๋ชจ๋ ๋งค๋ฌผ ์์ /์ญ์ (๋ค๋ฅธ ๋ง๋ด๋ ์ ๋งค๋ฌผ๋)
/users์์ ๊ถํ ๋ถ์ฌ/ํ์
๋ง์ง๋ง ์ฑ ์์ ๋ณดํธ
- ์ฑ ์์๊ฐ 1๋ช ์ด๋ฉด ๊ฐ๋ฑ/์ญ์ ๋ถ๊ฐ
- ์ฑ ์์ ์ฎ๊ธฐ๋ ค๋ฉด ๋จผ์ ๋ค๋ฅธ ์ฌ๋ ์น๊ธ ํ ๋ณธ์ธ ๊ฐ๋ฑ
4. ๋ฐ์ดํฐ๋ฒ ์ด์ค
- ๊ธฐ๋ณธ: SQLite (
meetcute/data/meetcute.db) - ์ฒซ ๋ถํ
์ ์๋ ์์ฑ, ์คํค๋ง ๋ง์ด๊ทธ๋ ์ด์
๋ ์๋ (
ALTER TABLE ADD COLUMN) MEETCUTE_DB_URL๋ก MySQL/Postgres ์ ํ ๊ฐ๋ฅ (mysql+pymysql://...,postgresql+psycopg2://...)
์๋ ๋ฐ์ดํฐ (๊ฐ์ง ๋งค๋ฌผ 8๋ช + ๋ง๋จ + ์ด๋ ฅ)
python -m app.seed # ๋น DB ์ผ ๋๋ง
python -m app.seed --force # ๋ค ์ง์ฐ๊ณ ๋ค์
DB ์ํ ํ์ธ
/settings ํ์ด์ง์ "๐พ ๋ฐ์ดํฐ / ์์คํ
์ํ" ์นด๋:
- DB ํ์
/ ํ์ผ ๊ฒฝ๋ก / ํ์ผ ํฌ๊ธฐ
- ์ฌ์ง ํด๋ ํฌ๊ธฐ / ์ฌ์ง ๊ฐ์
- ํ
์ด๋ธ๋ณ row ๊ฐ์
5. ๋ฐฑ์ (Dropbox / iCloud)
๋ฐ์ดํฐ๋ ๋ ํด๋ + ๋ ํค ํ์ผ์๋ง ์์:
- meetcute/data/meetcute.db โ DB
- meetcute/uploads/ โ ์ฌ์ง
- meetcute/.secret โ ์ธ์
์ฟ ํค ์๋ช
ํค (์ฌ๋ผ์ง๋ฉด ๋ชจ๋ ์ฌ์ฉ์ ๋ค์ ๋ก๊ทธ์ธ)
- meetcute/.encryption_key โ DB ํ๋ ์ํธํ ํค (์ฌ๋ผ์ง๋ฉด ๊ฑฐ์ฃผ์ง/๋์ด + ๋ง๋ด๋ ์ด๋ฉ์ผ/๋น๋ฐ๋ฒํธ ๋ณต๊ตฌ ๋ถ๊ฐ = ๋ก๊ทธ์ธ ์์ฒด ๋ถ๊ฐ. ์๋ ์ ์ํธํ๋ผ ์์ง ํ๋ฌธ์ผ๋ก ์ ๋ฎ์ด์ด ๋ฐ์ดํฐ๋ ๋ชป ์ฝ์)
โ ๏ธ .encryption_key ๊ฐ ๊ฐ์ฅ ์ค์. ์ด ํ์ผ ์์ผ๋ฉด ์ํธํ๋ ํ ์คํธ ์๊ตฌ ๋ณต๊ตฌ ๋ถ๊ฐ. ๋ฐฑ์ ํด๋์ ๊ผญ ๊ฐ์ด ๋ณต์ฌํ์ธ์.
๋ฐฉ๋ฒ A โ ์ฌ๋ณผ๋ฆญ ๋งํฌ ์๋ (1ํ ์ ์ , โญ ์ถ์ฒ)
Ctrl+C # dev.sh ์ข
๋ฃ
cd ~/easyseop/meetcute
mkdir -p ~/Dropbox/meetcute-backup
mv data ~/Dropbox/meetcute-backup/data
mv uploads ~/Dropbox/meetcute-backup/uploads
ln -s ~/Dropbox/meetcute-backup/data data
ln -s ~/Dropbox/meetcute-backup/uploads uploads
./dev.sh
โ ๏ธ ๋ ธํธ๋ถ ํ ๋์์๋ง ์ฌ์ฉ (SQLite ๋จ์ผ writer). ๊ฐ์ Dropbox DB ๋ฅผ ๋ ๋ ธํธ๋ถ์์ ๋์ ์ ๊ทผํ๋ฉด ๊นจ์ง ์ ์์.
๋ฐฉ๋ฒ B โ ์ฃผ๊ธฐ์ rsync
crontab -e
# ๋งค์ผ ์๋ฒฝ 2์
0 2 * * * rsync -a ~/easyseop/meetcute/data ~/easyseop/meetcute/uploads ~/Dropbox/meetcute-backup/
๋ฐฉ๋ฒ C โ ๋ฐฑ์ ๋ช ๋ น (โญ ์ถ์ฒ, ํค ํ์ผ๊น์ง ๊ฐ์ด ๋ฌถ์)
ํ ์ค๋ก data/ + uploads/ + .encryption_key + .secret ๋ชจ๋๋ฅผ timestamped .tar.gz ๋ก:
cd ~/easyseop/meetcute
python -m app.backup # โ BASE_DIR/backups/meetcute-YYYYMMDD-HHMMSS.tar.gz
python -m app.backup --out ~/Dropbox/meetcute-bk # ๋ค๋ฅธ ํด๋๋ก
python -m app.backup --keep 30 # ์ต์ 30๊ฐ๋ง ์ ์ง (๊ธฐ๋ณธ 14)
ํ๊ฒฝ๋ณ์ MEETCUTE_BACKUP_DIR=~/Dropbox/meetcute-bk ๋ก ๊ธฐ๋ณธ ์ถ๋ ฅ ํด๋ ์ง์ ๊ฐ๋ฅ. cron ์:
0 2 * * * cd ~/easyseop/meetcute && /usr/bin/python -m app.backup --quiet
๋ณต๊ตฌ
Ctrl+C
# ๋ฐฉ๋ฒ A/B (ํด๋ ๋ฐฑ์
)
cp -R ~/Dropbox/meetcute-backup/data/* data/
cp -R ~/Dropbox/meetcute-backup/uploads/* uploads/
# ๋ฐฉ๋ฒ C (tar.gz ๋ฐฑ์
)
tar xzf ~/Dropbox/meetcute-bk/meetcute-2026XXXX-XXXXXX.tar.gz -C ~/easyseop/meetcute/
./dev.sh
์ฌ์ง ์ธ๋ค์ผ ๋ฐฑํ (ํ ๋ฒ๋ง, ์๋ ํฅ์)
์ ๊ท ์ ๋ก๋๋ ์๋์ผ๋ก 800px ์ธ๋ค์ผ์ ๋ง๋ค์ง๋ง, ์ธ๋ค์ผ ๊ธฐ๋ฅ ์ด์ ์ ์ฌ๋ผ์จ ์ฌ์ง์ _thumb ํ์ผ์ด ์์ด์ ์นด๋ ๊ทธ๋ฆฌ๋๊ฐ ๋งค๋ฒ ์๋ณธ(1600px)์ ๋ถ๋ฌ์ ๋๋ฆผ. ํ ๋ฒ ๋๋ ค๋๋ฉด ์ ์ฌ์ง๋ ์ ์ฌ์ง์ฒ๋ผ ๋นจ๋ผ์ง:
cd ~/easyseop/meetcute
python -m app.backfill_thumbs
# ์์ฑ: N๊ฐ / ๊ฑด๋๋(์ด๋ฏธ ์์): M๊ฐ / ์คํจ: 0๊ฐ
idempotent โ ์ฌ๋ฌ ๋ฒ ๋๋ ค๋ ์์ .
6. ๋ณด์
์๋ ์ ์ฉ
- ์ธ์
์ฟ ํค ์๋ช
(
.secret์๋ ์์ฑ, chmod 600) - DB ํ๋ ์ํธํ (
.encryption_key์๋ ์์ฑ, Fernet/AES-128-CBC + HMAC) - ๋งค๋ฌผ PII: ๊ฑฐ์ฃผ์ง, ๋์ด ๋ ๊ฐ๋ง (์ง์ฅ์ ํ๋ฌธ)
- ๋ง๋ด๋ ๊ณ์ : ์ด๋ฉ์ผ, ๋น๋ฐ๋ฒํธ ํด์ โ ๋น๋ฐ๋ฒํธ๋ ์ด๋ฏธ bcrypt ๋จ๋ฐฉํฅ ํด์์ง๋ง ๊ทธ ์์ ํ ๋ฒ ๋ ์ํธํ (DB ๋ง ํธ๋ฆฌ๊ณ
.encryption_key์ ํธ๋ฆฐ ๊ฒฝ์ฐ ์คํ๋ผ์ธ ํฌ๋ํน ์ฐจ๋จ) - ๋น์ํธํ: ๊ทธ ์ธ ๋ชจ๋ ๊ฒ (public_id, ์ฑ๋ณ, ํค, ์ง์ฅ, ์ด๋ฆ(alias), ์ด์ํ, ์ฃผ์ ์ ๋ฉ๋ชจ, ๋ง๋จ ๋ฉ๋ชจ, ๋ณ๊ฒฝ ์ด๋ ฅ, ์์ฒญ/์๋ต ๋ฉ๋ชจ, ๋๋ค์, ํ ๋ ๊ทธ๋จ)
- ๋ฐฑ์๋ ํธํ: ๊ธฐ์กด ํ๋ฌธ์ ๊ทธ๋๋ก ์ฝํ. ์๋ ์ enc1: ๋ก ์ํธํ๋ ๋ฐ์ดํฐ(์ง์ฅ/alias/์ด์ํ/๋ฉ๋ชจ ๋ฑ)๋ ์๋ ๋ณตํธํํด์ ์ฝ๋ค๊ฐ, ํ ๋ฒ ์์ /์ ์ฅ๋๋ฉด ํ๋ฌธ์ผ๋ก ์ ์ฐฉ
- ์ด๋ฉ์ผ์ ๋น๊ฒฐ์ ์ ์ํธ๋ฌธ์ด๋ผ DB ์ธ๋ฑ์ค lookup ์ ๋จ โ ๋ก๊ทธ์ธ/์ค๋ณต์ฒดํฌ๋ ์ ์ฒด ์ ์ ์ค์บ + ๋ณตํธํ ๋น๊ต (๋ง๋ด๋ ์๊ฐ ์ ์ด ๋น์ฉ ๋ฌด์)
- ๋งค๋ฌผ ๊ณต๊ฐ ๋ฒ์ (PUBLIC / RESTRICTED): RESTRICTED ๋งค๋ฌผ์ owner + ์ฑ ์์ + ํ๋ฝ๋ admin ๋ง ์ ๊ทผ
- bcrypt ๋น๋ฐ๋ฒํธ ํด์ฑ
- SameSite=Lax ์ฟ ํค (CSRF ์ฐจ๋จ)
- ์ฌ์ง URL ์ธ์ฆ ๊ฒ์ดํธ (๋ก๊ทธ์ธ ์ ํ ์ฌ๋์ ๋ชป ๋ด)
- ๋ก๊ทธ์ธ 5ํ ์คํจ ์ IP 10๋ถ ์ ๊ธ
/uploads/path traversal ์ฐจ๋จ- ์ฑ ์์๋ง ๋ค๋ฅธ ์ฌ๋ chat_id ์ด๋
- ์ด๋ฆ(๋ฉ๋ชจ) ์ owner ๋ณธ์ธ/์ฑ ์์์๊ฒ๋ง ํ์
์ธ๋ถ ๋ ธ์ถ ์ ์ถ๊ฐ ๊ถ์ฅ
.env์MEETCUTE_PUBLIC=onโ HTTPS ์ฟ ํค ๊ฐ์ - ๊ฐํ
MEETCUTE_SECRET(.secretํ์ผ ์๋ ์์ฑ, 64์ hex) - cloudflared ๊ฐ HTTPS ์๋
- ๊ฐ์ ์ ๋๊ตฌ๋ ๊ฐ๋ฅํ์ง๋ง ์ฑ ์์ ์น์ธ ์์ด๋ ์๋ฌด ๊ธฐ๋ฅ ๋ชป ์
๋น๋ฐ๋ฒํธ ์์์ ๋ (์ฌ์ค์ UI ์์)
/tmp/meetcute-venv/bin/python -c "
from app.database import engine
from sqlmodel import Session, select
from app.models import User
from app.auth import hash_password
with Session(engine) as s:
u = s.exec(select(User).where(User.email == '๋ณธ์ธ์ด๋ฉ์ผ')).first()
u.password_hash = hash_password('์๋น๋ฐ๋ฒํธ')
s.add(u); s.commit()
print('๋ณ๊ฒฝ ์๋ฃ')
"
๋๋ ๊ทธ ์ฌ๋์ user row ๋ฅผ ์ญ์ ํ ์ฌ๊ฐ์ .
7. ์ง๋จ / ํธ๋ฌ๋ธ์ํ
| ์ฆ์ | ํ์ธ |
|---|---|
| ํ์ด์ง ๋ก๋ฉ ์ ๋จ | dev.sh ์ฝ์์ ์๋ฌ ์๋์ง |
| ํ ๋ ๊ทธ๋จ ๋ด ์๋ต X | .env ์ MEETCUTE_TELEGRAM_BOT_TOKEN ์๋์ง + dev.sh ์ฌ์์ ํ๋์ง |
๋ด ์ธ์ฆ ์คํจ: SSL ์ธ์ฆ์ |
macOS python.org ์ค์น ์ โ ์๋ ์ฒ๋ฆฌ๋จ (certifi ์ฌ์ฉ). ๊ทธ๋๋ ๋๋ฉด pip install -e . ๋ค์ |
MEETCUTE_SECRET ํ๊ฒฝ๋ณ์๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค |
.secret ํ์ผ ์๋ ์์ฑ๋จ โ dev.sh ์ฌ์์ |
getUpdates ๊ฐ ๋น์ด์์ด ์๋ ๊ฐ์ง ์ ๋จ |
๋ด username ์๋ชป (๋ค๋ฅธ ๋ดํํ
๋ฉ์์ง ๋ณด๋). /settings ํ์ด์ง์ ๐ข ๋ฐ์ค์์ ์ ํํ username ํ์ธ |
์ฝ์ ๋ก๊ทธ: dev.sh ๋์ด ํฐ๋ฏธ๋.