diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..57f6a75 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = true diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..22fc107 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,19 @@ +name: Deploy to Production + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: self-hosted + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: Build and deploy with Docker Compose + run: | + # Clean up previous containers if needed + docker compose down --remove-orphans || true + docker compose up -d --build diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..72070d5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +# Stage 1: Build the application +FROM node:lts-alpine as build-stage + +# Set working directory +WORKDIR /app + +# Copy package files first to leverage Docker cache +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the rest of the application code +COPY . . + +# Build the application +RUN npm run build + +# Stage 2: Serve the application with Nginx +FROM nginx:stable-alpine as production-stage + +# Copy the built artifacts from the build stage +COPY --from=build-stage /app/dist /usr/share/nginx/html + +# Copy custom Nginx configuration +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Expose port 80 +EXPOSE 80 + +# Healthcheck to ensure Nginx is running and serving +HEALTHCHECK --interval=30s --timeout=3s \ + CMD wget --quiet --tries=1 --spider http://localhost/health || exit 1 + +# Start Nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..23d0069 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +name: tools-app + +services: + tools-app: + container_name: tools-app + build: + context: . + dockerfile: Dockerfile + ports: + - "8091:80" + expose: + - "80" + restart: unless-stopped + # volumes: + # - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + networks: + - npm_public + +networks: + npm_public: + external: true diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..f207243 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,45 @@ +server { + listen 80; + listen [::]:80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 10240; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript; + gzip_disable "MSIE [1-6]\."; + + # Security Headers + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options "nosniff"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + # CSP: Adjust as needed. This is a strict starting point. + # Allowing unsafe-inline for styles is often necessary for Vue apps unless using nonces. + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;"; + + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, no-transform"; + } + + # Health check + location /health { + access_log off; + return 200 "healthy\n"; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +}