Skip to content

Commit 6ab78ab

Browse files
authored
feat: add docker and github workflows (#2)
* 🚨 test: add sanity test * πŸ› fix: update biome configuration to include tests in file includes and add useBlockStatements rule * πŸ”¨ refactor: reorganize imports in sanity tests for consistency * πŸ€– ci: update test workflow to use pnpm for consistency * πŸ€– ci: add Docker support with Dockerfile and docker-compose for automated deployment * πŸ› fix: simplify command registration by removing unnecessary guildId check * πŸ› fix: update build and test scripts for improved functionality * πŸ› fix: add pnpm setup step in CI workflows * πŸ€– ci: comment out push trigger in deployment workflow for manual control * πŸ€– ci: remove unnecessary git stash command from deployment workflow * πŸ€– ci: simplify deployment status checks by removing unnecessary log outputs * 🧹 chore: update test script pattern * 🚨 test: add smoke and sanity tests * 🧹 chore: update Node.js version to 22.20.0 in Dockerfile * 🧹 chore: remove duplicate GitHub secrets section from README.md * 🧹 chore: refactor Dockerfile and docker-compose.yml for improved build stages and clarity
1 parent 3d99b57 commit 6ab78ab

11 files changed

Lines changed: 460 additions & 138 deletions

File tree

β€Ž.dockerignoreβ€Ž

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Dependencies
2+
node_modules
3+
npm-debug.log*
4+
yarn-debug.log*
5+
yarn-error.log*
6+
7+
# Runtime data
8+
pids
9+
*.pid
10+
*.seed
11+
*.pid.lock
12+
13+
# Directory for instrumented libs generated by jscoverage/JSCover
14+
lib-cov
15+
16+
# Coverage directory used by tools like istanbul
17+
coverage
18+
*.lcov
19+
20+
# nyc test coverage
21+
.nyc_output
22+
23+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
24+
.grunt
25+
26+
# Bower dependency directory (https://bower.io/)
27+
bower_components
28+
29+
# node-waf configuration
30+
.lock-wscript
31+
32+
# Compiled binary addons (https://nodejs.org/api/addons.html)
33+
build/Release
34+
35+
# Dependency directories
36+
jspm_packages/
37+
38+
# TypeScript cache
39+
*.tsbuildinfo
40+
41+
# Optional npm cache directory
42+
.npm
43+
44+
# Optional eslint cache
45+
.eslintcache
46+
47+
# Microbundle cache
48+
.rpt2_cache/
49+
.rts2_cache_cjs/
50+
.rts2_cache_es/
51+
.rts2_cache_umd/
52+
53+
# Optional REPL history
54+
.node_repl_history
55+
56+
# Output of 'npm pack'
57+
*.tgz
58+
59+
# Yarn Integrity file
60+
.yarn-integrity
61+
62+
# dotenv environment variables file
63+
.env
64+
.env.local
65+
.env.development.local
66+
.env.test.local
67+
.env.production.local
68+
69+
# parcel-bundler cache (https://parceljs.org/)
70+
.cache
71+
.parcel-cache
72+
73+
# Next.js build output
74+
.next
75+
76+
# Nuxt.js build / generate output
77+
.nuxt
78+
dist
79+
80+
# Gatsby files
81+
.cache/
82+
public
83+
84+
# Storybook build outputs
85+
.out
86+
.storybook-out
87+
88+
# Temporary folders
89+
tmp/
90+
temp/
91+
92+
# Logs
93+
logs
94+
*.log
95+
96+
# Runtime data
97+
pids
98+
*.pid
99+
*.seed
100+
*.pid.lock
101+
102+
# OS generated files
103+
.DS_Store
104+
.DS_Store?
105+
._*
106+
.Spotlight-V100
107+
.Trashes
108+
ehthumbs.db
109+
Thumbs.db
110+
111+
# Editor directories and files
112+
.vscode/
113+
.idea/
114+
*.swp
115+
*.swo
116+
*~
117+
118+
# Git
119+
.git
120+
.gitignore
121+
122+
# CI/CD
123+
.github/
124+
125+
# Testing
126+
coverage/
127+
.nyc_output/
128+
129+
# Build artifacts
130+
build/
131+
dist/

β€Ž.github/workflows/deploy.ymlβ€Ž

Lines changed: 91 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,110 @@
1-
name: Deploy
1+
name: Build, Test and Deploy Discord Bot to VPS
22

33
on:
4-
workflow_dispatch: # Manual trigger only
5-
# Uncomment below when VPS is ready:
64
# push:
75
# branches: [ "main" ]
8-
# paths-ignore:
9-
# - 'docs/**'
10-
# - '.gitignore'
11-
# - 'LICENSE'
6+
workflow_dispatch: # Allow manual deployment
127

138
concurrency:
149
group: ${{ github.workflow }}-${{ github.ref }}
15-
cancel-in-progress: false
10+
cancel-in-progress: true
1611

1712
jobs:
18-
build-test-and-deploy:
13+
test-and-deploy:
1914
runs-on: ubuntu-latest
15+
2016
steps:
21-
- name: Checkout
22-
uses: actions/checkout@v4
23-
24-
- name: Setup Node.js
25-
uses: actions/setup-node@v4
26-
with:
27-
node-version-file: .nvmrc
28-
29-
- name: Install dependencies
30-
run: |
31-
if [ -f package-lock.json ]; then
32-
npm ci --no-audit --no-fund
33-
else
34-
npm install --no-audit --no-fund
35-
fi
17+
- name: Checkout code
18+
uses: actions/checkout@v4
3619

37-
- name: Lint
38-
run: npm run lint
20+
- name: Setup Node.js
21+
uses: actions/setup-node@v4
22+
with:
23+
node-version-file: .nvmrc
3924

40-
- name: Build
41-
run: npm run build:ci
42-
env:
43-
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
44-
CLIENT_ID: ${{ secrets.CLIENT_ID }}
25+
- name: Setup pnpm
26+
uses: pnpm/action-setup@v4
4527

46-
- name: Run tests
47-
run: npm test
28+
- name: Install dependencies
29+
run: pnpm install --frozen-lockfile
4830

49-
- name: Package artifact
50-
run: |
51-
tar -czf release.tar.gz dist package.json package-lock.json .nvmrc || tar -czf release.tar.gz dist package.json .nvmrc
31+
- name: Lint
32+
run: pnpm lint
5233

53-
- name: Create .env file from secrets
54-
env:
55-
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
56-
APPLICATION_ID: ${{ secrets.APPLICATION_ID }}
57-
run: |
34+
- name: Type check
35+
run: pnpm typecheck
36+
37+
- name: Build
38+
run: pnpm build:ci
39+
40+
- name: Deploy to VPS
41+
uses: appleboy/ssh-action@v1.0.3
42+
with:
43+
host: ${{ secrets.VPS_HOST }}
44+
username: ${{ secrets.VPS_USER }}
45+
key: ${{ secrets.VPS_SSH_KEY }}
46+
timeout: 30m
47+
script: |
5848
set -euo pipefail
59-
printf "DISCORD_TOKEN=%s\n" "$DISCORD_TOKEN" > .env
60-
printf "APPLICATION_ID=%s\n" "$APPLICATION_ID" >> .env
61-
printf "NODE_ENV=production\n" >> .env
62-
63-
- name: Copy artifact to VPS
64-
env:
65-
SSH_HOST: ${{ secrets.SSH_HOST }}
66-
SSH_USER: ${{ secrets.SSH_USER }}
67-
SSH_PORT: ${{ secrets.SSH_PORT }}
68-
SSH_KEY: ${{ secrets.SSH_KEY }}
69-
run: |
70-
mkdir -p ~/.ssh
71-
echo "$SSH_KEY" > ~/.ssh/id_ed25519
72-
chmod 600 ~/.ssh/id_ed25519
73-
ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 -p ${SSH_PORT:-22} $SSH_USER@$SSH_HOST "mkdir -p ~/apps/webdev-bot/releases"
74-
scp -i ~/.ssh/id_ed25519 -P ${SSH_PORT:-22} -o StrictHostKeyChecking=no release.tar.gz $SSH_USER@$SSH_HOST:~/apps/webdev-bot/releases/release.tar.gz
75-
76-
- name: Upload .env to VPS
77-
env:
78-
SSH_HOST: ${{ secrets.SSH_HOST }}
79-
SSH_USER: ${{ secrets.SSH_USER }}
80-
SSH_PORT: ${{ secrets.SSH_PORT }}
81-
SSH_KEY: ${{ secrets.SSH_KEY }}
82-
APP_DIR: ${{ secrets.APP_DIR }}
83-
run: |
84-
mkdir -p ~/.ssh
85-
echo "$SSH_KEY" > ~/.ssh/id_ed25519
86-
chmod 600 ~/.ssh/id_ed25519
87-
ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 -p ${SSH_PORT:-22} $SSH_USER@$SSH_HOST "mkdir -p ${APP_DIR:-\"~/apps/webdev-bot\"}/shared && chmod 700 ${APP_DIR:-\"~/apps/webdev-bot\"}/shared"
88-
scp -i ~/.ssh/id_ed25519 -P ${SSH_PORT:-22} -o StrictHostKeyChecking=no .env $SSH_USER@$SSH_HOST:${APP_DIR:-"~/apps/webdev-bot"}/shared/.env
89-
ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 -p ${SSH_PORT:-22} $SSH_USER@$SSH_HOST "chmod 600 ${APP_DIR:-\"~/apps/webdev-bot\"}/shared/.env"
90-
91-
- name: Deploy on VPS
92-
env:
93-
SSH_HOST: ${{ secrets.SSH_HOST }}
94-
SSH_USER: ${{ secrets.SSH_USER }}
95-
SSH_PORT: ${{ secrets.SSH_PORT }}
96-
SSH_KEY: ${{ secrets.SSH_KEY }}
97-
APP_DIR: ${{ secrets.APP_DIR }}
98-
run: |
99-
mkdir -p ~/.ssh
100-
echo "$SSH_KEY" > ~/.ssh/id_ed25519
101-
chmod 600 ~/.ssh/id_ed25519
102-
ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 -p ${SSH_PORT:-22} $SSH_USER@$SSH_HOST << 'EOF'
103-
set -euo pipefail
104-
APP_DIR=${APP_DIR:-"~/apps/webdev-bot"}
105-
mkdir -p "$APP_DIR/current" "$APP_DIR/releases" "$APP_DIR/shared"
106-
cd "$APP_DIR"
107-
rm -rf current/*
108-
tar -xzf releases/release.tar.gz -C current
109-
cd current
110-
# Load env from shared/.env for the PM2 process
111-
set -a
112-
if [ -f "$APP_DIR/shared/.env" ]; then . "$APP_DIR/shared/.env"; fi
113-
set +a
114-
if [ -f package-lock.json ]; then
115-
npm ci --omit=dev --no-audit --no-fund || true
116-
else
117-
npm install --omit=dev --no-audit --no-fund || true
118-
fi
119-
pm2 describe webdev-bot >/dev/null 2>&1 && pm2 restart webdev-bot || pm2 start "node dist/index.js" --name webdev-bot
120-
pm2 save || true
49+
50+
echo "πŸš€ Starting deployment..."
51+
52+
# Create project directory if it doesn't exist
53+
PROJECT_DIR="/home/${{ secrets.VPS_USER }}/moderation-tool-bot"
54+
if [ ! -d "$PROJECT_DIR" ]; then
55+
echo "πŸ“ Creating project directory..."
56+
mkdir -p "$PROJECT_DIR"
57+
fi
58+
59+
# Navigate to project directory
60+
cd "$PROJECT_DIR"
61+
62+
# Check if this is a git repository
63+
if [ ! -d ".git" ]; then
64+
echo "πŸ“¦ Cloning repository..."
65+
# Clone the repository if not already cloned
66+
git clone https://github.com/r-webdev/moderation-tool.git .
67+
git checkout main
68+
fi
69+
70+
71+
echo "πŸ”„ Pulling latest changes..."
72+
# Pull latest changes
73+
git checkout main
74+
git pull origin main --no-edit
75+
76+
echo "βš™οΈ Setting up environment..."
77+
# Read Node version from .nvmrc
78+
NODE_VERSION=$(cat .nvmrc | sed 's/v//')
79+
echo "Using Node version: $NODE_VERSION"
80+
81+
# Create .env.local file with secrets
82+
cat > .env.local << EOF
83+
DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }}
84+
CLIENT_ID=${{ secrets.CLIENT_ID }}
85+
SERVER_ID=${{ secrets.SERVER_ID }}
12186
EOF
12287
88+
echo "🐳 Stopping existing containers..."
89+
# Stop any existing containers (ignore errors if none running)
90+
docker compose down --remove-orphans || true
91+
92+
echo "πŸ—οΈ Building and starting production container..."
93+
# Build and start production container
94+
docker compose --profile prod up -d --build --force-recreate
95+
96+
echo "⏳ Waiting for container to be healthy..."
97+
# Wait a bit for the container to start
98+
sleep 10
99+
100+
echo "πŸ” Checking deployment status..."
101+
# Check container status
102+
if docker compose ps | grep -q "Up"; then
103+
echo "βœ… Deployment successful!"
104+
else
105+
echo "❌ Deployment failed!"
106+
exit 1
107+
fi
108+
109+
echo "πŸŽ‰ Deployment completed successfully!"
110+

β€Ž.github/workflows/test.ymlβ€Ž

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,29 @@ jobs:
1616
build-and-test:
1717
runs-on: ubuntu-latest
1818
steps:
19-
- name: Checkout
19+
- name: Checkout code
2020
uses: actions/checkout@v4
2121

2222
- name: Setup Node.js
2323
uses: actions/setup-node@v4
2424
with:
2525
node-version-file: .nvmrc
2626

27+
- name: Setup pnpm
28+
uses: pnpm/action-setup@v4
29+
2730
- name: Install dependencies
28-
run: |
29-
if [ -f package-lock.json ]; then
30-
npm ci --no-audit --no-fund
31-
else
32-
npm install --no-audit --no-fund
33-
fi
31+
run: pnpm install --frozen-lockfile
3432

3533
- name: Lint
36-
run: npm run lint
34+
run: pnpm lint
35+
36+
- name: Type check
37+
run: pnpm typecheck
3738

3839
- name: Build
39-
run: npm run build:ci
40-
env:
41-
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
42-
CLIENT_ID: ${{ secrets.CLIENT_ID }}
40+
run: pnpm build:ci
4341

4442
- name: Run tests
45-
run: npm test
43+
run: pnpm test
4644

0 commit comments

Comments
Β (0)