From 0a937fd1b47d7402f5c5fdc4b3ba74fc798ffe7e Mon Sep 17 00:00:00 2001 From: ilia Date: Sun, 4 Jan 2026 16:59:48 -0500 Subject: [PATCH] feat(app_setup): Improves deployment reliability for app projects and adds support for mirrormatch deployment with Prisma/Next.js requirements. (#5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Improves deployment reliability for app projects and adds support for mirrormatch deployment with Prisma/Next.js requirements. ## Changes ### Core Improvements (affects all app projects) 1. **Deploy Script (`deploy_app.sh.j2`)** - Fixed clone logic to handle non-git directories gracefully - Preserves `.env.*` files during repository clone - Uses temporary directory for initial clone to avoid permission issues - Added `sudo` to systemctl restart commands (appuser needs sudo for service management) 2. **Environment Template (`env.j2`)** - Removed comment lines to prevent `xargs` errors when sourcing env files - Cleaner, more reliable env file format 3. **App Setup Role (`app_setup/tasks/main.yml`)** - Added initial deploy task to run deploy script during first configure - Ensures app is fully deployed before systemd service starts 4. **Configure Playbook (`configure_app.yml`)** - Fixed migrate command precedence: checks `env_def.backend_migrate_cmd` first - Allows per-environment override of migrate commands (e.g., `db:push` for dev/qa) ### Mirrormatch-Specific Configuration - Added `mirrormatch` project definition with dev/qa/prod environments - Configured `backend_migrate_cmd: "npm run db:push"` for dev/qa (no shadow DB needed) - Added `backend_seed_cmd` support for dev/qa environments - Configured NextAuth v5 environment variables (`AUTH_TRUST_HOST`) ### Documentation - Updated `docs/guides/app_stack_proxmox.md` with: - Project-specific configuration examples - Environment file naming notes - Command precedence documentation ## Impact Analysis ### ✅ Backward Compatible - **pote**: No impact (uses separate `pote` role) - **punimTagFE/BE**: Will benefit from improved deploy script, no breaking changes - **mirrormatch**: Uses new features, fully supported ### Project-Specific Configs (isolated) All mirrormatch-specific settings are in `app_projects.mirrormatch` and don't affect other projects: - `backend_migrate_cmd: "npm run db:push"` (per-environment) - `backend_seed_cmd: "npm run db:seed"` (per-environment) - `AUTH_TRUST_HOST: "true"` (in env_vars) ## Testing - ✅ Mirrormatch dev environment successfully deployed - ✅ Service starts correctly after deployment - ✅ Environment variables loaded properly - ✅ Database schema pushed and seeded ## Related Fixes deployment issues encountered during mirrormatch setup: - Non-git directory handling - Env file preservation during clone - Service restart permissions - Prisma migrate vs db:push workflow Reviewed-on: https://git.levkin.ca/ilia/ansible/pulls/5 --- docs/guides/app_stack_proxmox.md | 62 ++++++++ .../production/group_vars/all/main.yml | 89 +++++++++++ .../group_vars/all/vault.example.yml | 31 ++++ .../production/group_vars/all/vault.yml | 145 ++++++++++++------ inventories/production/hosts | 2 +- playbooks/app/configure_app.yml | 18 ++- playbooks/app/provision_one_guest.yml | 2 +- playbooks/app/provision_vms.yml | 6 + roles/app_setup/defaults/main.yml | 1 + roles/app_setup/tasks/main.yml | 28 ++-- roles/app_setup/templates/deploy_app.sh.j2 | 51 +++++- roles/app_setup/templates/env.j2 | 5 - roles/base_os/tasks/main.yml | 13 ++ 13 files changed, 374 insertions(+), 79 deletions(-) diff --git a/docs/guides/app_stack_proxmox.md b/docs/guides/app_stack_proxmox.md index 54f8a7f..682ff60 100644 --- a/docs/guides/app_stack_proxmox.md +++ b/docs/guides/app_stack_proxmox.md @@ -25,6 +25,7 @@ Under `app_projects`, define projects like: - `projectA.envs.dev|qa|prod.ip/gateway/branch` - `projectA.guest_defaults` (cores/memory/rootfs sizing) - `projectA.deploy.*` (install/build/migrate/start commands) +- Optional: per-env `backend_seed_cmd` (e.g., `npm run db:seed` for dev/qa) Adding **projectB** is just adding another top-level `app_projects.projectB` entry. @@ -77,6 +78,34 @@ Only OS/app configuration: ansible-playbook -i inventories/production playbooks/app/configure_app.yml -e app_project=projectA ``` +### Limiting to a single env (dev/qa/prod) +- Pass `-e app_env=dev` (or qa/prod) to provision/configure only that environment for the selected project. +- Example (provision dev only): + ```bash + ansible-playbook -i inventories/production playbooks/app/provision_vms.yml -e app_project=mirrormatch -e app_env=dev + ``` + Configure/dev only: + ```bash + ansible-playbook -i inventories/production playbooks/app/configure_app.yml -e app_project=mirrormatch -e app_env=dev + ``` + +### Example: mirrormatch project + +- Config lives in `inventories/production/group_vars/all/main.yml` under `app_projects.mirrormatch` (dev/qa/prod guests, repo URL, migrate/start commands, env vars). +- Secrets live in the vault: `vault_mirrormatch_database_url_*` (and optional `vault_mirrormatch_shadow_database_url_*`), plus an optional repo deploy key `vault_mirrormatch_git_ssh_key`. +- Run end-to-end: + +```bash +make app PROJECT=mirrormatch +``` + +Run provisioning only / configure only: + +```bash +make app-provision PROJECT=mirrormatch +make app-configure PROJECT=mirrormatch +``` + ## Optional: SSH aliases on your workstation To write `~/.ssh/config` entries (disabled by default): @@ -87,4 +116,37 @@ ansible-playbook -i inventories/production playbooks/app/ssh_client_config.yml - This creates aliases like `projectA-dev`, `projectA-qa`, `projectA-prod`. +## Project-Specific Configuration + +### Environment-Specific Commands + +Projects can override deploy commands per environment: + +```yaml +envs: + dev: + backend_migrate_cmd: "npm run db:push" # Override default migrate command + backend_seed_cmd: "npm run db:seed" # Optional: seed database +``` + +**Precedence order:** +1. `env_def.backend_migrate_cmd` (per-environment override) +2. `project_def.deploy.backend_migrate_cmd` (project default) +3. Global default (`npm run migrate`) + +### Environment File Naming + +The systemd service uses `EnvironmentFile=/srv/app/.env.` (e.g., `.env.dev`). Systemd loads these variables into the service environment. + +**Note:** Next.js has its own env file loading that looks for `.env`, `.env.local`, `.env.production`, etc. If your Next.js app isn't reading env vars, consider: +- Using `.env.local` for dev (Next.js loads this automatically) +- Or ensure your app reads from `process.env` (systemd-injected vars) + +### Project Types + +- **Standard Node.js apps** (mirrormatch, punimTagBE/FE): Use `app_setup` role +- **Python apps** (pote): Use `pote` role (completely separate deployment path) + +Changes to `app_setup` role affect all Node.js projects but are backward-compatible. + diff --git a/inventories/production/group_vars/all/main.yml b/inventories/production/group_vars/all/main.yml index e2d6e74..217e944 100644 --- a/inventories/production/group_vars/all/main.yml +++ b/inventories/production/group_vars/all/main.yml @@ -296,3 +296,92 @@ app_projects: env_vars: APP_ENV: "prod" SECRET_PLACEHOLDER: "change-me" + + mirrormatch: + description: "Mirrormatch Prisma/Node backend (dev/qa/prod)." + repo_url: "gitea@10.0.30.169:ilia/mirror_match.git" + repo_dest: "/srv/app" + backend_dir: "/srv/app" + components: + backend: true + frontend: false + # Next.js app listens on 3000 by default + backend_port: 3000 + guest_defaults: + guest_type: "{{ proxmox_guest_type }}" + cores: 2 + memory_mb: 2048 + swap_mb: 512 + rootfs_size_gb: 16 + deploy: + backend_install_cmd: "npm ci" + backend_build_cmd: "npm run build" + backend_migrate_cmd: "npm run db:migrate" + backend_start_cmd: "npm run start" + envs: + dev: + name: "mirrormatch-dev" + vmid: 9401 + ip: "10.0.10.141/24" + gateway: "10.0.10.1" + branch: "dev" + backend_migrate_cmd: "npm run db:push" + backend_seed_cmd: "npm run db:seed" + env_vars: + APP_ENV: "dev" + NODE_ENV: "production" + PORT: "3000" + DATABASE_URL: "{{ vault_mirrormatch_database_url_dev }}" + NEXTAUTH_URL: "https://mirrormatchdev.levkin.ca" + NEXTAUTH_SECRET: "{{ vault_mirrormatch_nextauth_secret_dev }}" + AUTH_TRUST_HOST: "true" + # Dev/QA often use Ethereal/console email; leave SMTP unset or fill if needed + # SMTP_HOST: "" + # SMTP_PORT: "" + # SMTP_USER: "" + # SMTP_PASSWORD: "" + # SMTP_FROM: "" + # Optional: provide if your Prisma workflow needs a shadow DB + # SHADOW_DATABASE_URL: "{{ vault_mirrormatch_shadow_database_url_dev }}" + qa: + name: "mirrormatch-qa" + vmid: 9402 + ip: "10.0.10.144/24" + gateway: "10.0.10.1" + branch: "qa" + backend_migrate_cmd: "npm run db:push" + backend_seed_cmd: "npm run db:seed" + env_vars: + APP_ENV: "qa" + NODE_ENV: "production" + PORT: "3000" + DATABASE_URL: "{{ vault_mirrormatch_database_url_qa }}" + NEXTAUTH_URL: "https://mirrormatchqa.levkin.ca" + NEXTAUTH_SECRET: "{{ vault_mirrormatch_nextauth_secret_qa }}" + AUTH_TRUST_HOST: "true" + # SMTP_HOST: "" + # SMTP_PORT: "" + # SMTP_USER: "" + # SMTP_PASSWORD: "" + # SMTP_FROM: "" + # SHADOW_DATABASE_URL: "{{ vault_mirrormatch_shadow_database_url_qa }}" + prod: + name: "mirrormatch-prod" + vmid: 9403 + ip: "10.0.10.145/24" + gateway: "10.0.10.1" + branch: "main" + backend_migrate_cmd: "npx prisma migrate deploy" + env_vars: + APP_ENV: "prod" + NODE_ENV: "production" + PORT: "3000" + DATABASE_URL: "{{ vault_mirrormatch_database_url_prod }}" + NEXTAUTH_URL: "https://mirrormatch.example.com" + NEXTAUTH_SECRET: "{{ vault_mirrormatch_nextauth_secret_prod }}" + AUTH_TRUST_HOST: "true" + SMTP_HOST: "{{ vault_mirrormatch_smtp_host | default('') }}" + SMTP_PORT: "{{ vault_mirrormatch_smtp_port | default('587') }}" + SMTP_USER: "{{ vault_mirrormatch_smtp_user | default('') }}" + SMTP_PASSWORD: "{{ vault_mirrormatch_smtp_password | default('') }}" + SMTP_FROM: "{{ vault_mirrormatch_smtp_from | default('') }}" diff --git a/inventories/production/group_vars/all/vault.example.yml b/inventories/production/group_vars/all/vault.example.yml index 03d5865..7193bf9 100644 --- a/inventories/production/group_vars/all/vault.example.yml +++ b/inventories/production/group_vars/all/vault.example.yml @@ -39,4 +39,35 @@ vault_pote_db_password_prod: "CHANGE_ME" # SMTP password for reports vault_pote_smtp_password: "CHANGE_ME" +# ----------------------------------------------------------------------------- +# Mirrormatch (Prisma/Node backend) secrets +# ----------------------------------------------------------------------------- +# Optional deploy key for private repo access +vault_mirrormatch_git_ssh_key: | + -----BEGIN OPENSSH PRIVATE KEY----- + CHANGE_ME + -----END OPENSSH PRIVATE KEY----- + +# Per-environment database URLs (use external Postgres VM/cluster) +vault_mirrormatch_database_url_dev: "postgresql://mm_dev_user:CHANGE_ME@10.0.10.181:5432/mirrormatch_dev" +vault_mirrormatch_database_url_qa: "postgresql://mm_qa_user:CHANGE_ME@10.0.10.181:5432/mirrormatch_qa" +vault_mirrormatch_database_url_prod: "postgresql://mm_prod_user:CHANGE_ME@10.0.10.181:5432/mirrormatch_prod" + +# Optional shadow DB URLs if your Prisma workflow needs them +vault_mirrormatch_shadow_database_url_dev: "postgresql://mm_dev_shadow:CHANGE_ME@10.0.10.181:5432/mirrormatch_dev_shadow" +vault_mirrormatch_shadow_database_url_qa: "postgresql://mm_qa_shadow:CHANGE_ME@10.0.10.181:5432/mirrormatch_qa_shadow" +vault_mirrormatch_shadow_database_url_prod: "postgresql://mm_prod_shadow:CHANGE_ME@10.0.10.181:5432/mirrormatch_prod_shadow" + +# NEXTAUTH secrets per env +vault_mirrormatch_nextauth_secret_dev: "CHANGE_ME" +vault_mirrormatch_nextauth_secret_qa: "CHANGE_ME" +vault_mirrormatch_nextauth_secret_prod: "CHANGE_ME" + +# SMTP (prod) +vault_mirrormatch_smtp_host: "smtp.example.com" +vault_mirrormatch_smtp_port: "587" +vault_mirrormatch_smtp_user: "smtp-user" +vault_mirrormatch_smtp_password: "CHANGE_ME" +vault_mirrormatch_smtp_from: "MirrorMatch " + diff --git a/inventories/production/group_vars/all/vault.yml b/inventories/production/group_vars/all/vault.yml index 3d6f828..c96d344 100644 --- a/inventories/production/group_vars/all/vault.yml +++ b/inventories/production/group_vars/all/vault.yml @@ -1,47 +1,100 @@ $ANSIBLE_VAULT;1.1;AES256 -36643038376636383030343730626264613839396462366365633837636130623639393361656634 -3238353261633635353662653036393835313963373562390a646535376366656163383632313835 -39646666653362336661633736333365343962346432653131613134353361366263373162386631 -3134613438626132320a313765343338643535343837306339616564336564303166626164356530 -63663363656535303137663431613861343662303664313332626166373463393931323937613230 -66333665316331323637663437653339353737653336633864393033336630336438646162643662 -31656164363933333036376263303034646366393134636630663631353235373831303264363762 -66643865616130306537383836646237613730643133656333666632326538613764383530363363 -61386161646637316166303633643665383365346534323939383034613430386362303038313761 -36303364396436373466653332303562653038373962616539356633373065643130303036363161 -65353163326136383066393332376236386333653532326337613163346334616234643562643265 -62316134386365343733636661336130623364386634383965386135616633323132643365613231 -34636435333031376136396336316337666161383562343834383865316436633333333065323138 -37343865363731303137666330306131373734623637343531623562353332353437646631343363 -30393565376435303430396535643165616534313334326462363130626639343038643835336335 -33613630336534666163356631353438373462306566376134323536373832643264633365653465 -62386363326436623330653430383262653732376235626432656362306363303663623834653664 -31373762306539376431353137393664396165396261613364653339373765393863633833396131 -36666235666234633430373338323331313531643736656137303937653865303431643164373161 -39633238383265396366386230303536613461633431333565353433643935613231333232333063 -36643435376165656262623863373039393837643564366531666462376162653630626634663037 -39373439336239646131306133663566343734656339346462356662373561306264333364383966 -38343463616666613037636335333137633737666166633364343736646232396566373866633531 -34303734376137386363373039656565323364333539626630323465666636396465323861333365 -35376161663630356132373638333937376164316361303531303637396334306133373237656265 -36356532623130323565396531306136363339363437376364343138653139653335343765316365 -38313035366137393365316139326236326330386365343665376335313339666231333632333133 -32353865626531373462346261653832386234396531653136323162653865303861396233376261 -34616232363965313635373833333737336166643734373633313865323066393930666562316136 -36373763356365646361656436383463393237623461383531343134373336663763663464336361 -38396532383932643065303731663565353366373033353237383538636365323064396531386134 -61643964613930373439383032373364316437303239393434376465393639373634663738623461 -37386366616333626434363761326361373533306635316164316363393264303633353939613239 -37353266303637323139653630663236663633313061306633316139666539376632306630313362 -34633834326433646230303634313266303530633236353262633066396462646365623935343161 -34393166643666366164313438383939386434366665613330653739383139613732396633383261 -33633664303131383163356362316639353064373861343132623565636631333135663034373461 -61303031616634333235303066633939643337393862653031323936363932633438303035323238 -66323066353737316166383533636661336637303265343937633064626164623462656134333732 -33316536336430636636646561626232666633656266326339623732363531326131643764313838 -62356537326166346666313930383639386466633432626235373738633833393164646238366465 -62373938363739373036666238666433303061633732663565666433333631326432626461353037 -39636263636632313431353364386566383134653139393762623562643561616166633035353038 -39326462356332616563303462636536636132633933336532383938373030666333363264346632 -64643063373830353130613662323131353964313038323735626464313363326364653732323732 -3663393964633138376665323435366463623463613237366465 +38316537376634623462313731323238666165383731656632373665653534623163386333303865 +3865383030316132663831303932376437346335323233630a643331663539383163306666393764 +38313265656561343839616565343663353037663237663032366632373831363336306632626266 +3361643865333533340a356233663034343932323831323236356161396237346532323838373135 +33393239313730363336613338373039663735323431323562613363343863326234633833663631 +66343462623231663932633537373361313764393630356666393662653135356139663935613038 +65383261363065633235343031346535373564373931373063386265343335623265653739613830 +32656233393330633362623932316431383761306332393466313936396533333839313831663331 +34353864356336303331663233653666363966376162303731626134313235306238323363303439 +32333039653235326632303637303065386161616138356463623561366637376366326262303166 +38323763393934666539373063323265333961666164613437316164633565393035626538353365 +33386562336665383863636639643232623161643933313664396534383362303838663362653736 +64393334616165336638306235363734653431646431616139373336656333623963386538646230 +39663230363063386231343730663162313463666135323265613261626637626332353534396535 +31623664363766646332396336396133613662643232366433323330373962633839613635333763 +63306230623438346639323863353137363330316630316130326134323731326635643736373736 +62336362656265633233623165376436373231656666303832373966353732313031623865316663 +63356163636238346230623732326232646434623532633439646536656362393162613535613565 +66616539316362376561386263373464623030636661663435383839643565393632616232663035 +34653735383964653930633664346330386566343830336238306562343164366131643138643339 +35313366356637643262636238366263353535306434633732623335643266396335666636666663 +37333232393765306433326164663538663839623034373535653737633366303665633831303334 +32303061363863386139613464326466336136396534663538643163343439343763383534306636 +62353733613330376163386331626463656462336237656339356132643135363537343638303261 +33366332653439313137613665386136666536356537346665333935366336623734393738346434 +63326265346362636564366265373134336662626332653464646139656635313961656230336537 +63666638326337643033363964643339666130386139363138656165666333356465643337396165 +30336330633632353231613938646165383966613863366330646162646266346139343434393865 +66346365663230626531643963383462636465363965393762336233366538393133313138616335 +32353834313762363265643031343237633732393166343139363163326439666162396332353038 +31306530626666343361313736313636613335376163383237303063393333386663333333336137 +37346166316231623638386635613230663063653037643930333961316434643361633035633734 +65643937636361653433383262643265373165613437336236633631323635613034663834646665 +30373730373438613132633932333565376665333565383932356334653738646166393934626362 +30666666303832613633316230623038343165396338343535663931383639623430643238656261 +39623037333063306266323335303736346236636137633863353866343136346335353865303961 +31346331333066376330306361396262333762393838303165383134303435353630366130303536 +34386532356239326166386665623435646432636561363564656161646563306234333138333839 +38316337656631313763393135396464643338386636336234346663653538353863643636323032 +35326133623064363838386662653138613438386564316635373838366262656364666633636539 +61306563666138656161336466323537626161313366616662623362643036636132663634313137 +39653437306662646162613763343736636530356465346132646238633166373838353836326461 +36326666323636353239303262623436643932353164323630326635653635653233363265316264 +30653763643431626539356161376534396437636463303363663134373961616561363561333333 +34306537326666383664336464656464623731656566653132613565336536323438666333366466 +64613738653730333633383062653837366266316536653139643362373039383831363666333934 +34383833336266356436666636323239336432386133303466636138643934356266326533643161 +36393664313963393930383533623565383332613933396639613037323266663439313138326261 +30353861303661303836343165353362663632306430626337356562343637653164396237333566 +37656230363530323836373363646334356262646633313932383161303264613238373936353036 +61376264633930356465626266623930333039383032316163633037323035346130343934616261 +31666166393462366561303833353135326566356637376466613934376233303162323033623031 +63656131333439353537623662363530383866326432306361316465383137633536666364623662 +37353561633839623530333663643130326131333330626661396636343234666139336539653162 +62383636663137626637303535333862366434626161353239393232313537343865646564626331 +39366665363030643764663963316163343033326434373265343664393439316333346434363563 +61346164396561343865626362616433306230333130653166656230353364316536626432373333 +35383133363530666263316431396462383133363965336637386632363263656261353963313161 +36383632326264373436383638383064346334336238656239393833653531656461356136303434 +37663434663732306631656334306361663562303863386135623066633963373034373139666332 +35393433646333363839666434663535363661616330386234366132303161383063663836626561 +35393064343735303032313266643338623834383838633834636536363539656466663864613366 +66636363623330326436363936313938333638323939323035616232366563316364343834376630 +66656434336661643861613737616138396330383832386230383331646462323363373363393733 +63363237636137373566363438663966396432613964336164326138623737393636396234646232 +64343361363365356135666235623833396131626663303839653535663732313831633163643638 +35396262373837343238343838663635353838373338663732626330613237623332336436643136 +38653833383430393837383566643765653834306636356466326364303334653034626262356630 +34333338333336373433356235386337346666343830303164363235303265313134323339653339 +63316238346132653663653165313635336638646362356337643766366564383531633565303431 +66616433663630343439336661346266336139613537653438653432326666326137306364376137 +66333939643262633532363966623439373434393862353237613135646663623236646331643537 +31353566653464313433636635393330646166613232633734346639326534373163383064353732 +32373861303064346266643338316465653031646633633936373738663837383162643534623131 +31633662356534343636313834386139656439663733333762323962323939623032396239356437 +37633739613433613365313337383835623936623530363831383535663337343264356532616434 +39393634396664636166346631313764343733666534613935393637363233373331303837656463 +37363266363634353136316532333462396266373733333633356239653334363835326261323661 +66323032346364356230613831643236316530356132343863393361343462373433383265336333 +30343730316366366234333263343965633466333439653739663333643939303631353664316435 +36396139623562656632666165666662626263643436396431326135633932393965656531633761 +39303634643936366438336534613532303134343164326661626363656562383564623264636132 +39656636303636393761653035303832386430646162343830343834316534636263373763643765 +61366335643531666232303231656336643833396238336639333437363564636566636632303364 +62623738336237393638363436396662656565653839643164356565313563663561666237383036 +33626464663465643230376164653062663063636630613064643632643235643662653566333333 +62353763643830363638323731303537633837393235656661333263323536363330356362643333 +34346666656432626365383639326538643862346265316263326531623631383962383734316330 +39333430613761663337306331623461643635653431343336663163343766373464366538313335 +61643538643231333636643836663663313534356662386532633331346664653262353839643066 +36393366653131316636646336313362656662666163333635633132323438353435373430643839 +37623936393962333065663536306238653466363634386632366637363265303734356535333735 +64623330303965393533326563643063303762646664666464643239386435343065326234306632 +35346338373866303838613933653230373737396134653533376265356432333933356237636338 +66656536393530316435323863373962636465333331653364626162326562393565313538633264 +34613633393862333731336563636136666166613037613833333063303162373339663539646631 +36303962356562306239616634376339356135666663303836353061663039343836356262373932 +65346466373532633365383835323062313531623130396130376531626333653862393462643631 +366330333666336262373364663864336633 diff --git a/inventories/production/hosts b/inventories/production/hosts index c643af9..0e99df8 100644 --- a/inventories/production/hosts +++ b/inventories/production/hosts @@ -32,7 +32,7 @@ listmonk ansible_host=10.0.10.149 ansible_user=root nextcloud ansible_host=10.0.10.25 ansible_user=root actual ansible_host=10.0.10.158 ansible_user=root vikanjans ansible_host=10.0.10.159 ansible_user=root -n8n ansible_host=10.0.10.158 ansible_user=root +n8n ansible_host=10.0.10.154 ansible_user=root giteaVM ansible_host=10.0.30.169 ansible_user=root portainerVM ansible_host=10.0.30.69 ansible_user=ladmin homepageVM ansible_host=10.0.30.12 ansible_user=homepage diff --git a/playbooks/app/configure_app.yml b/playbooks/app/configure_app.yml index 974ad51..6324183 100644 --- a/playbooks/app/configure_app.yml +++ b/playbooks/app/configure_app.yml @@ -20,6 +20,12 @@ if (app_project is not defined or app_project | length == 0) else [app_project] }} + selected_envs: >- + {{ + [app_env] + if (app_env is defined and app_env | length > 0) + else ['dev', 'qa', 'prod'] + }} app_bootstrap_user_default: root # If true, configure plays will use vault_lxc_root_password for initial SSH bootstrap. bootstrap_with_root_password_default: false @@ -47,7 +53,7 @@ }} app_project: "{{ item.0 }}" app_env: "{{ item.1 }}" - loop: "{{ selected_projects | product(['dev', 'qa', 'prod']) | list }}" + loop: "{{ selected_projects | product(selected_envs) | list }}" when: - app_projects[item.0] is defined - app_projects[item.0].envs[item.1] is defined @@ -116,16 +122,20 @@ # app_env is already set per-host via add_host (dev/qa/prod) app_owner: "{{ project_def.os_user | default(appuser_name) }}" app_group: "{{ project_def.os_user | default(appuser_name) }}" + app_backend_dir: "{{ project_def.backend_dir | default(project_def.repo_dest | default(app_repo_dest)) }}" + app_frontend_dir: "{{ project_def.frontend_dir | default(project_def.repo_dest | default(app_repo_dest) + '/frontend') }}" # Use different variable names to avoid self-referential recursion - app_backend_port: "{{ project_def.backend_port | default(3001) }}" + app_backend_port: "{{ project_def.backend_port | default(3000) }}" app_frontend_port: "{{ project_def.frontend_port | default(3000) }}" app_enable_backend: "{{ project_def.components.backend | default(true) }}" app_enable_frontend: "{{ project_def.components.frontend | default(true) }}" app_backend_install_cmd: "{{ project_def.deploy.backend_install_cmd | default('npm ci') }}" - app_backend_migrate_cmd: "{{ project_def.deploy.backend_migrate_cmd | default('npm run migrate') }}" - app_backend_start_cmd: "{{ project_def.deploy.backend_start_cmd | default('npm start') }}" + app_backend_build_cmd: "{{ project_def.deploy.backend_build_cmd | default('npm run build') }}" + app_backend_migrate_cmd: "{{ env_def.backend_migrate_cmd | default(project_def.deploy.backend_migrate_cmd | default('npm run migrate')) }}" + app_backend_seed_cmd: "{{ env_def.backend_seed_cmd | default(project_def.deploy.backend_seed_cmd | default('')) }}" + app_backend_start_cmd: "{{ project_def.deploy.backend_start_cmd | default('npm run start') }}" app_frontend_install_cmd: "{{ project_def.deploy.frontend_install_cmd | default('npm ci') }}" app_frontend_build_cmd: "{{ project_def.deploy.frontend_build_cmd | default('npm run build') }}" diff --git a/playbooks/app/provision_one_guest.yml b/playbooks/app/provision_one_guest.yml index bdf9fdf..f18bd81 100644 --- a/playbooks/app/provision_one_guest.yml +++ b/playbooks/app/provision_one_guest.yml @@ -16,7 +16,7 @@ - (project_def.envs | length) > 0 - name: Provision each environment for project ansible.builtin.include_tasks: provision_one_env.yml - loop: "{{ project_def.envs | dict2items }}" + loop: "{{ project_def.envs | dict2items | selectattr('key', 'in', selected_envs) | list }}" loop_control: loop_var: env_item diff --git a/playbooks/app/provision_vms.yml b/playbooks/app/provision_vms.yml index 3b1f6b2..bccfc47 100644 --- a/playbooks/app/provision_vms.yml +++ b/playbooks/app/provision_vms.yml @@ -20,6 +20,12 @@ if (app_project is not defined or app_project | length == 0) else [app_project] }} + selected_envs: >- + {{ + [app_env] + if (app_env is defined and app_env | length > 0) + else ['dev', 'qa', 'prod'] + }} tasks: - name: Validate requested project exists diff --git a/roles/app_setup/defaults/main.yml b/roles/app_setup/defaults/main.yml index 89b680b..3f89c62 100644 --- a/roles/app_setup/defaults/main.yml +++ b/roles/app_setup/defaults/main.yml @@ -29,6 +29,7 @@ app_frontend_port: 3000 # Commands (Node defaults; override per project as needed) app_backend_install_cmd: "npm ci" app_backend_migrate_cmd: "npm run migrate" +app_backend_seed_cmd: "" app_backend_start_cmd: "npm start" app_frontend_install_cmd: "npm ci" diff --git a/roles/app_setup/tasks/main.yml b/roles/app_setup/tasks/main.yml index 2ed6eff..bb08306 100644 --- a/roles/app_setup/tasks/main.yml +++ b/roles/app_setup/tasks/main.yml @@ -10,24 +10,6 @@ group: "{{ app_group }}" mode: "0755" -- name: Ensure backend directory exists - ansible.builtin.file: - path: "{{ app_backend_dir }}" - state: directory - owner: "{{ app_owner }}" - group: "{{ app_group }}" - mode: "0755" - when: app_enable_backend | bool - -- name: Ensure frontend directory exists - ansible.builtin.file: - path: "{{ app_frontend_dir }}" - state: directory - owner: "{{ app_owner }}" - group: "{{ app_group }}" - mode: "0755" - when: app_enable_frontend | bool - - name: Deploy environment file for this env ansible.builtin.template: src: env.j2 @@ -44,6 +26,16 @@ group: root mode: "0755" +- name: Run initial deploy (clone, install, build, migrate, seed) + ansible.builtin.command: + cmd: /usr/local/bin/deploy_app.sh + become: true + become_user: "{{ app_owner }}" + args: + chdir: "{{ app_root }}" + register: deploy_result + changed_when: true + - name: Deploy systemd unit for backend ansible.builtin.template: src: app-backend.service.j2 diff --git a/roles/app_setup/templates/deploy_app.sh.j2 b/roles/app_setup/templates/deploy_app.sh.j2 index 52224e1..55bf665 100644 --- a/roles/app_setup/templates/deploy_app.sh.j2 +++ b/roles/app_setup/templates/deploy_app.sh.j2 @@ -11,10 +11,45 @@ ENV_FILE="{{ app_root }}/.env.{{ app_env }}" echo "[deploy] repo=${REPO_URL} branch=${BRANCH} root=${APP_ROOT}" +# Load env for build/migrate steps (needed for Prisma/Next build) +if [[ -f "${ENV_FILE}" ]]; then + set -a + # shellcheck disable=SC1090 + source "${ENV_FILE}" + set +a +fi + if [[ ! -d "${APP_ROOT}/.git" ]]; then echo "[deploy] cloning repo" - install -d -m 0755 "${APP_ROOT}" - git clone --branch "${BRANCH}" --single-branch "${REPO_URL}" "${APP_ROOT}" + + # Preserve existing env files + env_tmp="$(mktemp -d)" + shopt -s nullglob dotglob + for f in "${APP_ROOT}"/.env.*; do + [[ -f "$f" ]] && cp "$f" "${env_tmp}/" || true + done + shopt -u nullglob dotglob + + # Clone to temp location + clone_tmp="$(mktemp -d)" + git clone --branch "${BRANCH}" --single-branch "${REPO_URL}" "${clone_tmp}/repo" + + # Clean app root (keep directory and .env files) + find "${APP_ROOT}" -mindepth 1 -maxdepth 1 ! -name '.env.*' -exec rm -rf {} + 2>/dev/null || true + + # Move cloned repo contents to app root (including hidden files) + shopt -s dotglob + mv "${clone_tmp}/repo"/* "${APP_ROOT}"/ 2>/dev/null || true + shopt -u dotglob + rm -rf "${clone_tmp}" + + # Restore env files + shopt -s nullglob + for f in "${env_tmp}"/.env.*; do + [[ -f "$f" ]] && cp "$f" "${APP_ROOT}/" || true + done + shopt -u nullglob + rm -rf "${env_tmp}" fi echo "[deploy] syncing branch" @@ -31,8 +66,16 @@ echo "[deploy] backend install" cd "${BACKEND_DIR}" {{ app_backend_install_cmd }} +echo "[deploy] backend build" +{{ app_backend_build_cmd }} + echo "[deploy] backend migrations" {{ app_backend_migrate_cmd }} + +{% if app_backend_seed_cmd | default('') | length > 0 %} +echo "[deploy] backend seed" +{{ app_backend_seed_cmd }} +{% endif %} fi if [[ "{{ app_enable_frontend | bool }}" == "True" ]]; then @@ -46,10 +89,10 @@ fi echo "[deploy] restarting services" {% if app_enable_backend | bool %} -systemctl restart app-backend.service +sudo systemctl restart app-backend.service {% endif %} {% if app_enable_frontend | bool %} -systemctl restart app-frontend.service +sudo systemctl restart app-frontend.service {% endif %} echo "[deploy] done" diff --git a/roles/app_setup/templates/env.j2 b/roles/app_setup/templates/env.j2 index dbfa0aa..9826614 100644 --- a/roles/app_setup/templates/env.j2 +++ b/roles/app_setup/templates/env.j2 @@ -1,11 +1,6 @@ -# Ansible-managed environment file for {{ app_env }} -# Loaded by systemd units and deploy script. - -# Common APP_ENV={{ app_env }} BACKEND_PORT={{ app_backend_port }} FRONTEND_PORT={{ app_frontend_port }} - {% for k, v in (app_env_vars | default({})).items() %} {{ k }}={{ v }} {% endfor %} diff --git a/roles/base_os/tasks/main.yml b/roles/base_os/tasks/main.yml index 95fa7b3..322860d 100644 --- a/roles/base_os/tasks/main.yml +++ b/roles/base_os/tasks/main.yml @@ -12,6 +12,14 @@ name: "{{ base_os_packages }}" state: present +- name: Ensure /etc/sudoers.d exists + ansible.builtin.file: + path: /etc/sudoers.d + state: directory + owner: root + group: root + mode: "0750" + - name: Ensure app user exists ansible.builtin.user: name: "{{ base_os_user }}" @@ -37,6 +45,11 @@ mode: "0440" when: base_os_passwordless_sudo | bool +- name: Ensure ufw is installed + ansible.builtin.apt: + name: ufw + state: present + - name: Ensure UFW allows SSH community.general.ufw: rule: allow