Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci: 개발 서버 #698

Merged
merged 5 commits into from
Sep 2, 2023
Merged
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
21 changes: 20 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
.*
.git
.github
**/logs
**/node_modules
**/.env
**/.env.*
database
.github
**/dist
**/*.md
**/.vscode
**/.eslintrc.json
**/*.log
**/.npmignore
**/LICENSE
docker-compose*
Dockerfile
appspec.yml
scripts
packages
nginx*
docs
backend/jest.config.js
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,5 @@ package-lock.json
.idea

# nginx
.htpasswd
nginx.dev/certbot
**/*.htpasswd
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ FROM pnpm-installed as workspace
COPY ./pnpm-lock.yaml .
COPY patches patches

RUN pnpm fetch
RUN pnpm fetch --prod

FROM workspace as prod
ADD . ./

RUN pnpm -r install --frozen-lockfile --offline
RUN pnpm -r install --frozen-lockfile --offline --prod
RUN pnpm -r run build

RUN rm -rf /app/.pnpm-store

WORKDIR /app/backend

2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"ts-pattern": "^5.0.1",
"typeorm": "^0.3.11",
"vite": "^4.4.7",
"vite-node": "https://github.com/scarf005/vitest/releases/download/v0.33.0-2023-07-30/vite-node-0.33.0.tgz",
"vite-node": "^0.34.1",
"vite-tsconfig-paths": "^4.2.0",
"winston": "^3.6.0",
"winston-daily-rotate-file": "^4.6.1"
Expand Down
13 changes: 10 additions & 3 deletions backend/src/@types/process.env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,15 @@ export declare global {
/** 42 API OAuth 리다이렉트 URL */
REDIRECT_URL: string;

/** 레포지토리 선택 모드 */
MODE: 'local' | 'RDS' | 'prod';
/**
* 레포지토리 선택 모드
*
* - local: 호스트 머신의 DB에 연결
* - prod: 도커 컨테이너의 DB에 연결
* - RDS: AWS RDS에 연결
* - https: RDS + SSL에 연결
*/
MODE: 'local' | 'RDS' | 'prod' | 'https';

