Skip to content
Merged

Tuan #106

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/backend-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ jobs:
echo "=== Initial disk space ==="
df -h

- name: Upload nginx config to server
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.LIGHTSAIL_HOST }}
username: ${{ secrets.LIGHTSAIL_USER }}
key: ${{ secrets.LIGHTSAIL_SSH_KEY }}
source: "backend/nginx/nginx.conf,backend/nginx/conf.d/cors-map.conf"
target: "/tmp/nginx-deploy"

Comment on lines +51 to +59

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== backend-cd workflow excerpt ==\n'
sed -n '45,110p' .github/workflows/backend-cd.yml | cat -n

printf '\n== search for strip_components / nginx-deploy usage ==\n'
rg -n "strip_components|/tmp/nginx-deploy|nginx.conf|cors-map.conf" .github/workflows/backend-cd.yml CI_CD.md

Repository: EXE201-MenuGreen/MenuGreenSystem

Length of output: 6285


🌐 Web query:

appleboy/scp-action default path behavior strip_components source target preserve relative source path v0.1.7

💡 Result:

In version v0.1.7 of appleboy/scp-action, the strip_components parameter is designed to remove a specified number of leading path elements from the source files during transfer [1]. Key behavior and usage details for v0.1.7: 1. Purpose: When you specify a source path (e.g., dist/app/), the action by default creates that directory structure on the target server (e.g., target/dist/app/). Setting strip_components allows you to remove one or more of these leading directories so that only the inner contents are placed directly into the target directory [1][2]. 2. Configuration: The parameter accepts an integer value representing the number of directory levels to remove [1][3]. For example, setting strip_components: 1 with a source of dist/app/ will effectively copy the contents of app/ into the target directory [1][2]. 3. Context and Limitations: * While intended to be an integer, user discussions indicate that in some versions and use cases, confusion has arisen regarding whether it expects a boolean or integer, though the documented and intended usage is an integer count [4][3][5]. * Users frequently use this parameter to prevent the creation of unwanted parent folder structures on the remote server when they only intend to copy the contents of a source directory [2][5]. * If strip_components does not provide the desired flexibility, newer versions of the action have introduced a flatten parameter to handle similar use cases [5]. Users experiencing issues where the source folder is still created despite using strip_components often find that the count needs to be adjusted based on the depth of the source path provided [6][5].

Citations:


Upload the nginx files at the path this step expects
source: "backend/nginx/nginx.conf,backend/nginx/conf.d/cors-map.conf" will land under /tmp/nginx-deploy/backend/nginx/... by default, but the SSH step checks /tmp/nginx-deploy/nginx.conf and /tmp/nginx-deploy/conf.d/cors-map.conf. Add strip_components: 2 or adjust the later paths, otherwise nginx changes will be skipped every run.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/backend-cd.yml around lines 51 - 59, The nginx upload step
uses source paths that preserve extra directory components, while the subsequent
SSH deployment expects files directly under /tmp/nginx-deploy. Update the
scp-action configuration in “Upload nginx config to server” to strip two path
components, or consistently change the later checks and copy paths to match the
uploaded layout; preserve the expected nginx.conf and conf.d/cors-map.conf
locations.

- name: Deploy via SSH
uses: appleboy/ssh-action@v1.1.0
with:
Expand All @@ -63,6 +72,57 @@ jobs:
IMAGE=$IMAGE_NAME
SHA=${{ github.sha }}

# =================================================================
# BƯỚC 0: Apply nginx config (TRƯỚC khi restart container)
# - Đã SCP file nginx lên /tmp/nginx-deploy/ ở step trước
# - Tự rollback nếu nginx -t fail
# =================================================================
echo "=== [0/10] Apply nginx config from git ==="
if [ -f /tmp/nginx-deploy/nginx.conf ] && [ -f /tmp/nginx-deploy/conf.d/cors-map.conf ]; then
NGINX_TS=$(date +"%Y%m%d_%H%M%S")

