diff --git a/deployment/database/migrations/001_init.sql b/deployment/database/migrations/1.0.0_init.sql similarity index 100% rename from deployment/database/migrations/001_init.sql rename to deployment/database/migrations/1.0.0_init.sql diff --git a/deployment/develop/Dockerfile b/deployment/develop/Dockerfile new file mode 100644 index 0000000..8b4d497 --- /dev/null +++ b/deployment/develop/Dockerfile @@ -0,0 +1,17 @@ +FROM node:22-alpine +WORKDIR /home/node/app + +COPY ./package.json ./ +COPY ./dist ./dist + +COPY tsconfig.json ./ + +COPY ./deployment/database/migrations ./deployment/database/migrations +COPY ./deployment/develop/start.sh ./ + +RUN npm config set @sf-alvar:registry https://git.savefamilygps.net/api/packages/SaveFamily/npm/ &&\ + npm install &&\ + chmod +x start.sh + +EXPOSE ${PORT} +ENTRYPOINT [ "./start.sh" ] diff --git a/deployment/develop/docker-compose.yaml b/deployment/develop/docker-compose.yaml new file mode 100644 index 0000000..6f4a31c --- /dev/null +++ b/deployment/develop/docker-compose.yaml @@ -0,0 +1,50 @@ +name: sf-monitorizacion-health +networks: + savefamily: + external: true + proxy: + external: true + internal: + driver: bridge + +services: + sf-monitorizacion-health: + container_name: sf-monitorizacion-health + image: sf-monitorizacion-health + build: + context: . + dockerfile: Dockerfile + args: + PORT: "${PORT:-3000}" + ports: + - ${PORT}:${PORT} + networks: + - internal + - savefamily + - proxy + env_file: + - .env + restart: unless-stopped + healthcheck: + test: + [ + "CMD-SHELL", + "wget -q --spider http://127.0.0.1:${PORT:-3000}/health || exit 1", + ] + interval: 60s + timeout: 5s + retries: 5 + start_period: 15s + labels: + - "io.portainer.accesscontrol.teams=develop" + - "traefik.enable=true" + - "traefik.http.routers.sf-monitorizacion-health.entrypoints=web" + - "traefik.http.routers.sf-monitorizacion-health.rule=Host(`sf-monitorizacion-health.savefamilygps.net`)" + - "traefik.http.middlewares.sf-monitorizacion-health-https-redirect.redirectscheme.scheme=https" + - "traefik.http.routers.sf-monitorizacion-health.middlewares=sf-monitorizacion-health-https-redirect" + - "traefik.http.routers.sf-monitorizacion-health-secure.entrypoints=websecure" + - "traefik.http.routers.sf-monitorizacion-health-secure.rule=Host(`sf-monitorizacion-health.savefamilygps.net`)" + - "traefik.http.routers.sf-monitorizacion-health-secure.tls=true" + - "traefik.http.routers.sf-monitorizacion-health-secure.service=sf-monitorizacion-health" + - "traefik.http.services.sf-monitorizacion-health.loadbalancer.server.port=${PORT}" + - "traefik.docker.network=proxy" diff --git a/deployment/develop/jenkinsfile.groovy b/deployment/develop/jenkinsfile.groovy new file mode 100644 index 0000000..b7990ea --- /dev/null +++ b/deployment/develop/jenkinsfile.groovy @@ -0,0 +1,109 @@ +#!/usr/bin/env groovy +String BASE_REMOTE_PATH = "//home/devops" +String APP_REMOTE_PATH = "//mnt/docker-storage/containers/savefamily/sf-monitorizacion-health" + +// Muchos paths son mas simples, hay que cambiar movimientos de archivos +pipeline { + agent any + tools { nodejs "22.15.0" } + + environment { + GENERAL_CHANGES = "false" + } + + stages { + stage('πŸ“¦ Install dependencies') { + steps { + sh 'npm install' + } + } + stage("🧱 Building") { + steps { + sh 'npm run build:esbuild' + } + } + stage("πŸ— Deploying") { + steps { + sshPublisher( + publishers: [ + sshPublisherDesc( + verbose: true, + configName: "Save Family", + transfers: [ + sshTransfer( + cleanRemote: false, + execCommand: "mkdir -p $APP_REMOTE_PATH" + ), + sshTransfer( + cleanRemote: false, + execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-monitorizacion-health/.env $APP_REMOTE_PATH/.env" + ), + sshTransfer( + cleanRemote: false, + remoteDirectory: "$APP_REMOTE_PATH", + sourceFiles: "docs/**/*", + ), + sshTransfer( + cleanRemote: false, + remoteDirectory: "$APP_REMOTE_PATH", + sourceFiles: "deployment/develop/**/*", + removePrefix:"deployment/develop", + excludes: "deployment/develop/docker" + ), + sshTransfer( + cleanRemote: false, + remoteDirectory: "$APP_REMOTE_PATH", + sourceFiles: "deployment/develop/docker/**/*", + removePrefix:"deployment/develop/docker" + ), + sshTransfer( + cleanRemote: false, + remoteDirectory: "$APP_REMOTE_PATH", + sourceFiles: "deployment/database/**/*", + ), + sshTransfer( + cleanRemote: false, + remoteDirectory: "$APP_REMOTE_PATH", + sourceFiles: "package.json", + ), + sshTransfer( + cleanRemote: false, + remoteDirectory: "$APP_REMOTE_PATH", + sourceFiles: "src/**/*" + ), + sshTransfer( + cleanRemote: false, + remoteDirectory: "$APP_REMOTE_PATH", + sourceFiles: "dist/**/*" + ), + sshTransfer( + cleanRemote: false, + remoteDirectory: "$APP_REMOTE_PATH", + sourceFiles: "tsconfig.json" + ), + sshTransfer( + cleanRemote: false, + remoteDirectory: "$APP_REMOTE_PATH", + sourceFiles: "package-lock.json", + ), + sshTransfer( + cleanRemote: false, + execCommand: "sh $APP_REMOTE_PATH/rebuild.sh" + ) + ] + ) + ] + ) + } + } + } + + post { + failure { + echo 'πŸ‘ŽπŸΌ Processing failed' + } + success { + echo 'πŸ‘πŸΌ Processing success' + } + } +} diff --git a/deployment/develop/rebuild.sh b/deployment/develop/rebuild.sh new file mode 100644 index 0000000..4a4a5fe --- /dev/null +++ b/deployment/develop/rebuild.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +cd /mnt/docker-storage/containers/savefamily/sf-monitorizacion-health + +docker compose -f docker-compose.yaml down || echo "Error parando servidor" +docker rm sf-monitorizacion-health || echo "Error eliminando contenedor antiguo" +docker rmi sf-monitorizacion-health || echo "Error eliminando (rmi) contenedor antiguo" + +docker compose -f docker-compose.yaml up --build -d diff --git a/deployment/develop/start.sh b/deployment/develop/start.sh new file mode 100644 index 0000000..2cf77c9 --- /dev/null +++ b/deployment/develop/start.sh @@ -0,0 +1,6 @@ +#!/bin/sh +cd /home/node/app +echo "Lanzando migraciones e iniciando servidor" +# npm config set @sf-alvar:registry https://git.savefamilygps.net/api/packages/SaveFamily/npm/ #npm install +npm run migrate +npm run start diff --git a/deployment/local/Dockerfile.local b/deployment/local/Dockerfile.local new file mode 100644 index 0000000..d561ff3 --- /dev/null +++ b/deployment/local/Dockerfile.local @@ -0,0 +1,23 @@ +FROM node:22-alpine + +WORKDIR /home/node/app + +COPY ./package.json ./package-lock.json ./ +COPY ./src ./src + +# copia el codigo en general +COPY tsconfig.json ./ +COPY .env* ./ +COPY ./deployment/local/start.sh ./ + +# Copiar el archivo de migrations? porque ahora no creo que se estΓ© lanzando nada +COPY ./deployment/database/migrations ./deployment/database/migrations + +RUN npm config set registry https://git.savefamilygps.net/api/packages/SaveFamily/npm/ &&\ + echo "registry=https://registry.npmjs.org/" >> .npmrc &&\ + npm install &&\ + ls && npm run build:esbuild &&\ + chmod +x start.sh +EXPOSE ${PORT} +ENTRYPOINT [ "./start.sh" ] + diff --git a/deployment/local/docker-compose.yaml b/deployment/local/docker-compose.yaml new file mode 100644 index 0000000..d4e364f --- /dev/null +++ b/deployment/local/docker-compose.yaml @@ -0,0 +1,57 @@ +name: sf-monitorizacion-health +networks: + default: + driver: bridge + name: network-test # Tiene que coincidir con el compose objetivo + +services: + sf-monitorizacion-health: + container_name: sf-monitorizacion-health + image: sf-monitorizacion-health + build: + context: ./ + dockerfile: deployment/local/docker/Dockerfile.local + args: + PORT: "${PORT:-3000}" + develop: + watch: + - path: ./src + action: sync + target: /usr/local/app/packages + - path: ./package.json + action: rebuild + ports: + - ${PORT}:${PORT} + env_file: + - .env + restart: unless-stopped + healthcheck: + test: + [ + "CMD-SHELL", + "wget -q --spider http://127.0.0.1:${PORT:-3000}/health || exit 1", + ] + interval: 10s + timeout: 5s + retries: 5 + start_period: 15s + depends_on: + postgresql-health: + condition: service_healthy + + postgresql-health: + container_name: postgresql-health + image: postgres:16.1 + env_file: + - .env + ports: + - "${POSTGRES_PORT}:5432" + volumes: + - ./sql-data/:/var/lib/postgres/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + interval: 5s + retries: 5 + start_period: 5s + timeout: 5s + command: -p 5432 diff --git a/package-lock.json b/package-lock.json index 7d9ce7f..097b215 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "tsx": "^4.21.0" }, "devDependencies": { + "@sf-alvar/db-migrate": "^1.0.10", "@types/ejs": "^3.1.5", "@types/express": "^5.0.6", "@types/node": "^25.6.0", @@ -483,6 +484,20 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@sf-alvar/db-migrate": { + "version": "1.0.10", + "resolved": "https://git.savefamilygps.net/api/packages/SaveFamily/npm/%40sf-alvar%2Fdb-migrate/-/1.0.10/db-migrate-1.0.10.tgz", + "integrity": "sha512-tD8Ziytn0Yh88vCIL+vOKTwwSh3hDp+1ZX02eaPUQtmbec/Iz1Beq2izHU+vre1dHMGUPI9Tu79HsDbx159nfQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "pg": "^8.18.0", + "yargs": "^18.0.0" + }, + "bin": { + "db-migrate": "lib/index.js" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", @@ -667,6 +682,32 @@ "node": ">=0.4.0" } }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -841,6 +882,21 @@ "fsevents": "~2.3.2" } }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -989,6 +1045,13 @@ "node": ">=0.12.18" } }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -1084,6 +1147,16 @@ "@esbuild/win32-x64": "0.27.7" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1274,6 +1347,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2111,6 +2207,40 @@ "node": ">= 0.8" } }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -2286,6 +2416,24 @@ "node": ">= 0.8" } }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -2301,6 +2449,44 @@ "node": ">=0.4" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index ca2500d..c4d2d94 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "author": "", "license": "ISC", "devDependencies": { + "@sf-alvar/db-migrate": "^1.0.10", "@types/ejs": "^3.1.5", "@types/express": "^5.0.6", "@types/node": "^25.6.0", @@ -35,4 +36,4 @@ "pg": "^8.20.0", "tsx": "^4.21.0" } -} \ No newline at end of file +}