ci.yml 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. name: UNA CI
  2. on:
  3. push:
  4. branches: [ "master" ]
  5. pull_request:
  6. branches: [ "master" ]
  7. types: [opened, synchronize, reopened, closed]
  8. permissions:
  9. contents: read
  10. pull-requests: write
  11. # A newer push to a PR, or closing it, cancels that PR's run in progress, so the teardown can't
  12. # race a preview deploy that is still creating the environment. Master pushes never cancel.
  13. concurrency:
  14. group: ci-${{ github.event.pull_request.number && format('pr-{0}', github.event.pull_request.number) || github.sha }}
  15. cancel-in-progress: ${{ github.event_name == 'pull_request' }}
  16. jobs:
  17. dependencies:
  18. # Composer (plugins/) and Yarn (plugins_public/, via the postinstall build) are the same
  19. # tree for every PHP version, so they are installed once and the test legs download them.
  20. name: Install dependencies
  21. if: github.event_name == 'push' || github.event.action != 'closed'
  22. runs-on: ubuntu-latest
  23. steps:
  24. - uses: actions/checkout@v4
  25. # WARNING: after `composer update` or `composer upgrade`, replace this commit SHA
  26. # with the current shivammathur/setup-php release. Both Setup PHP steps must match.
  27. - name: Setup PHP
  28. uses: shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962 # 2.40.0
  29. with:
  30. php-version: '8.5'
  31. extensions: intl, curl, gd, mbstring, zip, exif, pdo_mysql
  32. ini-values: short_open_tag=On, memory_limit=256M
  33. tools: composer
  34. - name: Setup Node
  35. uses: actions/setup-node@v4
  36. with:
  37. node-version: '24'
  38. cache: yarn
  39. - name: Install PHP dependencies
  40. run: composer install --prefer-dist --no-progress --no-dev
  41. - name: Install JS dependencies
  42. run: yarn install --frozen-lockfile
  43. - name: Upload dependencies
  44. uses: actions/upload-artifact@v4
  45. with:
  46. name: una-dependencies
  47. retention-days: 1
  48. include-hidden-files: true
  49. if-no-files-found: error
  50. path: |
  51. plugins
  52. plugins_public
  53. test:
  54. # Units and Integration each get a runner per PHP version. Deploy waits on every leg, and a
  55. # failure in one does not cancel the other. The package is built only on the PHP 8.5 Units
  56. # leg so it is uploaded once.
  57. name: ${{ matrix.suite == 'Units' && 'Unit tests' || 'Integration tests' }} (PHP ${{ matrix.php }})
  58. needs: dependencies
  59. if: github.event_name == 'push' || github.event.action != 'closed'
  60. runs-on: ubuntu-latest
  61. strategy:
  62. fail-fast: false
  63. matrix:
  64. suite: [Units, Integration]
  65. php: ['8.3', '8.4', '8.5']
  66. services:
  67. mysql:
  68. image: mariadb:11
  69. env:
  70. MYSQL_ROOT_PASSWORD: root
  71. MYSQL_DATABASE: una
  72. MYSQL_USER: una
  73. MYSQL_PASSWORD: una
  74. ports:
  75. - 3306:3306
  76. options: >-
  77. --health-cmd="healthcheck.sh --connect --innodb_initialized"
  78. --health-interval=10s
  79. --health-timeout=5s
  80. --health-retries=10
  81. steps:
  82. - uses: actions/checkout@v4
  83. # WARNING: after `composer update` or `composer upgrade`, replace this commit SHA
  84. # with the current shivammathur/setup-php release. Both Setup PHP steps must match.
  85. - name: Setup PHP
  86. uses: shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962 # 2.40.0
  87. with:
  88. php-version: ${{ matrix.php }}
  89. extensions: intl, curl, gd, mbstring, zip, exif, pdo_mysql
  90. ini-values: short_open_tag=On, memory_limit=256M
  91. tools: phing, phpunit:12
  92. - name: Download dependencies
  93. uses: actions/download-artifact@v4
  94. with:
  95. name: una-dependencies
  96. - name: Prepare writable paths
  97. run: |
  98. mkdir -p cache cache_public logs tmp storage
  99. chmod a+rwX inc cache cache_public logs tmp storage
  100. test -f plugins/ffmpeg/ffmpeg.exe && chmod 0755 plugins/ffmpeg/ffmpeg.exe
  101. - name: Install UNA
  102. working-directory: install
  103. run: >
  104. php cmd.php
  105. -m bx_antispam
  106. --db_host=127.0.0.1
  107. --db_name=una
  108. --db_user=una
  109. --db_password=una
  110. --server_http_host=localhost
  111. --server_php_self=/install/index.php
  112. - name: Run PHPUnit
  113. run: phpunit -c tests/phpunit.xml --testsuite "${{ matrix.suite }}"
  114. - name: Build package
  115. if: success() && matrix.suite == 'Units' && matrix.php == '8.5'
  116. run: phing package -Dbuild=${{ github.run_number }}
  117. - name: Upload package artifact
  118. if: success() && matrix.suite == 'Units' && matrix.php == '8.5'
  119. uses: actions/upload-artifact@v4
  120. with:
  121. name: una-package
  122. path: packages/*.zip
  123. if-no-files-found: error
  124. preview-env:
  125. # Same-repo PRs only: forks and Dependabot get no secrets, and Dependabot PRs need no preview.
  126. name: Prepare PR environment
  127. if: >-
  128. github.event_name == 'pull_request' && github.event.action != 'closed'
  129. && github.event.pull_request.head.repo.full_name == github.repository
  130. && github.event.pull_request.user.login != 'dependabot[bot]'
  131. runs-on: ubuntu-latest
  132. env:
  133. RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN }}
  134. RAILWAY_PROJECT_ID: ${{ secrets.RAILWAY_PROJECT_ID }}
  135. RAILWAY_BASE_ENVIRONMENT: ${{ secrets.RAILWAY_ENVIRONMENT }}
  136. RAILWAY_TEMPLATE_ENVIRONMENT: pr-template
  137. RAILWAY_PR_ENVIRONMENT: pr-${{ github.event.pull_request.number }}
  138. # unacms.app hosts: UNA (service una) and the NEO web client (service neo), both on Railway
  139. VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
  140. VERCEL_TEAM: yasko
  141. PREVIEW_API_HOST: api-pr-${{ github.event.pull_request.number }}.unacms.app
  142. PREVIEW_CLIENT_HOST: pr-${{ github.event.pull_request.number }}.unacms.app
  143. UNA_PREVIEW_API_KEY: ${{ secrets.UNA_PREVIEW_API_KEY }}
  144. outputs:
  145. domains: ${{ steps.domains.outputs.ok }}
  146. steps:
  147. - uses: actions/checkout@v4
  148. with:
  149. sparse-checkout: |
  150. .github/actions
  151. scripts/railway
  152. - uses: ./.github/actions/railway-cli
  153. - name: Provision PR environment
  154. run: |
  155. if [ -z "$RAILWAY_API_TOKEN" ] || [ -z "$RAILWAY_PROJECT_ID" ]; then
  156. echo "Set secrets RAILWAY_API_TOKEN (account token) and RAILWAY_PROJECT_ID" >&2
  157. exit 1
  158. fi
  159. base="${RAILWAY_BASE_ENVIRONMENT:-production}"
  160. template="${RAILWAY_TEMPLATE_ENVIRONMENT:-pr-template}"
  161. railway link --project "$RAILWAY_PROJECT_ID" --environment "$base" --json
  162. # Railway services are project-scoped: mariadb, una and neo already exist, so `railway add`
  163. # cannot create them again and a plain new environment holds no instances of them
  164. # (every preview after the first failed this way). A PR environment therefore gets
  165. # its instances by duplicating the template environment. An environment left over
  166. # from a failed run has no una instance; recreate it.
  167. if ! railway service status --service una --environment "$RAILWAY_PR_ENVIRONMENT" >/dev/null 2>&1; then
  168. railway environment delete "$RAILWAY_PR_ENVIRONMENT" -y >/dev/null 2>&1 || true
  169. railway environment new "$RAILWAY_PR_ENVIRONMENT" --duplicate "$template" --json
  170. # a duplicate may carry the template's deployments; the builds below replace them
  171. railway down --service una --environment "$RAILWAY_PR_ENVIRONMENT" -y >/dev/null 2>&1 || true
  172. railway down --service neo --environment "$RAILWAY_PR_ENVIRONMENT" -y >/dev/null 2>&1 || true
  173. fi
  174. railway link --project "$RAILWAY_PROJECT_ID" --environment "$RAILWAY_PR_ENVIRONMENT" --json
  175. # An environment duplicated before the neo service existed lacks it.
  176. scripts/railway/neo.sh instance "$RAILWAY_PR_ENVIRONMENT"
  177. # The duplicated una instance is what `railway up` deploys into. Setting its variables
  178. # doubles as the readiness check: it fails with "ServiceInstance not found" until it exists.
  179. for i in $(seq 1 12); do
  180. railway variable set --service una --environment "$RAILWAY_PR_ENVIRONMENT" --skip-deploys \
  181. MYSQLHOST=mariadb.railway.internal \
  182. MYSQLPORT=3306 \
  183. MYSQLDATABASE=una \
  184. MYSQLUSER=una \
  185. MYSQLPASSWORD=una \
  186. UNA_DEMO_SEED=1 \
  187. UNA_CLIENT_URL="https://$PREVIEW_CLIENT_HOST" \
  188. ${UNA_PREVIEW_API_KEY:+UNA_API_KEY="$UNA_PREVIEW_API_KEY"} \
  189. && break
  190. sleep 5
  191. done
  192. railway domain --service una --environment "$RAILWAY_PR_ENVIRONMENT" || true
  193. railway domain --service neo --environment "$RAILWAY_PR_ENVIRONMENT" || true
  194. # NEO talks to this PR's UNA through its Railway domain, which has a certificate from
  195. # the start (the api-pr host gets one minutes later). UNA_AUTO_HOSTNAME makes UNA build
  196. # its URLs from that host.
  197. una_host=$(railway domain list --service una --environment "$RAILWAY_PR_ENVIRONMENT" --json | jq -r '.domains[] | select(.type == "service") | .domain' | head -n1)
  198. [ -n "$una_host" ] || { echo "No Railway domain for una" >&2; exit 1; }
  199. scripts/railway/neo.sh vars "$RAILWAY_PR_ENVIRONMENT" "https://${una_host#https://}" "https://$PREVIEW_CLIENT_HOST"
  200. - name: Preview domains on unacms.app
  201. id: domains
  202. if: env.VERCEL_TOKEN != ''
  203. run: |
  204. scripts/railway/domains.sh railway-domain "$PREVIEW_API_HOST" "$RAILWAY_PR_ENVIRONMENT" una
  205. # Until 2026-10 the client host was a Vercel alias of the neo-ci build; drop it if present.
  206. scripts/railway/domains.sh client-unalias "$PREVIEW_CLIENT_HOST"
  207. scripts/railway/domains.sh railway-domain "$PREVIEW_CLIENT_HOST" "$RAILWAY_PR_ENVIRONMENT" neo
  208. echo "ok=1" >> "$GITHUB_OUTPUT"
  209. deploy:
  210. name: Deploy PR to Railway (una)
  211. needs: [test, preview-env]
  212. runs-on: ubuntu-latest
  213. env:
  214. RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN }}
  215. RAILWAY_PROJECT_ID: ${{ secrets.RAILWAY_PROJECT_ID }}
  216. RAILWAY_PR_ENVIRONMENT: pr-${{ github.event.pull_request.number }}
  217. steps:
  218. - uses: actions/checkout@v4
  219. - uses: actions/download-artifact@v4
  220. with:
  221. name: una-package
  222. path: packages
  223. - name: Prepare artefact
  224. run: |
  225. mkdir -p deploy
  226. unzip -q packages/*.zip -d /tmp/una-pkg
  227. pkg_dir=$(find /tmp/una-pkg -mindepth 1 -maxdepth 1 -type d | head -n 1)
  228. cp -a "$pkg_dir"/. deploy/
  229. cp scripts/railway/Dockerfile scripts/railway/entrypoint.sh scripts/railway/railway.toml scripts/railway/.railwayignore \
  230. scripts/railway/configure.php scripts/railway/seed.php deploy/
  231. chmod +x deploy/entrypoint.sh
  232. - uses: ./.github/actions/railway-cli
  233. - name: Deploy artefact
  234. run: railway up ./deploy --path-as-root --no-gitignore --verbose --ci --service una --environment "$RAILWAY_PR_ENVIRONMENT" --project "$RAILWAY_PROJECT_ID"
  235. deploy-neo:
  236. # NEO isn't covered by `test`, so it builds alongside it.
  237. name: Deploy PR to Railway (neo)
  238. needs: preview-env
  239. runs-on: ubuntu-latest
  240. env:
  241. RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN }}
  242. RAILWAY_PROJECT_ID: ${{ secrets.RAILWAY_PROJECT_ID }}
  243. RAILWAY_PR_ENVIRONMENT: pr-${{ github.event.pull_request.number }}
  244. steps:
  245. - uses: actions/checkout@v4
  246. - uses: ./.github/actions/railway-cli
  247. - name: Deploy NEO
  248. run: |
  249. scripts/railway/neo.sh stage /tmp/deploy-neo
  250. railway up /tmp/deploy-neo --path-as-root --no-gitignore --verbose --ci --service neo --environment "$RAILWAY_PR_ENVIRONMENT" --project "$RAILWAY_PROJECT_ID"
  251. preview-comment:
  252. name: Comment preview URLs
  253. needs: [preview-env, deploy, deploy-neo]
  254. if: always() && needs.preview-env.result == 'success'
  255. runs-on: ubuntu-latest
  256. env:
  257. RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN }}
  258. RAILWAY_PROJECT_ID: ${{ secrets.RAILWAY_PROJECT_ID }}
  259. RAILWAY_PR_ENVIRONMENT: pr-${{ github.event.pull_request.number }}
  260. PREVIEW_API_HOST: api-pr-${{ github.event.pull_request.number }}.unacms.app
  261. PREVIEW_CLIENT_HOST: pr-${{ github.event.pull_request.number }}.unacms.app
  262. DOMAINS_OK: ${{ needs.preview-env.outputs.domains }}
  263. UNA_RESULT: ${{ needs.deploy.result }}
  264. NEO_RESULT: ${{ needs.deploy-neo.result }}
  265. GH_TOKEN: ${{ github.token }}
  266. PR_NUMBER: ${{ github.event.pull_request.number }}
  267. steps:
  268. - uses: actions/checkout@v4
  269. with:
  270. sparse-checkout: .github/actions
  271. - uses: ./.github/actions/railway-cli
  272. - name: Comment preview URLs on PR
  273. run: |
  274. railway link --project "$RAILWAY_PROJECT_ID" --environment "$RAILWAY_PR_ENVIRONMENT" --json >/dev/null
  275. service_url() {
  276. local host
  277. host=$(railway domain list --service "$1" --environment "$RAILWAY_PR_ENVIRONMENT" --json | jq -r '.domains[] | select(.type == "service") | .domain' | head -n1)
  278. [ -n "$host" ] && echo "https://${host#https://}"
  279. }
  280. una_url=$(service_url una || true)
  281. neo_url=$(service_url neo || true)
  282. state() { [ "$1" = success ] && echo "" || echo " (deploy: $1)"; }
  283. marker="<!-- railway-preview -->"
  284. if [ "$DOMAINS_OK" = "1" ]; then
  285. body=$(printf '%s\n**Preview**: https://%s (NEO web client%s, also %s)\n**API**: https://%s (UNA%s, also %s)\n\nRailway environment: `%s`. A new custom domain needs a few minutes for its certificate, and a fresh UNA install about 10.' \
  286. "$marker" "$PREVIEW_CLIENT_HOST" "$(state "$NEO_RESULT")" "$neo_url" "$PREVIEW_API_HOST" "$(state "$UNA_RESULT")" "$una_url" "$RAILWAY_PR_ENVIRONMENT")
  287. else
  288. body=$(printf '%s\n**Preview**: %s (NEO web client%s)\n**API**: %s (UNA%s)\n\nRailway environment: `%s`' \
  289. "$marker" "$neo_url" "$(state "$NEO_RESULT")" "$una_url" "$(state "$UNA_RESULT")" "$RAILWAY_PR_ENVIRONMENT")
  290. fi
  291. comment_id=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" --paginate \
  292. --jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | contains(\"${marker}\"))) | .id" \
  293. | head -n1)
  294. if [ -n "$comment_id" ]; then
  295. gh api --method PATCH "repos/${GITHUB_REPOSITORY}/issues/comments/${comment_id}" -f body="$body"
  296. else
  297. gh pr comment "$PR_NUMBER" --body "$body"
  298. fi
  299. deploy-production:
  300. # Runs next to the Jenkins deploy to ci.una.io, which is unchanged.
  301. name: Deploy master to Railway (api.unacms.app)
  302. needs: test
  303. if: github.event_name == 'push' && github.ref == 'refs/heads/master'
  304. runs-on: ubuntu-latest
  305. concurrency:
  306. group: railway-production
  307. cancel-in-progress: false
  308. environment:
  309. name: production
  310. url: https://api.unacms.app
  311. env:
  312. RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN }}
  313. RAILWAY_PROJECT_ID: ${{ secrets.RAILWAY_PROJECT_ID }}
  314. RAILWAY_ENVIRONMENT: production
  315. VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
  316. VERCEL_TEAM: yasko
  317. UNA_API_KEY: ${{ secrets.UNA_API_KEY }}
  318. steps:
  319. - uses: actions/checkout@v4
  320. - uses: actions/download-artifact@v4
  321. with:
  322. name: una-package
  323. path: packages
  324. - name: Prepare artefact
  325. run: |
  326. mkdir -p deploy
  327. unzip -q packages/*.zip -d /tmp/una-pkg
  328. pkg_dir=$(find /tmp/una-pkg -mindepth 1 -maxdepth 1 -type d | head -n 1)
  329. cp -a "$pkg_dir"/. deploy/
  330. cp scripts/railway/Dockerfile scripts/railway/entrypoint.sh scripts/railway/railway.toml scripts/railway/.railwayignore \
  331. scripts/railway/configure.php scripts/railway/seed.php deploy/
  332. chmod +x deploy/entrypoint.sh
  333. - uses: ./.github/actions/railway-cli
  334. - name: Configure production
  335. run: |
  336. if [ -z "$RAILWAY_API_TOKEN" ] || [ -z "$RAILWAY_PROJECT_ID" ]; then
  337. echo "Set secrets RAILWAY_API_TOKEN (account token) and RAILWAY_PROJECT_ID" >&2
  338. exit 1
  339. fi
  340. railway link --project "$RAILWAY_PROJECT_ID" --environment "$RAILWAY_ENVIRONMENT" --json
  341. # Database credentials and UNA_HASH_SECRET live in the Railway environment, not here.
  342. railway variable set --service una --environment "$RAILWAY_ENVIRONMENT" --skip-deploys \
  343. UNA_CLIENT_URL=https://unacms.app \
  344. ${UNA_API_KEY:+UNA_API_KEY="$UNA_API_KEY"}
  345. - name: Deploy artefact
  346. run: railway up ./deploy --path-as-root --no-gitignore --verbose --ci --service una --environment "$RAILWAY_ENVIRONMENT" --project "$RAILWAY_PROJECT_ID"
  347. - name: api.unacms.app DNS
  348. if: env.VERCEL_TOKEN != ''
  349. run: scripts/railway/domains.sh railway-domain api.unacms.app "$RAILWAY_ENVIRONMENT" una
  350. - name: Smoke test
  351. # A fresh production install takes about 4 minutes; allow 10.
  352. run: |
  353. for i in $(seq 1 60); do
  354. code=$(curl -s -o /dev/null -w '%{http_code}' https://api.unacms.app/ || true)
  355. [ "$code" = "200" ] && { echo "api.unacms.app is up"; exit 0; }
  356. # Until the certificate is issued, check the Railway hostname instead.
  357. host=$(railway domain list --service una --environment "$RAILWAY_ENVIRONMENT" --json | jq -r '.domains[] | select(.type == "service") | .domain' | head -n1)
  358. if [ -n "$host" ] && [ "$(curl -s -o /dev/null -w '%{http_code}' "https://${host#https://}/" || true)" = "200" ]; then
  359. echo "up at https://$host (api.unacms.app answered $code; certificate may still be pending)"
  360. exit 0
  361. fi
  362. sleep 10
  363. done
  364. echo "UNA did not come up" >&2
  365. exit 1
  366. deploy-production-neo:
  367. # The NEO web client for unacms.app. NEO_PRODUCTION_HOST (a repository variable) is the
  368. # public host; until it is set, the client is only on its Railway domain.
  369. name: Deploy master to Railway (neo)
  370. if: github.event_name == 'push' && github.ref == 'refs/heads/master'
  371. runs-on: ubuntu-latest
  372. concurrency:
  373. group: railway-production-neo
  374. cancel-in-progress: false
  375. environment:
  376. name: production-neo
  377. url: ${{ steps.urls.outputs.app_url }}
  378. env:
  379. RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN }}
  380. RAILWAY_PROJECT_ID: ${{ secrets.RAILWAY_PROJECT_ID }}
  381. RAILWAY_ENVIRONMENT: production
  382. VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
  383. VERCEL_TEAM: yasko
  384. NEO_PRODUCTION_HOST: ${{ vars.NEO_PRODUCTION_HOST }}
  385. steps:
  386. - uses: actions/checkout@v4
  387. - uses: ./.github/actions/railway-cli
  388. - name: Configure neo
  389. id: urls
  390. run: |
  391. railway link --project "$RAILWAY_PROJECT_ID" --environment "$RAILWAY_ENVIRONMENT" --json
  392. railway domain --service neo --environment "$RAILWAY_ENVIRONMENT" || true
  393. neo_host=$(railway domain list --service neo --environment "$RAILWAY_ENVIRONMENT" --json | jq -r '.domains[] | select(.type == "service") | .domain' | head -n1)
  394. neo_host=${neo_host#https://}
  395. [ -n "$neo_host" ] || { echo "No Railway domain for neo" >&2; exit 1; }
  396. app_url="https://${NEO_PRODUCTION_HOST:-$neo_host}"
  397. scripts/railway/neo.sh vars "$RAILWAY_ENVIRONMENT" https://api.unacms.app "$app_url"
  398. echo "app_url=$app_url" >> "$GITHUB_OUTPUT"
  399. echo "neo_url=https://$neo_host" >> "$GITHUB_OUTPUT"
  400. - name: Deploy NEO
  401. run: |
  402. scripts/railway/neo.sh stage /tmp/deploy-neo
  403. railway up /tmp/deploy-neo --path-as-root --no-gitignore --verbose --ci --service neo --environment "$RAILWAY_ENVIRONMENT" --project "$RAILWAY_PROJECT_ID"
  404. - name: Production host DNS
  405. if: env.NEO_PRODUCTION_HOST != '' && env.VERCEL_TOKEN != ''
  406. run: scripts/railway/domains.sh railway-domain "$NEO_PRODUCTION_HOST" "$RAILWAY_ENVIRONMENT" neo
  407. - name: Smoke test
  408. env:
  409. NEO_URL: ${{ steps.urls.outputs.neo_url }}
  410. run: |
  411. for i in $(seq 1 30); do
  412. code=$(curl -s -o /dev/null -w '%{http_code}' "$NEO_URL/" || true)
  413. [ "$code" = "200" ] && { echo "NEO is up at $NEO_URL"; exit 0; }
  414. sleep 10
  415. done
  416. echo "NEO did not answer 200 at $NEO_URL (last: $code)" >&2
  417. exit 1
  418. teardown:
  419. name: Remove PR Railway environment
  420. if: >-
  421. github.event_name == 'pull_request' && github.event.action == 'closed'
  422. && github.event.pull_request.head.repo.full_name == github.repository
  423. && github.event.pull_request.user.login != 'dependabot[bot]'
  424. runs-on: ubuntu-latest
  425. env:
  426. RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN }}
  427. RAILWAY_PROJECT_ID: ${{ secrets.RAILWAY_PROJECT_ID }}
  428. RAILWAY_BASE_ENVIRONMENT: ${{ secrets.RAILWAY_ENVIRONMENT }}
  429. RAILWAY_PR_ENVIRONMENT: pr-${{ github.event.pull_request.number }}
  430. VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
  431. VERCEL_TEAM: yasko
  432. PREVIEW_API_HOST: api-pr-${{ github.event.pull_request.number }}.unacms.app
  433. PREVIEW_CLIENT_HOST: pr-${{ github.event.pull_request.number }}.unacms.app
  434. steps:
  435. - uses: actions/checkout@v4
  436. with:
  437. sparse-checkout: |
  438. .github/actions
  439. scripts/railway
  440. - name: Remove preview domains
  441. if: env.VERCEL_TOKEN != ''
  442. continue-on-error: true
  443. run: |
  444. scripts/railway/domains.sh client-unalias "$PREVIEW_CLIENT_HOST"
  445. scripts/railway/domains.sh dns-remove "$PREVIEW_CLIENT_HOST"
  446. scripts/railway/domains.sh dns-remove "$PREVIEW_API_HOST"
  447. - uses: ./.github/actions/railway-cli
  448. - name: Delete PR environment
  449. run: |
  450. if [ -z "$RAILWAY_API_TOKEN" ] || [ -z "$RAILWAY_PROJECT_ID" ]; then
  451. echo "Set secrets RAILWAY_API_TOKEN (account token) and RAILWAY_PROJECT_ID" >&2
  452. exit 1
  453. fi
  454. base="${RAILWAY_BASE_ENVIRONMENT:-production}"
  455. railway link --project "$RAILWAY_PROJECT_ID" --environment "$base" --json
  456. # The run for the PR's last push is cancelled above (workflow concurrency), so nothing
  457. # can create the environment after this. A PR closed before its first deploy has none.
  458. if railway environment delete "$RAILWAY_PR_ENVIRONMENT" -y; then
  459. echo "Deleted $RAILWAY_PR_ENVIRONMENT"
  460. else
  461. echo "No environment $RAILWAY_PR_ENVIRONMENT to delete"
  462. fi