학교 출석 시스템 설치 패키지 (docker-compose + 원클릭 설치 스크립트)
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# 이 파일을 .env로 복사한 뒤 값을 채우세요: cp .env.example .env
|
||||
|
||||
# 학교 도메인 (DNS A레코드를 이 서버 IP로 미리 연결해두세요. Caddy가 자동으로 HTTPS 인증서를 발급합니다)
|
||||
DOMAIN=attendance.myschool.ac.kr
|
||||
|
||||
# DB 비밀번호 (임의의 강력한 문자열로 변경하세요)
|
||||
DB_PASSWORD=change_this_password
|
||||
|
||||
# 교사 회원가입 시 필요한 인증 코드 (학교마다 다르게 설정)
|
||||
TEACHER_SECRET_CODE=change_this_secret_code
|
||||
|
||||
# /reset-db (출석 DB 초기화) 엔드포인트 보호용 비밀번호
|
||||
ADMIN_RESET_PASSWORD=change_this_admin_password
|
||||
@@ -0,0 +1,60 @@
|
||||
# 다른 학교에 설치하기
|
||||
|
||||
이 시스템을 새 학교 서버에 설치하는 절차입니다. 백엔드는 도커로 뜨고, 도메인만 지정하면 HTTPS 인증서는 자동으로 발급됩니다.
|
||||
|
||||
## 1. Firebase 프로젝트 만들기 (학교마다 1개씩)
|
||||
|
||||
푸시 알림(FCM)과 웹 대시보드 호스팅에 필요합니다.
|
||||
|
||||
1. https://console.firebase.google.com 에서 새 프로젝트 생성
|
||||
2. Flutter 앱 폴더에서 `flutterfire configure` 실행 → 방금 만든 프로젝트 선택
|
||||
- `lib/firebase_options.dart`, `android/app/google-services.json`이 새 프로젝트 값으로 교체됩니다
|
||||
3. Firebase 콘솔 → 프로젝트 설정 → Cloud Messaging → **웹 푸시 인증서**에서 VAPID 키 발급 (아래 3번에서 사용)
|
||||
4. Firebase 콘솔 → 프로젝트 설정 → 서비스 계정 → **새 비공개 키 생성** → `firebase-key.json`으로 저장 (서버에 올릴 파일, 4번에서 사용)
|
||||
|
||||
## 2. 안드로이드 앱 ID 변경 (학교마다 다르게)
|
||||
|
||||
- `android/app/build.gradle.kts`의 `applicationId`를 학교별 고유 값으로 변경 (예: `com.myschool.attendance`)
|
||||
- 릴리즈 키스토어 새로 생성하고 `android/key.properties`에 경로/비밀번호 등록 (기존 백산고 릴리즈와 절대 같은 키스토어 쓰지 않기)
|
||||
|
||||
## 3. 서버에 백엔드 배포
|
||||
|
||||
가장 쉬운 방법 — 설치 스크립트 한 줄:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://hootongerungit.mcv.kr/sihoo/school-attendance-deploy/raw/branch/main/install.sh | bash
|
||||
```
|
||||
|
||||
Docker가 없으면 자동으로 설치하고, 도메인/교사 인증코드를 물어본 뒤 미리 빌드해둔 이미지(`hootongerungit.mcv.kr/sihoo/school-backend`)를 내려받아 바로 실행합니다. (소스코드는 비공개 레포에 있고, 이 이미지는 그 코드로 미리 빌드해서 공개로 올려둔 것 — 설치할 땐 소스코드 접근이 필요 없습니다.)
|
||||
|
||||
수동으로 하고 싶다면:
|
||||
|
||||
```bash
|
||||
git clone https://hootongerungit.mcv.kr/sihoo/school-attendance-deploy.git
|
||||
cd school-attendance-deploy
|
||||
cp .env.example .env
|
||||
vi .env # DOMAIN, DB_PASSWORD, TEACHER_SECRET_CODE, ADMIN_RESET_PASSWORD 채우기
|
||||
# 1-4번에서 만든 firebase-key.json을 이 폴더에 복사
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
- `DOMAIN`으로 지정한 주소의 DNS A레코드를 이 서버의 공인 IP로 미리 연결해두세요 (포트 80/443 오픈 필요). Caddy가 첫 요청부터 자동으로 Let's Encrypt 인증서를 발급합니다.
|
||||
- `docker compose logs -f backend`로 정상 기동(DB 연결 완료 로그) 확인.
|
||||
- 백엔드 코드가 업데이트되면 `docker compose pull && docker compose up -d`로 최신 이미지로 갱신하면 됩니다.
|
||||
|
||||
## 4. Flutter 앱 빌드 (학교별 서버 주소로)
|
||||
|
||||
```bash
|
||||
flutter build web --release -t lib/main_client_app.dart \
|
||||
--dart-define=BASE_URL=https://<2번에서 지정한 DOMAIN> \
|
||||
--dart-define=WEB_VAPID_KEY=<1-3번에서 발급받은 VAPID 키> \
|
||||
--dart-define=SCHOOL_NAME=<학교 이름>
|
||||
|
||||
flutter build apk --release --split-per-abi -t lib/main_client_app.dart \
|
||||
--dart-define=BASE_URL=https://<DOMAIN> \
|
||||
--dart-define=WEB_VAPID_KEY=<VAPID 키> \
|
||||
--dart-define=SCHOOL_NAME=<학교 이름>
|
||||
```
|
||||
|
||||
인자를 안 주면 기존 백산고 서버(backsanhi.mcv.kr) 기본값으로 빌드되니, 새 학교용 빌드에는 반드시 세 값을 다 넘겨야 합니다.
|
||||
@@ -0,0 +1,52 @@
|
||||
services:
|
||||
db:
|
||||
image: postgres:16
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: school_management
|
||||
POSTGRES_USER: school_admin
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U school_admin -d school_management"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
backend:
|
||||
image: hootongerungit.mcv.kr/sihoo/school-backend:${IMAGE_TAG:-latest}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DB_HOST: db
|
||||
DB_NAME: school_management
|
||||
DB_USER: school_admin
|
||||
DB_PASS: ${DB_PASSWORD}
|
||||
TEACHER_SECRET_CODE: ${TEACHER_SECRET_CODE}
|
||||
ADMIN_RESET_PASSWORD: ${ADMIN_RESET_PASSWORD}
|
||||
FIREBASE_KEY_PATH: /run/secrets/firebase-key.json
|
||||
volumes:
|
||||
- ./firebase-key.json:/run/secrets/firebase-key.json:ro
|
||||
|
||||
caddy:
|
||||
image: caddy:2
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
environment:
|
||||
DOMAIN: ${DOMAIN}
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
depends_on:
|
||||
- backend
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
# 🏫 학교 출석 시스템 원클릭 설치 스크립트
|
||||
# 사용법: curl -fsSL <이 파일의 raw 주소> | bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_URL="https://hootongerungit.mcv.kr/sihoo/school-attendance-deploy.git"
|
||||
INSTALL_DIR="school-attendance"
|
||||
|
||||
echo "🏫 학교 출석 시스템 설치를 시작합니다."
|
||||
echo ""
|
||||
|
||||
# ── 1. Docker 확인/설치 ──────────────────────────────────────
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo "🐳 Docker가 없어서 자동으로 설치합니다..."
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
else
|
||||
echo "✅ Docker 확인됨"
|
||||
fi
|
||||
|
||||
if ! docker compose version &> /dev/null; then
|
||||
echo "❌ 'docker compose' 명령을 찾을 수 없습니다. Docker Compose 플러그인을 설치한 뒤 다시 시도하세요."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v git &> /dev/null; then
|
||||
echo "❌ git이 설치되어 있지 않습니다. 먼저 설치해주세요 (예: sudo apt install git)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── 2. 저장소 받기 ────────────────────────────────────────────
|
||||
if [ -d "$INSTALL_DIR/.git" ]; then
|
||||
echo "📦 이미 받아져 있어서 최신 버전으로 갱신합니다."
|
||||
git -C "$INSTALL_DIR" pull
|
||||
else
|
||||
echo "📦 저장소를 내려받는 중..."
|
||||
git clone "$REPO_URL" "$INSTALL_DIR"
|
||||
fi
|
||||
cd "$INSTALL_DIR"
|
||||
|
||||
# ── 3. 설정값 입력받기 (curl | bash로 실행돼도 터미널에서 직접 입력받도록 /dev/tty 사용) ──
|
||||
if [ -f .env ]; then
|
||||
echo "⚠️ .env가 이미 있어서 기존 설정을 그대로 사용합니다. (다시 설정하려면 .env를 지우고 재실행)"
|
||||
else
|
||||
echo ""
|
||||
read -rp "🌐 이 서버에서 쓸 도메인 (예: attendance.myschool.ac.kr): " SCHOOL_DOMAIN < /dev/tty
|
||||
read -rp "🔑 교사 회원가입용 인증 코드 (선생님들께 알려줄 코드): " TEACHER_CODE < /dev/tty
|
||||
|
||||
if command -v openssl &> /dev/null; then
|
||||
DB_PW=$(openssl rand -hex 16)
|
||||
ADMIN_PW=$(openssl rand -hex 8)
|
||||
else
|
||||
DB_PW=$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')
|
||||
ADMIN_PW=$(head -c 16 /dev/urandom | od -An -tx1 | tr -d ' \n')
|
||||
fi
|
||||
|
||||
cat > .env <<EOF
|
||||
DOMAIN=${SCHOOL_DOMAIN}
|
||||
DB_PASSWORD=${DB_PW}
|
||||
TEACHER_SECRET_CODE=${TEACHER_CODE}
|
||||
ADMIN_RESET_PASSWORD=${ADMIN_PW}
|
||||
EOF
|
||||
|
||||
echo "✅ .env 생성 완료 (DB/관리자 비밀번호는 자동 생성 — 설치 끝나면 화면에 다시 보여드려요)"
|
||||
fi
|
||||
|
||||
# ── 4. firebase-key.json 확인 ─────────────────────────────────
|
||||
if [ ! -f firebase-key.json ]; then
|
||||
echo ""
|
||||
echo "⚠️ firebase-key.json이 없어서 빈 파일로 우선 설치를 진행합니다 (알림 기능은 꺼진 상태로 시작)."
|
||||
echo " Firebase 콘솔에서 키를 받아 $(pwd)/firebase-key.json으로 덮어쓴 뒤"
|
||||
echo " 'docker compose restart backend' 하면 알림 기능이 켜집니다."
|
||||
echo "{}" > firebase-key.json
|
||||
fi
|
||||
|
||||
# ── 5. 실행 (이미지는 새로 빌드하지 않고 미리 만들어둔 이미지를 내려받아 사용) ──
|
||||
echo ""
|
||||
echo "🚀 이미지를 내려받고 컨테이너를 실행합니다..."
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source .env
|
||||
|
||||
echo ""
|
||||
echo "🎉 설치 완료!"
|
||||
echo "──────────────────────────────────────────"
|
||||
echo " 도메인: $DOMAIN"
|
||||
echo " 교사 인증코드: $TEACHER_SECRET_CODE"
|
||||
echo " DB 비밀번호: $DB_PASSWORD (꼭 기록해두세요)"
|
||||
echo " 관리자 초기화 비밀번호: $ADMIN_RESET_PASSWORD"
|
||||
echo "──────────────────────────────────────────"
|
||||
echo "DNS A레코드를 이 서버의 공인 IP로 연결하면 몇 분 안에 https://$DOMAIN 으로 접속됩니다."
|
||||
echo "상태 확인: cd $INSTALL_DIR && docker compose logs -f backend"
|
||||
Reference in New Issue
Block a user