Repository navigation
Expand file tree
/
Copy pathrender.yaml
More file actions
153 lines (153 loc) · 9.61 KB
/
Copy pathrender.yaml
File metadata and controls
153 lines (153 loc) · 9.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# Render Blueprint for the GL&R HR Spring Boot backend.
# Import via Render Dashboard -> New -> Blueprint, pointing at this repo.
# The service name is pinned so the public URL is deterministic:
# https://gl-r-erp.onrender.com
# which is what the Vercel frontend proxies /api to (see frontend/vercel.json).
#
# ── RUNS A PRE-BUILT IMAGE, SO THE LICENSED THAI FONTS ARE ACTUALLY PRESENT (#666) ───────
# Render builds from a fresh git clone, and the licensed Thai fonts are proprietary and git-ignored,
# so an image Render builds itself can NEVER contain them. Building from source here is what made
# every customer-facing quotation and deposit notice render in substitute fonts — with no Thai
# coverage at all, the text came out as empty boxes, not merely mis-metricked.
#
# The image is therefore built where the fonts are reachable and pushed to a private registry:
#
# ./scripts/build-push-backend-image.sh v2026-08-14
#
# That script builds --platform linux/amd64 (Render is amd64; this repo is developed on Apple
# Silicon, where the default build produces an arm64 image Render cannot start) and with
# ALLOW_MISSING_FONTS=false, so a missing or substituted font face fails the build instead of
# shipping. backend/Dockerfile checks each required family AND STYLE — a family-only check passes
# while a bold face silently substitutes, which is exactly how this shipped for months.
#
# #670 tried this and was reverted because image.url was a PLACEHOLDER, so every deploy failed.
# The URL below is real and pushed. Two preconditions before this file is merged or deployed:
# 1. The tag below exists in the registry and is PRIVATE (the image embeds licensed fonts).
# 2. A Render registry credential named `ghcr-glr` exists (Dashboard → Settings → Registry
# Credentials) holding a GitHub PAT with read:packages. Without it the pull 401s.
#
# Bumping the deployed version = push a new tag, set it on the service in the Render dashboard, keep
# image.url below in step, deploy. autoDeploy is off, so nothing rolls out until someone chooses to.
#
# ⚠️ THE RENDER DASHBOARD DECIDES WHAT RUNS, NOT THIS FILE (verified 2026-09-13). From 2026-09-10 to
# 2026-09-13 the dashboard's Source for gl-r-erp was a Git build of `main` with auto-deploy on every
# push, overriding both `runtime: image` and `autoDeploy: false` below: prod never ran the pinned
# image, had no licensed fonts, and every merge to main deployed the backend. The owner switched the
# Source back to Existing Image v2026-09-13 on 2026-09-13. Whether the blueprint still syncs this file
# to the service is unverified — read Settings -> Build before assuming this file is what runs.
services:
- type: web
name: gl-r-erp
runtime: image
image:
url: ghcr.io/waritctd/glr-hr-backend:v2026-11-10
creds:
fromRegistryCreds:
name: ghcr-glr
region: singapore # closest Render region to the Supabase ap-northeast-1 DB
plan: starter # free tier is CPU-throttled (slow startup races the port scan) and cold-starts; starter avoids both
# Owner ruling 2026-08-03: auto-deploy off. This is UAT sitting on the same DB as HR/sales
# data people actually rely on -- a push to main should not roll out until someone chooses to
# deploy it. Trigger deploys manually from the Render dashboard (or `render deploys create`).
autoDeploy: false
envVars:
- key: SPRING_PROFILES_ACTIVE
value: prod,demo
# The hosted Render service uses a database that already has demo migrations
# in flyway_schema_history. Keep the location explicit so deploys still
# resolve V21/V32 even if dashboard profile settings drift.
- key: SPRING_FLYWAY_LOCATIONS
value: classpath:db/migration,classpath:db/migration-demo
- key: APP_FLYWAY_ENABLED
value: "true"
# Explicit rather than relying on application-prod.yml's default -- real production (not
# just this showcase) has Flyway history drift too; see that file's comment on
# validate-on-migrate for the specific versions and what re-enabling would require.
- key: APP_FLYWAY_VALIDATE_ON_MIGRATE
value: "false"
# Frontend origin is only needed as a CORS fallback; the Vercel proxy keeps calls
# same-origin so CORS normally isn't exercised.
- key: APP_CORS_ALLOWED_ORIGINS
sync: false
# Secrets — set these in the Render dashboard (sync:false keeps them out of git).
- key: SPRING_DATASOURCE_URL
sync: false
- key: SPRING_DATASOURCE_USERNAME
sync: false
- key: SPRING_DATASOURCE_PASSWORD
sync: false
- key: APP_ATTENDANCE_AGENT_TOKEN
sync: false
- key: APP_UPLOADS_DIR
sync: false
# BOT issues a separate key per API, and BOT's rate limit is 100 calls/hour PER TOKEN -- so
# BotFxFetchService (app.bot.fx-api-token) and BotHolidayFetchService
# (app.bot.holiday-api-token) each need their own key, with no fallback between them. Neither
# was previously declared here, so both fetchers have been silently no-op'ing in this
# environment -- each property defaults to blank and its service logs a WARN/INFO and returns
# rather than throwing, so there is no error, only an unset value and a quiet skip.
- key: BOT_FX_API_TOKEN
sync: false
- key: BOT_HOLIDAY_API_TOKEN
sync: false
# Notification email: transport is pluggable behind th.co.glr.hr.mail.Mailer
# (app.mail.provider). This service must run provider=resend, never provider=smtp — Render
# blocks outbound SMTP on ports 25/465/587, confirmed live twice, most recently on plan
# `starter` with real Gmail credentials, as a TCP connect timeout (not an auth failure), so
# no credential or port change can make SMTP work from here. provider itself is not a
# secret, so it is checked in with a literal value rather than sync:false.
- key: APP_MAIL_PROVIDER
value: resend
- key: APP_MAIL_RESEND_API_KEY
sync: false
# The verified sending address for this service's Resend domain — set by hand on the Render
# dashboard once a real sender exists. Never invented in this file: application.yml's
# APP_MAIL_FROM default is blank (not Resend's sandbox sender), and
# ProductionReadinessConfig now classifies a blank value as REQUIRED whenever
# app.mail.provider=resend/smtp. Because this service runs prod,demo (see
# SPRING_PROFILES_ACTIVE above), a gap here WARNS at boot rather than crash-looping the
# service, the same demo-profile leniency APP_UPLOADS_DIR already has — see
# ProductionReadinessConfig's Javadoc. A real on-prem prod deployment (no demo profile) hard
# -fails on the same gap instead.
#
# ⚠️ INTERIM, owner ruling 2026-08-15: this service sends from the SAME verified Resend
# domain as UAT (glr-trial.lat) until a company domain is verified. Two consequences a
# reader should not have to rediscover:
# - Recipients cannot tell prod mail from UAT mail by SENDER. UAT's " (UAT)" subject
# suffix is the only distinguishing mark, and it is set on the uat service alone.
# - Sender REPUTATION is shared. UAT test traffic marked as spam degrades deliverability
# for real notification and payslip mail from this service, and vice versa.
# The domain was registered 2026-08-05, so it also has very little sending history — a
# first bulk send to personal gmail/hotmail addresses is the case most likely to be
# filtered. Verify a company domain and repoint this when one exists.
- key: APP_MAIL_FROM
sync: false
# The portal URL every notification/payslip email links back to. application.yml has no
# fallback for this either, for the same reason: its old literal fallback was the UAT Vercel
# URL, which would have put every production email's link on the wrong deployment.
- key: APP_MAIL_APP_BASE_URL
sync: false
# Optional. Unset here deliberately — do not invent an address. A Reply-To that resolves to
# a real, monitored mailbox is a positive deliverability signal; set APP_MAIL_REPLY_TO on the
# dashboard to one if/when an operator designates one. Blank = no Reply-To header at all
# (replies fall back to APP_MAIL_FROM, a valid default, not an error state).
- key: APP_MAIL_REPLY_TO
sync: false
# Deliberately NOT declared here: APP_MAIL_OVERRIDE_TO and APP_MAIL_SUBJECT_SUFFIX. Both
# default to blank in application.yml, so this service addresses each real employee directly
# with an unmodified subject line — the owner-confirmed intent for production mail. Setting
# either here would redirect real notifications (leave/OT/commission/attendance, and
# PayslipDistributionService's payslips) to a test inbox or mark them as test mail.
# Shared HR inbox the auto-generated leave-submission email (ใบลา F-HR-020 PDF) is addressed TO
# (requester + manager CC'd). application.yml defaults it to jobs.glr.co.th@gmail.com, so an
# unset var still routes correctly; declared here so an operator can repoint it on the
# dashboard without a rebuild.
- key: APP_LEAVE_SUBMISSION_INBOX
sync: false
# Recipient of the month-end attendance-day count email (last day of month, 12:00 Bangkok).
# Blank = off. Set on the dashboard; the address is deliberately not in this public repo.
- key: APP_ATTENDANCE_MONTHLY_REPORT_TO
sync: false
# CC list for the same email, comma-separated. Blank = no CC. Set on the dashboard.
- key: APP_ATTENDANCE_MONTHLY_REPORT_CC
sync: false