# 1. Backup config hiện tại
if [ -f /etc/nginx/conf.d/cors-map.conf ]; then
sudo cp /etc/nginx/conf.d/cors-map.conf \
/etc/nginx/conf.d/cors-map.conf.bak.$NGINX_TS
fi
if [ -f /etc/nginx/nginx.conf ] && [ ! -f /etc/nginx/nginx.conf.original.backup ]; then
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.original.backup
fi
if [ -f /etc/nginx/nginx.conf ]; then
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$NGINX_TS
fi

# 2. Copy file mới
sudo cp /tmp/nginx-deploy/nginx.conf /etc/nginx/nginx.conf
sudo mkdir -p /etc/nginx/conf.d
sudo cp /tmp/nginx-deploy/conf.d/cors-map.conf /etc/nginx/conf.d/cors-map.conf

# 3. Test syntax - FAIL thì rollback NGAY, không restart container
if sudo nginx -t 2>&1; then
# 4. Reload nginx (zero downtime)
sudo systemctl reload nginx
echo " ✓ Nginx config applied and reloaded"
else
echo ">>> FATAL: nginx -t failed, rolling back nginx config..."
if [ -f /etc/nginx/conf.d/cors-map.conf.bak.$NGINX_TS ]; then
sudo cp /etc/nginx/conf.d/cors-map.conf.bak.$NGINX_TS \
/etc/nginx/conf.d/cors-map.conf
fi
if [ -f /etc/nginx/nginx.conf.bak.$NGINX_TS ]; then
sudo cp /etc/nginx/nginx.conf.bak.$NGINX_TS \
/etc/nginx/nginx.conf
fi
echo ">>> ABORTING: Nginx config rolled back, deployment stopped"
exit 1
fi

# Cleanup tmp
rm -rf /tmp/nginx-deploy
else
echo " ⊘ No nginx files uploaded (skip nginx deploy)"
fi

echo "=== Cleanup disk space - Before ==="
sudo docker system prune -af --volumes || true
echo "=== Disk space after cleanup ==="
Expand Down
40 changes: 40 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -503,3 +503,43 @@ monitoring/nginx/.htpasswd
# User configuration files
backend/MenuGreen.API/appsettings.json
backend/MenuGreen.API/appsettings.Development.json

# ─── Secrets & deployment keys (NEVER commit) ───────────────────
# SSL certificates (Let's Encrypt / self-signed)
**/*.pem
**/*.key
**/*.crt
**/*.cer
# Keep nginx/ssl/ folder tracked via .gitkeep but exclude actual keys
backend/nginx/ssl/fullchain.pem
backend/nginx/ssl/privkey.pem
backend/nginx/ssl/*.pem
backend/nginx/ssl/*.key

# Android signing & API keys
frontend/android/app/keystore_pass.txt
frontend/android/app/*.jks
frontend/android/app/*.keystore
frontend/android/app/google-services.json
frontend/firebase/service-account*.json
Comment on lines +519 to +524

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Ignore the actual signing credential files and keystore location.

frontend/android/key.properties contains signing passwords but is not ignored. The checklist creates menugreen_release.jks under frontend/, while the current pattern only covers frontend/android/app/*.jks. Add the exact paths or broader **/*.jks, **/*.keystore, and **/key.properties rules before credentials are committed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.gitignore around lines 519 - 524, Update the Android signing ignore rules
in .gitignore to cover frontend/android/key.properties and the keystore location
under frontend/, including menugreen_release.jks. Use exact paths or
appropriately broad patterns for key.properties, .jks, and .keystore files so
signing credentials cannot be committed.


# Flutter build artifacts (do NOT push)
frontend/app-release.aab
frontend/app-debug.apk
frontend/build/app/outputs/**/*.aab
frontend/build/app/outputs/**/*.apk
frontend/build/
frontend/.dart_tool/
frontend/.flutter-plugins
frontend/.flutter-plugins-dependencies
frontend/.packages
frontend/pubspec.lock

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## .gitignore around reported line\n'
nl -ba .gitignore | sed -n '520,545p'

printf '\n## frontend files\n'
git ls-files frontend | sed -n '1,120p'

printf '\n## frontend/pubspec.yaml if present\n'
if [ -f frontend/pubspec.yaml ]; then
  nl -ba frontend/pubspec.yaml | sed -n '1,220p'
fi

printf '\n## search for Flutter app/library indicators in frontend\n'
rg -n --hidden --glob 'frontend/**' 'environment:\s*sdk:|flutter:|publish_to:|dependency_overrides:|name:' frontend/pubspec.yaml frontend/README* frontend/lib frontend/test frontend/analysis_options.yaml 2>/dev/null || true

Repository: EXE201-MenuGreen/MenuGreenSystem

Length of output: 246


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## .gitignore around reported line\n'
python3 - <<'PY'
from pathlib import Path
p = Path('.gitignore')
for i, line in enumerate(p.read_text().splitlines(), 1):
    if 520 <= i <= 545:
        print(f"{i:4d} {line}")
PY

printf '\n## tracked frontend files\n'
git ls-files frontend | sed -n '1,200p'

printf '\n## frontend/pubspec.yaml\n'
python3 - <<'PY'
from pathlib import Path
p = Path('frontend/pubspec.yaml')
if p.exists():
    for i, line in enumerate(p.read_text().splitlines(), 1):
        if i <= 220:
            print(f"{i:4d} {line}")
else:
    print("missing")
PY

printf '\n## frontend/pubspec.lock status\n'
python3 - <<'PY'
from pathlib import Path
for path in [Path('frontend/pubspec.lock'), Path('frontend/.gitignore')]:
    print(path, "exists" if path.exists() else "missing")
PY

printf '\n## frontend pubspec indicators\n'
rg -n --hidden --glob 'frontend/**' 'publish_to:|environment:|sdk:|flutter:' frontend/pubspec.yaml frontend/README* frontend/lib frontend/test frontend/analysis_options.yaml 2>/dev/null || true

Repository: EXE201-MenuGreen/MenuGreenSystem

Length of output: 18510


Commit frontend/pubspec.lock instead of ignoring it. The Flutter app should keep its lockfile in version control so dependency resolution stays reproducible across builds. Remove this ignore rule.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.gitignore at line 536, Remove the frontend/pubspec.lock entry from the
ignore rules so the Flutter app’s lockfile can be committed and tracked in
version control.


# Generated platforms (avoid noisy diffs)
frontend/macos/Flutter/GeneratedPluginRegistrant.swift
frontend/windows/flutter/GeneratedPluginRegistrant.cpp
frontend/linux/flutter/GeneratedPluginRegistrant.cc

# Local helper scripts (keystore generators, screenshot tools, etc.)
# Keep code in scripts/ and ci/ directories; ignore ad-hoc assets/
/assets/
47 changes: 47 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Git
.git
.gitignore

# IDE
.vs/
.vscode/
.idea/
*.suo
*.user

# Build outputs
**/bin/
**/obj/
**/out/

# Docker
Dockerfile
docker-compose*.yml
docker-compose*.yaml
.docker/

# Node (frontend)
node_modules/

