공개: KoTalk 최신 기준선

This commit is contained in:
Ian 2026-04-16 09:24:26 +09:00
commit debf62f76e
572 changed files with 41689 additions and 0 deletions

11
deploy/.env.example Normal file
View file

@ -0,0 +1,11 @@
COMPOSE_PROJECT_NAME=kotalk
ACME_EMAIL=admin@example.com
API_HOST=api.example.com
DOWNLOAD_HOST=download-vstalk.phy.kr
DOWNLOAD_ROOT=/srv/kotalk/download
WEBAPP_HOST=vstalk.phy.kr
WEBAPP_RELEASE_ROOT=/srv/kotalk/webapp/current
BOOTSTRAP_INVITE_CODE=change-me-in-private-env
JWT_ISSUER=KoTalk
JWT_AUDIENCE=KoTalk.Client
JWT_SIGNING_KEY=change-me-to-a-long-random-value

49
deploy/Caddyfile Normal file
View file

@ -0,0 +1,49 @@
{
email {$ACME_EMAIL}
}
{$DOWNLOAD_HOST} {
encode zstd gzip
root * /srv/download
redir /windows /windows/latest 302
redir /android /android/latest 302
redir /windows/latest /windows/latest/VsMessenger-win-x64.zip 302
redir /android/latest /android/latest/VsMessenger-android-universal.apk 302
redir /latest /latest/version.json 302
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
file_server
}
{$WEBAPP_HOST:vstalk.phy.kr} {
encode zstd gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
handle /v1/* {
reverse_proxy api:8080
}
handle /health {
reverse_proxy api:8080
}
handle {
reverse_proxy webapp:80
}
}
{$API_HOST} {
encode zstd gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
}
reverse_proxy api:8080
}

29
deploy/README.md Normal file
View file

@ -0,0 +1,29 @@
# Deployment Guide
이 디렉터리는 KoTalk의 범용 배포 골격을 담습니다.
## Public Endpoints
- 모바일 웹: [vstalk.phy.kr](https://vstalk.phy.kr)
- 공식 다운로드 미러: [download-vstalk.phy.kr](https://download-vstalk.phy.kr)
- 버전 메타데이터: [download-vstalk.phy.kr/latest/version.json](https://download-vstalk.phy.kr/latest/version.json)
## Intended Shape
- `Caddyfile`: 웹 진입점, 다운로드 미러, API 프록시
- `compose*.yml`: API, 정적 웹, 보조 서비스 구성
- `docker/`: 이미지 빌드 정의
## Public Rules
- 실서비스 호스트 주소, 관리자 계정, 비밀값은 공개 문서에 적지 않습니다.
- 운영 중인 컨테이너명과 네트워크명은 공개 표면의 필수 정보가 아닙니다.
- 배포 예시는 범용 구조 중심으로 유지합니다.
## Download Layout
- `/windows/latest`
- `/android/latest`
- `/latest/version.json`
실제 공개 릴리즈 경로는 [RELEASING.md](../RELEASING.md)와 함께 봐야 합니다.

42
deploy/compose.mvp.yml Normal file
View file

@ -0,0 +1,42 @@
services:
caddy:
image: caddy:2.9-alpine
restart: unless-stopped
depends_on:
- api
ports:
- "80:80"
- "443:443"
environment:
ACME_EMAIL: ${ACME_EMAIL}
API_HOST: ${API_HOST}
DOWNLOAD_HOST: ${DOWNLOAD_HOST}
WEBAPP_HOST: ${WEBAPP_HOST:-vstalk.phy.kr}
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
- ${DOWNLOAD_ROOT}:/srv/download:ro
api:
build:
context: ..
dockerfile: deploy/docker/api.Dockerfile
restart: unless-stopped
environment:
ASPNETCORE_ENVIRONMENT: Production
ASPNETCORE_URLS: http://0.0.0.0:8080
ConnectionStrings__Main: Data Source=/data/vs-messenger.db
Auth__Jwt__Issuer: ${JWT_ISSUER}
Auth__Jwt__Audience: ${JWT_AUDIENCE}
Auth__Jwt__SigningKey: ${JWT_SIGNING_KEY}
Bootstrap__InviteCodes__0: ${BOOTSTRAP_INVITE_CODE}
volumes:
- api-data:/data
expose:
- "8080"
volumes:
caddy-config:
caddy-data:
api-data:

View file

@ -0,0 +1,9 @@
services:
webapp:
image: nginx:1.27-alpine
restart: unless-stopped
volumes:
- ${WEBAPP_RELEASE_ROOT:-/srv/vs-messanger/webapp/current}:/usr/share/nginx/html:ro
- ./docker/webapp.nginx.conf:/etc/nginx/conf.d/default.conf:ro
expose:
- "80"

View file

@ -0,0 +1,25 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["PhysOn.sln", "./"]
COPY ["global.json", "./"]
COPY ["src/PhysOn.Api/PhysOn.Api.csproj", "src/PhysOn.Api/"]
COPY ["src/PhysOn.Application/PhysOn.Application.csproj", "src/PhysOn.Application/"]
COPY ["src/PhysOn.Contracts/PhysOn.Contracts.csproj", "src/PhysOn.Contracts/"]
COPY ["src/PhysOn.Domain/PhysOn.Domain.csproj", "src/PhysOn.Domain/"]
COPY ["src/PhysOn.Infrastructure/PhysOn.Infrastructure.csproj", "src/PhysOn.Infrastructure/"]
RUN dotnet restore "src/PhysOn.Api/PhysOn.Api.csproj"
COPY . .
RUN dotnet publish "src/PhysOn.Api/PhysOn.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
ENV ASPNETCORE_URLS=http://0.0.0.0:8080
EXPOSE 8080
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "PhysOn.Api.dll"]

View file

@ -0,0 +1,23 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /assets/ {
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable";
try_files $uri =404;
}
location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|webp|woff2?)$ {
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable";
try_files $uri =404;
}
}

View file

@ -0,0 +1,22 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["PhysOn.sln", "./"]
COPY ["global.json", "./"]
COPY ["src/PhysOn.Worker/PhysOn.Worker.csproj", "src/PhysOn.Worker/"]
COPY ["src/PhysOn.Application/PhysOn.Application.csproj", "src/PhysOn.Application/"]
COPY ["src/PhysOn.Contracts/PhysOn.Contracts.csproj", "src/PhysOn.Contracts/"]
COPY ["src/PhysOn.Domain/PhysOn.Domain.csproj", "src/PhysOn.Domain/"]
COPY ["src/PhysOn.Infrastructure/PhysOn.Infrastructure.csproj", "src/PhysOn.Infrastructure/"]
RUN dotnet restore "src/PhysOn.Worker/PhysOn.Worker.csproj"
COPY . .
RUN dotnet publish "src/PhysOn.Worker/PhysOn.Worker.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "PhysOn.Worker.dll"]

View file

@ -0,0 +1,16 @@
[Unit]
Description=vs-messanger MVP docker compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
[Service]
Type=oneshot
WorkingDirectory=/srv/vs-messanger/app
RemainAfterExit=yes
ExecStart=/usr/bin/docker compose --project-name vs-messanger --env-file deploy/.env -f deploy/compose.mvp.yml up -d --build --remove-orphans
ExecStop=/usr/bin/docker compose --project-name vs-messanger --env-file deploy/.env -f deploy/compose.mvp.yml down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,16 @@
[Unit]
Description=vs-messanger mobile webapp compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
[Service]
Type=oneshot
WorkingDirectory=/srv/vs-messanger/app
RemainAfterExit=yes
ExecStart=/usr/bin/docker compose --project-name vs-messanger --env-file deploy/.env -f deploy/compose.mvp.yml -f deploy/compose.webapp.yml up -d webapp caddy
ExecStop=/usr/bin/docker compose --project-name vs-messanger --env-file deploy/.env -f deploy/compose.mvp.yml -f deploy/compose.webapp.yml stop webapp
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target