Skip to content

Commit 35f2e0b

Browse files
authored
Merge pull request #1 from OpenRailAssociation/setup
2 parents db403ee + 8a40b28 commit 35f2e0b

File tree

3 files changed

+371
-0
lines changed

3 files changed

+371
-0
lines changed

.github/workflows/deploy.yml

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
name: Deploy Production and Preview
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
artifact_name:
7+
required: true
8+
type: string
9+
condition_production:
10+
required: true
11+
type: boolean
12+
domain_preview:
13+
required: true
14+
type: string
15+
ssh_host:
16+
required: true
17+
type: string
18+
ssh_user:
19+
required: true
20+
type: string
21+
ssh_port:
22+
required: false
23+
default: 22
24+
type: number
25+
ssh_timeout:
26+
required: false
27+
default: "1m"
28+
type: string
29+
ssh_command_timeout:
30+
required: false
31+
default: "2m"
32+
type: string
33+
ssh_rm:
34+
required: false
35+
default: true
36+
type: boolean
37+
ssh_strip_components:
38+
required: false
39+
default: 1
40+
type: number
41+
dir_base:
42+
required: true
43+
type: string
44+
dir_production:
45+
required: true
46+
type: string
47+
dir_preview_base:
48+
required: true
49+
type: string
50+
dir_preview_subdir:
51+
required: true
52+
type: string
53+
linkchecker_enabled:
54+
required: false
55+
default: true
56+
type: boolean
57+
linkchecker_exclude:
58+
required: false
59+
default: ""
60+
type: string
61+
linkchecker_include_fragments:
62+
required: false
63+
default: "true"
64+
type: string
65+
linkchecker_max_concurrency:
66+
required: false
67+
default: 1
68+
type: number
69+
linkchecker_user_agent:
70+
required: false
71+
default: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
72+
type: string
73+
linkchecker_retry_times:
74+
required: false
75+
default: 5
76+
type: number
77+
linkchecker_fail_on_errors:
78+
required: false
79+
default: false
80+
type: boolean
81+
sticky_comment_enabled:
82+
required: false
83+
default: true
84+
type: boolean
85+
secrets:
86+
ssh_key:
87+
required: true
88+
89+
jobs:
90+
vars:
91+
runs-on: ubuntu-24.04
92+
outputs:
93+
pr_closed_merged: ${{ steps.pr_status.outputs.pr_closed_merged }}
94+
datetime: ${{ steps.datetime.outputs.datetime }}
95+
linkchecker_fragments: ${{ steps.linkchecker_fragments.outputs.linkchecker_fragments }}
96+
linkchecker_exclude: ${{ steps.linkchecker_exclude.outputs.linkchecker_exclude }}
97+
steps:
98+
- id: pr_status
99+
run: echo "pr_closed_merged=${{ github.event.pull_request.closed_at || github.event.pull_request.merged }}" >> "$GITHUB_OUTPUT"
100+
101+
- id: datetime
102+
run: echo "datetime=$(date '+%Y-%m-%d %H:%M:%S %Z')" >> "$GITHUB_OUTPUT"
103+
104+
- id: linkchecker_fragments
105+
run: |
106+
if [ "${{ inputs.linkchecker_include_fragments }}" == 'true' ]; then
107+
echo "linkchecker_fragments=--include-fragments" >> "$GITHUB_OUTPUT"
108+
else
109+
echo "linkchecker_fragments=" >> "$GITHUB_OUTPUT"
110+
fi
111+
112+
- id: linkchecker_exclude
113+
run: |
114+
if [ -n "${{ inputs.linkchecker_exclude }}" ]; then
115+
excludes=$(echo "${{ inputs.linkchecker_exclude }}" | sed 's/,/ --exclude /g; s/^/--exclude /')
116+
echo "linkchecker_exclude=$excludes" >> "$GITHUB_OUTPUT"
117+
else
118+
echo "linkchecker_exclude=" >> "$GITHUB_OUTPUT"
119+
fi
120+
121+
linkchecker:
122+
runs-on: ubuntu-24.04
123+
needs: vars
124+
if: inputs.linkchecker_enabled == true && (needs.vars.outputs.pr_closed_merged == '' || needs.vars.outputs.pr_closed_merged == 'false')
125+
steps:
126+
- name: Download artifact
127+
uses: actions/download-artifact@v4
128+
with:
129+
name: ${{ inputs.artifact_name }}
130+
path: .
131+
132+
- name: Link Checker
133+
uses: lycheeverse/lychee-action@v2
134+
with:
135+
args: >-
136+
-r ${{ inputs.linkchecker_retry_times }}
137+
-u "${{ inputs.linkchecker_user_agent }}"
138+
${{ needs.vars.outputs.linkchecker_fragments }}
139+
${{ needs.vars.outputs.linkchecker_exclude }}
140+
--max-concurrency ${{ inputs.linkchecker_max_concurrency }} .
141+
fail: ${{ inputs.linkchecker_fail_on_errors }}
142+
143+
# Deployment jobs
144+
deploy-production:
145+
runs-on: ubuntu-24.04
146+
needs:
147+
- vars
148+
# Only deploy if production condition is met
149+
if: inputs.condition_production
150+
steps:
151+
- name: Download artifact
152+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
153+
with:
154+
name: ${{ inputs.artifact_name }}
155+
path: ./${{ inputs.artifact_name }}
156+
157+
- name: Copy website to host
158+
uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0
159+
with:
160+
host: ${{ inputs.ssh_host }}
161+
username: ${{ inputs.ssh_user }}
162+
key: ${{ secrets.ssh_key }}
163+
port: ${{ inputs.ssh_port }}
164+
timeout: ${{ inputs.ssh_timeout }}
165+
command_timeout: ${{ inputs.ssh_command_timeout }}
166+
target: ${{ inputs.dir_base }}/${{ inputs.dir_production }}/
167+
source: "${{ inputs.artifact_name }}/*"
168+
rm: ${{ inputs.ssh_rm }}
169+
strip_components: ${{ inputs.ssh_strip_components }}
170+
171+
deploy-preview:
172+
runs-on: ubuntu-24.04
173+
needs:
174+
- vars
175+
# Only deploy if non-closed/non-merged pull request
176+
if: github.event.pull_request && needs.vars.outputs.pr_closed_merged == 'false'
177+
steps:
178+
- name: Download artifact
179+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
180+
with:
181+
name: ${{ inputs.artifact_name }}
182+
path: ./${{ inputs.artifact_name }}
183+
184+
- name: Copy website to host
185+
uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0
186+
with:
187+
host: ${{ inputs.ssh_host }}
188+
username: ${{ inputs.ssh_user }}
189+
key: ${{ secrets.ssh_key }}
190+
port: ${{ inputs.ssh_port }}
191+
timeout: ${{ inputs.ssh_timeout }}
192+
command_timeout: ${{ inputs.ssh_command_timeout }}
193+
target: ${{ inputs.dir_base }}/${{ inputs.dir_preview_base }}/${{ inputs.dir_preview_subdir }}/
194+
source: "${{ inputs.artifact_name }}/*"
195+
rm: ${{ inputs.ssh_rm }}
196+
strip_components: ${{ inputs.ssh_strip_components }}
197+
198+
- name: Inform about Preview URL
199+
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
200+
if: inputs.sticky_comment_enabled == true
201+
with:
202+
header: pr-preview
203+
message: |
204+
Pull Request Live Preview
205+
:---:
206+
| <p><br />:rocket: View preview at <br />https://${{ inputs.domain_preview }}/${{ inputs.dir_preview_subdir }}/<br /><br /></p>
207+
| <p>Last updated: ${{ needs.vars.outputs.datetime }}</p>
208+
209+
remove-preview:
210+
runs-on: ubuntu-24.04
211+
needs:
212+
- vars
213+
# Only deploy if non-closed/non-merged pull request
214+
if: github.event.pull_request && needs.vars.outputs.pr_closed_merged != 'false'
215+
steps:
216+
- name: Create deletion notice
217+
run: mkdir -p ${{ inputs.artifact_name }} && echo "This PR has been closed or merged. The preview has been removed." > ${{ inputs.artifact_name }}/index.html
218+
219+
- name: Copy empty folder to host
220+
uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0
221+
with:
222+
host: ${{ inputs.ssh_host }}
223+
username: ${{ inputs.ssh_user }}
224+
key: ${{ secrets.ssh_key }}
225+
port: ${{ inputs.ssh_port }}
226+
timeout: ${{ inputs.ssh_timeout }}
227+
command_timeout: ${{ inputs.ssh_command_timeout }}
228+
target: ${{ inputs.dir_base }}/${{ inputs.dir_preview_base }}/${{ inputs.dir_preview_subdir }}/
229+
source: "${{ inputs.artifact_name }}/*"
230+
rm: ${{ inputs.ssh_rm }}
231+
strip_components: ${{ inputs.ssh_strip_components }}
232+
233+
- name: Inform about preview removal
234+
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
235+
if: inputs.sticky_comment_enabled == true
236+
with:
237+
header: pr-preview
238+
message: |
239+
Pull Request Live Preview
240+
:---:
241+
| <p><br />:x: Preview has been removed.<br /><br /></p>