# Logs
**/*.log
logs/

# OS
.DS_Store
Thumbs.db

# Certificates (these are mounted as volumes or generated at runtime)
# ssl/

# Secrets (these are passed via .env or environment variables)
.env
.env.*
!.env.example

# Database migrations backup
**/Migrations/*.backup.sql

# Test results
TestResults/
coverage/
45 changes: 45 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# ==============================================
# MenuGreen Docker Environment Configuration
# ==============================================
# Copy file: cp .env.example .env
# Edit .env với giá trị thực của bạn

# ==============================================
# PostgreSQL Database
# ==============================================
POSTGRES_DB=MenuGreenDb
POSTGRES_USER=postgres
POSTGRES_PASSWORD=ChangeMe123!
POSTGRES_PORT=5432

# ==============================================
# JWT Authentication
# ==============================================
JWT_SECRET_KEY=YourSuperSecretKeyHere_ChangeMeInProduction_MinLength32Chars!
JWT_ISSUER=MenuGreenAPI
JWT_AUDIENCE=MenuGreenApp

# ==============================================
# Allowed CORS Origins (comma-separated)
# ==============================================
# Mặc định đã có: admin.menugreen.food, www.menugreen.food, menugreen.food
# Thêm domain mới bằng cách uncomment và thêm vào đây
ALLOWED_ORIGINS=https://admin.menugreen.food,https://www.menugreen.food,https://menugreen.food

# ==============================================
# Firebase Cloud Messaging (Optional)
# ==============================================
# Đường dẫn đến Firebase service account JSON file
# Đặt file trong thư mục backend/credentials/
# FIREBASE_CREDENTIAL_PATH=credentials/firebase-service-account.json

# ==============================================
# Redis Cache (Optional)
# ==============================================
# Nếu không có Redis, app sẽ dùng In-Memory cache
REDIS_CONNECTION_STRING=redis://localhost:6379

# ==============================================
# API Port
# ==============================================
API_PORT=5000
38 changes: 38 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src

# Copy csproj files and restore dependencies
COPY ["MenuGreen.API/MenuGreen.API.csproj", "MenuGreen.API/"]
COPY ["MenuGreen.BusinessLogicLayer/MenuGreen.BusinessLogicLayer.csproj", "MenuGreen.BusinessLogicLayer/"]
COPY ["MenuGreen.DataAccessLayer/MenuGreen.DataAccessLayer.csproj", "MenuGreen.DataAccessLayer/"]
RUN dotnet restore "MenuGreen.API/MenuGreen.API.csproj"

# Copy everything else and build
COPY . .
WORKDIR "/src/MenuGreen.API"
RUN dotnet build "MenuGreen.API.csproj" -c Release -o /app/build

# Stage 2: Publish
FROM build AS publish
RUN dotnet publish "MenuGreen.API.csproj" -c Release -o /app/publish /p:UseAppHost=false

# Stage 3: Final runtime image
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final
WORKDIR /app

# Install curl for health checks
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

# Copy published app
COPY --from=publish /app/publish .

# Expose port (Render injects PORT env)
EXPOSE 5000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl -f http://localhost:5000/health/live || exit 1

# Run the app
ENTRYPOINT ["dotnet", "MenuGreen.API.dll"]
Comment on lines +20 to +38

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Container runs as root — add a non-root USER directive.

The final stage has no USER instruction, so the .NET process runs as root inside the container. This violates least-privilege and is flagged by Trivy (DS-0002). The .NET runtime does not require root privileges.

🔒 Proposed fix: add non-root user
 FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final
 WORKDIR /app
 
+# Create a non-root user
+RUN groupadd -r appuser && useradd -r -g appuser appuser
+
 # Install curl for health checks
 RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
 
 # Copy published app
 COPY --from=publish /app/publish .
 
+USER appuser
+
 # Expose port (Render injects PORT env)
 EXPOSE 5000
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Stage 3: Final runtime image
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final
WORKDIR /app
# Install curl for health checks
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Copy published app
COPY --from=publish /app/publish .
# Expose port (Render injects PORT env)
EXPOSE 5000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl -f http://localhost:5000/health/live || exit 1
# Run the app
ENTRYPOINT ["dotnet", "MenuGreen.API.dll"]
# Stage 3: Final runtime image
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final
WORKDIR /app
# Create a non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Install curl for health checks
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Copy published app
COPY --from=publish /app/publish .
USER appuser
# Expose port (Render injects PORT env)
EXPOSE 5000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl -f http://localhost:5000/health/live || exit 1
# Run the app
ENTRYPOINT ["dotnet", "MenuGreen.API.dll"]
🧰 Tools
🪛 Trivy (0.69.3)

[error] 25-25: 'apt-get' missing '--no-install-recommends'

'--no-install-recommends' flag is missed: 'apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*'

Rule: DS-0029

Learn more

(IaC/Dockerfile)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/Dockerfile` around lines 20 - 38, Add a non-root USER directive in
the final runtime image stage before the ENTRYPOINT, using the runtime image’s
existing unprivileged user or create a dedicated user with permissions to read
and run the published application. Keep the health check and application startup
behavior unchanged.

Loading
Loading