Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
H
hrsystem
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
hrsystem
Commits
698489ca
Commit
698489ca
authored
Apr 05, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 3 files via Son of Anton
parent
f28d9aac
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
881 additions
and
100 deletions
+881
-100
Dockerfile.frontend
Dockerfile.frontend
+32
-49
deploy-thegrind.sh
deploy-thegrind.sh
+841
-0
next.config.js
frontend/next.config.js
+8
-51
No files found.
Dockerfile.frontend
View file @
698489ca
###############################################
# Stage 1: Dependencies
###############################################
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
# ═══════════════════════════════════════════════════════
# THE GRIND — Frontend Dockerfile (CapRover-ready)
# ═══════════════════════════════════════════════════════
# ── Stage 1: Dependencies ──
FROM node:20-alpine AS deps
WORKDIR /build
#
── shared package ──────────────────────────
#
Shared package
COPY shared/package.json ./shared/
COPY shared/tsconfig.json ./shared/
COPY shared/src/ ./shared/src/
#
── frontend dependencies ───────────────────
COPY frontend/package
*
.json ./frontend/
#
Frontend package
COPY frontend/package.json ./frontend/
WORKDIR /build/frontend
RUN npm install --legacy-peer-deps
# Install deps
RUN cd shared && npm install --ignore-scripts
RUN cd frontend && npm install --ignore-scripts
###############################################
# Stage 2: Build
###############################################
# ── Stage 2: Build ──
FROM node:20-alpine AS builder
RUN apk add --no-cache libc6-compat
WORKDIR /build
# Copy deps
# API URLs — set at build time via ARG, baked into the Next.js bundle
ARG NEXT_PUBLIC_API_URL=https://thegrind-api.caprover.al-arcade.com
ARG NEXT_PUBLIC_WS_URL=wss://thegrind-api.caprover.al-arcade.com
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
ENV NEXT_PUBLIC_WS_URL=${NEXT_PUBLIC_WS_URL}
COPY --from=deps /build/shared ./shared
COPY --from=deps /build/frontend/node_modules ./frontend/node_modules
# Copy frontend source
COPY frontend/ ./frontend/
# Shared needs to be accessible for transpilePackages
COPY shared/ ./shared/
COPY frontend/ ./frontend/
WORKDIR /build/frontend
# Build-time env vars (these get baked into the client bundle)
# Set safe defaults; override in CapRover env vars
ARG NEXT_PUBLIC_API_URL=https://thegrind-api.example.com
ARG NEXT_PUBLIC_WS_URL=wss://thegrind-api.example.com
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_WS_URL=$NEXT_PUBLIC_WS_URL
ENV NEXT_TELEMETRY_DISABLED=1
# Build Next.js (standalone mode)
RUN npm run build
###############################################
# Stage 3: Production
###############################################
FROM node:20-alpine
RUN apk add --no-cache libc6-compat tini
RUN cd shared && npm run build 2>/dev/null || true
RUN cd frontend && npm run build
# ── Stage 3: Production ──
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
# standalone output puts everything we need in .next/standalone
COPY --from=builder /build/frontend/.next/standalone ./
COPY --from=builder /build/frontend/.next/static ./.next/static
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /build/frontend/public ./public
COPY --from=builder --chown=nextjs:nodejs /build/frontend/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /build/frontend/.next/static ./.next/static
EXPOSE 3000
USER nextjs
ENTRYPOINT ["/sbin/tini", "--"]
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
\ No newline at end of file
deploy-thegrind.sh
0 → 100644
View file @
698489ca
#!/bin/bash
# ═══════════════════════════════════════════════════════════════════════
# THE GRIND — CapRover Full Deployment v3
#
# Usage: chmod +x deploy-thegrind.sh && sudo ./deploy-thegrind.sh
# GitLab: GITLAB_USER=root GITLAB_PASS=xxx sudo ./deploy-thegrind.sh
#
# Idempotent — safe to re-run. Existing apps skip creation.
# ═══════════════════════════════════════════════════════════════════════
set
-euo
pipefail
# ┌──────────────────────────────────────────────────────────────────┐
# │ CONFIGURATION │
# └──────────────────────────────────────────────────────────────────┘
CAPTAIN_URL
=
"https://captain.caprover.al-arcade.com"
CAPTAIN_PASSWORD
=
"Alarcade123#"
GITLAB_HOST
=
"gitlab.caprover.al-arcade.com"
GITLAB_REPO_PATH
=
"root/hrsystem"
GITLAB_BRANCH
=
"main"
GITLAB_USER
=
"
${
GITLAB_USER
:-}
"
GITLAB_PASS
=
"
${
GITLAB_PASS
:-}
"
APP_DB
=
"thegrind-db"
APP_REDIS
=
"thegrind-redis"
APP_MINIO
=
"thegrind-minio"
APP_BACKEND
=
"thegrind-api"
APP_FRONTEND
=
"thegrind"
ROOT_DOMAIN
=
$(
echo
"
$CAPTAIN_URL
"
|
sed
's|https://captain\.||;s|http://captain\.||;s|/||g'
)
BE_URL
=
"https://
${
APP_BACKEND
}
.
${
ROOT_DOMAIN
}
"
FE_URL
=
"https://
${
APP_FRONTEND
}
.
${
ROOT_DOMAIN
}
"
BE_WS
=
"wss://
${
APP_BACKEND
}
.
${
ROOT_DOMAIN
}
"
MINIO_URL
=
"https://
${
APP_MINIO
}
.
${
ROOT_DOMAIN
}
"
DB_NAME
=
"thegrind"
DB_USER
=
"postgres"
DB_PASSWORD
=
"
$(
openssl rand
-hex
20
)
"
MINIO_USER
=
"minioadmin"
MINIO_PASS
=
"
$(
openssl rand
-hex
20
)
"
MINIO_BUCKET
=
"hr-files"
JWT_SECRET
=
"
$(
openssl rand
-hex
32
)
"
DB_HOST
=
"srv-captain--
${
APP_DB
}
"
REDIS_HOST
=
"srv-captain--
${
APP_REDIS
}
"
MINIO_HOST
=
"srv-captain--
${
APP_MINIO
}
"
DATABASE_URL
=
"postgresql://
${
DB_USER
}
:
${
DB_PASSWORD
}
@
${
DB_HOST
}
:5432/
${
DB_NAME
}
?schema=public"
WORK_DIR
=
"/tmp/thegrind-deploy-
$$
"
REPO_DIR
=
"
${
WORK_DIR
}
/hrsystem"
CREDS_FILE
=
"/root/thegrind-credentials.txt"
TOKEN
=
""
# ═══════════════════════════════════════════════════════════════
# HELPERS
# ═══════════════════════════════════════════════════════════════
R
=
'\033[0;31m'
G
=
'\033[0;32m'
Y
=
'\033[1;33m'
C
=
'\033[0;36m'
M
=
'\033[0;35m'
B
=
'\033[1m'
D
=
'\033[2m'
N
=
'\033[0m'
log
()
{
echo
-e
"
${
C
}
[
$(
date
+%H:%M:%S
)
]
${
N
}
$1
"
;
}
ok
()
{
echo
-e
"
${
G
}
[✓]
${
N
}
$1
"
;
}
warn
()
{
echo
-e
"
${
Y
}
[⚠]
${
N
}
$1
"
;
}
err
()
{
echo
-e
"
${
R
}
[✗]
${
N
}
$1
"
;
}
fatal
()
{
echo
-e
"
${
R
}
[FATAL]
${
N
}
$1
"
;
rm
-rf
"
$WORK_DIR
"
2>/dev/null
;
exit
1
;
}
banner
()
{
echo
-e
"
\n
${
M
}${
B
}
═══
$1
═══
${
N
}
\n
"
;
}
line
()
{
echo
-e
"
${
D
}
──────────────────────────────────────────
${
N
}
"
;
}
trap
'rm -rf "$WORK_DIR" 2>/dev/null'
ERR
build_git_url
()
{
if
[
-n
"
$GITLAB_USER
"
]
&&
[
-n
"
$GITLAB_PASS
"
]
;
then
local
EP
EP
=
$(
python3
-c
"import urllib.parse; print(urllib.parse.quote('
${
GITLAB_PASS
}
',safe=''))"
2>/dev/null
||
echo
"
$GITLAB_PASS
"
)
echo
"http://
${
GITLAB_USER
}
:
${
EP
}
@
${
GITLAB_HOST
}
/
${
GITLAB_REPO_PATH
}
.git"
else
echo
"http://
${
GITLAB_HOST
}
/
${
GITLAB_REPO_PATH
}
.git"
fi
}
countdown
()
{
log
"Starting in 5s... Ctrl+C to abort"
for
i
in
5 4 3 2 1
;
do
printf
"
\r
${
Y
}${
B
}
%d...
${
N
}
"
"
$i
"
;
sleep
1
;
done
printf
"
\r
${
G
}${
B
}
GO!
${
N
}
\n\n
"
}
# ═══════════════════════════════════════════════════════════════
# CAPROVER API v2
# ═══════════════════════════════════════════════════════════════
# Low-level: returns JSON body
_api
()
{
local
method
=
"
$1
"
endpoint
=
"
$2
"
data
=
"
${
3
:-}
"
local
url
=
"
${
CAPTAIN_URL
}
/api/v2
${
endpoint
}
"
local
args
=(
-s
-S
-w
'\n%{http_code}'
-X
"
$method
"
--insecure
--max-time
180
)
args+
=(
-H
'Content-Type: application/json'
)
[
-n
"
$TOKEN
"
]
&&
args+
=(
-H
"x-captain-auth:
${
TOKEN
}
"
)
[
-n
"
$data
"
]
&&
args+
=(
-d
"
$data
"
)
local
raw http body
raw
=
$(
curl
"
${
args
[@]
}
"
"
$url
"
2>&1
)
||
raw
=
$'
\n
000'
http
=
$(
echo
"
$raw
"
|
tail
-1
)
body
=
$(
echo
"
$raw
"
|
sed
'$d'
)
echo
"
${
body
}
"
return
0
}
# High-level: checks status, returns 0/1
cap_api
()
{
local
method
=
"
$1
"
endpoint
=
"
$2
"
data
=
"
${
3
:-}
"
local
body status desc
body
=
$(
_api
"
$method
"
"
$endpoint
"
"
$data
"
)
status
=
$(
echo
"
$body
"
| jq
-r
'.status // -1'
2>/dev/null
||
echo
"-1"
)
if
[
"
$status
"
=
"100"
]
;
then
echo
"
$body
"
return
0
fi
desc
=
$(
echo
"
$body
"
| jq
-r
'.description // empty'
2>/dev/null
||
echo
""
)
[
-z
"
$desc
"
]
&&
desc
=
$(
echo
"
$body
"
|
head
-c
300
)
echo
"
$desc
"
>
&2
return
1
}
# Upload tarball
cap_upload
()
{
local
app
=
"
$1
"
tar
=
"
$2
"
local
url
=
"
${
CAPTAIN_URL
}
/api/v2/user/apps/appData/
${
app
}
"
local
raw http body status
raw
=
$(
curl
-s
-S
-w
'\n%{http_code}'
-X
POST
--insecure
--max-time
600
\
-H
"x-captain-auth:
${
TOKEN
}
"
\
-F
"sourceFile=@
${
tar
}
"
\
-F
"gitHash=
$(
date
+%s
)
"
\
"
$url
"
2>&1
)
||
raw
=
$'
\n
000'
http
=
$(
echo
"
$raw
"
|
tail
-1
)
body
=
$(
echo
"
$raw
"
|
sed
'$d'
)
status
=
$(
echo
"
$body
"
| jq
-r
'.status // -1'
2>/dev/null
||
echo
"-1"
)
if
[
"
$status
"
=
"100"
]
;
then
return
0
fi
local
desc
desc
=
$(
echo
"
$body
"
| jq
-r
'.description // empty'
2>/dev/null
||
echo
"HTTP
$http
"
)
echo
"
$desc
"
>
&2
return
1
}
# ═══════════════════════════════════════════════════════════════
# APP MANAGEMENT HELPERS
# ═══════════════════════════════════════════════════════════════
create_app
()
{
local
name
=
"
$1
"
persistent
=
"
${
2
:-
false
}
"
log
"Creating app:
${
B
}${
name
}${
N
}
"
local
res
if
res
=
$(
cap_api POST
"/user/apps/appDefinitions/register"
\
"{
\"
appName
\"
:
\"
${
name
}
\"
,
\"
hasPersistentData
\"
:
${
persistent
}
}"
2>&1
)
;
then
ok
"Created '
${
name
}
'"
else
if
echo
"
$res
"
|
grep
-qi
"already exist"
;
then
warn
"'
${
name
}
' already exists — OK"
else
err
"Create '
${
name
}
' failed:
$res
"
return
1
fi
fi
sleep
2
}
set_env_vars
()
{
local
name
=
"
$1
"
shift
local
vars
=(
"
$@
"
)
# Build envVars JSON array
local
json_arr
=
"["
local
first
=
true
for
pair
in
"
${
vars
[@]
}
"
;
do
local
key
=
"
${
pair
%%=*
}
"
local
val
=
"
${
pair
#*=
}
"
# Escape val for JSON
val
=
$(
echo
"
$val
"
| jq
-Rs
'.'
|
sed
's/^"//;s/"$//'
)
[
"
$first
"
=
true
]
&&
first
=
false
||
json_arr+
=
","
json_arr+
=
"{
\"
key
\"
:
\"
${
key
}
\"
,
\"
value
\"
:
\"
${
val
}
\"
}"
done
json_arr+
=
"]"
local
payload
=
"{
\"
appName
\"
:
\"
${
name
}
\"
,
\"
envVars
\"
:
${
json_arr
}
}"
log
"Setting env vars for
${
B
}${
name
}${
N
}
..."
if
cap_api POST
"/user/apps/appDefinitions/update"
"
$payload
"
>
/dev/null 2>&1
;
then
ok
"Env vars set for '
${
name
}
'"
else
err
"Failed to set env vars for '
${
name
}
'"
return
1
fi
}
set_app_config
()
{
local
name
=
"
$1
"
payload
=
"
$2
"
log
"Configuring
${
B
}${
name
}${
N
}
..."
if
cap_api POST
"/user/apps/appDefinitions/update"
"
$payload
"
>
/dev/null 2>&1
;
then
ok
"Configured '
${
name
}
'"
else
err
"Config failed for '
${
name
}
'"
return
1
fi
}
deploy_image
()
{
local
name
=
"
$1
"
image
=
"
$2
"
log
"Deploying
${
B
}${
image
}${
N
}
→
${
name
}
"
local
def
def
=
$(
printf
'{"schemaVersion":2,"imageName":"%s"}'
"
$image
"
)
local
escaped
escaped
=
$(
echo
"
$def
"
| jq
-Rs
'.'
)
if
cap_api POST
"/user/apps/appData/
${
name
}
"
\
"{
\"
captainDefinitionContent
\"
:
${
escaped
}
,
\"
gitHash
\"
:
\"
$(
date
+%s
)
\"
}"
>
/dev/null 2>&1
;
then
ok
"Deployed '
${
image
}
' → '
${
name
}
'"
else
err
"Deploy '
${
image
}
' → '
${
name
}
' failed"
return
1
fi
}
deploy_dockerfile_inline
()
{
local
name
=
"
$1
"
shift
local
content
=
"
$*
"
log
"Deploying inline Dockerfile →
${
B
}${
name
}${
N
}
"
local
tmp
tmp
=
$(
mktemp
-d
)
echo
'{"schemaVersion":2,"dockerfilePath":"./Dockerfile"}'
>
"
${
tmp
}
/captain-definition"
echo
"
$content
"
>
"
${
tmp
}
/Dockerfile"
tar
-cf
"
${
tmp
}
/deploy.tar"
-C
"
$tmp
"
captain-definition Dockerfile
if
cap_upload
"
$name
"
"
${
tmp
}
/deploy.tar"
2>&1
;
then
ok
"Deployed Dockerfile → '
${
name
}
'"
else
err
"Dockerfile deploy to '
${
name
}
' failed"
fi
rm
-rf
"
$tmp
"
}
wait_build
()
{
local
name
=
"
$1
"
max
=
"
${
2
:-
480
}
"
elapsed
=
0
log
"Waiting for
${
B
}${
name
}${
N
}
build (max
${
max
}
s)..."
while
[
$elapsed
-lt
"
$max
"
]
;
do
sleep
10
elapsed
=
$((
elapsed
+
10
))
local
body
body
=
$(
_api GET
"/user/apps/appDefinitions"
)
local
building
building
=
$(
echo
"
$body
"
| jq
-r
".data.appDefinitions[] | select(.appName==
\"
${
name
}
\"
) | .isAppBuilding"
2>/dev/null
||
echo
"true"
)
if
[
"
$building
"
=
"false"
]
;
then
local
failed
failed
=
$(
echo
"
$body
"
| jq
-r
".data.appDefinitions[] | select(.appName==
\"
${
name
}
\"
) | .deployedVersion == 0"
2>/dev/null
||
echo
"false"
)
if
[
"
$failed
"
=
"true"
]
;
then
err
"'
${
name
}
' build FAILED (
${
elapsed
}
s) — check CapRover logs"
else
ok
"'
${
name
}
' build done (
${
elapsed
}
s)"
fi
return
0
fi
printf
"."
done
echo
""
warn
"'
${
name
}
' still building after
${
max
}
s"
}
health_check
()
{
local
label
=
"
$1
"
url
=
"
$2
"
retries
=
"
${
3
:-
5
}
"
log
"Health:
${
B
}${
label
}${
N
}
→
${
url
}
"
for
i
in
$(
seq
1
"
$retries
"
)
;
do
local
code
code
=
$(
curl
-sk
-o
/dev/null
-w
"%{http_code}"
"
$url
"
2>/dev/null
||
echo
"000"
)
case
"
$code
"
in
200|201|301|302|308
)
ok
"
${
label
}
is
${
G
}
HEALTHY
${
N
}
(HTTP
${
code
}
)"
return
0
;;
esac
[
"
$i
"
-lt
"
$retries
"
]
&&
{
warn
"
${
label
}
HTTP
${
code
}
— retry
${
i
}
/
${
retries
}
in 20s..."
;
sleep
20
;
}
done
warn
"
${
label
}
not responding after
${
retries
}
attempts"
return
1
}
enable_ssl
()
{
local
name
=
"
$1
"
log
"SSL for
${
B
}${
name
}
.
${
ROOT_DOMAIN
}${
N
}
..."
if
cap_api POST
"/user/apps/appDefinitions/enablebasedomainssl"
\
"{
\"
appName
\"
:
\"
${
name
}
\"
}"
>
/dev/null 2>&1
;
then
ok
"SSL:
${
name
}
"
sleep
3
cap_api POST
"/user/apps/appDefinitions/update"
\
"{
\"
appName
\"
:
\"
${
name
}
\"
,
\"
forceSsl
\"
:true}"
>
/dev/null 2>&1
&&
\
ok
"HTTPS forced:
${
name
}
"
||
warn
"Force HTTPS failed for
${
name
}
"
else
warn
"SSL failed for
${
name
}
— DNS may not be ready"
fi
}
# ═══════════════════════════════════════════════════════════════
# PHASE 0: PREREQUISITES
# ═══════════════════════════════════════════════════════════════
phase0
()
{
banner
"PHASE 0: PREREQUISITES"
[
"
$EUID
"
-ne
0
]
&&
fatal
"Run as root: sudo ./deploy-thegrind.sh"
apt-get update
-qq
>
/dev/null 2>&1
||
true
for
pkg
in
jq git curl openssl python3
;
do
command
-v
"
$pkg
"
&>/dev/null
&&
ok
"
${
pkg
}
✓"
||
{
apt-get
install
-y
-qq
"
$pkg
"
>
/dev/null 2>&1
&&
ok
"
${
pkg
}
installed"
}
done
command
-v
node &>/dev/null
&&
ok
"node
$(
node
-v
)
✓"
||
{
curl
-fsSL
https://deb.nodesource.com/setup_20.x | bash -
>
/dev/null 2>&1
apt-get
install
-y
-qq
nodejs
>
/dev/null 2>&1
ok
"node
$(
node
-v
)
installed"
}
command
-v
caprover &>/dev/null
&&
ok
"caprover CLI ✓"
||
{
npm i
-g
caprover
>
/dev/null 2>&1
&&
ok
"caprover CLI installed"
}
local
code
code
=
$(
curl
-sk
-o
/dev/null
-w
"%{http_code}"
"
$CAPTAIN_URL
"
2>/dev/null
||
echo
000
)
[
"
$code
"
=
"000"
]
&&
fatal
"Can't reach CapRover at
$CAPTAIN_URL
"
ok
"CapRover reachable (HTTP
$code
)"
code
=
$(
curl
-sk
-o
/dev/null
-w
"%{http_code}"
"http://
${
GITLAB_HOST
}
"
2>/dev/null
||
echo
000
)
[
"
$code
"
!=
"000"
]
&&
ok
"GitLab reachable (HTTP
$code
)"
||
warn
"GitLab not reachable externally"
mkdir
-p
"
$WORK_DIR
"
line
echo
-e
"
${
B
}
Plan:
${
N
}
"
echo
-e
" Frontend:
${
C
}${
FE_URL
}${
N
}
"
echo
-e
" Backend:
${
C
}${
BE_URL
}${
N
}
"
echo
-e
" Repo:
${
D
}
http://
${
GITLAB_HOST
}
/
${
GITLAB_REPO_PATH
}
.git (
${
GITLAB_BRANCH
}
)
${
N
}
"
line
countdown
}
# ═══════════════════════════════════════════════════════════════
# PHASE 1: AUTH
# ═══════════════════════════════════════════════════════════════
phase1
()
{
banner
"PHASE 1: AUTHENTICATE"
local
res
res
=
$(
cap_api POST
"/login"
"{
\"
password
\"
:
\"
${
CAPTAIN_PASSWORD
}
\"
}"
2>&1
)
||
\
fatal
"Login failed:
$res
"
TOKEN
=
$(
echo
"
$res
"
| jq
-r
'.data.token // empty'
)
[
-z
"
$TOKEN
"
]
&&
fatal
"No token in response"
ok
"Authenticated"
# Quick sanity — list apps
local
apps
apps
=
$(
_api GET
"/user/apps/appDefinitions"
)
local
count
count
=
$(
echo
"
$apps
"
| jq
'.data.appDefinitions | length'
2>/dev/null
||
echo
"?"
)
ok
"API working —
${
count
}
existing apps"
}
# ═══════════════════════════════════════════════════════════════
# PHASE 2: CLONE + VERIFY REPO
# ═══════════════════════════════════════════════════════════════
phase2
()
{
banner
"PHASE 2: CLONE REPOSITORY"
export
GIT_SSL_NO_VERIFY
=
1
local
GIT_URL
GIT_URL
=
$(
build_git_url
)
log
"Cloning
${
B
}${
GITLAB_BRANCH
}${
N
}
from GitLab..."
if
!
git clone
--depth
1
--branch
"
$GITLAB_BRANCH
"
"
$GIT_URL
"
"
$REPO_DIR
"
2>&1
;
then
if
[
-z
"
$GITLAB_USER
"
]
;
then
warn
"Clone failed — need auth?"
read
-rp
" GitLab username (or 'skip'): "
GITLAB_USER
[
"
$GITLAB_USER
"
=
"skip"
]
&&
fatal
"Can't continue without repo"
read
-rsp
" GitLab password/token: "
GITLAB_PASS
;
echo
GIT_URL
=
$(
build_git_url
)
git clone
--depth
1
--branch
"
$GITLAB_BRANCH
"
"
$GIT_URL
"
"
$REPO_DIR
"
2>&1
||
\
fatal
"Clone failed with credentials"
else
fatal
"Clone failed"
fi
fi
ok
"Cloned"
cd
"
$REPO_DIR
"
line
log
"Verifying files..."
local
FAIL
=
0
for
f
in
Dockerfile.backend Dockerfile.frontend captain-definition-backend captain-definition-frontend
\
prisma/schema.prisma backend/package.json frontend/package.json backend/src/main.ts
;
do
[
-f
"
$f
"
]
&&
ok
"
$f
"
||
{
err
"MISSING:
$f
"
;
FAIL
=
1
;
}
done
[
"
$FAIL
"
-eq
1
]
&&
fatal
"Missing critical files"
ok
"
$(
ls
-1
prisma/
*
.prisma |
wc
-l
)
Prisma schemas"
for
f
in
captain-definition-backend captain-definition-frontend
;
do
local
sv
sv
=
$(
jq
-r
'.schemaVersion'
"
$f
"
2>/dev/null
||
echo
"?"
)
local
dp
dp
=
$(
jq
-r
'.dockerfilePath'
"
$f
"
2>/dev/null
||
echo
"?"
)
ok
"
${
f
}
→ v
${
sv
}
,
${
dp
}
"
done
# ── Ensure next.config.js has standalone ──
line
if
[
-f
frontend/next.config.js
]
&&
grep
-q
"standalone"
frontend/next.config.js
;
then
ok
"next.config.js has standalone"
else
log
"Fixing next.config.js..."
cat
>
frontend/next.config.js
<<
'
EOF
'
const nextConfig = {
output: 'standalone',
reactStrictMode: true,
transpilePackages: ['shared'],
images: { unoptimized: true },
eslint: { ignoreDuringBuilds: true },
typescript: { ignoreBuildErrors: true },
};
module.exports = nextConfig;
EOF
ok
"Created next.config.js"
fi
# ── Inject API URLs into Dockerfile.frontend ──
log
"Setting API URLs in Dockerfile.frontend..."
if
grep
-q
"NEXT_PUBLIC_API_URL"
Dockerfile.frontend
;
then
sed
-i
"s|ARG NEXT_PUBLIC_API_URL=.*|ARG NEXT_PUBLIC_API_URL=
${
BE_URL
}
|"
Dockerfile.frontend
sed
-i
"s|ARG NEXT_PUBLIC_WS_URL=.*|ARG NEXT_PUBLIC_WS_URL=
${
BE_WS
}
|"
Dockerfile.frontend
else
sed
-i
"0,/^FROM .*/s|^
\(
FROM .*
\)
|
\1\n
ARG NEXT_PUBLIC_API_URL=
${
BE_URL
}
\n
ARG NEXT_PUBLIC_WS_URL=
${
BE_WS
}
\n
ENV NEXT_PUBLIC_API_URL=
\$
{NEXT_PUBLIC_API_URL}
\n
ENV NEXT_PUBLIC_WS_URL=
\$
{NEXT_PUBLIC_WS_URL}|"
Dockerfile.frontend
fi
ok
"API URLs injected"
# Also .env.production as fallback
printf
"NEXT_PUBLIC_API_URL=%s
\n
NEXT_PUBLIC_WS_URL=%s
\n
"
"
$BE_URL
"
"
$BE_WS
"
>
frontend/.env.production
ok
"frontend/.env.production created"
# Clean junk
rm
-f
backend/routes/__init__.py backend/routes/design_routes.py backend/services/design_templates.py 2>/dev/null
||
true
ok
"Repo ready"
}
# ═══════════════════════════════════════════════════════════════
# PHASE 3: INFRASTRUCTURE
# ═══════════════════════════════════════════════════════════════
phase3
()
{
banner
"PHASE 3: INFRASTRUCTURE"
# ── PostgreSQL ──
line
log
"
${
B
}
PostgreSQL 16
${
N
}
"
create_app
"
$APP_DB
"
true
set_app_config
"
$APP_DB
"
"{
\"
appName
\"
:
\"
${
APP_DB
}
\"
,
\"
instanceCount
\"
:1,
\"
notExposeAsWebApp
\"
:true,
\"
volumes
\"
:[{
\"
volumeName
\"
:
\"
${
APP_DB
}
-data
\"
,
\"
containerPath
\"
:
\"
/var/lib/postgresql/data
\"
}]}"
set_env_vars
"
$APP_DB
"
\
"POSTGRES_DB=
${
DB_NAME
}
"
\
"POSTGRES_USER=
${
DB_USER
}
"
\
"POSTGRES_PASSWORD=
${
DB_PASSWORD
}
"
deploy_image
"
$APP_DB
"
"postgres:16-alpine"
# ── Redis ──
line
log
"
${
B
}
Redis 7
${
N
}
"
create_app
"
$APP_REDIS
"
true
set_app_config
"
$APP_REDIS
"
"{
\"
appName
\"
:
\"
${
APP_REDIS
}
\"
,
\"
instanceCount
\"
:1,
\"
notExposeAsWebApp
\"
:true,
\"
volumes
\"
:[{
\"
volumeName
\"
:
\"
${
APP_REDIS
}
-data
\"
,
\"
containerPath
\"
:
\"
/data
\"
}]}"
deploy_image
"
$APP_REDIS
"
"redis:7-alpine"
# ── MinIO ──
line
log
"
${
B
}
MinIO
${
N
}
"
create_app
"
$APP_MINIO
"
true
set_app_config
"
$APP_MINIO
"
"{
\"
appName
\"
:
\"
${
APP_MINIO
}
\"
,
\"
instanceCount
\"
:1,
\"
notExposeAsWebApp
\"
:false,
\"
containerHttpPort
\"
:9001,
\"
volumes
\"
:[{
\"
volumeName
\"
:
\"
${
APP_MINIO
}
-data
\"
,
\"
containerPath
\"
:
\"
/data
\"
}]}"
set_env_vars
"
$APP_MINIO
"
\
"MINIO_ROOT_USER=
${
MINIO_USER
}
"
\
"MINIO_ROOT_PASSWORD=
${
MINIO_PASS
}
"
local
MINIO_DF
read
-r
-d
''
MINIO_DF
<<
'
HEREDOC
' || true
FROM minio/minio:latest
EXPOSE 9000 9001
CMD ["server", "/data", "--console-address", ":9001"]
HEREDOC
deploy_dockerfile_inline
"
$APP_MINIO
"
"
$MINIO_DF
"
# ── Wait ──
line
log
"Waiting 45s for infra startup..."
for
i
in
$(
seq
1 45
)
;
do
printf
"
\r
${
D
}
%02d/45
${
N
}
"
"
$i
"
;
sleep
1
;
done
printf
"
\r
"
ok
"Infra wait done "
# ── Create bucket ──
line
log
"Creating MinIO bucket..."
if
command
-v
docker &>/dev/null
;
then
local
NET
NET
=
$(
docker network
ls
--format
'{{.Name}}'
|
grep
captain |
head
-1
||
echo
"captain-overlay-network"
)
docker run
--rm
--network
"
$NET
"
--entrypoint
""
minio/mc:latest sh
-c
"
mc alias set m http://
${
MINIO_HOST
}
:9000
${
MINIO_USER
}
${
MINIO_PASS
}
2>&1 &&
mc mb --ignore-existing m/
${
MINIO_BUCKET
}
2>&1
"
2>&1 |
tail
-3
&&
ok
"Bucket '
${
MINIO_BUCKET
}
' ready"
||
\
warn
"Auto-bucket failed — create at
${
MINIO_URL
}
"
else
warn
"No docker CLI — create bucket manually at
${
MINIO_URL
}
"
fi
}
# ═══════════════════════════════════════════════════════════════
# PHASE 4: CONFIGURE APPS
#
# KEY FIX: separate calls for config, env vars, and nginx.
# Sending them all at once caused 500 Internal Server Error.
# ═══════════════════════════════════════════════════════════════
phase4
()
{
banner
"PHASE 4: APPLICATION CONFIGURATION"
# ──── BACKEND ────
line
log
"
${
B
}
Backend (
${
APP_BACKEND
}
)
${
N
}
"
create_app
"
$APP_BACKEND
"
false
# 4a. Basic config — port, websocket, NOT nginx
set_app_config
"
$APP_BACKEND
"
"{
\"
appName
\"
:
\"
${
APP_BACKEND
}
\"
,
\"
instanceCount
\"
: 1,
\"
notExposeAsWebApp
\"
: false,
\"
containerHttpPort
\"
: 3001,
\"
websocketSupport
\"
: true,
\"
forceSsl
\"
: false
}"
# 4b. Env vars — separate call to avoid payload size issues
set_env_vars
"
$APP_BACKEND
"
\
"NODE_ENV=production"
\
"PORT=3001"
\
"DATABASE_URL=
${
DATABASE_URL
}
"
\
"REDIS_HOST=
${
REDIS_HOST
}
"
\
"REDIS_PORT=6379"
\
"MINIO_ENDPOINT=
${
MINIO_HOST
}
"
\
"MINIO_PORT=9000"
\
"MINIO_USE_SSL=false"
\
"MINIO_ACCESS_KEY=
${
MINIO_USER
}
"
\
"MINIO_SECRET_KEY=
${
MINIO_PASS
}
"
\
"MINIO_BUCKET=
${
MINIO_BUCKET
}
"
\
"JWT_SECRET=
${
JWT_SECRET
}
"
\
"FRONTEND_URL=
${
FE_URL
}
"
\
"CORS_ORIGINS=
${
FE_URL
}
"
\
"API_PREFIX=api"
\
"SESSION_TIMEOUT_HOURS=8"
\
"MAX_LOGIN_ATTEMPTS=5"
\
"LOCKOUT_DURATION_MINUTES=30"
\
"MAX_DAILY_LOGIN_ATTEMPTS=15"
\
"JWT_ACCESS_EXPIRY=15m"
\
"JWT_REFRESH_EXPIRY=7d"
\
"JWT_REFRESH_EXPIRY_DAYS=7"
\
"MAX_FILE_SIZE_BYTES=26214400"
\
"MAX_PROFILE_PHOTO_SIZE_BYTES=5242880"
# 4c. Custom nginx — SEPARATE call with minimal JSON
# Using printf to build the escaped string cleanly
log
"Setting nginx config (WebSocket + 100MB uploads)..."
local
NX
=
'<%\nif (s.forceSsl) {\n%>\n return 301 https://$host$request_uri;\n<%\n} else {\n%>\n client_max_body_size 100m;\n location / {\n <%- s.nginx.upstream %>\n proxy_http_version 1.1;\n proxy_cache_bypass $http_upgrade;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_read_timeout 86400s;\n proxy_send_timeout 86400s;\n }\n<%\n}\n%>'
local
NX_PAYLOAD
NX_PAYLOAD
=
$(
printf
'{"appName":"%s","customNginxConfig":"%s"}'
"
$APP_BACKEND
"
"
$NX
"
)
if
cap_api POST
"/user/apps/appDefinitions/update"
"
$NX_PAYLOAD
"
>
/dev/null 2>&1
;
then
ok
"Nginx: WebSocket proxy + 100MB upload"
else
warn
"Custom nginx failed — WS still works via flag. Large uploads may fail."
warn
"Fix: CapRover →
${
APP_BACKEND
}
→ HTTP Settings → paste nginx config"
fi
# ──── FRONTEND ────
line
log
"
${
B
}
Frontend (
${
APP_FRONTEND
}
)
${
N
}
"
create_app
"
$APP_FRONTEND
"
false
set_app_config
"
$APP_FRONTEND
"
"{
\"
appName
\"
:
\"
${
APP_FRONTEND
}
\"
,
\"
instanceCount
\"
: 1,
\"
notExposeAsWebApp
\"
: false,
\"
containerHttpPort
\"
: 3000,
\"
websocketSupport
\"
: false,
\"
forceSsl
\"
: false
}"
set_env_vars
"
$APP_FRONTEND
"
\
"NODE_ENV=production"
\
"NEXT_PUBLIC_API_URL=
${
BE_URL
}
"
\
"NEXT_PUBLIC_WS_URL=
${
BE_WS
}
"
ok
"Phase 4 complete"
}
# ═══════════════════════════════════════════════════════════════
# PHASE 5: BUILD & DEPLOY
# ═══════════════════════════════════════════════════════════════
phase5
()
{
banner
"PHASE 5: BUILD & DEPLOY"
cd
"
$REPO_DIR
"
local
EXCL
=(
--exclude
=
'.git'
--exclude
=
'node_modules'
--exclude
=
'.next'
--exclude
=
'dist'
--exclude
=
'build'
--exclude
=
'coverage'
--exclude
=
'*.log'
--exclude
=
'.env'
--exclude
=
'.env.local'
--exclude
=
'.env.development*'
--exclude
=
'.vscode'
--exclude
=
'.idea'
--exclude
=
'minio_data'
--exclude
=
'postgres_data'
--exclude
=
'redis_data'
)
# ── Backend ──
line
log
"
${
B
}
Deploying Backend
${
N
}
(typically 4-8 min)..."
cp
captain-definition-backend captain-definition
tar
-cf
"
${
WORK_DIR
}
/be.tar"
"
${
EXCL
[@]
}
"
.
2>/dev/null
log
"Tarball:
$(
du
-sh
"
${
WORK_DIR
}
/be.tar"
|
cut
-f1
)
"
if
cap_upload
"
$APP_BACKEND
"
"
${
WORK_DIR
}
/be.tar"
2>&1
;
then
ok
"Backend uploaded — building"
else
err
"Backend upload failed"
fi
# ── Frontend ──
line
log
"
${
B
}
Deploying Frontend
${
N
}
(typically 5-12 min)..."
cp
captain-definition-frontend captain-definition
tar
-cf
"
${
WORK_DIR
}
/fe.tar"
"
${
EXCL
[@]
}
"
.
2>/dev/null
log
"Tarball:
$(
du
-sh
"
${
WORK_DIR
}
/fe.tar"
|
cut
-f1
)
"
if
cap_upload
"
$APP_FRONTEND
"
"
${
WORK_DIR
}
/fe.tar"
2>&1
;
then
ok
"Frontend uploaded — building"
else
err
"Frontend upload failed"
fi
rm
-f
captain-definition
# ── Wait for both ──
line
wait_build
"
$APP_BACKEND
"
480
wait_build
"
$APP_FRONTEND
"
600
}
# ═══════════════════════════════════════════════════════════════
# PHASE 6: SSL + HEALTH + SEED
# ═══════════════════════════════════════════════════════════════
phase6
()
{
banner
"PHASE 6: FINALIZE"
# ── SSL ──
line
for
app
in
"
$APP_BACKEND
"
"
$APP_FRONTEND
"
"
$APP_MINIO
"
;
do
enable_ssl
"
$app
"
sleep
2
done
# ── Health ──
line
log
"Waiting 30s for containers to start..."
sleep
30
local
be_ok
=
0
fe_ok
=
0
health_check
"Backend"
"
${
BE_URL
}
/api/health"
5
&&
be_ok
=
1
health_check
"Frontend"
"
${
FE_URL
}
"
5
&&
fe_ok
=
1
# ── Seed ──
line
if
[
"
$be_ok
"
-eq
1
]
&&
command
-v
docker &>/dev/null
;
then
log
"Running database seed..."
local
CID
CID
=
$(
docker ps
-qf
"name=srv-captain--
${
APP_BACKEND
}
"
|
head
-1
)
if
[
-n
"
$CID
"
]
;
then
ok
"Container:
${
CID
:0:12
}
"
if
docker
exec
"
$CID
"
sh
-c
'cd /app 2>/dev/null || true; npx prisma db seed --schema=./prisma/schema.prisma 2>&1'
2>&1 |
tail
-15
;
then
ok
"Database seeded"
else
warn
"Seed may have failed — try manually:"
warn
" docker exec
${
CID
}
npx prisma db seed --schema=./prisma/schema.prisma"
fi
else
warn
"Backend container not found yet — seed manually"
fi
else
warn
"Backend not healthy or no docker CLI — seed manually later"
fi
}
# ═══════════════════════════════════════════════════════════════
# PHASE 7: SUMMARY
# ═══════════════════════════════════════════════════════════════
phase7
()
{
banner
"DONE"
# ── Save credentials ──
cat
>
"
$CREDS_FILE
"
<<
EOCREDS
# THE GRIND — Credentials (
$(
date
-u
'+%Y-%m-%d %H:%M:%S UTC'
)
)
FRONTEND_URL=
${
FE_URL
}
BACKEND_URL=
${
BE_URL
}
BACKEND_WS=
${
BE_WS
}
CAPROVER_URL=
${
CAPTAIN_URL
}
MINIO_CONSOLE=
${
MINIO_URL
}
# DB
DATABASE_URL=
${
DATABASE_URL
}
DB_PASSWORD=
${
DB_PASSWORD
}
# Redis
REDIS_HOST=
${
REDIS_HOST
}
# MinIO
MINIO_USER=
${
MINIO_USER
}
MINIO_PASS=
${
MINIO_PASS
}
MINIO_BUCKET=
${
MINIO_BUCKET
}
# JWT
JWT_SECRET=
${
JWT_SECRET
}
# Commands
# Backend logs: docker logs \
$(
docker ps
-qf
name
=
srv-captain--
${
APP_BACKEND
})
-f --tail 200
# Frontend logs: docker logs \
$(
docker ps
-qf
name
=
srv-captain--
${
APP_FRONTEND
})
-f --tail 200
# DB seed: docker exec \
$(
docker ps
-qf
name
=
srv-captain--
${
APP_BACKEND
})
npx prisma db seed --schema=./prisma/schema.prisma
# Restart BE: docker restart \
$(
docker ps
-qf
name
=
srv-captain--
${
APP_BACKEND
})
# Restart FE: docker restart \
$(
docker ps
-qf
name
=
srv-captain--
${
APP_FRONTEND
})
# Shell BE: docker exec -it \
$(
docker ps
-qf
name
=
srv-captain--
${
APP_BACKEND
})
sh
EOCREDS
chmod
600
"
$CREDS_FILE
"
echo
""
echo
-e
"
${
G
}${
B
}
╔═══════════════════════════════════════════════════════════╗
${
N
}
"
echo
-e
"
${
G
}${
B
}
║ 🔥 THE GRIND — DEPLOYED 🔥 ║
${
N
}
"
echo
-e
"
${
G
}${
B
}
╚═══════════════════════════════════════════════════════════╝
${
N
}
"
echo
""
echo
-e
"
${
B
}
🌐 Frontend:
${
N
}
${
C
}${
FE_URL
}${
N
}
"
echo
-e
"
${
B
}
🔧 Backend API:
${
N
}
${
C
}${
BE_URL
}
/api
${
N
}
"
echo
-e
"
${
B
}
📦 MinIO:
${
N
}
${
C
}${
MINIO_URL
}${
N
}
"
echo
-e
"
${
B
}
🚀 CapRover:
${
N
}
${
C
}${
CAPTAIN_URL
}${
N
}
"
echo
""
line
echo
-e
"
${
B
}
PostgreSQL:
${
N
}
${
D
}${
DB_HOST
}
:5432 /
${
DB_NAME
}
/
${
DB_PASSWORD
}${
N
}
"
echo
-e
"
${
B
}
Redis:
${
N
}
${
D
}${
REDIS_HOST
}
:6379
${
N
}
"
echo
-e
"
${
B
}
MinIO:
${
N
}
${
D
}${
MINIO_USER
}
/
${
MINIO_PASS
}${
N
}
"
echo
-e
"
${
B
}
JWT:
${
N
}
${
D
}${
JWT_SECRET
:0:24
}
...
${
N
}
"
line
echo
""
echo
-e
"
${
Y
}${
B
}
TODO:
${
N
}
"
echo
-e
" 1. Check builds:
${
D
}${
CAPTAIN_URL
}${
N
}
"
echo
-e
" 2. Health:
${
D
}
curl -k
${
BE_URL
}
/api/health
${
N
}
"
echo
-e
" 3. Seed (if failed):
${
D
}
docker exec
\$
(docker ps -qf name=srv-captain--
${
APP_BACKEND
}
) npx prisma db seed --schema=./prisma/schema.prisma
${
N
}
"
echo
-e
" 4. Credentials:
${
D
}
cat
${
CREDS_FILE
}${
N
}
"
echo
-e
" 5.
${
R
}
CHANGE YOUR CAPROVER PASSWORD
${
N
}
"
echo
""
echo
-e
"
${
B
}
Redeploy:
${
N
}
"
echo
-e
"
${
D
}
cd /tmp && rm -rf hrsystem && git clone --depth 1 http://
${
GITLAB_HOST
}
/
${
GITLAB_REPO_PATH
}
.git && cd hrsystem
${
N
}
"
echo
-e
"
${
D
}
cp captain-definition-backend captain-definition && caprover deploy -a
${
APP_BACKEND
}${
N
}
"
echo
-e
"
${
D
}
cp captain-definition-frontend captain-definition && caprover deploy -a
${
APP_FRONTEND
}${
N
}
"
echo
""
rm
-rf
"
$WORK_DIR
"
2>/dev/null
||
true
}
# ═══════════════════════════════════════════════════════════════
# MAIN
# ═══════════════════════════════════════════════════════════════
main
()
{
echo
-e
"
${
M
}${
B
}
"
cat
<<
'
ART
'
╔════════════════════════════════════════════════════╗
║ ████████╗██╗ ██╗███████╗ ║
║ ██╔══╝██║ ██║██╔════╝ ║
║ ██║ ███████║█████╗ ║
║ ██║ ██╔══██║██╔══╝ ║
║ ██║ ██║ ██║███████╗ ║
║ ╚═╝ ╚═╝ ╚═╝╚══════╝ ║
║ ██████╗ ██████╗ ██╗███╗ ██╗██████╗ ║
║ ██╔════╝ ██╔══██╗██║████╗ ██║██╔══██╗ ║
║ ██║ ███╗██████╔╝██║██╔██╗ ██║██║ ██║ ║
║ ██║ ██║██╔══██╗██║██║╚██╗██║██║ ██║ ║
║ ╚██████╔╝██║ ██║██║██║ ╚████║██████╔╝ ║
║ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═════╝ ║
║ ║
║ CapRover Deploy v3 — Son of Anton ║
╚════════════════════════════════════════════════════╝
ART
echo
-e
"
${
N
}
"
local
T
=
$SECONDS
phase0
;
phase1
;
phase2
;
phase3
;
phase4
;
phase5
;
phase6
;
phase7
local
E
=
$((
SECONDS
-
T
))
echo
-e
"
${
D
}
Total:
$((
E/60
))
m
$((
E%60
))
s
${
N
}
\n
"
}
main
"
$@
"
\ No newline at end of file
frontend/next.config.js
View file @
698489ca
/** @type {import('next').NextConfig} */
const
nextConfig
=
{
// CRITICAL: Required for Docker/CapRover deployment
output
:
'standalone'
,
reactStrictMode
:
true
,
// Transpile the shared workspace package
transpilePackages
:
[
'shared'
],
// Image optimization — disable external loader since self-hosted
images
:
{
unoptimized
:
true
,
},
// Suppress build errors so deployment doesn't fail on type warnings
typescript
:
{
ignoreBuildErrors
:
true
,
},
eslint
:
{
ignoreDuringBuilds
:
true
,
},
// Strip console.log in production (keep errors/warns)
images
:
{
unoptimized
:
true
},
eslint
:
{
ignoreDuringBuilds
:
true
},
typescript
:
{
ignoreBuildErrors
:
true
},
compiler
:
{
removeConsole
:
process
.
env
.
NODE_ENV
===
'production'
?
{
exclude
:
[
'error'
,
'warn'
]
}
:
false
,
removeConsole
:
process
.
env
.
NODE_ENV
===
'production'
?
{
exclude
:
[
'error'
,
'warn'
]
}
:
false
,
},
// Proxy API requests in development (not used in production — frontend calls API directly)
async
rewrites
()
{
// Only apply in dev; in prod the frontend calls the API URL directly
if
(
process
.
env
.
NODE_ENV
===
'development'
)
{
return
[
{
source
:
'/api/:path*'
,
destination
:
`
${
process
.
env
.
NEXT_PUBLIC_API_URL
||
'http://localhost:3001'
}
/api/:path*`
,
},
];
}
return
[];
},
// Security headers
async
headers
()
{
return
[
{
source
:
'/(.*)'
,
headers
:
[
{
key
:
'X-Frame-Options'
,
value
:
'DENY'
},
{
key
:
'X-Content-Type-Options'
,
value
:
'nosniff'
},
{
key
:
'Referrer-Policy'
,
value
:
'strict-origin-when-cross-origin'
},
{
key
:
'Permissions-Policy'
,
value
:
'camera=(), microphone=(), geolocation=()'
},
],
},
];
// Allow API URL to be set at build time
env
:
{
NEXT_PUBLIC_API_URL
:
process
.
env
.
NEXT_PUBLIC_API_URL
,
NEXT_PUBLIC_WS_URL
:
process
.
env
.
NEXT_PUBLIC_WS_URL
,
},
};
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment