mirror of
				https://github.com/kyantech/Palmr.git
				synced 2025-11-03 21:43:20 +00:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
			v2.0.0-bet
			...
			v2.0.0-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					9984a21b76 | ||
| 
						 | 
					431086a614 | ||
| 
						 | 
					d40ef51695 | ||
| 
						 | 
					8f3ad71850 | ||
| 
						 | 
					a9191d6b54 | ||
| 
						 | 
					b8465df016 | ||
| 
						 | 
					5a44d79279 | 
							
								
								
									
										70
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
# Git
 | 
			
		||||
.git
 | 
			
		||||
.gitignore
 | 
			
		||||
 | 
			
		||||
# Documentation
 | 
			
		||||
README.md
 | 
			
		||||
CONTRIBUTING.md
 | 
			
		||||
*.md
 | 
			
		||||
 | 
			
		||||
# Node modules
 | 
			
		||||
node_modules
 | 
			
		||||
*/node_modules
 | 
			
		||||
**/node_modules
 | 
			
		||||
 | 
			
		||||
# Build outputs
 | 
			
		||||
.next
 | 
			
		||||
dist
 | 
			
		||||
build
 | 
			
		||||
 | 
			
		||||
# Development files
 | 
			
		||||
.env*
 | 
			
		||||
.vscode
 | 
			
		||||
.idea
 | 
			
		||||
 | 
			
		||||
# Logs
 | 
			
		||||
*.log
 | 
			
		||||
logs
 | 
			
		||||
 | 
			
		||||
# Runtime data
 | 
			
		||||
pids
 | 
			
		||||
*.pid
 | 
			
		||||
*.seed
 | 
			
		||||
*.pid.lock
 | 
			
		||||
 | 
			
		||||
# Coverage directory used by tools like istanbul
 | 
			
		||||
coverage
 | 
			
		||||
 | 
			
		||||
# nyc test coverage
 | 
			
		||||
.nyc_output
 | 
			
		||||
 | 
			
		||||
# Dependency directories
 | 
			
		||||
jspm_packages/
 | 
			
		||||
 | 
			
		||||
# Optional npm cache directory
 | 
			
		||||
.npm
 | 
			
		||||
 | 
			
		||||
# Optional REPL history
 | 
			
		||||
.node_repl_history
 | 
			
		||||
 | 
			
		||||
# Output of 'npm pack'
 | 
			
		||||
*.tgz
 | 
			
		||||
 | 
			
		||||
# Yarn Integrity file
 | 
			
		||||
.yarn-integrity
 | 
			
		||||
 | 
			
		||||
# dotenv environment variables file
 | 
			
		||||
.env
 | 
			
		||||
 | 
			
		||||
# Docker files
 | 
			
		||||
Dockerfile*
 | 
			
		||||
docker-compose*
 | 
			
		||||
 | 
			
		||||
# OS generated files
 | 
			
		||||
.DS_Store
 | 
			
		||||
.DS_Store?
 | 
			
		||||
._*
 | 
			
		||||
.Spotlight-V100
 | 
			
		||||
.Trashes
 | 
			
		||||
ehthumbs.db
 | 
			
		||||
Thumbs.db 
 | 
			
		||||
							
								
								
									
										160
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,160 @@
 | 
			
		||||
FROM node:18-alpine AS base
 | 
			
		||||
 | 
			
		||||
# Install system dependencies
 | 
			
		||||
RUN apk add --no-cache \
 | 
			
		||||
  netcat-openbsd \
 | 
			
		||||
  gcompat \
 | 
			
		||||
  supervisor \
 | 
			
		||||
  curl
 | 
			
		||||
 | 
			
		||||
# Enable pnpm
 | 
			
		||||
RUN corepack enable pnpm
 | 
			
		||||
 | 
			
		||||
# Set working directory
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
 | 
			
		||||
# === SERVER BUILD STAGE ===
 | 
			
		||||
FROM base AS server-deps
 | 
			
		||||
WORKDIR /app/server
 | 
			
		||||
 | 
			
		||||
# Copy server package files
 | 
			
		||||
COPY apps/server/package*.json ./
 | 
			
		||||
COPY apps/server/pnpm-lock.yaml ./
 | 
			
		||||
 | 
			
		||||
# Install server dependencies
 | 
			
		||||
RUN pnpm install --frozen-lockfile
 | 
			
		||||
 | 
			
		||||
FROM base AS server-builder
 | 
			
		||||
WORKDIR /app/server
 | 
			
		||||
 | 
			
		||||
# Copy server dependencies
 | 
			
		||||
COPY --from=server-deps /app/server/node_modules ./node_modules
 | 
			
		||||
 | 
			
		||||
# Copy server source code
 | 
			
		||||
COPY apps/server/ ./
 | 
			
		||||
 | 
			
		||||
# Generate Prisma client
 | 
			
		||||
RUN npx prisma generate
 | 
			
		||||
 | 
			
		||||
# Build server
 | 
			
		||||
RUN pnpm build
 | 
			
		||||
 | 
			
		||||
# === WEB BUILD STAGE ===
 | 
			
		||||
FROM base AS web-deps
 | 
			
		||||
WORKDIR /app/web
 | 
			
		||||
 | 
			
		||||
# Copy web package files
 | 
			
		||||
COPY apps/web/package.json apps/web/pnpm-lock.yaml ./
 | 
			
		||||
 | 
			
		||||
# Install web dependencies
 | 
			
		||||
RUN pnpm install --frozen-lockfile
 | 
			
		||||
 | 
			
		||||
FROM base AS web-builder
 | 
			
		||||
WORKDIR /app/web
 | 
			
		||||
 | 
			
		||||
# Copy web dependencies
 | 
			
		||||
COPY --from=web-deps /app/web/node_modules ./node_modules
 | 
			
		||||
 | 
			
		||||
