Merge commit 'd49272e17e6b162b9bf4a6934d0f89de3285316e' into HEAD
@@ -0,0 +1,67 @@
|
||||
---
|
||||
name: "📝 API/DB Feature Request"
|
||||
about: Suggest a new backend API or database feature
|
||||
title: "[Feature] <short description here>"
|
||||
labels: [feature, backend]
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
## ✨ API/Database Feature Request
|
||||
|
||||
### Summary
|
||||
|
||||
Describe the feature being requested. What functionality does it add to the backend or database? What problem does it solve?
|
||||
|
||||
## 📘 Use Case
|
||||
|
||||
Explain the use case behind this feature. Who is it for, and why is it needed now?
|
||||
|
||||
## 📥 Example API Request
|
||||
|
||||
```
|
||||
POST /api/example-endpoint
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <token>
|
||||
|
||||
{
|
||||
"exampleField": "value",
|
||||
"anotherField": 123
|
||||
}
|
||||
```
|
||||
|
||||
## 📤 Example API Response
|
||||
|
||||
```
|
||||
// JSON response
|
||||
{
|
||||
"id": "abc123",
|
||||
"status": "success",
|
||||
"data": {
|
||||
"result": true,
|
||||
"timestamp": "2025-04-30T18:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📦 Database Considerations
|
||||
|
||||
- **New Tables**: Yes/No
|
||||
If yes, describe the schema or include a rough layout.
|
||||
|
||||
- **Modified Tables**: Yes/No
|
||||
Describe what changes are needed and why.
|
||||
|
||||
- **Migrations Required**: Yes/No
|
||||
|
||||
- **Indexes Required**: Yes/No
|
||||
Include any thoughts on performance or query efficiency.
|
||||
|
||||
## 🔐 Security & Access Control
|
||||
|
||||
- Does the endpoint require authentication? Yes/No
|
||||
- Should it be limited to specific roles (e.g. admin, worker, user)?
|
||||
- Any sensitive data in the request or response?
|
||||
|
||||
## 📎 Additional Context
|
||||
|
||||
Add any screenshots, designs, relevant discussion links, or references to prior issues.
|
||||
@@ -1,38 +1,134 @@
|
||||
name: Deploy to Remote
|
||||
name: 🚀 Deploy
|
||||
|
||||
on:
|
||||
# Allow contributors to schedule manual deployments.
|
||||
# Permission to deploy can be restricted by requiring approval in environment configuration.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target_environment:
|
||||
description: "Deployment Environment"
|
||||
target_domain:
|
||||
description: "Deployment Domain"
|
||||
required: true
|
||||
default: "openfront.dev"
|
||||
type: choice
|
||||
options:
|
||||
- openfront.io
|
||||
- openfront.dev
|
||||
target_host:
|
||||
description: "Deployment Host"
|
||||
required: true
|
||||
default: "staging"
|
||||
type: choice
|
||||
options:
|
||||
- production
|
||||
- eu
|
||||
- us
|
||||
- staging
|
||||
target_subdomain:
|
||||
description: "Deployment Subdomain"
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
|
||||
# Automatic deployment on push
|
||||
# See https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onpushpull_requestpull_request_targetpathspaths-ignore
|
||||
push:
|
||||
branches:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy to ${{ inputs.target_environment }}
|
||||
# Don't deploy on push if this is a fork
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || github.repository == 'openfrontio/OpenFrontIO' }}
|
||||
# Use different logic based on event type
|
||||
name: Deploy to ${{
|
||||
github.event_name == 'push'
|
||||
&& (github.ref_name == 'main' && 'openfront.dev'
|
||||
|| format('{0}.openfront.dev', github.ref_name))
|
||||
|| inputs.target_subdomain && format('{0}.{1}', inputs.target_subdomain, inputs.target_domain)
|
||||
|| inputs.target_domain
|
||||
|| 'openfront.dev'
|
||||
}}
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.target_environment }}
|
||||
|
||||
environment: ${{
|
||||
github.event_name == 'push'
|
||||
&& (github.ref_name == 'main' && 'openfront.dev'
|
||||
|| format('{0}.openfront.dev', github.ref_name))
|
||||
|| inputs.target_subdomain && format('{0}.{1}', inputs.target_subdomain, inputs.target_domain)
|
||||
|| inputs.target_domain
|
||||
|| 'openfront.dev'
|
||||
}}
|
||||
env:
|
||||
DOMAIN: ${{ inputs.target_domain || 'openfront.dev' }}
|
||||
SUBDOMAIN: ${{ github.event_name == 'push' && github.ref_name || inputs.target_subdomain || 'main' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Update deployment status
|
||||
env:
|
||||
FQDN: ${{ env.SUBDOMAIN && format('{0}.{1}', env.SUBDOMAIN, env.DOMAIN) || env.DOMAIN || 'openfront.dev' }}
|
||||
run: |
|
||||
echo "FQDN=$FQDN" >> $GITHUB_ENV
|
||||
cat <<EOF >> $GITHUB_STEP_SUMMARY
|
||||
### In progress :ship:
|
||||
|
||||
Deploying from $GITHUB_REF to $FQDN
|
||||
EOF
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- run: |
|
||||
- name: Create SSH private key
|
||||
env:
|
||||
SERVER_HOST_EU: ${{ secrets.SERVER_HOST_EU }}
|
||||
SERVER_HOST_STAGING: ${{ secrets.SERVER_HOST_STAGING }}
|
||||
SERVER_HOST_US: ${{ secrets.SERVER_HOST_US }}
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
||||
echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
|
||||
test -n "$SERVER_HOST_STAGING" && ssh-keyscan -H "$SERVER_HOST_STAGING" >> ~/.ssh/known_hosts
|
||||
test -n "$SERVER_HOST_US" && ssh-keyscan -H "$SERVER_HOST_US" >> ~/.ssh/known_hosts
|
||||
test -n "$SERVER_HOST_EU" && ssh-keyscan -H "$SERVER_HOST_EU" >> ~/.ssh/known_hosts
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan -H ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts
|
||||
cat >.env <<EOF
|
||||
SERVER_HOST_STAGING=${{ secrets.SERVER_USERNAME }}@${{ secrets.SERVER_HOST }}
|
||||
DOCKER_REPO_STAGING=${{ vars.DOCKERHUB_REPO }}
|
||||
DOCKER_USERNAME=${{ vars.DOCKERHUB_USERNAME }}
|
||||
- name: Deploy
|
||||
env:
|
||||
ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }}
|
||||
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
||||
DOCKER_REPO: ${{ vars.DOCKERHUB_REPO }}
|
||||
DOCKER_USERNAME: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
ENV: ${{ inputs.target_domain == 'openfront.io' && 'prod' || 'staging' }}
|
||||
HOST: ${{ github.event_name == 'workflow_dispatch' && inputs.target_host || 'staging' }}
|
||||
MON_PASSWORD: ${{ secrets.MON_PASSWORD }}
|
||||
MON_USERNAME: ${{ secrets.MON_USERNAME }}
|
||||
OTEL_ENDPOINT: ${{ secrets.OTEL_ENDPOINT }}
|
||||
OTEL_PASSWORD: ${{ secrets.OTEL_PASSWORD }}
|
||||
OTEL_USERNAME: ${{ secrets.OTEL_USERNAME }}
|
||||
R2_ACCESS_KEY: ${{ secrets.R2_ACCESS_KEY }}
|
||||
R2_BUCKET: ${{ secrets.R2_BUCKET }}
|
||||
R2_SECRET_KEY: ${{ secrets.R2_SECRET_KEY }}
|
||||
SERVER_HOST_EU: ${{ secrets.SERVER_HOST_EU }}
|
||||
SERVER_HOST_STAGING: ${{ secrets.SERVER_HOST_STAGING }}
|
||||
SERVER_HOST_US: ${{ secrets.SERVER_HOST_US }}
|
||||
SSH_KEY: ~/.ssh/id_rsa
|
||||
VERSION_TAG: latest
|
||||
run: |
|
||||
echo "::group::deploy.sh"
|
||||
./deploy.sh "$ENV" "$HOST" "$SUBDOMAIN"
|
||||
echo "::endgroup::"
|
||||
- name: Update deployment status ✅
|
||||
if: success()
|
||||
run: |
|
||||
cat <<EOF >> $GITHUB_STEP_SUMMARY
|
||||
### Success! :rocket:
|
||||
|
||||
Deployed from $GITHUB_REF to $FQDN
|
||||
EOF
|
||||
- name: Update deployment status ❌
|
||||
if: failure()
|
||||
run: |
|
||||
cat <<EOF >> $GITHUB_STEP_SUMMARY
|
||||
### Failure! :fire:
|
||||
|
||||
Unable to deploy from $GITHUB_REF to $FQDN
|
||||
EOF
|
||||
./deploy.sh ${{ inputs.target_environment }}
|
||||
|
||||
@@ -5,5 +5,5 @@ static/
|
||||
TODO.txt
|
||||
resources/images/.DS_Store
|
||||
resources/.DS_Store
|
||||
.env
|
||||
.env*
|
||||
.DS_Store
|
||||
@@ -1 +1,8 @@
|
||||
npx lint-staged
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
# Add PATH setup to ensure npx is found
|
||||
export PATH="/usr/local/bin:$HOME/.npm-global/bin:$HOME/.nvm/versions/node/$(node -v)/bin:$PATH"
|
||||
|
||||
# Then run lint-staged if tests pass
|
||||
npx lint-staged
|
||||
@@ -9,6 +9,25 @@
|
||||
"runtimeArgs": ["run-script", "test"],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Server",
|
||||
"runtimeExecutable": "node",
|
||||
"runtimeArgs": [
|
||||
"--loader",
|
||||
"ts-node/esm",
|
||||
"--experimental-specifier-resolution=node",
|
||||
"${workspaceFolder}/src/server/Server.ts"
|
||||
],
|
||||
"env": {
|
||||
"GAME_ENV": "dev"
|
||||
},
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"sourceMaps": true,
|
||||
"restart": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,13 +1,31 @@
|
||||
# Use an official Node runtime as the base image
|
||||
FROM node:18
|
||||
FROM node:18 AS base
|
||||
|
||||
# Create dependency layer
|
||||
FROM base AS dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
nginx \
|
||||
supervisor \
|
||||
git \
|
||||
curl \
|
||||
jq \
|
||||
wget \
|
||||
apache2-utils \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb > cloudflared.deb \
|
||||
&& dpkg -i cloudflared.deb \
|
||||
&& rm cloudflared.deb
|
||||
|
||||
# Final image
|
||||
FROM base
|
||||
|
||||
# Copy installed packages from dependencies stage
|
||||
COPY --from=dependencies / /
|
||||
|
||||
ARG GIT_COMMIT=unknown
|
||||
ENV GIT_COMMIT=$GIT_COMMIT
|
||||
|
||||
# Install Nginx, Supervisor and Git (for Husky)
|
||||
RUN apt-get update && apt-get install -y nginx supervisor git && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set the working directory in the container
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
@@ -25,6 +43,10 @@ COPY . .
|
||||
# Build the client-side application
|
||||
RUN npm run build-prod
|
||||
|
||||
# So we can see which commit was used to build the container
|
||||
# https://openfront.io/commit.txt
|
||||
RUN echo $GIT_COMMIT > static/commit.txt
|
||||
|
||||
# Copy Nginx configuration and ensure it's used instead of the default
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
RUN rm -f /etc/nginx/sites-enabled/default
|
||||
@@ -33,8 +55,9 @@ RUN rm -f /etc/nginx/sites-enabled/default
|
||||
RUN mkdir -p /var/log/supervisor
|
||||
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
# Expose only the Nginx port
|
||||
EXPOSE 80 443
|
||||
# Copy and make executable the startup script
|
||||
COPY startup.sh /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/startup.sh
|
||||
|
||||
# Start Supervisor to manage both Node.js and Nginx
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
# Use the startup script as the entrypoint
|
||||
ENTRYPOINT ["/usr/local/bin/startup.sh"]
|
||||
@@ -116,21 +116,23 @@ This project is licensed under the terms found in the [LICENSE](LICENSE) file.
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
|
||||
1. Request to join the development [Discord](https://discord.gg/K9zernJB5z).
|
||||
1. Fork the repository
|
||||
2. Create your feature branch (`git checkout -b amazing-feature`)
|
||||
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
||||
4. Push to the branch (`git push origin amazing-feature`)
|
||||
5. Open a Pull Request
|
||||
1. Create your feature branch (`git checkout -b amazing-feature`)
|
||||
1. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
||||
1. Push to the branch (`git push origin amazing-feature`)
|
||||
1. Open a Pull Request
|
||||
|
||||
## 🌐 Translation
|
||||
|
||||
Translators are welcome! Please feel free to help translate into your language.
|
||||
How to help?
|
||||
|
||||
1. Request to join the translation [Discord](https://discord.gg/rUukAnz4Ww)
|
||||
1. Go to the project's Crowdin translation page: [https://crowdin.com/project/openfront-mls](https://crowdin.com/project/openfront-mls)
|
||||
2. Login if you already have an account/ Sign up if you don't have one
|
||||
3. Select the language you want to translate in/ If your language isn't on the list, click the "Request New Language" button and enter the language you want added there.
|
||||
4. Translate the strings
|
||||
1. Login if you already have an account/ Sign up if you don't have one
|
||||
1. Select the language you want to translate in/ If your language isn't on the list, click the "Request New Language" button and enter the language you want added there.
|
||||
1. Translate the strings
|
||||
|
||||
### Project Governance
|
||||
|
||||
|
||||
@@ -7,6 +7,48 @@
|
||||
|
||||
set -e # Exit immediately if a command exits with a non-zero status
|
||||
|
||||
# Initialize variables
|
||||
ENABLE_BASIC_AUTH=false
|
||||
|
||||
# Parse command line arguments
|
||||
POSITIONAL_ARGS=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--enable_basic_auth)
|
||||
ENABLE_BASIC_AUTH=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
POSITIONAL_ARGS+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Restore positional parameters
|
||||
set -- "${POSITIONAL_ARGS[@]}"
|
||||
|
||||
# Check command line arguments
|
||||
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
|
||||
echo "Error: Please specify environment and host, with optional subdomain"
|
||||
echo "Usage: $0 [prod|staging] [eu|us|staging|masters] [subdomain] [--enable_basic_auth]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate first argument (environment)
|
||||
if [ "$1" != "prod" ] && [ "$1" != "staging" ]; then
|
||||
echo "Error: First argument must be either 'prod' or 'staging'"
|
||||
echo "Usage: $0 [prod|staging] [eu|us|staging|masters] [subdomain] [--enable_basic_auth]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate second argument (host)
|
||||
if [ "$2" != "eu" ] && [ "$2" != "us" ] && [ "$2" != "staging" ] && [ "$2" != "masters" ]; then
|
||||
echo "Error: Second argument must be either 'eu', 'us', 'staging', or 'masters'"
|
||||
echo "Usage: $0 [prod|staging] [eu|us|staging|masters] [subdomain] [--enable_basic_auth]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to print section headers
|
||||
print_header() {
|
||||
echo "======================================================"
|
||||
@@ -14,53 +56,73 @@ print_header() {
|
||||
echo "======================================================"
|
||||
}
|
||||
|
||||
# Load environment variables
|
||||
ENV=$1
|
||||
HOST=$2
|
||||
SUBDOMAIN=$3 # Optional third argument for custom subdomain
|
||||
|
||||
# Set subdomain - use the custom subdomain if provided, otherwise use REGION
|
||||
if [ -n "$SUBDOMAIN" ]; then
|
||||
echo "Using custom subdomain: $SUBDOMAIN"
|
||||
else
|
||||
SUBDOMAIN=$HOST
|
||||
echo "Using host as subdomain: $SUBDOMAIN"
|
||||
fi
|
||||
|
||||
# Load common environment variables first
|
||||
if [ -f .env ]; then
|
||||
echo "Loading configuration from .env file..."
|
||||
echo "Loading common configuration from .env file..."
|
||||
export $(grep -v '^#' .env | xargs)
|
||||
fi
|
||||
|
||||
# Check command line argument
|
||||
if [ $# -ne 1 ] || ([ "$1" != "staging" ] && [ "$1" != "eu" ] && [ "$1" != "us" ]); then
|
||||
echo "Error: Please specify environment (staging, eu, or us)"
|
||||
echo "Usage: $0 [staging|eu|us]"
|
||||
exit 1
|
||||
# Load environment-specific variables
|
||||
if [ -f .env.$ENV ]; then
|
||||
echo "Loading $ENV-specific configuration from .env.$ENV file..."
|
||||
export $(grep -v '^#' .env.$ENV | xargs)
|
||||
fi
|
||||
|
||||
REGION=$1
|
||||
VERSION_TAG="latest"
|
||||
DOCKER_REPO=""
|
||||
ENV=""
|
||||
|
||||
# Set environment-specific variables
|
||||
if [ "$REGION" == "staging" ]; then
|
||||
print_header "DEPLOYING TO STAGING ENVIRONMENT"
|
||||
if [ "$HOST" == "staging" ]; then
|
||||
print_header "DEPLOYING TO STAGING HOST"
|
||||
SERVER_HOST=$SERVER_HOST_STAGING
|
||||
DOCKER_REPO=$DOCKER_REPO_STAGING
|
||||
ENV="staging"
|
||||
elif [ "$REGION" == "us" ]; then
|
||||
print_header "DEPLOYING TO US ENVIRONMENT"
|
||||
elif [ "$HOST" == "us" ]; then
|
||||
print_header "DEPLOYING TO US HOST"
|
||||
SERVER_HOST=$SERVER_HOST_US
|
||||
DOCKER_REPO=$DOCKER_REPO_PROD # Uses prod Docker repo for alt environment
|
||||
ENV="prod"
|
||||
elif [ "$HOST" == "masters" ]; then
|
||||
print_header "DEPLOYING TO MASTERS HOST"
|
||||
SERVER_HOST=$SERVER_HOST_MASTERS
|
||||
else
|
||||
print_header "DEPLOYING TO EU ENVIRONMENT"
|
||||
print_header "DEPLOYING TO EU HOST"
|
||||
SERVER_HOST=$SERVER_HOST_EU
|
||||
DOCKER_REPO=$DOCKER_REPO_PROD
|
||||
ENV="prod"
|
||||
fi
|
||||
|
||||
# Check required environment variables
|
||||
if [ -z "$SERVER_HOST" ]; then
|
||||
echo "Error: SERVER_HOST_${REGION^^} not defined in .env file or environment"
|
||||
echo "Error: ${HOST} not defined in .env file or environment"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if basic auth is enabled and credentials are available
|
||||
if [ "$ENABLE_BASIC_AUTH" = true ]; then
|
||||
print_header "BASIC AUTH ENABLED"
|
||||
if [ -z "$BASIC_AUTH_USER" ] || [ -z "$BASIC_AUTH_PASS" ]; then
|
||||
echo "Error: Basic Auth is enabled but BASIC_AUTH_USER or BASIC_AUTH_PASS not defined in .env file or environment"
|
||||
exit 1
|
||||
fi
|
||||
echo "Basic Authentication will be enabled with user: $BASIC_AUTH_USER"
|
||||
else
|
||||
# If basic auth is not enabled, set the variables to empty to ensure they don't get used
|
||||
BASIC_AUTH_USER=""
|
||||
BASIC_AUTH_PASS=""
|
||||
echo "Basic Authentication is disabled"
|
||||
fi
|
||||
|
||||
# Configuration
|
||||
SSH_KEY=${SSH_KEY:-"~/.ssh/id_rsa"} # Use default or override from .env
|
||||
DOCKER_USERNAME=${DOCKER_USERNAME} # Docker Hub username
|
||||
UPDATE_SCRIPT="./update.sh" # Path to your update script
|
||||
REMOTE_UPDATE_SCRIPT="/root/update-openfront.sh" # Where to place the script on server
|
||||
REMOTE_USER="openfront"
|
||||
REMOTE_UPDATE_PATH="/home/$REMOTE_USER"
|
||||
REMOTE_UPDATE_SCRIPT="$REMOTE_UPDATE_PATH/update-openfront.sh" # Where to place the script on server
|
||||
|
||||
VERSION_TAG=$(date +"%Y%m%d-%H%M%S")
|
||||
DOCKER_IMAGE="${DOCKER_USERNAME}/${DOCKER_REPO}:${VERSION_TAG}"
|
||||
|
||||
# Check if update script exists
|
||||
if [ ! -f "$UPDATE_SCRIPT" ]; then
|
||||
@@ -70,7 +132,9 @@ fi
|
||||
|
||||
# Step 1: Build and upload Docker image to Docker Hub
|
||||
print_header "STEP 1: Building and uploading Docker image to Docker Hub"
|
||||
echo "Region: ${REGION}"
|
||||
echo "Environment: ${ENV}"
|
||||
echo "Host: ${HOST}"
|
||||
echo "Subdomain: ${SUBDOMAIN}"
|
||||
echo "Using version tag: $VERSION_TAG"
|
||||
echo "Docker repository: $DOCKER_REPO"
|
||||
|
||||
@@ -81,7 +145,7 @@ echo "Git commit: $GIT_COMMIT"
|
||||
docker buildx build \
|
||||
--platform linux/amd64 \
|
||||
--build-arg GIT_COMMIT=$GIT_COMMIT \
|
||||
-t $DOCKER_USERNAME/$DOCKER_REPO:$VERSION_TAG \
|
||||
-t $DOCKER_IMAGE \
|
||||
--push \
|
||||
.
|
||||
|
||||
@@ -90,42 +154,46 @@ if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Failed to push image to Docker Hub. Stopping deployment."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Docker image built and pushed successfully."
|
||||
|
||||
# Step 2: Copy update script to Hetzner server
|
||||
print_header "STEP 2: Copying update script to server"
|
||||
echo "Target: $SERVER_HOST"
|
||||
echo "Target: $REMOTE_USER@$SERVER_HOST"
|
||||
|
||||
# Make sure the update script is executable
|
||||
chmod +x $UPDATE_SCRIPT
|
||||
|
||||
# Copy the update script to the server
|
||||
scp -i $SSH_KEY $UPDATE_SCRIPT $SERVER_HOST:$REMOTE_UPDATE_SCRIPT
|
||||
|
||||
# Copy environment variables if needed
|
||||
if [ -f .env ]; then
|
||||
scp -i $SSH_KEY .env $SERVER_HOST:/root/.env
|
||||
# Secure the .env file
|
||||
ssh -i $SSH_KEY $SERVER_HOST "chmod 600 /root/.env"
|
||||
fi
|
||||
scp -i $SSH_KEY $UPDATE_SCRIPT $REMOTE_USER@$SERVER_HOST:$REMOTE_UPDATE_SCRIPT
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Failed to copy update script to server. Stopping deployment."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Update script successfully copied to server."
|
||||
|
||||
# Step 3: Execute the update script on the server
|
||||
print_header "STEP 3: Executing update script on server"
|
||||
|
||||
# Make the script executable on the remote server and execute it with the environment parameter
|
||||
ssh -i $SSH_KEY $SERVER_HOST "chmod +x $REMOTE_UPDATE_SCRIPT && $REMOTE_UPDATE_SCRIPT $REGION $DOCKER_USERNAME $DOCKER_REPO"
|
||||
ssh -i $SSH_KEY $REMOTE_USER@$SERVER_HOST "chmod +x $REMOTE_UPDATE_SCRIPT && \
|
||||
cat > $REMOTE_UPDATE_PATH/.env << 'EOL'
|
||||
GAME_ENV=$ENV
|
||||
ENV=$ENV
|
||||
HOST=$HOST
|
||||
DOCKER_IMAGE=$DOCKER_IMAGE
|
||||
DOCKER_TOKEN=$DOCKER_TOKEN
|
||||
ADMIN_TOKEN=$ADMIN_TOKEN
|
||||
CF_ACCOUNT_ID=$CF_ACCOUNT_ID
|
||||
R2_ACCESS_KEY=$R2_ACCESS_KEY
|
||||
R2_SECRET_KEY=$R2_SECRET_KEY
|
||||
R2_BUCKET=$R2_BUCKET
|
||||
CF_API_TOKEN=$CF_API_TOKEN
|
||||
DOMAIN=$DOMAIN
|
||||
SUBDOMAIN=$SUBDOMAIN
|
||||
OTEL_USERNAME=$OTEL_USERNAME
|
||||
OTEL_PASSWORD=$OTEL_PASSWORD
|
||||
OTEL_ENDPOINT=$OTEL_ENDPOINT
|
||||
BASIC_AUTH_USER=$BASIC_AUTH_USER
|
||||
BASIC_AUTH_PASS=$BASIC_AUTH_PASS
|
||||
EOL
|
||||
chmod 600 $REMOTE_UPDATE_PATH/.env && \
|
||||
$REMOTE_UPDATE_SCRIPT"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Failed to execute update script on server."
|
||||
@@ -133,6 +201,9 @@ if [ $? -ne 0 ]; then
|
||||
fi
|
||||
|
||||
print_header "DEPLOYMENT COMPLETED SUCCESSFULLY"
|
||||
echo "✅ New version deployed to ${REGION} environment!"
|
||||
echo "🌐 Check your ${REGION} server to verify the deployment."
|
||||
echo "✅ New version deployed to ${ENV} environment in ${HOST} with subdomain ${SUBDOMAIN}!"
|
||||
if [ "$ENABLE_BASIC_AUTH" = true ]; then
|
||||
echo "🔒 Basic authentication enabled with user: $BASIC_AUTH_USER"
|
||||
fi
|
||||
echo "🌐 Check your server to verify the deployment."
|
||||
echo "======================================================="
|
||||
@@ -13,6 +13,7 @@ const gitignorePath = path.resolve(__dirname, ".gitignore");
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
includeIgnoreFile(gitignorePath),
|
||||
{ ignores: ["src/server/gatekeeper/**"] },
|
||||
{ files: ["**/*.{js,mjs,cjs,ts}"] },
|
||||
{ languageOptions: { globals: { ...globals.browser, ...globals.node } } },
|
||||
pluginJs.configs.recommended,
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
# AWS Configuration
|
||||
AWS_REGION=region-name
|
||||
AWS_ACCOUNT_ID=your-account-id
|
||||
# SSH Configuration
|
||||
SSH_KEY=~/.ssh/your-ssh-key
|
||||
|
||||
# ECR (Elastic Container Registry)
|
||||
ECR_REPO_NAME=your-repo-name
|
||||
# Docker Configuration
|
||||
DOCKER_USERNAME=username
|
||||
DOCKER_REPO=your-repo-name
|
||||
DOCKER_TOKEN=your_docker_token_here
|
||||
|
||||
# EC2 Deployment Hosts
|
||||
EC2_HOST_STAGING=ec2-user@your-staging-ip
|
||||
EC2_HOST_PROD=ec2-user@your-production-ip
|
||||
EC2_KEY=~/.ssh/your-key-file.pem
|
||||
# Admin credentials
|
||||
ADMIN_TOKEN=your_admin_token_here
|
||||
|
||||
# Application Secrets
|
||||
ADMIN_TOKEN=your-admin-token
|
||||
# Cloudflare Configuration
|
||||
CF_ACCOUNT_ID=your_cloudflare_account_id
|
||||
CF_API_TOKEN=your_cloudflare_api_token
|
||||
DOMAIN=your-domain.com
|
||||
|
||||
# R2 Configuration
|
||||
R2_ACCESS_KEY=your_r2_access_key
|
||||
R2_SECRET_KEY=your_r2_secret_key
|
||||
R2_BUCKET=your-bucket-name
|
||||
|
||||
# Server Hosts
|
||||
SERVER_HOST_STAGING=123.456.78.90
|
||||
SERVER_HOST_EU=123.456.78.91
|
||||
SERVER_HOST_US=123.456.78.92
|
||||
|
||||
# Monitoring Credentials
|
||||
MON_USERNAME=monitor_username
|
||||
MON_PASSWORD=monitor_password
|
||||
|
||||
# Version
|
||||
VERSION_TAG="latest"
|
||||
@@ -80,6 +80,18 @@
|
||||
"@aws-sdk/client-s3": "^3.758.0",
|
||||
"@datastructures-js/priority-queue": "^6.3.1",
|
||||
"@google-cloud/secret-manager": "^5.6.0",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/api-logs": "^0.200.0",
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.58.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-http": "^0.200.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
|
||||
"@opentelemetry/host-metrics": "^0.36.0",
|
||||
"@opentelemetry/resources": "^2.0.0",
|
||||
"@opentelemetry/sdk-logs": "^0.200.0",
|
||||
"@opentelemetry/sdk-metrics": "^2.0.0",
|
||||
"@opentelemetry/sdk-node": "^0.200.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.32.0",
|
||||
"@opentelemetry/winston-transport": "^0.11.0",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/google-protobuf": "^3.15.12",
|
||||
@@ -95,7 +107,7 @@
|
||||
"d3": "^7.9.0",
|
||||
"discord.js": "^14.16.3",
|
||||
"dompurify": "^3.1.7",
|
||||
"dotenv": "^16.4.7",
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^4.21.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"google-auth-library": "^9.14.0",
|
||||
@@ -104,6 +116,7 @@
|
||||
"html-webpack-plugin": "^5.6.3",
|
||||
"ip-anonymize": "^0.1.0",
|
||||
"jimp": "^0.22.12",
|
||||
"jose": "^6.0.10",
|
||||
"lit": "^3.2.1",
|
||||
"msgpack5": "^6.0.2",
|
||||
"nanoid": "^3.3.6",
|
||||
@@ -127,6 +140,7 @@
|
||||
"webpack-dev-server": "^5.0.4",
|
||||
"wheelnav": "^1.7.1",
|
||||
"winston": "^3.17.0",
|
||||
"winston-transport": "^4.9.0",
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 409 B After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 600 B After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="0 0 150 150">
|
||||
<image href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAADICAYAAACZBDirAAAAAXNSR0IArs4c6QAADSRJREFUeF7t3E+MXWUZx/Hv/TPTOy1tpdM2FIrSYlRMCcbEoCSEnYsSjDsJGHXhAnVjwoaFBsNGNiSuDFEXEkVwZSIpCzeGhUEWGDUY1ISKoWgtbYGWdqYz949950+saDmn5Tyd5/R+T9K06bz3uc/5PKe/vOee6XT4z9EDrgPuAu4BbgduBPoXrPGPCiigQJsEhsBrwAvAM8BzwFFgVE6is3YmJeT2n//CA2vhtxuYWwu/9TVtOml7VUABBYrABCghuAAcWwvBx4HD5e9LuJWd383AQ8DdwE6gq50CCihwlQmMgePAIeBR4JUSgDcADwL3A7su2BVeZefu6SiggAIrO8I3gCeBx0oA3nd+x/cIsM+dn5eHAgpMgUDZCZZb4IdLAD4FHAS2TcGJe4oKKKBAETgFPFsCsCThXmBGFwUUUGBKBJaBIyUAyx/KgxCf9k7J5D1NBRRY+SxwVEKv/MFDAQUUmDqBywrA8qJNnQ6b6TDb6TBDh577x6m7eDxhBTZKYDQpt64TliYTzjLh3GRyWTu5Sw7Acq883+1xW3+WT88M+FC3x55en62drt88uFFXg++rwBQJlEe4pydj/jka8vfxiN8uL/KH4RInxqPV/95xCUftAFzf9X2o2+fzgy0cnN3M7m6XWTr0O52VDxE9FFBAgSshUIJuOJmwxIRj4zGHls7yi8UzvDYeXtJusHYAllveT/Y38bW5rXyiP8sHuj13fFdi0r6HAgq8p0DZEb45HvHicIkfLpzmd8NzKyFY56gVgGXRrm6Pr85t5d7BNVzb6frIuI6uaxRQ4IoIlLgrIfj0uTP8aOE0b4xHtT4TrBWAg06HO2cGfGvLtezr9d35XZGR+iYKKHApAiv/vWO0zHfOvMXzy4u1doG1AnBHp8s3Nm/nvsGWlYcdHgoooEBGgVOTMT9bPMP3z77NyUmJxPc+agXgnm6P716zgztnB2zy5rfK1K8roMAGCSxOJvx6aYFvn3mTo+PqZ8K1AvCDvT4/2LqTj/dnvf3doMH6tgooUC1QIu+Py0t8/fTxlSfCVUetALyp1+cn23avfP7noYACCmQWODwa8qVTx3h11GAA/nTbbkoQeiiggAKZBUrwfdEAzDwie1NAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSiBxgNwb7fH97bOs7fbj+rZugoooEAjAkfGQ755+gRHxqPKeh1gUrVq0OnwkV6fAd2qpX5dAQUU2FCBRcb8dTRkcVIZbdQKwLKoRF/53UMBBRTILFBib1xnZ7eWadUxmfls7U0BBRS4TIFaO8DLrO3LFFBAgdQCBmDq8dicAgpEChiAkbrWVkCB1AIGYOrx2JwCCkQKGICRutZWQIHUAgZg6vHYnAIKRAoYgJG61lZAgdQCBmDq8dicAgpEChiAkbrWVkCB1AIGYOrx2JwCCkQKGICRutZWQIHUAgZg6vHYnAIKRAoYgJG61lZAgdQCBmDq8dicAgpEChiAkbrWVkCB1AIGYOrx2JwCCkQKGICRutZWQIHUAgZg6vHYnAIKRAoYgJG61lZAgdQCBmDq8dicAgpEChiAkbrWVkCB1AIGYOrx2JwCCkQKGICRutZWQIHUAgZg6vHYnAIKRAoYgJG61lZAgdQCBmDq8dicAgpEChiAkbrWVkCB1AIGYOrx2JwCCkQKGICRutZWQIHUAgZg6vHYnAIKRAoYgJG61lZAgdQCBmDq8dic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" x="7.500" y="32.813" width="135.000" height="84.375" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 432 KiB After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 441 KiB After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="0 0 150 150">
|
||||
<image href="data:image/png;base64,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" x="7.500" y="32.813" width="135.000" height="84.375" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="0 0 150 150">
|
||||
<image href="data:image/png;base64,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" x="7.500" y="32.813" width="135.000" height="84.375" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 507 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 262 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 241 KiB After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 36 KiB |