Skip to content
This repository was archived by the owner on Feb 15, 2026. It is now read-only.
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
54 changes: 54 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Git
.git
.gitignore
.github

# Dependencies
node_modules

# Build outputs
build
.svelte-kit
dist

# Data directories
pgdata
redis-data
logs
# Environment
.env
.env.*
!.env.example

# Debug logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# OS
.DS_Store
Thumbs.db

# IDE
.vscode
.idea
*.swp
*.swo

# Docker
Dockerfile
docker-compose*
.dockerignore

# Documentation
README.md
readme.md
LICENSE
licence

# Tests
**/*.test.ts
**/*.spec.ts
**/__tests__
**/__mocks__
79 changes: 50 additions & 29 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,39 +1,60 @@
SALT=***
SERVER_KEY=***
RATELIMIT_BYPASS_IPS="***,***,***"
RATELIMIT_BYPASS_USERIDS="***,***,***"
# === REQUIRED (Change before running in production) ===
# `SALT`: MUST be changed before deployment. Must be at least 32 characters and generated
# with a cryptographically secure random generator.
# Example: `openssl rand -hex 32`
SALT='CHANGE_ME_SALT_generate_with_openssl_rand_hex_32'

# `SERVER_KEY`: secret key used for server-to-server auth. MUST be changed before deployment.
# Generate a long random value (32+ bytes of hex) with a cryptographically secure generator.
# Example: `openssl rand -hex 32`
SERVER_KEY='CHANGE_ME_SERVER_KEY_generate_with_openssl_rand_hex_32'

# Optional rate-limit bypass lists (comma-separated)
RATELIMIT_BYPASS_IPS=""
RATELIMIT_BYPASS_USERIDS="1"

# === DATABASE (Change for your environment) ===
# PostgreSQL DB 설정
POSTGRESQL_HOST=***
POSTGRESQL_PORT=***
POSTGRESQL_USER=***
POSTGRESQL_PASSWORD='***'
POSTGRESQL_NAME=***
POSTGRESQL_MAX_CONNS=***
POSTGRESQL_CONN_MAX_LIFETIME=***
POSTGRESQL_CONN_MAX_IDLE_TIME=***
POSTGRESQL_SSL=require
POSTGRESQL_HOST=postgres
POSTGRESQL_PORT=5432
# `POSTGRESQL_USER`: DB username
POSTGRESQL_USER=postgres
# `POSTGRESQL_PASSWORD`: DB password (REQUIRED if not using trust)
POSTGRESQL_PASSWORD=postgres
# `POSTGRESQL_NAME`: database name
POSTGRESQL_NAME=pjsedb
# `POSTGRESQL_SSL`: set to 'disable' for local docker; use 'require' in production if supported
POSTGRESQL_SSL=disable
Comment on lines +26 to +27
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

Setting POSTGRESQL_SSL=disable by default in the example configuration encourages insecure deployments. While this may be appropriate for local development, the comment should more strongly emphasize that SSL should be enabled in production environments to prevent credentials and data from being transmitted in cleartext.

Suggested change
# `POSTGRESQL_SSL`: set to 'disable' for local docker; use 'require' in production if supported
POSTGRESQL_SSL=disable
# `POSTGRESQL_SSL`: SSL mode for DB connection.
# - PRODUCTION: set to 'require' (or stronger, e.g. 'verify-full') to prevent credentials/data being sent in cleartext.
# - LOCAL DEV (e.g. local Docker only): you may override this to 'disable' **only** on a trusted network.
POSTGRESQL_SSL=require

Copilot uses AI. Check for mistakes.

# Redis 설정
REDIS_HOST=***
REDIS_PORT=***
REDIS_USERNAME=***
REDIS_PASSWORD='***'
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_USERNAME=default
# `REDIS_PASSWORD`: MUST be set to a strong random value in production if your Redis requires AUTH.
# Example: `openssl rand -hex 32`. Do NOT use any example or default password in production.
REDIS_PASSWORD=""
REDIS_DB=0
REDIS_TLS=true
# 서버 설정
REDIS_TLS=false

