docs/sympl.md

1
# Serving Mercurial repositories on Sympl, from scratch
2
 
3
This walk-through sets up hgweb-static on a [Sympl](https://sympl.io/)
4
server (Debian 12 / bookworm). The result: `https://code.example.com`
5
serves a static site (repo index, rendered READMEs, highlighted source at
6
tip), while `hg clone` works normally.
7
 
8
Pushes happen over ssh and regenerate the site via a hook.
9
 
10
Throughout, replace `code.example.com` with your domain. Global pieces
11
(packages, scripts, the systemd template, the hook) are installed once;
12
everything domain-specific lives under `/srv/code.example.com/` in the
13
usual Sympl way, so several domains on one machine can each have their own
14
repos. Run the commands as the `sympl` user.
15
 
16
## Assumptions
17
 
18
- The domain already exists in Sympl (`/srv/code.example.com/` with a
19
  working website, DNS and SSL), and you can ssh in as `sympl`.
20
- This repository is checked out on the server (any location).
21
 
22
## 1. Packages (global)
23
 
24
Everything comes from Debian:
25
 
26
```sh
27
sudo apt install mercurial gunicorn python3-pygments python3-markdown-it python3-docutils
28
```
29
 
30
pygments/markdown/docutils are optional (highlighting and README
31
rendering); hgstatic degrades gracefully without them.
32
 
33
## 2. Install hgstatic and hgweb (global)
34
 
35
From this repository's checkout:
36
 
37
```sh
38
sudo install -m 755 hgstatic.py /usr/local/bin/hgstatic
39
sudo install -D -m 644 examples/hgweb_wsgi.py /usr/local/lib/hgweb/hgweb_wsgi.py
40
sudo install -m 644 examples/hgweb@.service /etc/systemd/system/hgweb@.service
41
sudo systemctl daemon-reload
42
sudo a2enmod rewrite proxy proxy_http
43
```
44
 
45
`hgweb@.service` is a template: `hgweb@code.example.com` reads that
46
domain's `config/hgweb.env`, so one unit file serves all domains.
47
 
48
## 3. The push hook (global)
49
 
50
Create `/etc/mercurial/hgrc.d/hgstatic.rc`:
51
 
52
```ini
53
[hooks]
54
changegroup.hgstatic = /usr/local/bin/hgstatic --only "$PWD"
55
```
56
 
57
Hooks run with the repo as working directory; hgstatic finds the owning
58
domain's `hgstatic.ini` by walking up from the repo (checking `config/`
59
subdirectories too). A changegroup in any repo that has no `hgstatic.ini`
60
above it (e.g. a clone in your home directory pulling changes) is silently
61
ignored, so this global hook is safe.
62
 
63
## 4. Repositories (per domain)
64
 
65
```sh
66
mkdir /srv/code.example.com/hg-repos
67
hg init /srv/code.example.com/hg-repos/myproject
68
```
69
 
70
Give each repo a one-line description for the index page, in e.g.
71
`/srv/code.example.com/hg-repos/myproject/.hg/hgrc`:
72
 
73
```ini
74
[web]
75
description = What myproject is
76
```
77
 
78
(For existing repos, `hg push ssh://sympl@code.example.com//srv/code.example.com/hg-repos/myproject`
79
from wherever they live now.)
80
 
81
## 5. Domain configuration (per domain)
82
 
83
Three small files in `/srv/code.example.com/config/`.
84
 
85
`hgstatic.ini`: the static site generator's settings:
86
 
87
```ini
88
[hgstatic]
89
root = /srv/code.example.com/hg-repos
90
out = /srv/code.example.com/public/htdocs
91
clone-base = https://code.example.com
92
title = code.example.com
93
```
94
 
95
`hgweb.config`: the wire-protocol hgweb (no push over HTTP, no
96
on-the-fly archives):
97
 
98
```ini
99
[paths]
100
/ = /srv/code.example.com/hg-repos/**
101
 
102
[web]
103
allow-push =
104
allow-archive =
105
```
106
 
107
`hgweb.env`: instance settings for the systemd template. Each domain
108
needs its own port; use 8420 for the first, 8421 for the next, and so on:
109
 
110
```sh
111
HGWEB_CONFIG=/srv/code.example.com/config/hgweb.config
112
PORT=8420
113
```
114
 
115
Then start it and check it speaks the protocol:
116
 
117
```sh
118
sudo systemctl enable --now hgweb@code.example.com
119
curl -s "http://127.0.0.1:8420/myproject?cmd=capabilities" | head -c 80; echo
120
```
121
 
122
## 6. Apache routing (per domain)
123
 
124
Sympl includes `/srv/code.example.com/config/apache.d/*.conf` inside the
125
domain's generated vhost, so no vhost editing is needed. Create
126
`config/apache.d/hgweb.conf` there (the port must match `hgweb.env`):
127
 
128
```apache
129
# Mercurial wire protocol -> hgweb; everything else is static files.
130
RewriteEngine On
131
RewriteCond %{QUERY_STRING} (^|&)cmd=
132
RewriteRule ^ http://127.0.0.1:8420%{REQUEST_URI} [P,L]
133
ProxyPreserveHost On
134
```
135
 
136
Check and reload:
137
 
138
```sh
139
sudo apache2ctl -t
140
sudo systemctl reload apache2
141
```
142
 
143
## 7. Generate the site
144
 
145
Remove any placeholder page Sympl left in
146
`/srv/code.example.com/public/htdocs/`, then:
147
 
148
```sh
149
hgstatic -c /srv/code.example.com/config/hgstatic.ini
150
```
151
 
152
This full run is only needed once (or after changing hgstatic.ini, or to
153
pick up a new version of hgstatic); pushes regenerate the pushed repo and
154
the index automatically via the hook.
155
 
156
## 8. Test from another machine
157
 
158
```sh
159
hg clone https://code.example.com/myproject
160
cd myproject
161
# hack hack
162
hg commit -m "..."
163
hg push ssh://sympl@code.example.com//srv/code.example.com/hg-repos/myproject
164
```
165
 
166
and browse `https://code.example.com/`. The pushed repo's page should
167
already show the new tip. To avoid typing the long push path, save it in
168
the clone's `.hg/hgrc`:
169
 
170
```ini
171
[paths]
172
default = https://code.example.com/myproject
173
default-push = ssh://sympl@code.example.com//srv/code.example.com/hg-repos/myproject
174
```
175
 
176
## Adding another domain
177
 
178
Repeat steps 4–7 with the new domain's paths and the next free port; the
179
packages, scripts, systemd template and hook are already in place.
180
 
181
## Notes
182
 
183
- Sympl's permission fixer may chown generated files under `public/` to
184
  `www-data:www-data`; the `sympl` user is in the `www-data` group, so
185
  regeneration keeps working.
186
- Repos live outside `public/`, so nothing under `.hg/` is ever
187
  web-accessible; the only web exposure is the `?cmd=` protocol endpoint,
188
  and pushing over it is disabled.
189
- If a push updates the site but a page looks stale, run the full
190
  generation from step 7 and check the hook is installed globally.