diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..6046dc7
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,45 @@
+version: 2
+updates:
+ # Python (requirements.txt, pyproject.toml)
+ - package-ecosystem: "pip"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ open-pull-requests-limit: 10
+
+ # React frontend (client/package.json)
+ - package-ecosystem: "npm"
+ directory: "/client"
+ schedule:
+ interval: "weekly"
+ open-pull-requests-limit: 10
+
+ # GitHub Actions workflows
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+
+ # Docker base image - FastAPI/Celery
+ - package-ecosystem: "docker"
+ directory: "/App"
+ schedule:
+ interval: "weekly"
+
+ # Docker base image - React/Nginx
+ - package-ecosystem: "docker"
+ directory: "/client"
+ schedule:
+ interval: "weekly"
+
+ # Docker base image - MySQL
+ - package-ecosystem: "docker"
+ directory: "/docker/mysql"
+ schedule:
+ interval: "weekly"
+
+ # Docker base image - Redis
+ - package-ecosystem: "docker"
+ directory: "/docker/redis"
+ schedule:
+ interval: "weekly"
diff --git a/.github/labeler.yml b/.github/labeler.yml
deleted file mode 100644
index 8f69fee..0000000
--- a/.github/labeler.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-model:
- - changed-files:
- - any-glob-to-any-file: ["src/model/**", "models/**", "*.pt", "*.pth"]
-
-data:
- - changed-files:
- - any-glob-to-any-file: ["data/**", "datasets/**", "src/data/**"]
-
-training:
- - changed-files:
- - any-glob-to-any-file: ["train*.py", "src/train/**", "configs/**"]
-
-api:
- - changed-files:
- - any-glob-to-any-file: ["api/**", "routers/**", "app.py"]
-
-test:
- - changed-files:
- - any-glob-to-any-file: ["tests/**", "test_*.py"]
-
-docs:
- - changed-files:
- - any-glob-to-any-file: ["docs/**", "*.md", "*.rst"]
-
-ci:
- - changed-files:
- - any-glob-to-any-file: [".github/**"]
-
-dependencies:
- - changed-files:
- - any-glob-to-any-file: ["requirements*.txt", "pyproject.toml", "setup.py"]
\ No newline at end of file
diff --git a/.github/workflows/docker-build-check.yml b/.github/workflows/docker-build-check.yml
index b07f70d..af11f2d 100644
--- a/.github/workflows/docker-build-check.yml
+++ b/.github/workflows/docker-build-check.yml
@@ -4,10 +4,19 @@ on:
pull_request:
branches: [main, develop]
paths:
- - "App/Dockerfile"
- - "client/Dockerfile"
- - "docker/mysql/Dockerfile"
- - "docker/redis/Dockerfile"
+ # fastapi / celery (App/Dockerfile이 COPY하는 대상)
+ - "App/**"
+ - "requirements.txt"
+ - "deepguard/**"
+ - "inference/**"
+ - "explainability/**"
+ - "preprocess/**"
+ - "pyproject.toml"
+ # react (client/Dockerfile이 COPY하는 대상)
+ - "client/**"
+ # mysql / redis
+ - "docker/mysql/**"
+ - "docker/redis/**"
permissions:
contents: read
@@ -17,69 +26,87 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
+ with:
+ lfs: true
- - name: Detect changed Dockerfiles
+ - name: Detect changed sources
id: changes
- uses: dorny/paths-filter@v3
+ uses: dorny/paths-filter@v4
with:
filters: |
app:
- - 'App/Dockerfile'
+ - 'App/**'
+ - 'requirements.txt'
+ - 'deepguard/**'
+ - 'inference/**'
+ - 'explainability/**'
+ - 'preprocess/**'
+ - 'pyproject.toml'
client:
- - 'client/Dockerfile'
+ - 'client/**'
mysql:
- - 'docker/mysql/Dockerfile'
+ - 'docker/mysql/**'
+ - 'App/sql/**'
redis:
- - 'docker/redis/Dockerfile'
+ - 'docker/redis/**'
- name: Set up Docker Buildx
if: steps.changes.outputs.app == 'true' || steps.changes.outputs.client == 'true' || steps.changes.outputs.mysql == 'true' || steps.changes.outputs.redis == 'true'
- uses: docker/setup-buildx-action@v3
+ uses: docker/setup-buildx-action@v4
# ── fastapi + celery (App/Dockerfile) ────────────────────────────────────
- - name: Build fastapi/celery (validate only)
+ - name: Build fastapi (validate only)
if: steps.changes.outputs.app == 'true'
- run: |
- set -euo pipefail
- docker buildx build \
- --target fastapi \
- --cache-from type=gha,scope=fastapi \
- --cache-to type=gha,mode=max,scope=fastapi \
- -f App/Dockerfile .
+ uses: docker/build-push-action@v7
+ with:
+ context: .
+ file: App/Dockerfile
+ target: fastapi
+ push: false
+ cache-from: type=gha,scope=fastapi
+ cache-to: type=gha,mode=max,scope=fastapi
- docker buildx build \
- --target celery \
- --cache-from type=gha,scope=celery \
- --cache-to type=gha,mode=max,scope=celery \
- -f App/Dockerfile .
+ - name: Build celery (validate only)
+ if: steps.changes.outputs.app == 'true'
+ uses: docker/build-push-action@v7
+ with:
+ context: .
+ file: App/Dockerfile
+ target: celery
+ push: false
+ cache-from: type=gha,scope=celery
+ cache-to: type=gha,mode=max,scope=celery
# ── react (client/Dockerfile) ─────────────────────────────────────────────
- name: Build react (validate only)
if: steps.changes.outputs.client == 'true'
- run: |
- set -euo pipefail
- docker buildx build \
- --cache-from type=gha,scope=react \
- --cache-to type=gha,mode=max,scope=react \
- -f client/Dockerfile client
+ uses: docker/build-push-action@v7
+ with:
+ context: client
+ file: client/Dockerfile
+ push: false
+ cache-from: type=gha,scope=react
+ cache-to: type=gha,mode=max,scope=react
# ── mysql (docker/mysql/Dockerfile) ───────────────────────────────────────
- name: Build mysql (validate only)
if: steps.changes.outputs.mysql == 'true'
- run: |
- set -euo pipefail
- docker buildx build \
- --cache-from type=gha,scope=mysql \
- --cache-to type=gha,mode=max,scope=mysql \
- -f docker/mysql/Dockerfile .
+ uses: docker/build-push-action@v7
+ with:
+ context: .
+ file: docker/mysql/Dockerfile
+ push: false
+ cache-from: type=gha,scope=mysql
+ cache-to: type=gha,mode=max,scope=mysql
# ── redis (docker/redis/Dockerfile) ───────────────────────────────────────
- name: Build redis (validate only)
if: steps.changes.outputs.redis == 'true'
- run: |
- set -euo pipefail
- docker buildx build \
- --cache-from type=gha,scope=redis \
- --cache-to type=gha,mode=max,scope=redis \
- -f docker/redis/Dockerfile .
+ uses: docker/build-push-action@v7
+ with:
+ context: .
+ file: docker/redis/Dockerfile
+ push: false
+ cache-from: type=gha,scope=redis
+ cache-to: type=gha,mode=max,scope=redis
diff --git a/.github/workflows/docker-bump-and-push.yml b/.github/workflows/docker-bump-and-push.yml
deleted file mode 100644
index 57cb4bc..0000000
--- a/.github/workflows/docker-bump-and-push.yml
+++ /dev/null
@@ -1,126 +0,0 @@
-name: Docker Image Bump & Push
-
-# Dockerfile이 main에 push되면 해당 이미지를 새 patch 버전 태그로 빌드해 Docker Hub에 푸시한다.
-# docker-compose.yml의 태그 갱신은 각 build step 로그에 찍히는 새 태그를 보고 수동으로 반영한다.
-
-on:
- push:
- branches: [main]
- paths:
- - "App/Dockerfile"
- - "client/Dockerfile"
- - "docker/mysql/Dockerfile"
- - "docker/redis/Dockerfile"
- workflow_dispatch:
-
-env:
- DOCKERHUB_NAMESPACE: ${{ secrets.DOCKERHUB_USERNAME }}
-
-jobs:
- bump-and-push:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Detect changed Dockerfiles
- id: changes
- uses: dorny/paths-filter@v3
- with:
- filters: |
- app:
- - 'App/Dockerfile'
- client:
- - 'client/Dockerfile'
- mysql:
- - 'docker/mysql/Dockerfile'
- redis:
- - 'docker/redis/Dockerfile'
-
- - name: Set up Docker Buildx
- if: steps.changes.outputs.app == 'true' || steps.changes.outputs.client == 'true' || steps.changes.outputs.mysql == 'true' || steps.changes.outputs.redis == 'true'
- uses: docker/setup-buildx-action@v3
-
- - name: Log in to Docker Hub
- if: steps.changes.outputs.app == 'true' || steps.changes.outputs.client == 'true' || steps.changes.outputs.mysql == 'true' || steps.changes.outputs.redis == 'true'
- uses: docker/login-action@v3
- with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_TOKEN }}
-
- # ── fastapi + celery (App/Dockerfile, 같은 버전으로 동시 bump) ──────────────
- - name: Bump & build fastapi/celery
- if: steps.changes.outputs.app == 'true'
- run: |
- set -euo pipefail
- current=$(grep -oP "seoyunje/deepguard-fastapi:\K[0-9]+\.[0-9]+\.[0-9]+" docker-compose.yml | head -1)
- IFS='.' read -r major minor patch <<< "$current"
- new_tag="${major}.${minor}.$((patch + 1))"
- echo "fastapi/celery: ${current} -> ${new_tag}"
-
- docker buildx build \
- --target fastapi \
- --tag "${DOCKERHUB_NAMESPACE}/deepguard-fastapi:${new_tag}" \
- --cache-from type=gha,scope=fastapi \
- --cache-to type=gha,mode=max,scope=fastapi \
- --push \
- -f App/Dockerfile .
-
- docker buildx build \
- --target celery \
- --tag "${DOCKERHUB_NAMESPACE}/deepguard-celery:${new_tag}" \
- --cache-from type=gha,scope=celery \
- --cache-to type=gha,mode=max,scope=celery \
- --push \
- -f App/Dockerfile .
-
- # ── react (client/Dockerfile) ────────────────────────────────────────────
- - name: Bump & build react
- if: steps.changes.outputs.client == 'true'
- run: |
- set -euo pipefail
- current=$(grep -oP "seoyunje/deepguard-react:\K[0-9]+\.[0-9]+\.[0-9]+" docker-compose.yml | head -1)
- IFS='.' read -r major minor patch <<< "$current"
- new_tag="${major}.${minor}.$((patch + 1))"
- echo "react: ${current} -> ${new_tag}"
-
- docker buildx build \
- --tag "${DOCKERHUB_NAMESPACE}/deepguard-react:${new_tag}" \
- --cache-from type=gha,scope=react \
- --cache-to type=gha,mode=max,scope=react \
- --push \
- -f client/Dockerfile client
-
- # ── mysql (docker/mysql/Dockerfile) ──────────────────────────────────────
- - name: Bump & build mysql
- if: steps.changes.outputs.mysql == 'true'
- run: |
- set -euo pipefail
- current=$(grep -oP "seoyunje/deepguard-mysql:\K[0-9]+\.[0-9]+\.[0-9]+" docker-compose.yml | head -1)
- IFS='.' read -r major minor patch <<< "$current"
- new_tag="${major}.${minor}.$((patch + 1))"
- echo "mysql: ${current} -> ${new_tag}"
-
- docker buildx build \
- --tag "${DOCKERHUB_NAMESPACE}/deepguard-mysql:${new_tag}" \
- --cache-from type=gha,scope=mysql \
- --cache-to type=gha,mode=max,scope=mysql \
- --push \
- -f docker/mysql/Dockerfile .
-
- # ── redis (docker/redis/Dockerfile) ──────────────────────────────────────
- - name: Bump & build redis
- if: steps.changes.outputs.redis == 'true'
- run: |
- set -euo pipefail
- current=$(grep -oP "seoyunje/deepguard-redis:\K[0-9]+\.[0-9]+\.[0-9]+" docker-compose.yml | head -1)
- IFS='.' read -r major minor patch <<< "$current"
- new_tag="${major}.${minor}.$((patch + 1))"
- echo "redis: ${current} -> ${new_tag}"
-
- docker buildx build \
- --tag "${DOCKERHUB_NAMESPACE}/deepguard-redis:${new_tag}" \
- --cache-from type=gha,scope=redis \
- --cache-to type=gha,mode=max,scope=redis \
- --push \
- -f docker/redis/Dockerfile .
diff --git a/.github/workflows/docker-bump-push-app.yml b/.github/workflows/docker-bump-push-app.yml
new file mode 100644
index 0000000..1d89224
--- /dev/null
+++ b/.github/workflows/docker-bump-push-app.yml
@@ -0,0 +1,94 @@
+name: Docker Bump & Push - FastAPI/Celery
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - "App/**"
+ - "requirements.txt"
+ - "deepguard/**"
+ - "inference/**"
+ - "explainability/**"
+ - "preprocess/**"
+ - "pyproject.toml"
+ workflow_dispatch:
+
+env:
+ DOCKERHUB_NAMESPACE: ${{ secrets.DOCKERHUB_USERNAME }}
+ GHCR_NAMESPACE: ghcr.io/hanmoonsub
+
+jobs:
+ bump-and-push:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v7
+ with:
+ lfs: true
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v4
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v4
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Log in to GHCR
+ uses: docker/login-action@v4
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Compute new tag from Docker Hub
+ id: bump
+ run: |
+ set -euo pipefail
+
+ get_latest_tag() {
+ local repo="$1"
+ local token tags_json
+ token=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${DOCKERHUB_NAMESPACE}/${repo}:pull" | python3 -c "import json,sys;print(json.load(sys.stdin).get('token',''))")
+ tags_json=$(curl -s -H "Authorization: Bearer ${token}" "https://registry-1.docker.io/v2/${DOCKERHUB_NAMESPACE}/${repo}/tags/list")
+ echo "$tags_json" | python3 -c "import json,sys; d=json.load(sys.stdin); v=sorted(tuple(int(p) for p in t.split('.')) for t in (d.get('tags') or []) if len(t.split('.'))==3 and all(p.isdigit() for p in t.split('.'))); print('%d.%d.%d'%v[-1] if v else '1.0.0')"
+ }
+
+ current_fastapi=$(get_latest_tag "deepguard-fastapi")
+ current_celery=$(get_latest_tag "deepguard-celery")
+ current=$(printf '%s\n%s\n' "$current_fastapi" "$current_celery" | sort -V | tail -1)
+
+ IFS='.' read -r major minor patch <<< "$current"
+ new_tag="${major}.${minor}.$((patch + 1))"
+ echo "fastapi/celery: 현재 최댓값 ${current} -> ${new_tag}"
+ echo "new_tag=${new_tag}" >> "$GITHUB_OUTPUT"
+
+ - name: Build & push fastapi
+ uses: docker/build-push-action@v7
+ with:
+ context: .
+ file: App/Dockerfile
+ target: fastapi
+ tags: |
+ ${{ env.DOCKERHUB_NAMESPACE }}/deepguard-fastapi:${{ steps.bump.outputs.new_tag }}
+ ${{ env.GHCR_NAMESPACE }}/deepguard-fastapi:${{ steps.bump.outputs.new_tag }}
+ push: true
+ cache-from: type=gha,scope=fastapi
+ cache-to: type=gha,mode=max,scope=fastapi
+
+ - name: Build & push celery
+ uses: docker/build-push-action@v7
+ with:
+ context: .
+ file: App/Dockerfile
+ target: celery
+ tags: |
+ ${{ env.DOCKERHUB_NAMESPACE }}/deepguard-celery:${{ steps.bump.outputs.new_tag }}
+ ${{ env.GHCR_NAMESPACE }}/deepguard-celery:${{ steps.bump.outputs.new_tag }}
+ push: true
+ cache-from: type=gha,scope=celery
+ cache-to: type=gha,mode=max,scope=celery
diff --git a/.github/workflows/docker-bump-push-mysql.yml b/.github/workflows/docker-bump-push-mysql.yml
new file mode 100644
index 0000000..afa337a
--- /dev/null
+++ b/.github/workflows/docker-bump-push-mysql.yml
@@ -0,0 +1,70 @@
+name: Docker Bump & Push - MySQL
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - "docker/mysql/**"
+ - "App/sql/**"
+ workflow_dispatch:
+
+env:
+ DOCKERHUB_NAMESPACE: ${{ secrets.DOCKERHUB_USERNAME }}
+ GHCR_NAMESPACE: ghcr.io/hanmoonsub
+
+jobs:
+ bump-and-push:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v7
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v4
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v4
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Log in to GHCR
+ uses: docker/login-action@v4
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Compute new tag from Docker Hub
+ id: bump
+ run: |
+ set -euo pipefail
+
+ get_latest_tag() {
+ local repo="$1"
+ local token tags_json
+ token=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${DOCKERHUB_NAMESPACE}/${repo}:pull" | python3 -c "import json,sys;print(json.load(sys.stdin).get('token',''))")
+ tags_json=$(curl -s -H "Authorization: Bearer ${token}" "https://registry-1.docker.io/v2/${DOCKERHUB_NAMESPACE}/${repo}/tags/list")
+ echo "$tags_json" | python3 -c "import json,sys; d=json.load(sys.stdin); v=sorted(tuple(int(p) for p in t.split('.')) for t in (d.get('tags') or []) if len(t.split('.'))==3 and all(p.isdigit() for p in t.split('.'))); print('%d.%d.%d'%v[-1] if v else '1.0.0')"
+ }
+
+ current=$(get_latest_tag "deepguard-mysql")
+ IFS='.' read -r major minor patch <<< "$current"
+ new_tag="${major}.${minor}.$((patch + 1))"
+ echo "mysql: 현재 최댓값 ${current} -> ${new_tag}"
+ echo "new_tag=${new_tag}" >> "$GITHUB_OUTPUT"
+
+ - name: Build & push mysql
+ uses: docker/build-push-action@v7
+ with:
+ context: .
+ file: docker/mysql/Dockerfile
+ tags: |
+ ${{ env.DOCKERHUB_NAMESPACE }}/deepguard-mysql:${{ steps.bump.outputs.new_tag }}
+ ${{ env.GHCR_NAMESPACE }}/deepguard-mysql:${{ steps.bump.outputs.new_tag }}
+ push: true
+ cache-from: type=gha,scope=mysql
+ cache-to: type=gha,mode=max,scope=mysql
diff --git a/.github/workflows/docker-bump-push-react.yml b/.github/workflows/docker-bump-push-react.yml
new file mode 100644
index 0000000..c17d66d
--- /dev/null
+++ b/.github/workflows/docker-bump-push-react.yml
@@ -0,0 +1,69 @@
+name: Docker Bump & Push - React
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - "client/**"
+ workflow_dispatch:
+
+env:
+ DOCKERHUB_NAMESPACE: ${{ secrets.DOCKERHUB_USERNAME }}
+ GHCR_NAMESPACE: ghcr.io/hanmoonsub
+
+jobs:
+ bump-and-push:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v7
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v4
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v4
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Log in to GHCR
+ uses: docker/login-action@v4
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Compute new tag from Docker Hub
+ id: bump
+ run: |
+ set -euo pipefail
+
+ get_latest_tag() {
+ local repo="$1"
+ local token tags_json
+ token=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${DOCKERHUB_NAMESPACE}/${repo}:pull" | python3 -c "import json,sys;print(json.load(sys.stdin).get('token',''))")
+ tags_json=$(curl -s -H "Authorization: Bearer ${token}" "https://registry-1.docker.io/v2/${DOCKERHUB_NAMESPACE}/${repo}/tags/list")
+ echo "$tags_json" | python3 -c "import json,sys; d=json.load(sys.stdin); v=sorted(tuple(int(p) for p in t.split('.')) for t in (d.get('tags') or []) if len(t.split('.'))==3 and all(p.isdigit() for p in t.split('.'))); print('%d.%d.%d'%v[-1] if v else '1.0.0')"
+ }
+
+ current=$(get_latest_tag "deepguard-react")
+ IFS='.' read -r major minor patch <<< "$current"
+ new_tag="${major}.${minor}.$((patch + 1))"
+ echo "react: 현재 최댓값 ${current} -> ${new_tag}"
+ echo "new_tag=${new_tag}" >> "$GITHUB_OUTPUT"
+
+ - name: Build & push react
+ uses: docker/build-push-action@v7
+ with:
+ context: client
+ file: client/Dockerfile
+ tags: |
+ ${{ env.DOCKERHUB_NAMESPACE }}/deepguard-react:${{ steps.bump.outputs.new_tag }}
+ ${{ env.GHCR_NAMESPACE }}/deepguard-react:${{ steps.bump.outputs.new_tag }}
+ push: true
+ cache-from: type=gha,scope=react
+ cache-to: type=gha,mode=max,scope=react
diff --git a/.github/workflows/docker-bump-push-redis.yml b/.github/workflows/docker-bump-push-redis.yml
new file mode 100644
index 0000000..1bfd5ef
--- /dev/null
+++ b/.github/workflows/docker-bump-push-redis.yml
@@ -0,0 +1,69 @@
+name: Docker Bump & Push - Redis
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - "docker/redis/**"
+ workflow_dispatch:
+
+env:
+ DOCKERHUB_NAMESPACE: ${{ secrets.DOCKERHUB_USERNAME }}
+ GHCR_NAMESPACE: ghcr.io/hanmoonsub
+
+jobs:
+ bump-and-push:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v7
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v4
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v4
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Log in to GHCR
+ uses: docker/login-action@v4
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Compute new tag from Docker Hub
+ id: bump
+ run: |
+ set -euo pipefail
+
+ get_latest_tag() {
+ local repo="$1"
+ local token tags_json
+ token=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${DOCKERHUB_NAMESPACE}/${repo}:pull" | python3 -c "import json,sys;print(json.load(sys.stdin).get('token',''))")
+ tags_json=$(curl -s -H "Authorization: Bearer ${token}" "https://registry-1.docker.io/v2/${DOCKERHUB_NAMESPACE}/${repo}/tags/list")
+ echo "$tags_json" | python3 -c "import json,sys; d=json.load(sys.stdin); v=sorted(tuple(int(p) for p in t.split('.')) for t in (d.get('tags') or []) if len(t.split('.'))==3 and all(p.isdigit() for p in t.split('.'))); print('%d.%d.%d'%v[-1] if v else '1.0.0')"
+ }
+
+ current=$(get_latest_tag "deepguard-redis")
+ IFS='.' read -r major minor patch <<< "$current"
+ new_tag="${major}.${minor}.$((patch + 1))"
+ echo "redis: 현재 최댓값 ${current} -> ${new_tag}"
+ echo "new_tag=${new_tag}" >> "$GITHUB_OUTPUT"
+
+ - name: Build & push redis
+ uses: docker/build-push-action@v7
+ with:
+ context: .
+ file: docker/redis/Dockerfile
+ tags: |
+ ${{ env.DOCKERHUB_NAMESPACE }}/deepguard-redis:${{ steps.bump.outputs.new_tag }}
+ ${{ env.GHCR_NAMESPACE }}/deepguard-redis:${{ steps.bump.outputs.new_tag }}
+ push: true
+ cache-from: type=gha,scope=redis
+ cache-to: type=gha,mode=max,scope=redis
diff --git a/.github/workflows/first-interaction.yml b/.github/workflows/first-interaction.yml
new file mode 100644
index 0000000..ebb3b88
--- /dev/null
+++ b/.github/workflows/first-interaction.yml
@@ -0,0 +1,27 @@
+name: First interaction
+
+# 처음 이슈/PR을 남기는 기여자에게 자동으로 환영 댓글을 남긴다.
+
+on:
+ issues:
+ types: [opened]
+ pull_request_target:
+ types: [opened]
+
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ greeting:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/first-interaction@v3.1.0
+ with:
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ issue_message: >
+ 이슈를 남겨주셔서 감사합니다. 첫 이슈네요.
+ 메인테이너가 확인 후 곧 답변드리겠습니다.
+ pr_message: >
+ PR을 열어주셔서 감사합니다. 첫 PR이네요.
+ 리뷰 후 피드백 남기겠습니다.
diff --git a/.github/workflows/lock-threads.yml b/.github/workflows/lock-threads.yml
new file mode 100644
index 0000000..e8aa43a
--- /dev/null
+++ b/.github/workflows/lock-threads.yml
@@ -0,0 +1,30 @@
+name: Lock Threads
+
+# 오래전에 closed된 이슈/PR을 자동으로 잠가서, 관련 없는 새 댓글(necro-posting)을 방지한다.
+
+on:
+ schedule:
+ - cron: "0 1 * * *" # 매일 UTC 01:00 (KST 10:00) 실행
+ workflow_dispatch:
+
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ lock:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: dessant/lock-threads@v6.0.2
+ with:
+ process-only: "issues, prs"
+ issue-inactive-days: "365"
+ pr-inactive-days: "365"
+ issue-comment: >
+ 이 이슈는 closed된 지 오래되어 자동으로 잠겼습니다.
+ 관련된 새 이슈가 있다면 새로 열어주세요.
+ pr-comment: >
+ 이 PR은 closed된 지 오래되어 자동으로 잠겼습니다.
+ 관련된 새 PR이 있다면 새로 열어주세요.
+ issue-lock-reason: "resolved"
+ pr-lock-reason: "resolved"
diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml
new file mode 100644
index 0000000..2903c78
--- /dev/null
+++ b/.github/workflows/pypi-publish.yml
@@ -0,0 +1,42 @@
+name: Publish to PyPI
+
+# main에 pyproject.toml이 push되면: README 이미지 경로를 절대 URL로 치환 -> 빌드 -> PyPI에 바로 배포.
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - "pyproject.toml"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write # PyPI Trusted Publishing(OIDC)에 필요 - API 토큰 불필요
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v7
+ with:
+ lfs: true
+
+ - name: Set up Python
+ uses: actions/setup-python@v7
+ with:
+ python-version: "3.11"
+ cache: "pip"
+
+ - name: Rewrite relative image paths for PyPI
+ run: sed -i -E 's#src="docs/#src="https://raw.githubusercontent.com/HanMoonSub/DeepGuard/main/docs/#g' README.md
+
+ - name: Install build tool
+ run: pip install build
+
+ - name: Build sdist & wheel
+ run: python -m build
+
+ - name: Publish to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 0000000..fac62e4
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,33 @@
+name: Close Stale Issues
+
+# 일정 기간 활동 없는 이슈/PR에 stale 라벨을 붙이고, 그 후에도 방치되면 자동으로 닫는다.
+
+on:
+ schedule:
+ - cron: "0 0 * * *" # 매일 UTC 00:00 (KST 09:00) 실행
+ workflow_dispatch:
+
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@v11
+ with:
+ days-before-stale: 60
+ days-before-close: 7
+ stale-issue-label: "stale"
+ stale-pr-label: "stale"
+ stale-issue-message: >
+ 이 이슈는 60일 동안 활동이 없어 stale 라벨이 추가되었습니다.
+ 7일 내에 추가 활동이 없으면 자동으로 닫힙니다.
+ stale-pr-message: >
+ 이 PR은 60일 동안 활동이 없어 stale 라벨이 추가되었습니다.
+ 7일 내에 추가 활동이 없으면 자동으로 닫힙니다.
+ close-issue-message: "60일 이상 활동이 없어 자동으로 닫습니다. 필요하면 다시 열어주세요."
+ close-pr-message: "60일 이상 활동이 없어 자동으로 닫습니다. 필요하면 다시 열어주세요."
+ exempt-issue-labels: "pinned,security"
+ exempt-pr-labels: "pinned"
diff --git a/README.md b/README.md
index 47997df..8320b37 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
-
+
+
+
+
+
🇰🇷 한국어 버전 | 🇯🇵 日本語版 | 📈 Model Evaluation | - 🔮 Try Demo + 🤗 Try Demo | + 🤗 Hugging Face
## 📌 Contents -- [💡 Install & Requirements](#-install--requirements) -- [🛠 SetUp](#-setup) +- [🐳 Docker Quick Start](#-docker-quick-start) - Run the full stack (MySQL, Redis, FastAPI, Celery, React) with Docker Compose +- [🤗 Try It Live: Hugging Face Spaces](#-try-it-live-hugging-face-spaces) - No-install browser demos for image, video, and XAI deepfake detection - [📚 DeepFake Video BenchMark Datasets](#-deepfake-video-benchmark-datasets) — Overview of Celeb-DF-v2, FF++, and KoDF datasets used for training. - [⚙️ Data Preparation](#data-preparation) — Efficient face detection and landmark extraction pipeline using YOLOv8 - [🏗 Model Architecture](#-model-architecture) — Detailed look into our hybrid CNN-ViT (MS-EffViT & MS-EffGCViT) designs. - [🧬 Model Zoo](#-model-zoo) — Comparison of model variants, parameter counts, and computational complexity (FLOPs). - [🚀 Training](#-training) - Step-by-step training scrips with Goolge Colab and W&B experiment tracking - [📈 Model Evaluation](#-model-evaluation) - Benchmarking results -- [💻 Model Usage](#-model-usage) - How to integrate DeepGuard models into your own Python code or via timm +- [💻 Model Usage](#-model-usage) - Load pretrained models via `pip install deepguard` or straight from the Hugging Face Hub - [🔮 Predict Image & Video](#-predict-image--video) - Simple Inference examples for detecting deepfakes in image and video - [🎨 DeepFake AI Explainability](#-deepfake-ai-explainability) - Visualizing model focus using Grad-CAM and attention maps - [📓 Tutorials](#-tutorials) - Hands-on Colab notebooks for inference and dual-branch XAI visualization -- [📬 Authors](#-authors) -- [📝 Reference](#-reference) -- [⚖️ License](#-license) +- [📬 Authors](#-authors) - Team behind this senior graduation project at Chungbuk National University +- [📝 Reference](#-reference) - Libraries, datasets, and prior work this project builds on +- [⚖️ License](#-license) - MIT license -## 💡 Install & Requirements +## 🐳 Docker Quick Start -To install requirements: +Run the full stack (MySQL + Redis + FastAPI + Celery + React) with Docker Compose — no local Python/Node setup required. -```python -pip install -r requirements.txt -``` +| **FastAPI** | **Celery** | **Redis** | **MySQL** | **React** | +| --- | --- | --- | --- | --- | +| REST API backend — routes, inference/explain services, DB access
+
+
|
+ 🖼️
+ Image Detection
+ + Upload an image → real / fake probability + +
|
+
+ 🎬
+ Video Detection
+ + Upload a video → frame-aggregated probability + +
|
+
+ 🎨
+ Detection XAI
+ + See why — dual-branch Grad-CAM heatmaps + +
|
+
+ 💛 Enjoying the demos? Please leave a ❤️ like on the Space — it means a lot to us!
+
+ Looking for the underlying checkpoints instead of the demo UI? Jump to 🤗 Model Usage → Hugging Face Hub.
+
Senior Graduation Project — Department of Software, Chungbuk National University (CBNU), Republic of Korea
+ +
+
+
+
+
🇺🇸 English Version | 🇰🇷 한국어 버전 | 📈 モデル評価 | - 🔮 デモ実行 + 🤗 デモを試す | + 🤗 Hugging Face
## 📌 目次 -- [💡 インストールと要件](#-インストールと要件) -- [🛠 セットアップ](#-セットアップ) +- [🐳 Docker Quick Start](#-docker-quick-start) - Docker Compose でフルスタック(MySQL, Redis, FastAPI, Celery, React)を起動 +- [🤗 今すぐ体験: Hugging Face Spaces](#-今すぐ体験-hugging-face-spaces) - インストール不要、ブラウザだけで試せる画像/動画/XAI ディープフェイク検出デモ - [📚 ディープフェイク動画ベンチマークデータセット](#-ディープフェイク動画ベンチマークデータセット) — 学習に使用した Celeb-DF-v2, FF++, KoDF データセットの概要。 - [⚙️ データ準備](#データ準備) — YOLOv8 を用いた効率的な顔検出およびランドマーク抽出パイプライン。 - [🏗 モデルアーキテクチャ](#-モデルアーキテクチャ) — ハイブリッド CNN-ViT (MS-EffViT & MS-EffGCViT) 設計の詳細。 - [🧬 Model Zoo](#-model-zoo) — モデルバリアントごとのパラメータ数および演算量(FLOPs)の比較。 - [🚀 学習](#-学習) - Google Colab および W&B を活用した段階的な学習スクリプト。 - [📈 モデル評価](#-モデル評価) - ベンチマーク結果。 -- [💻 モデルの使い方](#-モデルの使い方) - Python コードおよび timm を通じた DeepGuard モデルの統合方法。 +- [💻 モデルの使い方](#-モデルの使い方) - `pip install deepguard` または Hugging Face Hub から学習済みモデルをロード - [🔮 画像と動画の予測](#-画像と動画の予測) - ディープフェイク検出のためのシンプルな推論例。 - [🎨 ディープフェイクAI説明可能性(XAI)](#-ディープフェイクai説明可能性xai) - Grad-CAM およびアテンションマップによるモデル判断根拠の可視化。 - [📓 Tutorials](#-tutorials) - 推論とデュアルブランチXAI可視化のためのハンズオンノートブック -- [📬 制作者](#-制作者) -- [📝 参考文献](#-参考文献) -- [⚖️ ライセンス](#-ライセンス) +- [📬 制作者](#-制作者) - 忠北大学校の卒業制作を手がけたチーム紹介 +- [📝 参考文献](#-参考文献) - 本プロジェクトが参考にしたライブラリ、データセット、先行研究 +- [⚖️ ライセンス](#-ライセンス) - MIT ライセンス --- -## 💡 インストールと要件 +## 🐳 Docker Quick Start -必要なライブラリのインストール: +Docker Compose でフルスタック(MySQL + Redis + FastAPI + Celery + React)を起動します — ローカルに Python/Node 環境を用意する必要はありません。 -```bash -pip install -r requirements.txt -``` +| **FastAPI** | **Celery** | **Redis** | **MySQL** | **React** | +| --- | --- | --- | --- | --- | +| REST API バックエンド — ルーティング、推論/説明サービス、DBアクセス
+
+
|
+ 🖼️
+ 画像検出
+ + 画像をアップロード → real / fake の確率 + +
|
+
+ 🎬
+ 動画検出
+ + 動画をアップロード → フレーム集約による確率 + +
|
+
+ 🎨
+ 検出XAI
+ + 理由を可視化 — デュアルブランチ Grad-CAM ヒートマップ + +
|
+
+ 💛 デモが気に入りましたか? Space に ❤️ いいねをお願いします — とても励みになります!
+
+ デモUIではなく実際のチェックポイントをお探しですか? 🤗 モデルの使い方 → Hugging Face Hub へどうぞ。
+
忠北大学校(CBNU)ソフトウェア学部の卒業制作(Senior Graduation Project)
+ +
+
+
+
+
🇺🇸 English Version | 🇯🇵 日本語版 | 📈 모델 평가 | - 🔮 데모 실행 + 🤗 데모 체험 | + 🤗 Hugging Face
## 📌 목차 -- [💡 설치 및 요구사항](#-설치-및-요구사항) -- [🛠 설정](#-설정) +- [🐳 Docker Quick Start](#-docker-quick-start) - Docker Compose로 전체 스택(MySQL, Redis, FastAPI, Celery, React) 실행 +- [🤗 지금 바로 체험하기: Hugging Face Spaces](#-지금-바로-체험하기-hugging-face-spaces) - 설치 없이 브라우저에서 바로 체험하는 이미지/비디오/XAI 딥페이크 탐지 데모 - [📚 딥페이크 비디오 벤치마크 데이터셋](#-딥페이크-비디오-벤치마크-데이터셋) — 학습에 사용된 Celeb-DF-v2, FF++, KoDF 데이터셋 개요. - [⚙️ 데이터 준비](#데이터-준비) — YOLOv8을 이용한 효율적인 얼굴 검출 및 랜드마크 추출 파이프라인. - [🏗 모델 구조](#-모델-구조) — 하이브리드 CNN-ViT (MS-EffViT & MS-EffGCViT) 설계 상세. - [🧬 모델 주(Model Zoo)](#-model-zoo) — 모델 변체별 파라미터 수 및 연산량(FLOPs) 비교. - [🚀 학습](#-학습) - Google Colab 및 W&B를 활용한 단계별 학습 스크립트. - [📈 모델 평가](#-모델-평가) - 벤치마크 결과. -- [💻 모델 사용법](#-모델-사용법) - Python 코드 및 timm을 통한 DeepGuard 모델 통합 방법. +- [💻 모델 사용법](#-모델-사용법) - `pip install deepguard` 또는 Hugging Face Hub를 통한 사전 학습 모델 로드 - [🔮 이미지 및 비디오 예측](#-이미지-및-비디오-예측) - 딥페이크 탐지를 위한 간단한 추론 예시. - [🎨 딥페이크 AI 설명가능성(XAI)](#-딥페이크-ai-설명가능성xai) - Grad-CAM 및 어텐션 맵을 통한 모델 판단 근거 시각화. - [📓 Tutorials](#-tutorials) - 추론과 듀얼 브랜치 XAI 시각화를 위한 실습 노트북 -- [📬 제작자](#-제작자) -- [📝 참고 문헌](#-참고-문헌) -- [⚖️ 라이선스](#-라이선스) +- [📬 제작자](#-제작자) - 충북대학교 졸업 작품을 만든 팀 소개 +- [📝 참고 문헌](#-참고-문헌) - 이 프로젝트가 참고한 라이브러리, 데이터셋, 선행 연구 +- [⚖️ 라이선스](#-라이선스) - MIT 라이선스 --- -## 💡 설치 및 요구사항 +## 🐳 Docker Quick Start -필수 라이브러리 설치: +Docker Compose로 전체 스택(MySQL + Redis + FastAPI + Celery + React)을 실행합니다 — 로컬에 Python/Node 환경을 따로 설치할 필요가 없습니다. -```bash -pip install -r requirements.txt -``` +| **FastAPI** | **Celery** | **Redis** | **MySQL** | **React** | +| --- | --- | --- | --- | --- | +| REST API 백엔드 — 라우트, 추론/설명 서비스, DB 접근
+
+
|
+ 🖼️
+ 이미지 탐지
+ + 이미지를 업로드하면 → real / fake 확률 + +
|
+
+ 🎬
+ 비디오 탐지
+ + 비디오를 업로드하면 → 프레임 집계 확률 + +
|
+
+ 🎨
+ 탐지 XAI
+ + 왜 그렇게 판단했는지 확인 — 듀얼 브랜치 Grad-CAM 히트맵 + +
|
+
+ 💛 데모가 마음에 드셨나요? Space에 ❤️ 좋아요를 남겨주세요 — 큰 힘이 됩니다!
+
+ 데모 UI 대신 실제 체크포인트를 찾고 계신가요? 🤗 모델 사용법 → Hugging Face Hub로 이동하세요.
+
충북대학교(CBNU) 소프트웨어학부 졸업 작품(Senior Graduation Project)
+ +