doc/vcgen_service.md
Rama master · réplica de lectura del repositorio original.
# vcgen.vlang.io
vcgen.vlang.io regenerates V's bootstrap C snapshots, `v.c` and `v_win.c` in
[vlang/vc](https://github.com/vlang/vc), from vlang/v master. It is `cmd/tools/gen_vc` running as
`gen_vc serve` on the `vpm` server, and it takes over publishing from the `deploy` job of
`.github/workflows/gen_vc_ci.yml`. The workflow keeps a `validate` job that builds every pull
request's snapshot without any secret.
It starts in dry-run mode, where it never pushes. Publishing moves to the service only when the
maintainer switches it to push mode and sets the repository variable `VCGEN_SERVICE` to `live`.
## What a run does
The service queues a run at startup, every `--poll-minutes` (5), and on each signed GitHub push
webhook for master. Requests that arrive while a run is pending coalesce into it. A run:
1. Fetches master of vlang/vc, then of vlang/v. A snapshot names a vlang/v commit that was on
master before the snapshot was pushed, so the later vlang/v fetch contains it.
2. Stops early when nothing has to be built:
- neither master moved since the last run (`unchanged`);
- vlang/vc already holds the snapshot of vlang/v master (`up_to_date`, push mode);
- every path that changed since the snapshot's source commit is ignored (`ignored`): `*.md`,
`*.vv`, `*.out`, `*.yml`, `cmd/tools/**` except `cmd/tools/builders/*.v`, `vc.lock`, and the
vc sha in `workflow_contract_test.v`. So a vc.lock update never causes a new snapshot;
- the last run for the same commits failed less than `--retry-failed-minutes` (60) ago.
Polling during that delay keeps the retry pending until the delay expires.
3. Refuses to build while MemAvailable plus SwapFree is below `--min-available-mib` (2048).
4. Checks out vlang/v master at `--v-dir`, cleans it, and runs `make`.
5. Generates `v.c` (`-cross`) and `v_win.c` (`-cross -os windows -cc msvc`) at the checkout root,
prepends `#define V_COMMIT_HASH "<sha>"`, and runs the snapshot checks of
`cmd/tools/gen_vc/snapshot.v`, which `gen_vc_ci.yml` uses too.
6. Commits the snapshot on top of vlang/vc master as `[v:master] <sha> - <subject>` by
`vlang-bot`, in a bare blobless clone (no checkout of the old snapshot).
7. In push mode, checks that vlang/v master still is `<sha>`; otherwise the snapshot is `stale`
and the next run builds the newer master. Then it pushes without force (`published`).
In dry-run mode, step 7 is skipped (`would_publish`). When vlang/vc already holds a snapshot of
the same commit, a dry run builds anyway and compares the trees: `verified` means its snapshot is
byte-identical to the published one, `differs` names the file that is not.
The checkout path is embedded in the snapshot. GitHub's runners build at `/home/runner/work/v/v`,
so the service uses the same `--v-dir` to produce the same bytes.
During bootstrapping, the POSIX and Windows compatibility helpers relocate V-owned include
directives to the current checkout. They preserve the downloaded snapshot and leave header
processing to the C compiler.
## Endpoints
| Path | Answer |
| --- | --- |
| `GET /` | mode, state, and the last run |
| `GET /status.json` | the same as JSON |
| `GET /log` | the log of the last run |
| `POST /genhook` | GitHub push webhook; `401` unless signed with `VCGEN_WEBHOOK_SECRET` |
The webhook payload only triggers a run. What is built is always the master that the run
fetches itself, so pull request code never runs on `vpm`.
## Setup on vpm
`vpm` runs Debian 12 on 2 vCPUs with 2 GB of RAM and no swap, next to other services. Generating
`v_win.c` alone peaks at about 1.5 GB, so add swap first. Run as root:
```sh
fallocate -l 4G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
useradd --system --home-dir /var/lib/vcgen --shell /usr/sbin/nologin vcgen
install -d -o vcgen -g vcgen /var/lib/vcgen /home/runner/work/v
install -d /opt/vcgen
git clone --depth=1 https://github.com/vlang/v /opt/vcgen/src
make -C /opt/vcgen/src
/opt/vcgen/src/v -prod -o /opt/vcgen/gen_vc /opt/vcgen/src/cmd/tools/gen_vc
```
Create `/etc/vcgen.env`, owned by root with mode `0600`:
```ini
# openssl rand -hex 32; the same value goes into the GitHub webhook.
VCGEN_WEBHOOK_SECRET=
# Only for push mode: a fine-grained token of vlang-bot, Contents read and write on vlang/vc only.
VCGEN_PUSH_TOKEN=
```
The token reaches git through a credential helper that reads it from the environment, so it is
never part of an argument, a URL, the log, or a `.git/config`.
Create `/etc/systemd/system/vcgen.service`:
```ini
[Unit]
Description=vcgen.vlang.io: regenerate vlang/vc from vlang/v master
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=vcgen
Group=vcgen
Environment=HOME=/var/lib/vcgen
EnvironmentFile=/etc/vcgen.env
ExecStart=/opt/vcgen/gen_vc serve --mode dry-run --work-dir /var/lib/vcgen \
--v-dir /home/runner/work/v/v
Restart=on-failure
RestartSec=10
StateDirectory=vcgen
PrivateTmp=true
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/home/runner/work/v
Nice=10
CPUWeight=20
MemoryHigh=1G
MemoryMax=2G
MemorySwapMax=4G
[Install]
WantedBy=multi-user.target
```
The memory limits make a build swap instead of pushing the other services on `vpm` out.
Point `vcgen.vlang.io` at `vpm`, get a certificate as for the other vlang.io sites, and add
`/etc/nginx/sites-available/vcgen.vlang.io`, linked from `sites-enabled`:
```nginx
server {
listen 80;
listen [::]:80;
listen 443 ssl;
listen [::]:443 ssl;
server_name vcgen.vlang.io;
ssl_certificate /etc/letsencrypt/live/vcgen.vlang.io/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vcgen.vlang.io/privkey.pem;
client_max_body_size 10m;
location ^~ /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
}
location / {
proxy_pass http://127.0.0.1:7171;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 15s;
}
}
```
Then `systemctl daemon-reload && systemctl enable --now vcgen && nginx -t && systemctl reload
nginx`. Follow the runs with `journalctl -u vcgen -f` or at https://vcgen.vlang.io/.
The webhook is optional; without it, polling picks up master within `--poll-minutes`. To add it:
vlang/v, Settings, Webhooks, Add webhook. Payload URL `https://vcgen.vlang.io/genhook`, content
type `application/json`, the secret from `/etc/vcgen.env`, "Just the push event".
## Switching publishing to the service
1. Leave the service in dry-run mode until several runs report `verified`: each time the
Actions `deploy` job publishes a snapshot, the next dry run rebuilds it and compares.
2. Change `--mode dry-run` to `--mode push` in `ExecStart`, fill in `VCGEN_PUSH_TOKEN`, and run
`systemctl daemon-reload && systemctl restart vcgen`.
3. In vlang/v, Settings, Secrets and variables, Actions, Variables, set `VCGEN_SERVICE` to
`live`. From then on `gen_vc_ci.yml` runs only `validate`.
Both publishers may overlap between steps 2 and 3. That is harmless: whichever pushes second
finds the same snapshot already published.
To pause the service, run `systemctl stop vcgen`. To hand publishing back to Actions, also delete
the `VCGEN_SERVICE` variable or set it to anything other than `live`.
## Running it by hand
```sh
./v -o gen_vc cmd/tools/gen_vc
./gen_vc snapshot . /tmp/vc_snapshot
./gen_vc run-once --mode dry-run --work-dir /tmp/vcgen
```
`snapshot` only generates and checks the snapshot of an already built checkout. `run-once`
makes one complete run, which is a dry run unless `--mode push` is given, and exits with 1 when
the run failed. `gen_vc serve --help` lists every option.