# Copy web source code
 | 
			
		||||
COPY apps/web/ ./
 | 
			
		||||
 | 
			
		||||
# Set environment variables for build
 | 
			
		||||
ENV NEXT_TELEMETRY_DISABLED=1
 | 
			
		||||
ENV NODE_ENV=production
 | 
			
		||||
 | 
			
		||||
# Build web application
 | 
			
		||||
RUN pnpm run build
 | 
			
		||||
 | 
			
		||||
# === PRODUCTION STAGE ===
 | 
			
		||||
FROM base AS runner
 | 
			
		||||
 | 
			
		||||
# Set production environment
 | 
			
		||||
ENV NODE_ENV=production
 | 
			
		||||
ENV NEXT_TELEMETRY_DISABLED=1
 | 
			
		||||
 | 
			
		||||
# Create application user
 | 
			
		||||
RUN addgroup --system --gid 1001 nodejs
 | 
			
		||||
RUN adduser --system --uid 1001 palmr
 | 
			
		||||
 | 
			
		||||
# Create application directories and set permissions
 | 
			
		||||
RUN mkdir -p /app/server /app/web /home/palmr/.npm /home/palmr/.cache
 | 
			
		||||
RUN chown -R palmr:nodejs /app /home/palmr
 | 
			
		||||
 | 
			
		||||
# === Copy Server Files ===
 | 
			
		||||
WORKDIR /app/server
 | 
			
		||||
 | 
			
		||||
# Copy server production files
 | 
			
		||||
COPY --from=server-builder --chown=palmr:nodejs /app/server/dist ./dist
 | 
			
		||||
COPY --from=server-builder --chown=palmr:nodejs /app/server/node_modules ./node_modules
 | 
			
		||||
COPY --from=server-builder --chown=palmr:nodejs /app/server/prisma ./prisma
 | 
			
		||||
COPY --from=server-builder --chown=palmr:nodejs /app/server/package.json ./
 | 
			
		||||
 | 
			
		||||
# === Copy Web Files ===
 | 
			
		||||
WORKDIR /app/web
 | 
			
		||||
 | 
			
		||||
# Copy web production files
 | 
			
		||||
COPY --from=web-builder --chown=palmr:nodejs /app/web/public ./public
 | 
			
		||||
COPY --from=web-builder --chown=palmr:nodejs /app/web/.next/standalone ./
 | 
			
		||||
COPY --from=web-builder --chown=palmr:nodejs /app/web/.next/static ./.next/static
 | 
			
		||||
 | 
			
		||||
# === Setup Supervisor ===
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
 | 
			
		||||
# Create supervisor configuration
 | 
			
		||||
RUN mkdir -p /etc/supervisor/conf.d /var/log/supervisor
 | 
			
		||||
 | 
			
		||||
# Copy server start script
 | 
			
		||||
COPY infra/server-start.sh /app/server-start.sh
 | 
			
		||||
RUN chmod +x /app/server-start.sh
 | 
			
		||||
RUN chown palmr:nodejs /app/server-start.sh
 | 
			
		||||
 | 
			
		||||
# Copy supervisor configuration
 | 
			
		||||
COPY <<EOF /etc/supervisor/conf.d/supervisord.conf
 | 
			
		||||
[supervisord]
 | 
			
		||||
nodaemon=true
 | 
			
		||||
user=root
 | 
			
		||||
logfile=/var/log/supervisor/supervisord.log
 | 
			
		||||
pidfile=/var/run/supervisord.pid
 | 
			
		||||
 | 
			
		||||
[program:server]
 | 
			
		||||
command=/app/server-start.sh
 | 
			
		||||
directory=/app/server
 | 
			
		||||
user=palmr
 | 
			
		||||
autostart=true
 | 
			
		||||
autorestart=true
 | 
			
		||||
stderr_logfile=/var/log/supervisor/server.err.log
 | 
			
		||||
stdout_logfile=/var/log/supervisor/server.out.log
 | 
			
		||||
environment=PORT=3333,HOME="/home/palmr"
 | 
			
		||||
 | 
			
		||||
[program:web]
 | 
			
		||||
command=node server.js
 | 
			
		||||
directory=/app/web
 | 
			
		||||
user=palmr
 | 
			
		||||
autostart=true
 | 
			
		||||
autorestart=true
 | 
			
		||||
stderr_logfile=/var/log/supervisor/web.err.log
 | 
			
		||||
stdout_logfile=/var/log/supervisor/web.out.log
 | 
			
		||||
environment=PORT=5487,HOSTNAME="0.0.0.0",HOME="/home/palmr"
 | 
			
		||||
EOF
 | 
			
		||||
 | 
			
		||||
# Create main startup script
 | 
			
		||||
COPY <<EOF /app/start.sh
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
echo "Starting Palmr Application..."
 | 
			
		||||
 | 
			
		||||
# Start supervisor
 | 
			
		||||
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
 | 
			
		||||
EOF
 | 
			
		||||
 | 
			
		||||
RUN chmod +x /app/start.sh
 | 
			
		||||
 | 
			
		||||
# Expose ports
 | 
			
		||||
EXPOSE 3333 5487
 | 
			
		||||
 | 
			
		||||
# Health check
 | 
			
		||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
 | 
			
		||||
  CMD curl -f http://localhost:5487 || exit 1
 | 
			
		||||
 | 
			
		||||
# Start application
 | 
			
		||||
CMD ["/app/start.sh"] 
 | 
			
		||||
							
								
								
									
										47
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
.PHONY: help build start clean logs stop restart
 | 
			
		||||
 | 
			
		||||
# Default target
 | 
			
		||||