// local 또는 prod MODE에서
/** MySQL 데이터베이스 이름 */
Expand All @@ -48,7 +55,7 @@ export declare global {
/** MySQL 데이터베이스 사용자 이름 */
MYSQL_USER?: string;

// RDS MODE에서
// RDS 또는 https MODE에서
/** RDS 데이터베이스 이름 */
RDS_DB_NAME?: string;
/** RDS 데이터베이스 주소 */
Expand Down
1 change: 1 addition & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ app.use(
cors({
origin: [
'http://localhost:4242',
'https://localhost:4242',
'http://42library.kr',
'https://42library.kr',
'http://42jip.com',
Expand Down
10 changes: 7 additions & 3 deletions backend/src/config/JwtOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ import { z } from 'zod';
import { JwtOption, OauthUrlOption } from './config.type';
import { nonempty } from './envObject';
import { Mode } from './modeOption';
import { match } from 'ts-pattern';

type getJwtOption = (mode: Mode) => (option: OauthUrlOption) => JwtOption;
export const getJwtOption: getJwtOption = (mode) => ({ redirectURL, clientURL }) => {
const redirectDomain = new URL(redirectURL).hostname;
const clientDomain = new URL(clientURL).hostname;
const secure = mode === 'prod' || mode === 'https';

const issuer = mode === 'local' ? 'localhost' : redirectDomain;
const domain = mode === 'prod' ? clientDomain : 'localhost';
const secure = mode === 'prod';
const issuer = secure ? redirectDomain : 'localhost';
const domain = match(mode)
.with('prod', () => clientDomain)
.with('https', () => undefined)
.otherwise(() => 'localhost');

return { issuer, domain, secure };
};
Expand Down
4 changes: 2 additions & 2 deletions backend/src/config/config.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { levels } from './logOption';
/** JWT 발급 옵션 */
export type JwtOption = {
/** JWT 발급자 */
issuer: string | 'localhost'
issuer: string | 'localhost';

/** JWT 도메인 */
domain: string | 'localhost'
domain: string | 'localhost' | undefined;

/** Cookie Secure 사용 여부 */
secure: boolean;
Expand Down
2 changes: 1 addition & 1 deletion backend/src/config/getConnectOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Mode } from './modeOption';
/** DB 모드에 따라 사용할 DB 연결 옵션 스키마를 고르는 함수 */
const getConnectOptionSchema = (mode: Mode) => {
if (mode === 'local') return localSchema;
if (mode === 'RDS') return rdsSchema;
if (mode === 'RDS' || mode === 'https') return rdsSchema;
return prodSchema;
};

Expand Down
2 changes: 1 addition & 1 deletion backend/src/config/modeOption.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { z } from 'zod';

/** DB 모드를 정의하는 스키마 */
export const modeSchema = z.enum(['local', 'RDS', 'prod']);
export const modeSchema = z.enum(['local', 'RDS', 'prod', 'https']);

/** DB 선택 모드 */
export type Mode = z.infer<typeof modeSchema>;
Expand Down
53 changes: 53 additions & 0 deletions docker-compose.dev.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# https://github.com/wmnnd/nginx-certbot
# https://scribe.rip/@pentacent/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71
services:
nginx:
container_name: nginx
image: nginx:1.25.1-alpine
# image: nginx:dev
# build:
# context: nginx.dev
# dockerfile: nginx.dockerfile
restart: unless-stopped
ports:
- 80:80
- 443:443
environment:
- TZ=Asia/Seoul
volumes:
- ./nginx.dev/nginx.conf:/etc/nginx/nginx.conf
- ./nginx.dev/conf.d:/etc/nginx/conf.d

- ./nginx.dev/certbot/conf:/etc/letsencrypt
- ./nginx.dev/certbot/www:/var/www/certbot

- ./logs/nginx:/var/log/nginx/
# command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx'"

certbot:
image: certbot/dns-route53
restart: unless-stopped
volumes:
- ./nginx.dev/certbot/conf:/etc/letsencrypt
- ./nginx.dev/certbot/www:/var/www/certbot

- ./logs/certbot:/var/log/letsencrypt
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
env_file:
- .env.certbot

backend:
container_name: backend
image: backend:dev
build:
context: .
restart: on-failure
entrypoint: ["./node_modules/.bin/vite-node", "src/server.ts"]
volumes:
- ./backend/src:/app/backend/src

- ./logs/backend:/app/backend/logs
env_file: .env.https
environment:
- MODE=https
- TZ=Asia/Seoul
81 changes: 81 additions & 0 deletions init-letsencrypt.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/bin/bash

if ! [ -x "$(command -v docker)" ]; then
echo 'Error: docker is not installed.' >&2
exit 1
fi

domains=("dev.42library.kr")
rsa_key_size=4096
data_path="./nginx/certbot"
email="[email protected]" # Adding a valid address is strongly recommended
staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits
compose=docker-compose.dev.yaml

if [ -d "$data_path" ]; then
read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision
if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then
exit
fi
fi


if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then
echo "### Downloading recommended TLS parameters ..."
mkdir -p "$data_path/conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem"
echo
fi

echo "### Creating dummy certificate for $domains ..."
path="/etc/letsencrypt/live/$domains"
mkdir -p "$data_path/conf/live/$domains"
sudo docker compose -f $compose run --rm --entrypoint "\
openssl req -x509 -nodes -newkey rsa:$rsa_key_size -days 1\
-keyout '$path/privkey.pem' \
-out '$path/fullchain.pem' \
-subj '/CN=localhost'" certbot
echo


echo "### Starting nginx ..."
sudo docker compose -f $compose up --force-recreate -d nginx
echo

echo "### Deleting dummy certificate for $domains ..."
sudo docker compose -f $compose run --rm --entrypoint "\
rm -Rf /etc/letsencrypt/live/$domains && \
rm -Rf /etc/letsencrypt/archive/$domains && \
rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot
echo


echo "### Requesting Let's Encrypt certificate for $domains ..."
#Join $domains to -d args
domain_args=""
for domain in "${domains[@]}"; do
domain_args="$domain_args -d $domain"
done

# Select appropriate email arg
case "$email" in
"") email_arg="--register-unsafely-without-email" ;;
*) email_arg="--email $email" ;;
esac

# Enable staging mode if needed
if [ $staging != "0" ]; then staging_arg="--staging"; fi

sudo docker compose -f $compose run --rm --entrypoint "\
certbot certonly -n --dns-route53 \
$staging_arg \
--agree-tos \
$email_arg \
$domain_args \
--rsa-key-size $rsa_key_size \
--force-renewal" certbot
echo

echo "### Reloading nginx ..."
sudo docker compose -f $compose exec nginx nginx -s reload
77 changes: 77 additions & 0 deletions nginx.dev/conf.d/default.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
server {
listen 80;
server_name dev.42library.kr;

access_log /dev/stdout main;
# access_log /var/log/nginx/access.log main;

location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri; # Redirect all HTTP to HTTPS
}
}

server {
listen 443 ssl;
server_name dev.42library.kr;

access_log /var/log/nginx/https.access.log main;

# SSL configuration
ssl_certificate /etc/letsencrypt/live/dev.42library.kr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev.42library.kr/privkey.pem;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;

# Add HTTP Strict Transport Security (HSTS) to enforce HTTPS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # Enforces HTTPS on all requests

# Security headers
add_header X-Content-Type-Options "nosniff" always; # Prevents MIME type sniffing
add_header X-Frame-Options "SAMEORIGIN" always; # Prevents clickjacking
add_header X-XSS-Protection "1; mode=block" always; # Helps protect against XSS attacks
# Limits sources of content to only trusted domains
add_header Content-Security-Policy "default-src 'self'; frame-src 'self' https://api.intra.42.fr;" always;

location /api/ {
# Add rate limiting
limit_req zone=api burst=5;

proxy_pass http://backend:3000;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /swagger/ {
auth_basic "Admin page";
auth_basic_user_file /etc/nginx/conf.d/.htpasswd;

proxy_pass http://backend:3000;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /swagger-v2/ {
auth_basic "Admin page";
auth_basic_user_file /etc/nginx/conf.d/.htpasswd;

proxy_pass http://backend:3000;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
31 changes: 31 additions & 0 deletions nginx.dev/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
user nginx;
worker_processes auto;
# error_log /var/log/nginx/error.log warn;
error_log /dev/stdout info;
pid /var/run/nginx.pid;

events {
worker_connections 1024;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;

log_format main '[$time_iso8601] $remote_addr - $host $remote_user '
'"$request" $status b=$body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'ssl=$ssl_cipher rt=$request_time';

sendfile off;
# tcp_nopush on;

keepalive_timeout 65;

#gzip on;

limit_req_zone $binary_remote_addr zone=api:10m rate=1r/s;

include /etc/nginx/conf.d/*.conf;
}
Loading
Loading