.github/workflows/test.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Test Deploy Action
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
push:
7+
branches:
8+
- main
9+
10+
jobs:
11+
build:
12+
name: Build Website (Placeholder)
13+
runs-on: ubuntu-24.04
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v4
17+
18+
- name: Build website
19+
run: |
20+
echo "Simulating build..."
21+
mkdir -p dist
22+
echo "<html><body><p>Hello world: <a href="https://openrailassociation.org">LINK</a></p><p>${{ github.head_ref }}.${{ github.event.pull_request.head.sha }}</p></body></html>" > dist/index.html
23+
24+
- name: Upload artifact
25+
uses: actions/upload-artifact@v4
26+
with:
27+
name: website
28+
path: dist
29+
30+
deploy:
31+
name: Call Reusable Deploy Workflow
32+
needs: build
33+
uses: ./.github/workflows/deploy.yml
34+
with:
35+
artifact_name: website
36+
domain_preview: web-preview.openrailassociation.org
37+
ssh_host: uberspace1.openrailassociation.org
38+
ssh_user: openrail
39+
dir_base: "/var/www/virtual/openrail"
40+
dir_production: "web-deployment-action.openrailassociation.org"
41+
dir_preview_base: "web-preview.openrailassociation.org"
42+
dir_preview_subdir: "webdeploy-pr-${{ github.event.number }}"
43+
linkchecker_exclude: "example.com,example.org"
44+
condition_production: ${{ github.ref == 'refs/heads/main' }}
45+
secrets:
46+
ssh_key: ${{ secrets.SSH_PRIVATE_KEY }}