help:
 | 
			
		||||
	@echo "🚀 Palmr - Available Commands:"
 | 
			
		||||
	@echo ""
 | 
			
		||||
	@echo "  make build     - Build Docker image with multi-platform support"
 | 
			
		||||
	@echo "  make start     - Start the application using docker-compose"
 | 
			
		||||
	@echo "  make stop      - Stop all running containers"
 | 
			
		||||
	@echo "  make logs      - Show application logs"
 | 
			
		||||
	@echo "  make clean     - Clean up containers and images"
 | 
			
		||||
	@echo "  make shell     - Access the application container shell"
 | 
			
		||||
	@echo ""
 | 
			
		||||
	@echo "📁 Scripts location: ./infra/"
 | 
			
		||||
 | 
			
		||||
# Build Docker image using the build script
 | 
			
		||||
build:
 | 
			
		||||
	@echo "🏗️  Building Palmr Docker image..."
 | 
			
		||||
	@chmod +x ./infra/build-docker.sh
 | 
			
		||||
	@./infra/build-docker.sh
 | 
			
		||||
 | 
			
		||||
# Start the application
 | 
			
		||||
start:
 | 
			
		||||
	@echo "🚀 Starting Palmr application..."
 | 
			
		||||
	@docker-compose up -d
 | 
			
		||||
 | 
			
		||||
# Stop the application
 | 
			
		||||
stop:
 | 
			
		||||
	@echo "🛑 Stopping Palmr application..."
 | 
			
		||||
	@docker-compose down
 | 
			
		||||
 | 
			
		||||
# Show logs
 | 
			
		||||
logs:
 | 
			
		||||
	@echo "📋 Showing Palmr logs..."
 | 
			
		||||
	@docker-compose logs -f
 | 
			
		||||
 | 
			
		||||
# Clean up containers and images
 | 
			
		||||
clean:
 | 
			
		||||
	@echo "🧹 Cleaning up Docker containers and images..."
 | 
			
		||||
	@docker-compose down -v
 | 
			
		||||
	@docker system prune -f
 | 
			
		||||
	@echo "✅ Cleanup completed!"
 | 
			
		||||
 | 
			
		||||
# Access container shell
 | 
			
		||||
shell:
 | 
			
		||||
	@echo "🐚 Accessing Palmr container shell..."
 | 
			
		||||
	@docker-compose exec palmr /bin/sh
 | 
			
		||||
@@ -29,18 +29,19 @@ Below is the complete content of our `docker-compose.yaml` that can be copied di
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
services:
 | 
			
		||||
  palmr-api:
 | 
			
		||||
    image: kyantech/palmr-api:latest # Make sure to use the correct version (latest) of the image
 | 
			
		||||
    container_name: palmr-api
 | 
			
		||||
  palmr:
 | 
			
		||||
    image: kyantech/palmr:latest # Make sure to use the correct version (latest) of the image
 | 
			
		||||
    container_name: palmr
 | 
			
		||||
    depends_on:
 | 
			
		||||
      postgres:
 | 
			
		||||
        condition: "service_healthy"
 | 
			
		||||
      minio:
 | 
			
		||||
        condition: "service_healthy"
 | 
			
		||||
    environment:
 | 
			
		||||
      # Server environment variables
 | 
			
		||||
      - PORT=${API_INTERNAL_PORT:-3333} # Port for the backend service
 | 
			
		||||
      - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD:-postgresRootPassword}@postgres:5432/palmr_db?schema=public # Database URL with configurable password through POSTGRES_PASSWORD env var
 | 
			
		||||
      - MINIO_ENDPOINT=minio # This can change if your MinIO is at a different address
 | 
			
		||||
      - DATABASE_URL=postgresql://postgres:${POSTGRESQL_PASSWORD:-postgresRootPassword}@postgres:5432/palmr_db?schema=public # Database URL with configurable password through POSTGRESQL_PASSWORD env var
 | 
			
		||||
      - MINIO_ENDPOINT=${MINIO_ENDPOINT:-minio} # This can change if your MinIO is at a different address
 | 
			
		||||
      - MINIO_PORT=${MINIO_INTERNAL_API_PORT:-6421} # Default MinIO port (Change if yours is not the default)
 | 
			
		||||
      - MINIO_USE_SSL=false # MinIO uses SSL by default, but you can change it to true if needed
 | 
			
		||||
      - MINIO_ROOT_USER=${MINIO_ROOT_USER:-minio_root_user} # MinIO credentials can be configured through MINIO_ROOT_USER env vars
 | 
			
		||||