# Server settings
# `HOST`/`PORT`: server listen address and port
HOST=0.0.0.0
ORIGIN="http://localhost:5173"
PORT=4000
ID=1
PORT=8000
# `ORIGIN`: application origin used for CSRF/trusted origins
ORIGIN="http://localhost:8000"
CLOUDFLARED_TUNNEL=false
ALLOW_TEST_PAGE=true
ALLOW_REGISTER=true
SYS_LOG=true
SYS_LOG_STDOUT=true
SYS_LOG_LOCATION=./logs
# 이메일 설정
SMTP_HOST=***
SMTP_FROM=***
SMTP_USERNAME=***
SMTP_PASSWORD="***"

# Email (optional): fill these only if you want email features (recovery/confirmations)
SMTP_HOST=
SMTP_FROM=
SMTP_USERNAME=
SMTP_PASSWORD=""
SMTP_SECURITY=force_tls
SMTP_PORT=***
SMTP_PORT=
# 리버스 프록시 설정
#ADDRESS_HEADER=X-Forwarded-For
#XFF_DEPTH=1
51 changes: 51 additions & 0 deletions .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: workflow.yml

on:
push:
branches:
- next
- feat/**
- release/**
paths-ignore:
- 'readme.md'
- 'readme.*'
- 'docker-compose*'
- 'docs/**'
- 'licence'

jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and Push Docker Image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

The workflow builds only for linux/amd64 platform. For better compatibility and deployment flexibility, consider adding linux/arm64 to the platforms list, especially since many cloud providers now offer ARM-based instances that can be more cost-effective.

Copilot uses AI. Check for mistakes.
target: runtime
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/pjs2:latest
ghcr.io/${{ github.repository_owner }}/pjs2:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
72 changes: 72 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
FROM node:25.2.1-alpine AS builder
WORKDIR /usr/src/app

RUN apk update && apk upgrade --no-cache

RUN apk add --no-cache \
ca-certificates \
postgresql-client \
python3 \
make \
g++ \
gcc \
musl-dev \
openssl-dev \
pkgconfig

COPY package.json package-lock.json* ./
RUN --mount=type=cache,target=/root/.npm \
if [ -f package-lock.json ]; then npm ci --no-audit --prefer-offline; \
else npm install --no-audit --prefer-offline; fi

COPY . .

ENV POSTGRESQL_HOST=127.0.0.1
ENV POSTGRESQL_PORT=5432
ENV POSTGRESQL_USER=postgres
ENV POSTGRESQL_PASSWORD=postgres

Check warning on line 27 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-and-push

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "POSTGRESQL_PASSWORD") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 27 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-and-push

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "POSTGRESQL_PASSWORD") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ENV POSTGRESQL_NAME=pjsedb
ENV POSTGRESQL_SSL=disable
ENV SALT='super-secret-salt-at-least-32-characters-long-0000'
ENV SERVER_KEY='super-secret-server-key-000000000000'

Check warning on line 31 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-and-push

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "SERVER_KEY") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 31 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-and-push

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "SERVER_KEY") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Comment on lines +24 to +32
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

Hardcoded database credentials and secrets in the build stage are a security risk. These environment variables are baked into the image layer and can be extracted. Since these are only needed for the drizzle:generate command, consider using build arguments with --build-arg or ensure these are only placeholder values that get overridden at runtime. If drizzle:generate doesn't actually need valid credentials to work, document that these are dummy values.

Suggested change
ENV POSTGRESQL_HOST=127.0.0.1
ENV POSTGRESQL_PORT=5432
ENV POSTGRESQL_USER=postgres
ENV POSTGRESQL_PASSWORD=postgres
ENV POSTGRESQL_NAME=pjsedb
ENV POSTGRESQL_SSL=disable
ENV SALT='super-secret-salt-at-least-32-characters-long-0000'
ENV SERVER_KEY='super-secret-server-key-000000000000'
# Build-time database and crypto configuration used by `npm run drizzle:generate`.
# These MUST be provided via `--build-arg` and MUST NOT contain production secrets.
ARG POSTGRESQL_HOST
ARG POSTGRESQL_PORT
ARG POSTGRESQL_USER
ARG POSTGRESQL_PASSWORD
ARG POSTGRESQL_NAME
ARG POSTGRESQL_SSL
ARG SALT
ARG SERVER_KEY
ENV POSTGRESQL_HOST=${POSTGRESQL_HOST}
ENV POSTGRESQL_PORT=${POSTGRESQL_PORT}
ENV POSTGRESQL_USER=${POSTGRESQL_USER}
ENV POSTGRESQL_PASSWORD=${POSTGRESQL_PASSWORD}
ENV POSTGRESQL_NAME=${POSTGRESQL_NAME}
ENV POSTGRESQL_SSL=${POSTGRESQL_SSL}
ENV SALT=${SALT}
ENV SERVER_KEY=${SERVER_KEY}

Copilot uses AI. Check for mistakes.
ENV NODE_ENV=production
RUN npm run prepare || true
RUN npm run drizzle:generate
RUN npm run build

FROM node:25.2.1-alpine AS runtime
WORKDIR /usr/src/app

RUN apk update && apk upgrade --no-cache

RUN apk add --no-cache \
ca-certificates \
libstdc++ \
postgresql-client \
su-exec

COPY --from=builder /usr/src/app/package.json ./
COPY --from=builder /usr/src/app/package-lock.json ./

RUN apk add --no-cache --virtual .build-deps make g++ gcc musl-dev openssl-dev python3 \
&& if [ -f package-lock.json ]; then npm ci --omit=dev --no-audit --no-fund; \
else npm install --production --no-audit --no-fund; fi \
&& npm cache clean --force \
&& rm -rf /tmp/* /root/.npm \
&& apk del .build-deps

COPY --from=builder --chown=node:node /usr/src/app/build ./build
COPY --from=builder --chown=node:node /usr/src/app/docker-entrypoint.sh ./docker-entrypoint.sh
COPY --from=builder --chown=node:node /usr/src/app/.env.example ./.env.example
COPY --from=builder --chown=node:node /usr/src/app/exchanges ./exchanges
COPY --from=builder --chown=node:node /usr/src/app/drizzle ./drizzle

RUN chmod +x /usr/src/app/docker-entrypoint.sh

EXPOSE 8000

RUN mkdir -p /usr/src/app/logs && chown -R node:node /usr/src/app/logs

ENTRYPOINT ["/usr/src/app/docker-entrypoint.sh"]
CMD ["node", "build"]
1 change: 0 additions & 1 deletion FixList

This file was deleted.

77 changes: 77 additions & 0 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/bin/sh

set -e

if [ "$(id -u)" = "0" ]; then
echo "Running as root, fixing volume permissions..."

if [ -d /usr/src/app/logs ]; then
echo "Fixing permissions for /usr/src/app/logs"
if ! chown -R node:node /usr/src/app/logs; then
echo "Warning: Could not change ownership of /usr/src/app/logs" >&2
fi
fi

if [ -d /var/lib/postgresql/data ]; then
echo "Fixing permissions for /var/lib/postgresql/data"
if ! chown -R node:node /var/lib/postgresql/data; then
echo "Warning: Could not change ownership of /var/lib/postgresql/data" >&2
fi
fi

if [ -d /data/redis ]; then
echo "Fixing permissions for /data/redis"
if ! chown -R node:node /data/redis; then
echo "Warning: Could not change ownership of /data/redis" >&2
fi
fi

echo "Switching to node user with su-exec..."
if command -v su-exec >/dev/null 2>&1; then
exec su-exec node "$0" "$@"
else
echo "ERROR: su-exec not found! Falling back to su"
# shellcheck disable=SC2016
exec su node -s /bin/sh -c 'exec "$0" "$@"' -- "$0" "$@"
fi
fi

# 이하 node 유저로 실행
echo "Running as $(whoami) (UID: $(id -u))"

# PJSe.json 자동 다운로드
PJSe_FILE="/usr/src/app/exchanges/PJSe.json"
DEFAULT_PJSe_URL="https://raw.githubusercontent.com/0ghost0-dev/PJS2/refs/heads/next/exchanges/PJSe.json"

if [ ! -f "$PJSe_FILE" ]; then
echo "PJSe.json not found. Downloading from GitHub..."
mkdir -p /usr/src/app/exchanges
PJSe_URL="${PJSe_CONFIG_URL:-$DEFAULT_PJSe_URL}"

if command -v wget >/dev/null 2>&1; then
wget -q "$PJSe_URL" -O "$PJSe_FILE" || {
echo "Error: Failed to download PJSe.json from $PJSe_URL"
exit 1
}
elif command -v curl >/dev/null 2>&1; then
curl -fsSL "$PJSe_URL" -o "$PJSe_FILE" || {
echo "Error: Failed to download PJSe.json from $PJSe_URL"
exit 1
}
else
echo "Error: Neither wget nor curl available"
exit 1
fi

echo "Successfully downloaded PJSe.json from $PJSe_URL"
else
echo "PJSe.json already exists at $PJSe_FILE"
fi
Comment on lines +42 to +69
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

Downloading configuration files from a remote URL at runtime introduces security and reliability risks. If the GitHub URL becomes unavailable or is compromised, the application will fail to start or could execute malicious configuration. Consider bundling PJSe.json in the Docker image during build, or at minimum verify the downloaded file's integrity using a checksum.

Copilot uses AI. Check for mistakes.

echo "Waiting for Postgres at ${POSTGRESQL_HOST:-postgres}:${POSTGRESQL_PORT:-5432}..."
until pg_isready -h "${POSTGRESQL_HOST:-postgres}" -p "${POSTGRESQL_PORT:-5432}" -U "${POSTGRESQL_USER:-postgres}" >/dev/null 2>&1; do
sleep 1
done
Comment on lines +71 to +74
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

The pg_isready check waits indefinitely for PostgreSQL without a timeout. If PostgreSQL never becomes available, the container will hang forever in the startup loop. Consider adding a timeout or maximum retry count to fail fast and allow orchestration systems to restart the container.

Copilot uses AI. Check for mistakes.
Copy link
Owner Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback


echo "Postgres is available. Starting app..."
exec "$@"
26 changes: 13 additions & 13 deletions exchanges/PJSe.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
"logo": "https://example.com/logo.png",
"description": "Project. Stock Exchange is a fictional stock exchange used for demonstration purposes.",
"pre_market_sessions": {
"Monday": { "open": "08:30", "close": "09:00" },
"Tuesday": { "open": "08:30", "close": "09:00" },
"Wednesday": { "open": "08:30", "close": "09:00" },
"Thursday": { "open": "08:30", "close": "09:00" },
"Friday": { "open": "08:30", "close": "09:00" },
"Saturday": { "open": null, "close": null },
"Monday": { "open": "02:00", "close": "09:00" },
"Tuesday": { "open": "02:00", "close": "09:00" },
"Wednesday": { "open": "02:00", "close": "09:00" },
"Thursday": { "open": "02:00", "close": "09:00" },
"Friday": { "open": "02:00", "close": "09:00" },
"Saturday": { "open": "02:00", "close": "10:00" },
"Sunday": { "open": null, "close": null }
},
"regular_trading_sessions": {
Expand All @@ -29,16 +29,16 @@
"Wednesday": { "open": "09:00", "close": "15:30" },
"Thursday": { "open": "09:00", "close": "15:30" },
"Friday": { "open": "09:00", "close": "15:30" },
"Saturday": { "open": null, "close": null },
"Saturday": { "open": "10:00", "close": "14:00" },
"Sunday": { "open": null, "close": null }
},
"post_market_sessions": {
"Monday": { "open": "15:30", "close": "16:00" },
"Tuesday": { "open": "15:30", "close": "16:00" },
"Wednesday": { "open": "15:30", "close": "16:00" },
"Thursday": { "open": "15:30", "close": "16:00" },
"Friday": { "open": "15:30", "close": "16:00" },
"Saturday": { "open": null, "close": null },
"Monday": { "open": "15:30", "close": "00:00" },
"Tuesday": { "open": "15:30", "close": "00:00" },
"Wednesday": { "open": "15:30", "close": "00:00" },
"Thursday": { "open": "15:30", "close": "00:00" },
"Friday": { "open": "15:30", "close": "00:00" },
Comment on lines +36 to +40
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

The post-market session close time of "00:00" could be ambiguous - it's unclear whether this means midnight of the same day or the next day. This could cause issues with session calculations. Consider using "23:59" for same-day close or documenting that "00:00" represents the next day, and ensure the session logic handles this correctly.

Copilot uses AI. Check for mistakes.
"Saturday": { "open": "14:00", "close": "20:00" },
"Sunday": { "open": null, "close": null }
},
"anniversaries": [{
Expand Down
Loading