Task 1: both forms of uses: resolve a subfolder action at main. Adds ADR-0011 (Proposed, not yet reviewed), the review record RC-082 for the milestone results, and the traceability rows. Refs #182 Refs #190
13 KiB
MIL-012: VPN actions for deployment (WireGuard and Tailscale)
Metadata
| Key | Value |
|---|---|
| ID | MIL-012 |
| CrossReference | BC-001, US-001, MIL-004, MIL-011, ADR-0006, ADR-0011 |
Version History
| Date | Status | Author | Reviewer | Change | Commit |
|---|---|---|---|---|---|
| 2026-10-19 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version; reviewed (RC-080), accepted by the author in chat as stand-in for S01 with target date 2026-11-06 | c5d5941 |
| 2026-10-19 | Accepted | Jens Tirsvad Nielsen | S01 | Added the results of tasks 1, 6 and 9 and the fixes from the review of the first pull request; reviewed (RC-082), accepted by the author in chat as stand-in for S01 | pending |
Purpose
Decide whether a deployment job on a Gitea runner can reach a private network through a shared, versioned action, and release it if it can.
A deployment often has to reach hosts that are not on the internet. The GitHub marketplace has the action tailscale/github-action for a Tailscale network; it is written for GitHub Actions and has not been verified on Gitea Actions. WireGuard has no equivalent action at all. Two experimental composite actions for Gitea Actions already exist on the unreleased branch wireguard-vpn-action (actions/connect-wireguard, actions/connect-tailscale), with guides and offline tests (574 tests pass), but neither has run on a real runner, and the one question that decides whether the Tailscale action works is open: whether tailscaled, started in the background of one step, is still alive in the next step of the job.
This milestone runs both on a real runner of the instance, fixes what that shows, and releases them. It adds a capability, so it is a minor release (ADR-0006).
Deliverable
Release 2.2.0 of this repository with two composite actions, actions/connect-wireguard and actions/connect-tailscale, each with a guide, a README section, wiki pages in English, Danish and Bahasa Malaysia, a changelog entry under Added, and an offline test. A record of one successful deployment-shaped job per action on the instance. An ADR for the decision to deliver a VPN as a composite action. In TirSystem/sqa-qc-framework: the templates deploy-wireguard.yml and deploy-tailscale.yml pinned to the release tag instead of the branch, in a pull request of their own.
Go / No-Go Criteria
| # | Criterion (objectively checkable) | Go | No-Go |
|---|---|---|---|
| 1 | A uses: of a composite action in a subfolder of this repository (TirSystem/github-action/actions/<name>@<ref>) resolves on the instance; the form that works (short, or the full address) is recorded |
A run starts and executes the action; the form is recorded | No form resolves |
| 2 | connect-wireguard brings a tunnel up on a runner of the instance, a following step reaches a host behind the tunnel, and the down step removes the interface and the file |
Recorded run; wg show and the file are absent afterwards |
The tunnel is not up in the following step, or anything remains |
| 3 | connect-tailscale joins a tailnet on a runner of the instance in TUN mode and in userspace mode, a following step reaches a tailnet host, and down leaves no node in the tailnet |
Recorded run for both modes; the node list shows no node afterwards | tailscaled does not survive to the next step, or a node remains |
| 4 | No secret appears in any log of the runs above (auth key, OAuth secret, WireGuard keys), checked by a search of the run logs for the secret values | Count is 0 | A secret value appears in a log |
| 5 | The runner requirements are recorded as verified: the container options, the label, and whether wireguard-go or the kernel module was used |
Recorded in both guides | A requirement is only assumed |
| 6 | The scripts of the actions are linted (shellcheck, shfmt) and their tests run on every pull request in validate-workflows.yml; a seeded error fails the run |
Fails on a seeded error, passes on the fix | Not part of the validation |
| 7 | The README, both guides and the wiki pages (English, Danish, Bahasa Malaysia) describe the actions at @v2 and no longer call them experimental; a search of guides/, README.md and docs/wiki/ finds no @wireguard-vpn-action |
Count is 0 | A page still names the branch |
| 8 | The release is a minor (2.2.0): the changelog has an Added entry, the major tag v2 moves to it, and v1 is not touched |
Released | Another level, or v1 moved |
| 9 | The framework templates name the release tag, not the branch, in a pull request of TirSystem/sqa-qc-framework opened after release 2.2.0 |
Pull request open with the tag | A template names the branch |
| 10 | Reviewed | A review record (RC-*) for this milestone, the actions, the tests and the guides, each with a verdict of Go |
An instance without a record |
Dependencies
| Depends on | Reason |
|---|---|
| MIL-004 | The release process and the Release workflow that publish 2.2.0 |
| MIL-011 | The default tools-ref and the MAJOR_TAG file; the release is the first after the fix |
| ADR-0006 | A new capability is a minor release, and a moving major tag follows it |
A runner with NET_ADMIN, /dev/net/tun and a label of its own (S03) |
Both actions need a network interface; outside this repository, set up by the runner's operator |
| A WireGuard peer and a Tailscale tag with an OAuth client (S03) | Test peers for criteria 2 and 3; secrets held by the operator, never in this repository |
TirSystem/sqa-qc-framework, branch ssdf-workflow-selection-vpn |
Holds the templates and the rules that call the actions; criterion 9 changes it in its own pull request |
Traceability
| Business Case objective / KPI / user story | Reference |
|---|---|
| Business Case objective O6: a shared, versioned action that brings a VPN up for a deployment job on a Gitea runner | BC-001 |
| User stories | US-001 (no new story: the tasks below are technical; the actions are used by deployment jobs, not by a person) |
Ownership
| Role | Stakeholder ID (SA) |
|---|---|
| Owner | S05 |
| Approving reviewer | S01 |
Target Date
2026-11-06 (Friday), proposed: two weeks of work from 2026-10-26, because the trials need a runner with extra privileges and test peers that the operator must set up. It overlaps MIL-002 (2026-10-19 to 2026-10-31), which is documentation and does not touch the runners' network. The Business Case sets no deadline. S01 confirmed the date when accepting this milestone, in chat on 2026-10-19.
Tasks
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
|---|---|---|---|---|
| 1 | Check that a subfolder action resolves | On a throwaway branch of a repository, call actions/connect-wireguard and actions/connect-tailscale with the short form and with the full address, at the branch. Record which form starts a run and the message when one does not. |
No | |
| 2 | Prepare the trial runner | With the operator: a runner labelled vpn whose container has NET_ADMIN and /dev/net/tun; a WireGuard peer and a host behind it; a Tailscale tag, an OAuth client and a host in the tailnet. Secrets are stored as organization secrets and are not recorded here. |
No | |
| 3 | Verify connect-wireguard |
Run a deployment-shaped job (connect, reach the host over the tunnel, disconnect with if: always()) with a kernel module and with wireguard-go. Record the runs and what remains on the runner. Fix the script where it fails and extend the tests. |
No | MIL-012 |
| 4 | Verify connect-tailscale |
Run the same job in TUN and in userspace mode. Decide whether tailscaled survives to the next step; if it does not, change how it is started (for example setsid, or a post step) and test it. Record the node list after the run. |
No | MIL-012 |
| 5 | Search the logs for secrets | Search every run log of tasks 3 and 4 for the secret values and for the key file path; fix any leak. | No | |
| 6 | Lint and test the action scripts in CI | Add shellcheck and shfmt for actions/**/*.sh and the two test files to validate-workflows.yml; show a seeded error fails the run. |
No | MIL-001 |
| 7 | Remove the experimental notices | Update both guides with the verified runner requirements, change @wireguard-vpn-action to @v2 everywhere, and add the README section for the actions. |
No | |
| 8 | Wiki pages | Add pages for the two actions in English, Danish and Bahasa Malaysia, following the wiki rules of MIL-005, and register them in the wiki index. | No | MIL-005 |
| 9 | Record the decision and the objective | Write an ADR: a VPN is delivered as a composite action, not as a reusable workflow (a called workflow runs as a separate job), and not through the marketplace action (written for GitHub). Objective O6, its scope item and success criterion 6 are already in BC-001, and the schedule row, Gantt bar, scope entry, dependency and risk are in PP-001 (added at acceptance, RC-080, RC-081); this task writes the ADR only. | No | ADR-0006 |
| 10 | Release 2.2.0 | Add the Added entry to the changelog, run the Release workflow (dry run first), and check that v2 points at v2.2.0 and v1 is unchanged. |
No | ADR-0006 |
| 11 | Pin the framework templates | In TirSystem/sqa-qc-framework, change @wireguard-vpn-action to @v2 in the two templates and the branch guide, remove the "experimental" notes that the release resolves, and open the pull request after release 2.2.0. This is a change in another repository, in its own pull request. |
No | |
| 12 | Review | Record RC-* for this milestone, the actions with their tests, and the guides, each with a verdict. |
No |
Verification results
Task 1 was run on 2026-10-19 with the throwaway branch verify-subfolder-action, which calls both actions in the short form (TirSystem/github-action/actions/<name>@<ref>) and with the full address (https://git.tirsystem.com/TirSystem/github-action/actions/<name>@<ref>), with every input empty, so that a resolved action fails with its own [configuration] message.
| Call | Result | Evidence |
|---|---|---|
Either form at @wireguard-vpn-action |
Fails before the step: Unable to resolve wireguard-vpn-action: reference not found. The branch had been deleted when its pull request merged, so the ref did not exist; this is not a defect of the form. |
run 417 (2424) |
Short form at @main, both actions |
Resolves and runs: the log shows Run Pre ...@main, the action's mask step and its main step, which fails with [configuration] set config, or all of private-key, ... (WG_PRIVATE_KEY is empty) and [configuration] give authkey or oauth-secret |
run 418 (2425), jobs wg-short, ts-short |
Full address at @main, both actions |
The same result as the short form | run 418, jobs wg-full, ts-full |
Both forms resolve. The short form is the one the guides show; the full address stays in the guides as the fallback. gitea.action_path is set (the script path in the log is under /var/run/act/actions/<id>/actions/<name>/). A uses: of a branch that no longer exists fails with "reference not found", so a caller of an experimental ref breaks when its branch is deleted.
Tasks 6 and 9 were done on 2026-10-19 on the branch mil-012-review-fixes-and-docs:
- Lint (task 6):
tools/lint-action-scripts.shruns shellcheck and shfmt onactions/**/*.sh;validate-workflows.ymlruns it with pinned tools (shellcheck-py==0.10.0.1,shfmt-py==3.11.0.2, fromtests/requirements.txt), and.editorconfigsets the shell format (two spaces, indented case labels, as the shell conventions say).tests/test_lint_action_scripts.pyseeds a shellcheck problem and a badly formatted script and shows both fail, and shows the scripts of the actions pass. - Decision (task 9): ADR-0011 (Proposed). Objective O6 and the Project Plan entries were added at the acceptance of this milestone (RC-080, RC-081).
The review of pull request 194 (ten findings) is answered in the same branch: tailscaled is waited for before tailscale up; the wireguard-go choice survives sudo; sudo is used only when the job is not root; a tailscaled that already runs on the runner is not taken over and down leaves a daemon it did not start; the Tailscale install needs an exact version, with an optional sha256; a configuration field with a line break is refused; only the key values are masked; the key file is removed when wg-quick fails; and the guides call @main until the release. These are offline-tested with stubs; tasks 3 and 4 still have to show them on a runner.
Tasks 2, 3, 4, 5, 7, 8, 10, 11 and 12 are open: they need the trial runner (task 2), which is the operator's, or follow the verified guides (tasks 7 and 8) or the release (tasks 10 and 11).