@@ -50,34 +51,21 @@ services:
 | 
			
		||||
      - FRONTEND_URL=${APP_URL:-http://${SERVER_IP:-localhost}:${APP_EXTERNAL_PORT:-5487}} # Frontend URL - Make sure to use the correct frontend URL, depends on where the frontend is running, its prepared for localhost, but you can change it to your frontend URL if needed
 | 
			
		||||
      - SERVER_IP=${SERVER_IP:-localhost} # Server IP - Make sure to use the correct server IP if you running on a cloud provider or a virtual machine. This prepared for localhost, but you can change it to your server IP if needed
 | 
			
		||||
      - MAX_FILESIZE=${MAX_FILESIZE:-1073741824} # Max Filesize for upload - Declared in Bytes. Default is 1GiB
 | 
			
		||||
    ports:
 | 
			
		||||
      - "${API_EXTERNAL_PORT:-3333}:${API_INTERNAL_PORT:-3333}" # Backend port mapping 
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    healthcheck:
 | 
			
		||||
      test: ["CMD", "curl", "-f", "http://localhost:${API_INTERNAL_PORT:-3333}/health"]
 | 
			
		||||
      interval: 10s
 | 
			
		||||
      timeout: 5s
 | 
			
		||||
      retries: 5
 | 
			
		||||
      start_period: 30s
 | 
			
		||||
 | 
			
		||||
  palmr-app:
 | 
			
		||||
    image: kyantech/palmr-app:latest # Make sure to use the correct version (latest) of the image
 | 
			
		||||
    container_name: palmr-web
 | 
			
		||||
    depends_on:
 | 
			
		||||
      palmr-api:
 | 
			
		||||
        condition: "service_healthy"
 | 
			
		||||
    ports:
 | 
			
		||||
      - "${APP_EXTERNAL_PORT:-5487}:5487" # Frontend port mapping
 | 
			
		||||
    environment:
 | 
			
		||||
      
 | 
			
		||||
      # Web environment variables
 | 
			
		||||
      - NODE_ENV=production
 | 
			
		||||
      - NEXT_TELEMETRY_DISABLED=1
 | 
			
		||||
      - API_BASE_URL=http://palmr-api:${API_INTERNAL_PORT:-3333} # Here we use docker's internal network to reference the backend service (can be changed if needed)
 | 
			
		||||
      - API_BASE_URL=http://palmr:${API_INTERNAL_PORT:-3333} # Using Docker service name for internal communication
 | 
			
		||||
    ports:
 | 
			
		||||
      - "${API_EXTERNAL_PORT:-3333}:3333"  # Server port
 | 
			
		||||
      - "${APP_EXTERNAL_PORT:-5487}:5487"  # Web port
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    healthcheck:
 | 
			
		||||
      test: ["CMD", "curl", "-f", "http://localhost:5487"]
 | 
			
		||||
      test: ["CMD", "wget", "--no-verbose", "http://palmr:${API_INTERNAL_PORT:-3333}/health", "&&", "wget", "--no-verbose", "http://palmr:${APP_INTERNAL_PORT:-5487}"]
 | 
			
		||||
      interval: 30s
 | 
			
		||||
      timeout: 10s
 | 
			
		||||
      retries: 3
 | 
			
		||||
      start_period: 60s
 | 
			
		||||
 | 
			
		||||
  minio:
 | 
			
		||||
    image: minio/minio:RELEASE.2025-03-12T18-04-18Z # Use only version RELEASE.2025-03-12T18-04-18Z to avoid compatibility issues with the backend
 | 
			
		||||
@@ -121,10 +109,10 @@ services:
 | 
			
		||||
    container_name: palmr-postgres
 | 
			
		||||
    environment:
 | 
			
		||||
      # PostgreSQL credentials configurable through environment variables
 | 
			
		||||
      # POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB can be set to override defaults
 | 
			
		||||
      - POSTGRESQL_USERNAME=${POSTGRES_USER:-postgres}
 | 
			
		||||
      - POSTGRESQL_PASSWORD=${POSTGRES_PASSWORD:-postgresRootPassword}
 | 
			
		||||
      - POSTGRESQL_DATABASE=${POSTGRES_DB:-palmr_db}
 | 
			
		||||
      # POSTGRESQL_USERNAME, POSTGRESQL_PASSWORD, and POSTGRES_DB can be set to override defaults
 | 
			
		||||
      - POSTGRESQL_USERNAME=${POSTGRESQL_USERNAME:-postgres}
 | 
			
		||||
      - POSTGRESQL_PASSWORD=${POSTGRESQL_PASSWORD:-postgresRootPassword}
 | 
			
		||||
      - POSTGRESQL_DATABASE=${POSTGRES_DATABASE:-palmr_db}
 | 
			
		||||
    volumes:
 | 
			
		||||
      - postgres_data:/bitnami/postgresql
 | 
			
		||||
    ports:
 | 
			
		||||
@@ -138,7 +126,7 @@ services:
 | 
			
		||||
 | 
			
		||||
volumes:
 | 
			
		||||
  minio_data:
 | 
			
		||||
  postgres_data:
 | 
			
		||||
  postgres_data: 
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@@ -148,12 +136,11 @@ Notice that the `docker-compose.yaml` has several comments that help you configu
 | 
			
		||||
 | 
			
		||||
### ⚙️ Services Overview
 | 
			
		||||
 | 
			
		||||
Palmr. consists of five main services, each with specific responsibilities. Below, we present a detailed view of each component:
 | 
			
		||||
Palmr. consists of four main services, each with specific responsibilities. Below, we present a detailed view of each component:
 | 
			
		||||
 | 
			
		||||
| **Service** | **Image** | **Exposed Ports** | **Main Features** |
 | 
			
		||||
| --- | --- | --- | --- |
 | 
			
		||||
| palmr-api (Backend) | [kyantech/palmr-api:latest](https://hub.docker.com/repository/docker/kyantech/palmr-api/tags/latest/sha256-84245d3d0012aa65c588caba56322363729c4b68c3722a08dcda912904de9d1d) | **3333** | • Main API service<br/>• Depends on services: postgres and minio<br/>• Has healthcheck to ensure availability |
 | 
			
		||||
| palmr-app (Frontend) | [kyantech/palmr-app:latest](https://hub.docker.com/repository/docker/kyantech/palmr-app/tags/latest/sha256-33f568673ae8cc8529532146b6afc1acafa203387ced6c7bb3451a7ab4198a2f) | **5487** | • Web application interface<br/>• Depends on palmr-api service<br/>• Configured for production environment |
 | 
			
		||||
| palmr | [kyantech/palmr:latest](https://hub.docker.com/repository/docker/kyantech/palmr/general) | **3333** (API)<br/>**5487** (Web) | • Combined backend API and frontend service<br/>• Depends on services: postgres and minio<br/>• Has healthcheck to ensure availability |
 | 
			
		||||
| minio (Storage) | [minio/minio:RELEASE.2025-03-12T18-04-18Z](https://hub.docker.com/layers/minio/minio/RELEASE.2025-03-12T18-04-18Z/images/sha256-85f3e4cd1ca92a2711553ab79f222bcd8b75aa2c77a1a0b0ccf80d38e8ab2fe5) | **6421**(API)<br/>**6422**(Console) | • File storage service<br/>• Persistent volume for data |
 | 
			
		||||
| minio-init (Config) | [minio/mc:RELEASE.2025-03-12T17-29-24Z](https://hub.docker.com/layers/minio/mc/RELEASE.2025-03-12T17-29-24Z/images/sha256-68d8c80f43908b02daa285e55547131870a1d36b3ffe272c26d7d8f4d52d1e5c) | N/A | • Initially configures the "files" bucket<br/>• Runs only once during initialization |
 | 
			
		||||
| postgres (Database) | [bitnami/postgresql:17.2.0](https://hub.docker.com/layers/bitnami/postgresql/17.2.0/images/sha256-29c614afad4f514b12b5c0f4d006f38c98fa4b18c3582732ff93b3fe9a79d875) | **5432** | • PostgreSQL database<br/>• Persistent volume for data |
 | 
			
		||||
@@ -197,13 +184,17 @@ The table below shows all environment variables that can be set
 | 
			
		||||
 | 
			
		||||
In a localhost environment, there's no mystery. If you don't want to change any service exposure ports, you can simply run:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
docker compose pull && docker compose up -d
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This will execute all necessary services and give you access to the following URLs (if you haven't changed any ports):
 | 
			
		||||
 | 
			
		||||
- **Frontend:** [http://localhost:5487](http://localhost:5487)
 | 
			
		||||
- **Backend:** [http://localhost:3333](http://localhost:3333/)
 | 
			
		||||
- **MinIO API:** [http://localhost:6421](http://localhost:6421)
 | 
			
		||||
- **MinIO Console:** [http://localhost:6422](http://localhost:6422)
 | 
			
		||||
- **Postgres Database:** [http://localhost:5423](http://localhost:5423/) (Connection only)
 | 
			
		||||
- **Postgres Database:** [http://localhost:5432](http://localhost:5432/) (Connection only)
 | 
			
		||||
 | 
			
		||||
> *If you have changed any port, simply access the URL with the port you configured.*
 | 
			
		||||
> 
 | 
			
		||||
@@ -247,7 +238,7 @@ If you haven't changed the execution ports, you'll have access on your server at
 | 
			
		||||
- **Backend:** `[server_ip]:3333`
 | 
			
		||||
- **MinIO API:** `[server_ip]:6421`
 | 
			
		||||
- **MinIO Console:** `[server_ip]:6422`
 | 
			
		||||
- **Postgres Database:** `[server_ip]:5423` (Connection only)
 | 
			
		||||
- **Postgres Database:** `[server_ip]:5432` (Connection only)
 | 
			
		||||
 | 
			
		||||
> *If you've changed any port, simply access the URL with the port you configured.*
 | 
			
		||||
> 
 | 
			
		||||
 
 | 
			
		||||
@@ -25,4 +25,4 @@
 | 
			
		||||
    "gh-sponsor",
 | 
			
		||||
    "..."
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -83,7 +83,12 @@ export default function HomePage() {
 | 
			
		||||
function Hero() {
 | 
			
		||||
  return (
 | 
			
		||||
    <section className="relative z-[2] flex flex-col border-x border-t  px-6 pt-12 pb-10 md:px-12 md:pt-16 max-md:text-center">
 | 
			
		||||
      <h1 className="mb-8 text-5xl font-bold">🌴 Palmr. <span className="text-[10px] font-light text-muted-foreground/50 font-mono">v2.0.0-beta</span></h1>
 | 
			
		||||
      <h1 className="mb-8 text-5xl font-bold">
 | 
			
		||||
        🌴 Palmr.{" "}
 | 
			
		||||
        <span className="text-[10px] font-light text-muted-foreground/50 font-mono">
 | 
			
		||||
          v2.0.0-beta
 | 
			
		||||
        </span>
 | 
			
		||||
      </h1>
 | 
			
		||||
      <h1 className="hidden text-4xl font-medium max-w-[600px] md:block mb-4">
 | 
			
		||||
        Modern & efficient file sharing
 | 
			
		||||
      </h1>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import { source } from '@/lib/source';
 | 
			
		||||
import { createFromSource } from 'fumadocs-core/search/server';
 | 
			
		||||
 
 | 
			
		||||
import { source } from "@/lib/source";
 | 
			
		||||
import { createFromSource } from "fumadocs-core/search/server";
 | 
			
		||||
 | 
			
		||||
export const { GET } = createFromSource(source, (page) => {
 | 
			
		||||
  return {
 | 
			
		||||
    title: page.data.title,
 | 
			
		||||
@@ -8,6 +8,8 @@ export const { GET } = createFromSource(source, (page) => {
 | 
			
		||||
    url: page.url,
 | 
			
		||||
    id: page.url,
 | 
			
		||||
    structuredData: page.data.structuredData,
 | 
			
		||||
    tag: page.url.startsWith('/docs/2.0.0-beta') ? 'v2.0.0-beta' : 'v1.1.7-beta'
 | 
			
		||||
    tag: page.url.startsWith("/docs/2.0.0-beta")
 | 
			
		||||
      ? "v2.0.0-beta"
 | 
			
		||||
      : "v1.1.7-beta",
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -11,14 +11,15 @@ const inter = Inter({
 | 
			
		||||
 | 
			
		||||
export const metadata = {
 | 
			
		||||
  title: "🌴 Palmr. | Official Website",
 | 
			
		||||
  description: "Palmr. is a fast, simple and powerful document sharing platform.",
 | 
			
		||||
  description:
 | 
			
		||||
    "Palmr. is a fast, simple and powerful document sharing platform.",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function Layout({ children }: { children: ReactNode }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <html lang="en" className={inter.className} suppressHydrationWarning>
 | 
			
		||||
      <body className="flex flex-col min-h-screen">
 | 
			
		||||
        <Banner variant="rainbow" id="banner-v-2">
 | 
			
		||||
        <Banner variant="rainbow" id="banner-v-3">
 | 
			
		||||
          <Link href="/docs/2.0.0-beta">Palmr. v2.0.0-beta has released!</Link>
 | 
			
		||||
        </Banner>
 | 
			
		||||
        <RootProvider
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,10 @@
 | 
			
		||||
    "lint:fix": "eslint \"src/**/*.{js,jsx,ts,tsx}\" --fix",
 | 
			
		||||
    "format": "prettier . --write",
 | 
			
		||||
    "format:check": "prettier . --check",
 | 
			
		||||
    "db:seed": "ts-node prisma/seed.ts"
 | 
			
		||||
    "db:seed": "ts-node prisma/seed.js"
 | 
			
		||||
  },
 | 
			
		||||
  "prisma": {
 | 
			
		||||
    "seed": "node prisma/seed.js"
 | 
			
		||||
  },
 | 
			
		||||
  "keywords": [],
 | 
			
		||||
  "author": "",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
import { prisma } from "../src/shared/prisma";
 | 
			
		||||
import crypto from "node:crypto";
 | 
			
		||||
import { env } from '../src/env';
 | 
			
		||||
/* eslint-disable no-undef */
 | 
			
		||||
const { PrismaClient } = require('@prisma/client');
 | 
			
		||||
const crypto = require('crypto');
 | 
			
		||||
 | 
			
		||||
const prisma = new PrismaClient();
 | 
			
		||||
 | 
			
		||||
const defaultConfigs = [
 | 
			
		||||
  // General Configurations
 | 
			
		||||
@@ -37,7 +39,7 @@ const defaultConfigs = [
 | 
			
		||||
  // Storage Configurations
 | 
			
		||||
  {
 | 
			
		||||
    key: "maxFileSize",
 | 
			
		||||
    value: env.MAX_FILESIZE, // default 1GiB in bytes - 1073741824
 | 
			
		||||
    value: process.env.MAX_FILESIZE || "1073741824", // default 1GiB in bytes
 | 
			
		||||
    type: "bigint",
 | 
			
		||||
    group: "storage",
 | 
			
		||||
  },
 | 
			
		||||
@@ -124,28 +126,37 @@ const defaultConfigs = [
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
async function main() {
 | 
			
		||||
  console.log("Seeding app configurations...");
 | 
			
		||||
  console.log("🌱 Starting app configurations seed...");
 | 
			
		||||
  console.log("🛡️  Protected mode: Only creates missing configurations");
 | 
			
		||||
 | 
			
		||||
  let createdCount = 0;
 | 
			
		||||
  let skippedCount = 0;
 | 
			
		||||
 | 
			
		||||
  for (const config of defaultConfigs) {
 | 
			
		||||
    if (config.key === "jwtSecret") {
 | 
			
		||||
      const existingSecret = await prisma.appConfig.findUnique({
 | 
			
		||||
        where: { key: "jwtSecret" },
 | 
			
		||||
      });
 | 
			
		||||
    // Check if configuration already exists
 | 
			
		||||
    const existingConfig = await prisma.appConfig.findUnique({
 | 
			
		||||
      where: { key: config.key },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
      if (existingSecret) {
 | 
			
		||||
        console.log("JWT secret already exists, skipping...");
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
    if (existingConfig) {
 | 
			
		||||
      console.log(`⏭️  Configuration '${config.key}' already exists, skipping...`);
 | 
			
		||||
      skippedCount++;
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await prisma.appConfig.upsert({
 | 
			
		||||
      where: { key: config.key },
 | 
			
		||||
      update: config,
 | 
			
		||||
      create: config,
 | 
			
		||||
    // Only create if it doesn't exist
 | 
			
		||||
    await prisma.appConfig.create({
 | 
			
		||||
      data: config,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    console.log(`✅ Created configuration: ${config.key}`);
 | 
			
		||||
    createdCount++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  console.log("App configurations seeded successfully!");
 | 
			
		||||
  console.log("\n📊 Seed Summary:");
 | 
			
		||||
  console.log(`   ✅ Created: ${createdCount} configurations`);
 | 
			
		||||
  console.log(`   ⏭️  Skipped: ${skippedCount} configurations`);
 | 
			
		||||
  console.log("🎉 App configurations seeded successfully!");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
main()
 | 
			
		||||
@@ -155,4 +166,4 @@ main()
 | 
			
		||||
  })
 | 
			
		||||
  .finally(async () => {
 | 
			
		||||
    await prisma.$disconnect();
 | 
			
		||||
  });
 | 
			
		||||
  }); 
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
import { PrismaClient } from '@prisma/client';
 | 
			
		||||
 | 
			
		||||
const prisma = new PrismaClient();
 | 
			
		||||
 | 
			
		||||
async function main() {
 | 
			
		||||
  const count = await prisma.user.count();
 | 
			
		||||
  console.log(count);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
main()
 | 
			
		||||
  .catch((e) => {
 | 
			
		||||
    console.error(e);
 | 
			
		||||
    process.exit(1);
 | 
			
		||||
  })
 | 
			
		||||
  .finally(async () => {
 | 
			
		||||
    await prisma.$disconnect();
 | 
			
		||||
  });
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
echo "Waiting for PostgreSQL..."
 | 
			
		||||
while ! nc -z postgres 5432; do
 | 
			
		||||
  sleep 1
 | 
			
		||||
done
 | 
			
		||||
echo "PostgreSQL is up!"
 | 
			
		||||
 | 
			
		||||
echo "Generating Prisma client..."
 | 
			
		||||
npx prisma generate --schema=./prisma/schema.prisma
 | 
			
		||||
 | 
			
		||||
# Check if database needs migrations
 | 
			
		||||
echo "Checking migrations..."
 | 
			
		||||
npx prisma migrate deploy
 | 
			
		||||
 | 
			
		||||
# Check if database is empty using Prisma
 | 
			
		||||
USER_COUNT=$(node ./scripts/check-db.mjs)
 | 
			
		||||
 | 
			
		||||
if [ "$USER_COUNT" -eq "0" ]; then
 | 
			
		||||
    echo "Database is empty, running seeds..."
 | 
			
		||||
    pnpm db:seed
 | 
			
		||||
else
 | 
			
		||||
    echo "Database already has data, skipping seeds..."
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
echo "Starting application..."
 | 
			
		||||
pnpm start
 | 
			
		||||
@@ -1,16 +1,17 @@
 | 
			
		||||
services:
 | 
			
		||||
  palmr-api:
 | 
			
		||||
    image: kyantech/palmr-api:latest # Make sure to use the correct version (latest) of the image
 | 
			
		||||
    container_name: palmr-api
 | 
			
		||||
  palmr:
 | 
			
		||||
    image: kyantech/palmr:latest # Make sure to use the correct version (latest) of the image
 | 
			
		||||
    container_name: palmr
 | 
			
		||||
    depends_on:
 | 
			
		||||
      postgres:
 | 
			
		||||
        condition: "service_healthy"
 | 
			
		||||
      minio:
 | 
			
		||||
        condition: "service_healthy"
 | 
			
		||||
    environment:
 | 
			
		||||
      # Server environment variables
 | 
			
		||||
      - PORT=${API_INTERNAL_PORT:-3333} # Port for the backend service
 | 
			
		||||
      - DATABASE_URL=postgresql://postgres:${POSTGRESQL_PASSWORD:-postgresRootPassword}@postgres:5432/palmr_db?schema=public # Database URL with configurable password through POSTGRESQL_PASSWORD env var
 | 
			
		||||
      - MINIO_ENDPOINT=minio # This can change if your MinIO is at a different address
 | 
			
		||||
      - MINIO_ENDPOINT=${MINIO_ENDPOINT:-minio} # This can change if your MinIO is at a different address
 | 
			
		||||
      - MINIO_PORT=${MINIO_INTERNAL_API_PORT:-6421} # Default MinIO port (Change if yours is not the default)
 | 
			
		||||
      - MINIO_USE_SSL=false # MinIO uses SSL by default, but you can change it to true if needed
 | 
			
		||||
      - MINIO_ROOT_USER=${MINIO_ROOT_USER:-minio_root_user} # MinIO credentials can be configured through MINIO_ROOT_USER env vars
 | 
			
		||||
@@ -20,34 +21,21 @@ services:
 | 
			
		||||
      - FRONTEND_URL=${APP_URL:-http://${SERVER_IP:-localhost}:${APP_EXTERNAL_PORT:-5487}} # Frontend URL - Make sure to use the correct frontend URL, depends on where the frontend is running, its prepared for localhost, but you can change it to your frontend URL if needed
 | 
			
		||||
      - SERVER_IP=${SERVER_IP:-localhost} # Server IP - Make sure to use the correct server IP if you running on a cloud provider or a virtual machine. This prepared for localhost, but you can change it to your server IP if needed
 | 
			
		||||
      - MAX_FILESIZE=${MAX_FILESIZE:-1073741824} # Max Filesize for upload - Declared in Bytes. Default is 1GiB
 | 
			
		||||
    ports:
 | 
			
		||||
      - "${API_EXTERNAL_PORT:-3333}:${API_INTERNAL_PORT:-3333}" # Backend port mapping 
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    healthcheck:
 | 
			
		||||
      test: ["CMD", "curl", "-f", "http://localhost:${API_INTERNAL_PORT:-3333}/health"]
 | 
			
		||||
      interval: 10s
 | 
			
		||||
      timeout: 5s
 | 
			
		||||
      retries: 5
 | 
			
		||||
      start_period: 30s
 | 
			
		||||
 | 
			
		||||
  palmr-app:
 | 
			
		||||
    image: kyantech/palmr-app:latest # Make sure to use the correct version (latest) of the image
 | 
			
		||||
    container_name: palmr-web
 | 
			
		||||
    depends_on:
 | 
			
		||||
      palmr-api:
 | 
			
		||||
        condition: "service_healthy"
 | 
			
		||||
    ports:
 | 
			
		||||
      - "${APP_EXTERNAL_PORT:-5487}:5487" # Frontend port mapping
 | 
			
		||||
    environment:
 | 
			
		||||
      
 | 
			
		||||
      # Web environment variables
 | 
			
		||||
      - NODE_ENV=production
 | 
			
		||||
      - NEXT_TELEMETRY_DISABLED=1
 | 
			
		||||
      - API_BASE_URL=http://palmr-api:${API_INTERNAL_PORT:-3333} # Here we use docker's internal network to reference the backend service (can be changed if needed)
 | 
			
		||||
      - API_BASE_URL=http://palmr:${API_INTERNAL_PORT:-3333} # Using Docker service name for internal communication
 | 
			
		||||
    ports:
 | 
			
		||||
      - "${API_EXTERNAL_PORT:-3333}:3333"  # Server port
 | 
			
		||||
      - "${APP_EXTERNAL_PORT:-5487}:5487"  # Web port
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    healthcheck:
 | 
			
		||||
      test: ["CMD", "curl", "-f", "http://localhost:5487"]
 | 
			
		||||
      test: ["CMD", "wget", "--no-verbose", "http://palmr:${API_INTERNAL_PORT:-3333}/health", "&&", "wget", "--no-verbose", "http://palmr:${APP_INTERNAL_PORT:-5487}"]
 | 
			
		||||
      interval: 30s
 | 
			
		||||
      timeout: 10s
 | 
			
		||||
      retries: 3
 | 
			
		||||
      start_period: 60s
 | 
			
		||||
 | 
			
		||||
  minio:
 | 
			
		||||
    image: minio/minio:RELEASE.2025-03-12T18-04-18Z # Use only version RELEASE.2025-03-12T18-04-18Z to avoid compatibility issues with the backend
 | 
			
		||||
@@ -108,4 +96,4 @@ services:
 | 
			
		||||
 | 
			
		||||
volumes:
 | 
			
		||||
  minio_data:
 | 
			
		||||
  postgres_data:
 | 
			
		||||
  postgres_data: 
 | 
			
		||||
							
								
								
									
										33
									
								
								infra/SCRIPTS.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								infra/SCRIPTS.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
## 🚀 Quick Start
 | 
			
		||||
 | 
			
		||||
Palmr. includes a convenient Makefile to simplify development and deployment tasks:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# Show all available commands
 | 
			
		||||
make help
 | 
			
		||||
 | 
			
		||||
# Build Docker image with multi-platform support
 | 
			
		||||
make build
 | 
			
		||||
 | 
			
		||||
# Start the application
 | 
			
		||||
make start
 | 
			
		||||
 | 
			
		||||
# View application logs
 | 
			
		||||
make logs
 | 
			
		||||
 | 
			
		||||
# Stop the application
 | 
			
		||||
make stop
 | 
			
		||||
 | 
			
		||||
# Clean up containers and images
 | 
			
		||||
make clean
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Available Commands:
 | 
			
		||||
- `make build` - Build Docker image using the build script in `./infra/`
 | 
			
		||||
- `make start` - Start the application using docker-compose
 | 
			
		||||
- `make stop` - Stop all running containers
 | 
			
		||||
- `make logs` - Show application logs
 | 
			
		||||
- `make clean` - Clean up containers and images
 | 
			
		||||
- `make shell` - Access the application container shell
 | 
			
		||||
 | 
			
		||||
All infrastructure scripts are organized in the `./infra/` directory for better project organization.
 | 
			
		||||
							
								
								
									
										43
									
								
								infra/build-docker.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										43
									
								
								infra/build-docker.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
# Ask for tag interactively
 | 
			
		||||
echo "🏷️  Please enter a tag for the build (e.g., v1.0.0, production, beta):"
 | 
			
		||||
read -p "Tag: " TAG
 | 
			
		||||
 | 
			
		||||
# Check if tag was provided
 | 
			
		||||
if [ -z "$TAG" ]; then
 | 
			
		||||
    echo "❌ Error: Tag cannot be empty"
 | 
			
		||||
    echo "Please run the script again and provide a valid tag"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
echo "🚀 Building Palmr Unified Image for AMD64 and ARM..."
 | 
			
		||||
echo "📦 Building tags: latest and $TAG"
 | 
			
		||||
 | 
			
		||||
# Ensure buildx is available and create/use a builder instance
 | 
			
		||||
docker buildx create --name palmr-builder --use 2>/dev/null || docker buildx use palmr-builder
 | 
			
		||||
 | 
			
		||||
# Build the unified image for multiple platforms without cache
 | 
			
		||||
docker buildx build \
 | 
			
		||||
    --platform linux/amd64,linux/arm64 \
 | 
			
		||||
    --no-cache \
 | 
			
		||||
    -t kyantech/palmr:latest \
 | 
			
		||||
    -t kyantech/palmr:$TAG \
 | 
			
		||||
    --load \
 | 
			
		||||
    .
 | 
			
		||||
 | 
			
		||||
if [ $? -eq 0 ]; then
 | 
			
		||||
    echo "✅ Multi-platform build completed successfully!"
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "Built for platforms: linux/amd64, linux/arm64"
 | 
			
		||||
    echo "Built tags: palmr:latest and palmr:$TAG"
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "Access points:"
 | 
			
		||||
    echo "- API: http://localhost:3333"
 | 
			
		||||
    echo "- Web App: http://localhost:5487"
 | 
			
		||||
    echo ""
 | 
			
		||||
    echo "Read the docs for more information"
 | 
			
		||||
else
 | 
			
		||||
    echo "❌ Build failed!"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi 
 | 
			
		||||
							
								
								
									
										29
									
								
								infra/server-start.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								infra/server-start.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
echo "Starting Palmr Server..."
 | 
			
		||||
 | 
			
		||||
# Set proper environment
 | 
			
		||||
export HOME=/home/palmr
 | 
			
		||||
export NPM_CONFIG_CACHE=/home/palmr/.npm
 | 
			
		||||
export PNPM_HOME=/home/palmr/.pnpm
 | 
			
		||||
 | 
			
		||||
# Wait for PostgreSQL
 | 
			
		||||
echo "Waiting for PostgreSQL..."
 | 
			
		||||
while ! nc -z postgres 5432; do
 | 
			
		||||
  sleep 1
 | 
			
		||||
done
 | 
			
		||||
echo "PostgreSQL is up!"
 | 
			
		||||
 | 
			
		||||
cd /app/server
 | 
			
		||||
 | 
			
		||||
echo "Generating Prisma client..."
 | 
			
		||||
npx prisma generate --schema=./prisma/schema.prisma
 | 
			
		||||
 | 
			
		||||
echo "Running migrations..."
 | 
			
		||||
npx prisma migrate deploy
 | 
			
		||||
 | 
			
		||||
echo "Running database seeds..."
 | 
			
		||||
node prisma/seed.js || echo "Seeds failed or already exist, continuing..."
 | 
			
		||||
 | 
			
		||||
echo "Starting server application..."
 | 
			
		||||
exec node dist/server.js 
 | 
			
		||||
		Reference in New Issue
	
	Block a user