action.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: "Deploy production and preview website"
2+
description: "Deploys the production and preview website using GitHub Actions to an external webspace (e.g., Uberspace)."
3+
author: "OpenRail Association"
4+
5+
inputs:
6+
artifact_name:
7+
description: "The name of the artifact to deploy, e.g. 'website'"
8+
required: true
9+
domain_preview:
10+
description: "The domain for the preview deployment, e.g. 'preview.example.com'"
11+
required: true
12+
ssh_host:
13+
description: "The SSH host for the deployment, e.g. 'ssh.example.com'"
14+
required: true
15+
ssh_user:
16+
description: "The SSH user for the deployment, e.g. 'deploy'"
17+
required: true
18+
ssh_key:
19+
description: "The SSH private key for the deployment, e.g. '${{ secrets.SSH_PRIVATE_KEY }}'"
20+
required: true
21+
ssh_port:
22+
description: "The SSH port for the deployment, e.g. '22'"
23+
required: false
24+
default: "22"
25+
ssh_timeout:
26+
description: "The SSH connection timeout in seconds. Defaults to 1m."
27+
required: false
28+
default: "1m"
29+
ssh_command_timeout:
30+
description: "The SSH command timeout in seconds. Defaults to 2m."
31+
required: false
32+
default: "2m"
33+
ssh_rm:
34+
description: "Whether to remove the remote directory before deployment. Defaults to true."
35+
required: false
36+
default: "true"
37+
ssh_strip_components:
38+
description: "The number of leading components to strip from the source directory during deployment. Defaults to 1."
39+
required: false
40+
default: "1"
41+
dir_base:
42+
description: "The SSH virtual base directory for all deployments, e.g. '/var/www/'"
43+
required: true
44+
dir_production:
45+
description: "The SSH production directory for the deployment inside $dir_base, e.g. 'html'"
46+
required: true
47+
dir_preview_base:
48+
description: "The base directory for the preview deployment inside $dir_base, e.g. 'preview'"
49+
required: true
50+
dir_preview_subdir:
51+
description: "The segment for the preview deployment inside $dir_preview_base, e.g. 'pr-${{ github.event.number }}'"
52+
required: true
53+
linkchecker_enabled:
54+
description: "Whether to enable the link checker step. Defaults to true."
55+
required: false
56+
default: "true"
57+
linkchecker_exclude:
58+
description: "A comma-separated list of URLs to exclude from the link checker. Defaults to an empty string."
59+
required: false
60+
default: ""
61+
linkchecker_include_fragments:
62+
description: "Whether to include fragments in the link checker. Defaults to true."
63+
required: false
64+
default: "true"
65+
linkchecker_max_concurrency:
66+
description: "The maximum number of concurrent requests for the link checker. Defaults to 1."
67+
required: false
68+
default: "1"
69+
linkchecker_user_agent:
70+
description: "The user agent string for the link checker. Defaults to a common browser user agent."
71+
required: false
72+
default: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Apple WebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
73+
linkchecker_retry_times:
74+
description: "The time in seconds to wait for a retry for failed links in the link checker. Defaults to 5."
75+
required: false
76+
default: "5"
77+
linkchecker_fail_on_errors:
78+
description: "Whether to fail the action on link checker errors. Defaults to false."
79+
required: false
80+
default: "false"
81+
82+
runs:
83+
using: "workflow"
84+
main: ".github/workflows/deploy.yml"

0 commit comments

Comments
 (0)