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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
name: Packages
# deb and rpm packages (packaging/linux): build them from a release-style
# binary, install each on a systemd host — the deb on the runner itself, the
# rpm in a Fedora container booted with systemd — and require the rws service
# to start, serve, survive a reload and go away on removal. Windows: run
# rws.exe --windows-service as a real service, serve, stop.
on:
push:
branches:
paths:
pull_request:
paths:
workflow_dispatch:
env:
NFPM_VERSION: 2.47.0
jobs:
linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-musl
- run: sudo apt-get update && sudo apt-get install -y musl-tools
- uses: Swatinem/rust-cache@v2
with:
key: packaging
- name: Build (as the release does)
run: |
cargo build --release --bin rws --features "acme auth mimalloc wasm-plugins" --target x86_64-unknown-linux-musl
- name: Install nfpm
run: |
curl -sSfL "https://github.com/goreleaser/nfpm/releases/download/v$NFPM_VERSION/nfpm_${NFPM_VERSION}_Linux_x86_64.tar.gz" \
| sudo tar xz -C /usr/local/bin nfpm
- name: Throwaway signing key
run: |
gpg --batch --gen-key <<'KEY'
%no-protection
Key-Type: RSA
Key-Length: 3072
Name-Real: rws CI test
Name-Email: ci@example.invalid
Expire-Date: 1d
%commit
KEY
echo "GPG_KEY_ID=$(gpg --list-secret-keys --with-colons | awk -F: '/^fpr/ {print $10; exit}')" >> "$GITHUB_ENV"
- name: Package (as the release does)
run: |
version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name=="rust-web-server") | .version')
gpg --batch --armor --export-secret-keys "$GPG_KEY_ID" > "$RUNNER_TEMP/signing.asc"
RPM_SIGNING_KEY_FILE="$RUNNER_TEMP/signing.asc" \
packaging/build-packages.sh "$version" amd64 target/x86_64-unknown-linux-musl/release/rws packages
ls -l packages
- name: Repositories (as the release does)
run: |
sudo apt-get install -y apt-utils createrepo-c
packaging/build-repos.sh packages site http://localhost:8000
nohup setsid python3 -m http.server 8000 --directory site >/dev/null 2>&1 &
sleep 1 && curl -sSf localhost:8000/apt/dists/stable/InRelease | head -3
- name: deb on the runner (systemd)
run: |
sudo apt-get install -y ./packages/rws_*_amd64.deb
systemctl is-active rws
for i in $(seq 1 20); do curl -sf localhost:7878/healthz && break; sleep 1; done
curl -sSf localhost:7878/ | grep 'rws is running'
test "$(stat -c %U /etc/rws/rws.config.toml)" = root
systemd-analyze security rws.service | tail -1
sudo systemctl reload rws && sleep 1 && systemctl is-active rws
sudo apt-get install -y --reinstall ./packages/rws_*_amd64.deb && systemctl is-active rws
# An upgrade keeps a service the admin disabled disabled.
sudo systemctl disable --now rws
sudo apt-get install -y --reinstall ./packages/rws_*_amd64.deb
! systemctl is-active rws
sudo systemctl enable --now rws
sudo apt-get remove -y rws
! systemctl is-active rws
test -f /etc/rws/rws.config.toml # remove keeps config; purge deletes it
- name: deb from the apt repository
run: |
curl -sSf localhost:8000/apt/rws.gpg | sudo tee /usr/share/keyrings/rws.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/rws.gpg] http://localhost:8000/apt stable main" | sudo tee /etc/apt/sources.list.d/rws.list
sudo apt-get update -o Dir::Etc::sourcelist=/etc/apt/sources.list.d/rws.list -o Dir::Etc::sourceparts=-
sudo apt-get install -y rws
systemctl is-active rws
sudo apt-get purge -y rws
- name: rpm in a Fedora container (systemd)
run: |
docker run -d --name fedora --privileged --cgroupns=host -v /sys/fs/cgroup:/sys/fs/cgroup:rw \
--network host -v "$PWD/packages:/dist:ro" fedora:42 sh -c 'dnf install -y -q systemd curl && exec /usr/lib/systemd/systemd'
for i in $(seq 1 120); do docker exec fedora systemctl is-system-running 2>/dev/null | grep -qE 'running|degraded' && break; sleep 2; done
docker exec fedora sh -c 'rpm --import http://localhost:8000/rpm/rws.asc && rpm -K /dist/*.x86_64.rpm'
docker exec fedora dnf install -y "/dist/$(basename packages/rws-*.x86_64.rpm)"
docker exec fedora systemctl is-active rws
docker exec fedora sh -c 'for i in $(seq 1 20); do curl -sf localhost:7878/healthz && break; sleep 1; done'
docker exec fedora sh -c 'curl -sSf localhost:7878/ | grep "rws is running"'
docker exec fedora dnf remove -y rws
! docker exec fedora systemctl is-active rws
# From the signed repository (gpgcheck and repo_gpgcheck on):
docker exec fedora curl -sSf -o /etc/yum.repos.d/rws.repo http://localhost:8000/rpm/rws.repo
docker exec fedora dnf install -y rws
docker exec fedora systemctl is-active rws
- name: Diagnostics
if: failure()
run: |
sudo journalctl -u rws --no-pager | tail -50 || true
docker exec fedora journalctl -u rws --no-pager | tail -50 || true
- uses: actions/upload-artifact@v4
with:
name: packages
path: packages/*
windows-service:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: ilammy/setup-nasm@v1
- uses: Swatinem/rust-cache@v2
with:
key: windows-service
- run: cargo build --release --bin rws --features "acme auth mimalloc wasm-plugins"
- name: Install, start, serve, stop
shell: pwsh
run: |
$site = Join-Path $env:RUNNER_TEMP "rws-site"
New-Item -ItemType Directory -Force $site | Out-Null
Set-Content (Join-Path $site "index.html") "rws service is running"
$exe = (Resolve-Path target\release\rws.exe).Path
New-Service -Name rws -BinaryPathName "`"$exe`" --windows-service=`"$site`"" -StartupType Manual | Out-Null
Start-Service rws
$body = $null
for ($i = 0; $i -lt 30; $i++) {
try { $body = (Invoke-WebRequest http://localhost:7878/ -UseBasicParsing).Content; break } catch { Start-Sleep 1 }
}
if ($body -notmatch "rws service is running") { throw "unexpected response: $body" }
(Invoke-WebRequest http://localhost:7878/healthz -UseBasicParsing).StatusCode
Stop-Service rws
(Get-Service rws).WaitForStatus("Stopped", "00:00:30")
sc.exe delete rws
- name: Diagnostics
if: failure()
shell: pwsh
run: |
Get-Service rws -ErrorAction SilentlyContinue | Format-List *
Get-WinEvent -LogName System -MaxEvents 20 | Where-Object ProviderName -eq "Service Control Manager" | Format-List TimeCreated, Message
winget:
# packaging/winget manifests for the latest release: winget validates and
# installs them (the PR to microsoft/winget-pkgs is made per release).
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Generate
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
version=$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName -q .tagName)
echo "MANIFEST=$(packaging/winget/generate.sh "$version" "$RUNNER_TEMP/winget")" >> "$GITHUB_ENV"
- name: Validate and install
shell: pwsh
run: |
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
Install-PackageProvider -Name NuGet -Force | Out-Null
Install-Module Microsoft.WinGet.Client -Force -Repository PSGallery | Out-Null
Repair-WinGetPackageManager -AllUsers
}
winget --version
winget validate --manifest $env:MANIFEST
winget settings --enable LocalManifestFiles
winget install --manifest $env:MANIFEST --accept-source-agreements --accept-package-agreements --disable-interactivity
$rws = Join-Path $env:LOCALAPPDATA "Microsoft\WinGet\Links\rws.exe"
& $rws --version
nix:
# packaging/nix/package.nix (the nixpkgs expression) built from this checkout.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v31.11.1
with:
nix_path: nixpkgs=channel:nixos-unstable
- run: nix-build packaging/nix/ci.nix
- run: ./result/bin/rws --version