tailkitty 0.2.1__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tailkitty-0.2.1 → tailkitty-0.2.2}/AGENTS.md +36 -3
- tailkitty-0.2.2/BUILDING.md +216 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/CHANGELOG.md +11 -0
- tailkitty-0.2.2/COMPARISON.md +64 -0
- tailkitty-0.2.2/CONTRIBUTING.md +131 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/ITERATIONS.md +5 -0
- tailkitty-0.2.2/PKG-INFO +372 -0
- tailkitty-0.2.2/README.md +347 -0
- tailkitty-0.2.2/RELEASING.md +172 -0
- tailkitty-0.2.2/SECURITY.md +207 -0
- tailkitty-0.2.2/docs/architecture.md +203 -0
- tailkitty-0.2.2/docs/python-api.md +355 -0
- tailkitty-0.2.2/docs/recipes.md +341 -0
- tailkitty-0.2.2/docs/troubleshooting.md +259 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/pyproject.toml +15 -2
- {tailkitty-0.2.1 → tailkitty-0.2.2}/scripts/targets.py +8 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/src/tailkitty/__init__.py +4 -2
- {tailkitty-0.2.1 → tailkitty-0.2.2}/src/tailkitty/constants.py +1 -1
- {tailkitty-0.2.1 → tailkitty-0.2.2}/src/tailkitty/derp.py +3 -1
- {tailkitty-0.2.1 → tailkitty-0.2.2}/src/tailkitty/process.py +53 -5
- {tailkitty-0.2.1 → tailkitty-0.2.2}/tests/test_bundle.py +7 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/tests/test_cli.py +1 -1
- {tailkitty-0.2.1 → tailkitty-0.2.2}/tests/test_module.py +1 -1
- {tailkitty-0.2.1 → tailkitty-0.2.2}/tests/test_process.py +30 -2
- {tailkitty-0.2.1 → tailkitty-0.2.2}/tests/test_targets.py +2 -2
- {tailkitty-0.2.1 → tailkitty-0.2.2}/uv.lock +1 -1
- tailkitty-0.2.1/BUILDING.md +0 -50
- tailkitty-0.2.1/COMPARISON.md +0 -30
- tailkitty-0.2.1/PKG-INFO +0 -414
- tailkitty-0.2.1/README.md +0 -389
- tailkitty-0.2.1/SECURITY.md +0 -27
- {tailkitty-0.2.1 → tailkitty-0.2.2}/.gitignore +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/.mise.toml +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/.python-version +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/LICENSE +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/THIRD_PARTY_NOTICES.md +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/hatch_build.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/scripts/__init__.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/scripts/build_binary.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/scripts/build_wheels.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/scripts/check_upstream.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/scripts/smoke_wheel.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/scripts/verify_wheel.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/src/tailkitty/__main__.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/src/tailkitty/backend.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/src/tailkitty/bundle.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/src/tailkitty/cli.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/src/tailkitty/client.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/src/tailkitty/destination.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/src/tailkitty/diagnostics.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/src/tailkitty/py.typed +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/src/tailkitty/token.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/tests/test_backend.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/tests/test_derp.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/tests/test_destination.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/tests/test_project.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/tests/test_token.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/tests/test_token_validation.py +0 -0
- {tailkitty-0.2.1 → tailkitty-0.2.2}/tests/test_upstream.py +0 -0
|
@@ -12,6 +12,14 @@ Read these files before making architectural, packaging, security, or release ch
|
|
|
12
12
|
3. `BUILDING.md` for the reproducible bundle and wheel pipeline.
|
|
13
13
|
4. `pyproject.toml` and `.mise.toml` for supported versions and canonical tasks.
|
|
14
14
|
|
|
15
|
+
For a focused task, then read the matching guide:
|
|
16
|
+
|
|
17
|
+
- `docs/python-api.md` for public Python behavior.
|
|
18
|
+
- `docs/recipes.md` for user-facing command examples.
|
|
19
|
+
- `docs/troubleshooting.md` for failure and diagnostic guidance.
|
|
20
|
+
- `docs/architecture.md` for ownership and data flows.
|
|
21
|
+
- `RELEASING.md` before changing versions, tags, workflows, or publication.
|
|
22
|
+
|
|
15
23
|
Do not confuse Tailkitty with the separate `pytailcat` distribution on PyPI. This checkout is the
|
|
16
24
|
source of truth for Tailkitty; upstream Tailcat remains the data-plane source of truth.
|
|
17
25
|
|
|
@@ -44,11 +52,14 @@ compatibility and fail-closed bundle discovery are core requirements.
|
|
|
44
52
|
| `scripts/targets.py` | Supported build targets and wheel tags |
|
|
45
53
|
| `scripts/build_binary.py` | Reproducible cross-compilation and manifest creation |
|
|
46
54
|
| `patches/` | Auditable patches applied to the immutable upstream Tailcat source |
|
|
47
|
-
| `scripts/build_wheels.py` | Isolated
|
|
55
|
+
| `scripts/build_wheels.py` | Isolated six-target wheel orchestration |
|
|
48
56
|
| `scripts/verify_wheel.py` | Wheel metadata, archive, binary, and RECORD verification |
|
|
49
57
|
| `scripts/check_upstream.py` | Latest stable Tailcat release drift check |
|
|
50
58
|
| `hatch_build.py` | Platform-wheel build hook |
|
|
51
59
|
| `tests/` | Unit and subprocess behavior tests |
|
|
60
|
+
| `docs/` | Task-oriented user, API, troubleshooting, and architecture guides |
|
|
61
|
+
| `CONTRIBUTING.md` | Human contributor workflow and checklist |
|
|
62
|
+
| `RELEASING.md` | Release metadata, artifact, publishing, and verification checklist |
|
|
52
63
|
|
|
53
64
|
Public imports are curated in `src/tailkitty/__init__.py`. Adding a public API requires updating
|
|
54
65
|
that file, type annotations, tests, and README documentation.
|
|
@@ -67,6 +78,9 @@ mise run test
|
|
|
67
78
|
strict mypy, and pytest. During iteration, run the narrowest relevant test first, then run the full
|
|
68
79
|
gate before declaring completion.
|
|
69
80
|
|
|
81
|
+
Direct build-module commands must run through `mise exec --`; plain `uv run` can select an
|
|
82
|
+
unrelated system Go version.
|
|
83
|
+
|
|
70
84
|
Packaging changes also require:
|
|
71
85
|
|
|
72
86
|
```console
|
|
@@ -144,6 +158,24 @@ without an explicit compatibility decision.
|
|
|
144
158
|
keys, cache, bundle, or configured backend.
|
|
145
159
|
- Keep Ruff's 100-character line length and run formatting rather than hand-aligning code.
|
|
146
160
|
|
|
161
|
+
## Documentation conventions
|
|
162
|
+
|
|
163
|
+
- The README is the onboarding map, not the exhaustive reference. Put task recipes, detailed API
|
|
164
|
+
behavior, troubleshooting, and architecture in their dedicated guides.
|
|
165
|
+
- Documentation on `main` describes the current source tree. Cut a release promptly when the user
|
|
166
|
+
asks for GitHub, PyPI, and `main` to match.
|
|
167
|
+
- State which machine runs each side of multi-peer examples.
|
|
168
|
+
- Use complete copyable commands and quote `tc...` placeholders because addresses are
|
|
169
|
+
case-sensitive shell data.
|
|
170
|
+
- Never put a syntactically plausible private key or active address in documentation.
|
|
171
|
+
- Distinguish encryption, address possession, tunnel allow-lists, SSH keys, and application
|
|
172
|
+
authentication; do not collapse them into a generic “secure” claim.
|
|
173
|
+
- Do not claim typed Python support for an upstream CLI or Go-library feature unless a Python API
|
|
174
|
+
actually exists.
|
|
175
|
+
- Keep relative Markdown links resolvable from the file containing them and include new `docs/`
|
|
176
|
+
files in the source distribution.
|
|
177
|
+
- When a command or flag is version-sensitive, check it against the pinned backend's `--help`.
|
|
178
|
+
|
|
147
179
|
## Generated files and release safety
|
|
148
180
|
|
|
149
181
|
Do not manually edit or commit generated files under:
|
|
@@ -155,8 +187,9 @@ Do not manually edit or commit generated files under:
|
|
|
155
187
|
|
|
156
188
|
Use the scripts and mise tasks that own those artifacts. Publish only the `tailkitty` distribution;
|
|
157
189
|
the `pytailcat` name belongs to another project. Before a release, verify project URLs and the `pypi`
|
|
158
|
-
GitHub environment, build all
|
|
159
|
-
and retain checksums and provenance attestations.
|
|
190
|
+
GitHub environment, build all six wheels plus the source distribution, smoke-test the host wheel,
|
|
191
|
+
and retain checksums and provenance attestations. Follow `RELEASING.md`; do not race automated and
|
|
192
|
+
local publishers or upload from a directory containing artifacts from multiple versions.
|
|
160
193
|
|
|
161
194
|
## Definition of done
|
|
162
195
|
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Building Tailkitty
|
|
2
|
+
|
|
3
|
+
Tailkitty builds one upstream Tailcat executable per supported target, records its provenance in a
|
|
4
|
+
manifest, and packages it in a platform-specific Python wheel. Source distributions remain
|
|
5
|
+
binary-free.
|
|
6
|
+
|
|
7
|
+
For the end-to-end publication checklist, see [RELEASING.md](RELEASING.md).
|
|
8
|
+
|
|
9
|
+
## Pinned environment
|
|
10
|
+
|
|
11
|
+
Mise is the source of truth for build tools:
|
|
12
|
+
|
|
13
|
+
| Tool | Pin |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| Python | 3.13.11 |
|
|
16
|
+
| Go | 1.27.1 |
|
|
17
|
+
| uv | 0.12.7 |
|
|
18
|
+
| Tailcat | `v0.6.0` |
|
|
19
|
+
|
|
20
|
+
Install and synchronize them with:
|
|
21
|
+
|
|
22
|
+
```console
|
|
23
|
+
mise install
|
|
24
|
+
uv sync --all-groups --locked
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The builder sets `GOTOOLCHAIN=local` and rejects any Go version other than the exact pin. When
|
|
28
|
+
running a Python build module directly, enter mise's environment:
|
|
29
|
+
|
|
30
|
+
```console
|
|
31
|
+
mise exec -- uv run python -m scripts.build_wheels --target linux-x86_64
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Using plain `uv run` can select an unrelated system Go compiler.
|
|
35
|
+
|
|
36
|
+
## Canonical tasks
|
|
37
|
+
|
|
38
|
+
```console
|
|
39
|
+
mise run test # Ruff, formatting, strict mypy, pytest
|
|
40
|
+
mise run backend # source-checkout backend under .tools/bin
|
|
41
|
+
mise run bundle # host bundle under src/tailkitty/bin
|
|
42
|
+
mise run bundle-verify # verify the host bundle and manifest
|
|
43
|
+
mise run wheel # package the host bundle
|
|
44
|
+
mise run wheels # build and verify all six platform wheels
|
|
45
|
+
mise run upstream-check # compare the Tailcat pin with the latest stable release
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Generated content under `.tools/bin/`, `src/tailkitty/bin/`, and `dist/` is ignored and must not be
|
|
49
|
+
edited or committed.
|
|
50
|
+
|
|
51
|
+
## Build pipeline
|
|
52
|
+
|
|
53
|
+
`scripts.build_binary` performs these steps:
|
|
54
|
+
|
|
55
|
+
1. Resolve the requested target.
|
|
56
|
+
2. Verify the exact Go compiler.
|
|
57
|
+
3. Download `github.com/tailscale/tailcat@<immutable-pin>` through the Go module system.
|
|
58
|
+
4. Copy the source into a temporary writable workspace.
|
|
59
|
+
5. Check and apply every lexically sorted patch under `patches/`.
|
|
60
|
+
6. Read upstream's release build tags from `build-tags.txt`.
|
|
61
|
+
7. Cross-compile `./cmd/tailcat` with the reproducibility and compatibility settings below.
|
|
62
|
+
8. Check the executable header and minimum plausible size.
|
|
63
|
+
9. Atomically place the executable and write `manifest.json`.
|
|
64
|
+
|
|
65
|
+
The build uses:
|
|
66
|
+
|
|
67
|
+
- `CGO_ENABLED=0`
|
|
68
|
+
- `GOTOOLCHAIN=local`
|
|
69
|
+
- `GOAMD64=v1`
|
|
70
|
+
- `GOARM64=v8.0`
|
|
71
|
+
- `-trimpath`
|
|
72
|
+
- `-buildvcs=false`
|
|
73
|
+
- stripped symbols
|
|
74
|
+
- an empty Go build ID
|
|
75
|
+
- upstream's official release omission tags
|
|
76
|
+
|
|
77
|
+
Module downloads have three bounded attempts with short exponential backoff for transient failures.
|
|
78
|
+
Build failures are not silently converted into old or partial bundles.
|
|
79
|
+
|
|
80
|
+
## Bundle manifest
|
|
81
|
+
|
|
82
|
+
Each generated bundle contains its executable and a schema-1 JSON manifest. The manifest records:
|
|
83
|
+
|
|
84
|
+
- Internal target name and wheel platform tag.
|
|
85
|
+
- Executable filename, size, and SHA-256.
|
|
86
|
+
- Tailcat module and immutable version.
|
|
87
|
+
- Applied patch filenames and SHA-256 values.
|
|
88
|
+
- Go compiler version.
|
|
89
|
+
- Reproducibility flags and release build tags.
|
|
90
|
+
- CGO policy.
|
|
91
|
+
|
|
92
|
+
Build-time verification rechecks those inputs. Runtime verification additionally requires the
|
|
93
|
+
manifest target to match the current interpreter platform, rejects path-containing filenames and
|
|
94
|
+
symlinks, and refuses size or digest mismatches.
|
|
95
|
+
|
|
96
|
+
## Wheel matrix
|
|
97
|
+
|
|
98
|
+
| Build target | Go target | Wheel tag | Executable |
|
|
99
|
+
| --- | --- | --- | --- |
|
|
100
|
+
| `macos-arm64` | `darwin/arm64` | `macosx_12_0_arm64` | Mach-O `tailcat` |
|
|
101
|
+
| `macos-x86_64` | `darwin/amd64` | `macosx_12_0_x86_64` | Mach-O `tailcat` |
|
|
102
|
+
| `linux-x86_64` | `linux/amd64` | `manylinux_2_17_x86_64` | ELF `tailcat` |
|
|
103
|
+
| `linux-aarch64` | `linux/arm64` | `manylinux_2_17_aarch64` | ELF `tailcat` |
|
|
104
|
+
| `windows-x86_64` | `windows/amd64` | `win_amd64` | PE `tailcat.exe` |
|
|
105
|
+
| `windows-arm64` | `windows/arm64` | `win_arm64` | PE `tailcat.exe` |
|
|
106
|
+
|
|
107
|
+
Build one target:
|
|
108
|
+
|
|
109
|
+
```console
|
|
110
|
+
mise exec -- uv run python -m scripts.build_wheels \
|
|
111
|
+
--target windows-arm64 \
|
|
112
|
+
--output dist/wheels
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Build all targets:
|
|
116
|
+
|
|
117
|
+
```console
|
|
118
|
+
mise run wheels
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Each target uses a separate temporary bundle directory. Environment variables tell the Hatch build
|
|
122
|
+
hook which bundle and exact tag to include. This prevents one architecture's executable from
|
|
123
|
+
leaking into another wheel.
|
|
124
|
+
|
|
125
|
+
## Wheel verification
|
|
126
|
+
|
|
127
|
+
`scripts.verify_wheel` opens the wheel as an archive and checks:
|
|
128
|
+
|
|
129
|
+
- The filename and `WHEEL` metadata carry the expected platform tag.
|
|
130
|
+
- The wheel is marked non-pure.
|
|
131
|
+
- Archive members are unique and cannot escape through absolute or `..` paths.
|
|
132
|
+
- Total uncompressed content is bounded.
|
|
133
|
+
- `py.typed`, the manifest, and the one expected executable are present.
|
|
134
|
+
- Manifest target, module, Tailcat pin, patch set, tags, size, and digest match build inputs.
|
|
135
|
+
- The executable begins with the expected Mach-O, ELF, or PE magic.
|
|
136
|
+
- Every non-`RECORD` member has the exact hash and size recorded in `RECORD`.
|
|
137
|
+
- `RECORD` names exactly the archive contents.
|
|
138
|
+
|
|
139
|
+
Cross-compiled executables cannot be run on the build host, so this structural verification is
|
|
140
|
+
paired with native CI builds and the host smoke test.
|
|
141
|
+
|
|
142
|
+
## Host smoke test
|
|
143
|
+
|
|
144
|
+
Build or select the current host wheel, then run:
|
|
145
|
+
|
|
146
|
+
```console
|
|
147
|
+
uv run python -m scripts.smoke_wheel dist/wheels/<host-wheel>.whl
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The smoke test:
|
|
151
|
+
|
|
152
|
+
1. Creates a temporary isolated Python environment.
|
|
153
|
+
2. Installs only the selected wheel and its dependencies.
|
|
154
|
+
3. Runs `tailkitty doctor --json` and confirms the bundled backend is selected.
|
|
155
|
+
4. Starts an isolated local DERP/STUN relay through Tailcat's test mode.
|
|
156
|
+
5. Starts a peer and performs an actual encrypted ping.
|
|
157
|
+
6. Applies five-second startup and client bounds, followed by bounded termination.
|
|
158
|
+
|
|
159
|
+
It does not depend on public relay availability and does not touch saved developer keys.
|
|
160
|
+
|
|
161
|
+
## Source distribution
|
|
162
|
+
|
|
163
|
+
Build a source archive after removing any generated host bundle:
|
|
164
|
+
|
|
165
|
+
```console
|
|
166
|
+
mise run bundle-clean
|
|
167
|
+
uv build --sdist --out-dir dist/release
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The Hatch sdist configuration includes Python source, scripts, tests, patches, documentation, lock
|
|
171
|
+
and tool configuration, but excludes generated executables and manifests. A source installation can
|
|
172
|
+
use pure-Python address functionality immediately; networking requires a separately built or
|
|
173
|
+
configured backend.
|
|
174
|
+
|
|
175
|
+
## Reproducibility scope
|
|
176
|
+
|
|
177
|
+
Tailkitty pins the compiler, module version, build tags, target baseline, and flags that commonly
|
|
178
|
+
introduce host or VCS variation. The manifest makes every shipped executable auditable. Byte-for-byte
|
|
179
|
+
reproduction can still depend on the Go module proxy serving the same immutable module contents and
|
|
180
|
+
on the pinned tool downloads remaining available.
|
|
181
|
+
|
|
182
|
+
`SOURCE_DATE_EPOCH` is set during matrix wheel builds so archive metadata is stable. Reproducibility
|
|
183
|
+
does not mean one binary can be relabeled for another platform; each wheel is built and verified for
|
|
184
|
+
exactly one target.
|
|
185
|
+
|
|
186
|
+
## Upstream updates
|
|
187
|
+
|
|
188
|
+
```console
|
|
189
|
+
mise run upstream-check
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
This bounded GitHub API check fails when a newer stable Tailcat release exists. A daily scheduled
|
|
193
|
+
workflow runs the same command. Before updating the pin, review:
|
|
194
|
+
|
|
195
|
+
- Upstream changelog and security notes.
|
|
196
|
+
- `wire.go` for address fields and validation changes.
|
|
197
|
+
- CLI help and command ownership conflicts.
|
|
198
|
+
- `build-tags.txt` and Go version.
|
|
199
|
+
- New platform requirements.
|
|
200
|
+
- Whether local patches are now upstream or no longer apply.
|
|
201
|
+
|
|
202
|
+
Do not silently pin `main`. An immutable pseudo-version is acceptable only as an explicit,
|
|
203
|
+
documented compatibility decision.
|
|
204
|
+
|
|
205
|
+
## Common build failures
|
|
206
|
+
|
|
207
|
+
| Failure | Likely cause | Action |
|
|
208
|
+
| --- | --- | --- |
|
|
209
|
+
| Wrong Go version | Command ran outside mise | Prefix the direct command with `mise exec --` |
|
|
210
|
+
| Patch check fails | Upstream source changed | Review and update or remove the patch; never force it |
|
|
211
|
+
| One wheel expected, several found | Output contains another artifact with the same version/tag | Use a clean release directory |
|
|
212
|
+
| Wrong executable magic | Target or output staging mismatch | Inspect target selection; do not relabel the wheel |
|
|
213
|
+
| Host smoke timeout | Backend did not advertise or handshake in bounds | Inspect local relay logs and process cleanup |
|
|
214
|
+
| Public DERP timeout | External relay availability | Reproduce with the isolated smoke test |
|
|
215
|
+
|
|
216
|
+
More user-facing failures are covered in [docs/troubleshooting.md](docs/troubleshooting.md).
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.2 - 2026-09-08
|
|
4
|
+
|
|
5
|
+
- Use Tailcat's declarative `serve` subcommand in managed Python server processes.
|
|
6
|
+
- Add typed sync/async server options for DERP-map selection, PSK policy, file roots, and SSH
|
|
7
|
+
authorized-key sources.
|
|
8
|
+
- Add a verified Windows ARM64 platform wheel target.
|
|
9
|
+
- Replace the monolithic README with task-oriented recipes, API, troubleshooting, architecture,
|
|
10
|
+
contributor, build, security, and release documentation.
|
|
11
|
+
- Export `BackendNotFound` and `ServerStartError` from the public package and report the current
|
|
12
|
+
version in DERP-map requests.
|
|
13
|
+
|
|
3
14
|
## 0.2.1 - 2026-09-08
|
|
4
15
|
|
|
5
16
|
- Match upstream token rejection for trailing CBOR data, signed 64-bit overflows, and malformed
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Tailkitty and `pytailcat`
|
|
2
|
+
|
|
3
|
+
Tailkitty and the separate [`pytailcat`](https://pypi.org/project/pytailcat/) distribution are
|
|
4
|
+
independent Python projects built around upstream
|
|
5
|
+
[Tailscale Tailcat](https://github.com/tailscale/tailcat). They are not rename-compatible packages:
|
|
6
|
+
install and import the one you intend.
|
|
7
|
+
|
|
8
|
+
This comparison was checked on 2026-09-08 against `pytailcat` 0.1.4 and Tailkitty 0.2.2.
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
- Choose Tailkitty for current Tailcat behavior, pure-Python address tooling, asyncio, managed
|
|
13
|
+
lifecycle, fail-closed bundle verification, and reproducible release evidence.
|
|
14
|
+
- Choose `pytailcat` when Python 3.8–3.10 or older macOS deployment targets are required.
|
|
15
|
+
- Choose upstream Tailcat directly when no Python API or Python packaging is needed.
|
|
16
|
+
|
|
17
|
+
## Feature comparison
|
|
18
|
+
|
|
19
|
+
| Capability | `pytailcat` 0.1.4 | Tailkitty 0.2.2 |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| Distribution/import | `pytailcat` | `tailkitty` |
|
|
22
|
+
| Upstream data plane | Bundled Tailcat executable | Pinned Tailcat v0.6.0 executable |
|
|
23
|
+
| Upstream revision | Pre-release commit `c04c5af` | Signed stable tag `v0.6.0` |
|
|
24
|
+
| CLI compatibility | `tailcat` wrapper | `tailkitty` plus `tailcat` alias |
|
|
25
|
+
| Address parsing | Delegated to executable | Typed pure-Python `ConnInfo` model |
|
|
26
|
+
| Modern address fields | Depends on older bundled executable | Disco key and WireGuard PSK validated and preserved |
|
|
27
|
+
| Future address fields | Depends on executable | Unknown root and nested extensions round-trip losslessly |
|
|
28
|
+
| DERP expansion | Delegated to executable | Pure Python with bounded ETag cache and stale fallback |
|
|
29
|
+
| DNS destinations | Delegated to executable | Pure-Python sync and asyncio resolution |
|
|
30
|
+
| Client API | Thin synchronous process wrapper | Typed finite, streaming, sync, and asyncio clients |
|
|
31
|
+
| Server API | Synchronous `ServerProcess` | Managed sync/async servers with typed v0.6 options |
|
|
32
|
+
| Lifecycle cleanup | Basic process management | Bounded startup, terminate/kill escalation, reaping |
|
|
33
|
+
| Bundle verification | Package transport integrity | Runtime target, schema, size, and SHA-256 verification |
|
|
34
|
+
| Invalid bundle policy | Not documented as fail-closed | Explicit fail-closed behavior |
|
|
35
|
+
| Build environment | uv-based | mise + uv with exact Python, Go, and Tailcat pins |
|
|
36
|
+
| Wheel checks | Prebuilt wheel matrix | Archive, manifest, binary magic, and every `RECORD` entry |
|
|
37
|
+
| Data-plane smoke test | Server lifecycle startup | Real encrypted peer handshake through local DERP/STUN |
|
|
38
|
+
| Platforms | Five wheel targets | Six wheel targets, including Windows ARM64 |
|
|
39
|
+
| Python requirement | 3.8+ | 3.11+ |
|
|
40
|
+
| macOS floor | Older Intel/ARM targets | macOS 12+ |
|
|
41
|
+
| Project license | BSD-3-Clause | MIT; bundled Tailcat remains BSD-3-Clause |
|
|
42
|
+
|
|
43
|
+
Both projects retain the correct architectural boundary: the complex, interoperable network data
|
|
44
|
+
plane is native Go. Tailkitty's “pure Python” claim applies to address parsing, DNS lookup, and DERP
|
|
45
|
+
resolution—not to WireGuard, NAT traversal, or relay transport.
|
|
46
|
+
|
|
47
|
+
## What Tailkitty deliberately does not claim
|
|
48
|
+
|
|
49
|
+
- Tailkitty is not an official Tailscale product.
|
|
50
|
+
- It is not a pure-Python VPN or a replacement Tailscale control plane.
|
|
51
|
+
- It has not demonstrated a general throughput advantage over `pytailcat`; both ultimately depend
|
|
52
|
+
on Tailcat and network path behavior.
|
|
53
|
+
- More validation does not replace PyPI/package-manager trust or an upstream security review.
|
|
54
|
+
- Newer Python and macOS minimums are real compatibility costs, not hidden implementation details.
|
|
55
|
+
|
|
56
|
+
## Why a separate name exists
|
|
57
|
+
|
|
58
|
+
The `pytailcat` name belongs to another project. Tailkitty uses distinct distribution, import, and
|
|
59
|
+
primary CLI names to avoid dependency confusion and accidental replacement. The optional `tailcat`
|
|
60
|
+
command is only a compatibility alias inside an environment where Tailkitty was deliberately
|
|
61
|
+
installed.
|
|
62
|
+
|
|
63
|
+
For architectural details, see [docs/architecture.md](docs/architecture.md). For the exact current
|
|
64
|
+
platform matrix, see [README.md](README.md#supported-wheels).
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Contributing to Tailkitty
|
|
2
|
+
|
|
3
|
+
Thank you for improving Tailkitty. The project values upstream compatibility, explicit security
|
|
4
|
+
boundaries, deterministic tests, and documentation that can be followed without hidden context.
|
|
5
|
+
|
|
6
|
+
## Before starting
|
|
7
|
+
|
|
8
|
+
Read:
|
|
9
|
+
|
|
10
|
+
1. [README.md](README.md) for public behavior.
|
|
11
|
+
2. [SECURITY.md](SECURITY.md) for authorization and executable trust.
|
|
12
|
+
3. [docs/architecture.md](docs/architecture.md) for the Python/native boundary.
|
|
13
|
+
4. [BUILDING.md](BUILDING.md) for packaging changes.
|
|
14
|
+
|
|
15
|
+
Automated contributors must also follow [AGENTS.md](AGENTS.md).
|
|
16
|
+
|
|
17
|
+
## Development environment
|
|
18
|
+
|
|
19
|
+
Install [mise](https://mise.jdx.dev/) and clone the repository, then run:
|
|
20
|
+
|
|
21
|
+
```console
|
|
22
|
+
mise install
|
|
23
|
+
mise run setup
|
|
24
|
+
mise run test
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Mise pins Python, Go, and uv. Do not create a second toolchain configuration or commit a local
|
|
28
|
+
virtual environment. `mise run setup` synchronizes the uv environment and builds the pinned Tailcat
|
|
29
|
+
development backend.
|
|
30
|
+
|
|
31
|
+
The normal edit loop is:
|
|
32
|
+
|
|
33
|
+
```console
|
|
34
|
+
uv run pytest tests/test_token.py
|
|
35
|
+
mise run test
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use the narrowest relevant test while iterating, then run the complete quality gate before handing
|
|
39
|
+
off a change. The gate includes Ruff linting, Ruff formatting checks, strict mypy, and pytest.
|
|
40
|
+
|
|
41
|
+
## Change boundaries
|
|
42
|
+
|
|
43
|
+
Tailkitty owns the Python control plane:
|
|
44
|
+
|
|
45
|
+
- Address parsing, validation, and resolution.
|
|
46
|
+
- DNS TXT destination lookup.
|
|
47
|
+
- DERP-map caching.
|
|
48
|
+
- Typed sync and asyncio wrappers.
|
|
49
|
+
- Process lifecycle and diagnostics.
|
|
50
|
+
- Verified distribution of the pinned backend.
|
|
51
|
+
|
|
52
|
+
Upstream Tailcat owns magicsock, WireGuard, STUN, DERP transport, and gVisor netstack. Do not add a
|
|
53
|
+
Python-only transport that only interoperates with itself. If a behavior belongs in Tailcat,
|
|
54
|
+
consider an upstream contribution and pin the released result here.
|
|
55
|
+
|
|
56
|
+
The Python CLI intentionally owns only `parse`, `resolve`, `doctor`, `--version`, and top-level
|
|
57
|
+
help. New Python-native subcommands can shadow future Tailcat commands; adding one requires an
|
|
58
|
+
explicit compatibility decision.
|
|
59
|
+
|
|
60
|
+
## Tests
|
|
61
|
+
|
|
62
|
+
Good tests are:
|
|
63
|
+
|
|
64
|
+
- Deterministic and independent of public DERP availability.
|
|
65
|
+
- Bounded by short, explicit timeouts when processes or networks are involved.
|
|
66
|
+
- Isolated from saved user keys, caches, package directories, and configured backends.
|
|
67
|
+
- Paired across sync and asyncio APIs when behavior should match.
|
|
68
|
+
- Written as regressions for the exact failure being repaired.
|
|
69
|
+
|
|
70
|
+
Public relays can be slow or unavailable. Default tests must not use them. The wheel smoke test uses
|
|
71
|
+
an isolated local DERP/STUN relay and a five-second handshake bound.
|
|
72
|
+
|
|
73
|
+
For Python compatibility changes, test the minimum supported runtime:
|
|
74
|
+
|
|
75
|
+
```console
|
|
76
|
+
uv run --isolated --python 3.11 --all-groups pytest
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Documentation
|
|
80
|
+
|
|
81
|
+
Update documentation with the same change when public behavior changes:
|
|
82
|
+
|
|
83
|
+
- README for discovery, installation, quickstarts, and the public boundary.
|
|
84
|
+
- `docs/python-api.md` for Python parameters, results, exceptions, and lifecycle.
|
|
85
|
+
- `docs/recipes.md` for task-oriented CLI examples.
|
|
86
|
+
- `docs/troubleshooting.md` for new failure modes.
|
|
87
|
+
- SECURITY for authorization, secrets, and trust changes.
|
|
88
|
+
- BUILDING and RELEASING for packaging changes.
|
|
89
|
+
- CHANGELOG for user-visible changes.
|
|
90
|
+
- AGENTS when repository invariants or canonical workflows change.
|
|
91
|
+
|
|
92
|
+
Examples must use placeholders, never live addresses or private keys. Keep commands copyable and
|
|
93
|
+
state which machine runs each side of a multi-machine example.
|
|
94
|
+
|
|
95
|
+
## Packaging changes
|
|
96
|
+
|
|
97
|
+
Changes to bundle building, wheel tags, targets, manifests, dependencies, or build hooks require:
|
|
98
|
+
|
|
99
|
+
```console
|
|
100
|
+
mise run test
|
|
101
|
+
mise run bundle
|
|
102
|
+
mise run bundle-verify
|
|
103
|
+
mise run wheels
|
|
104
|
+
uv run python -m scripts.smoke_wheel dist/wheels/<host-wheel>.whl
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
When running a build module directly, keep it inside mise so the exact Go pin is selected:
|
|
108
|
+
|
|
109
|
+
```console
|
|
110
|
+
mise exec -- uv run python -m scripts.build_wheels --target windows-arm64
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Do not manually edit or commit generated content under `src/tailkitty/bin/`, `dist/`, `.tools/bin/`,
|
|
114
|
+
caches, or virtual environments.
|
|
115
|
+
|
|
116
|
+
## Pull-request checklist
|
|
117
|
+
|
|
118
|
+
- [ ] The change stays inside the Python/native ownership boundary.
|
|
119
|
+
- [ ] Public APIs have complete annotations and root-package exports where appropriate.
|
|
120
|
+
- [ ] Tests cover success, validation failure, timeout, and cleanup where relevant.
|
|
121
|
+
- [ ] Sync and asyncio behavior remain aligned.
|
|
122
|
+
- [ ] Documentation and changelog describe the public result.
|
|
123
|
+
- [ ] No active address, private key, credential, or verbose network log is committed.
|
|
124
|
+
- [ ] `mise run test` passes.
|
|
125
|
+
- [ ] Packaging verification passes when packaging changed.
|
|
126
|
+
- [ ] Remaining external limitations are stated explicitly.
|
|
127
|
+
|
|
128
|
+
## Reporting security issues
|
|
129
|
+
|
|
130
|
+
Do not open a public report containing secrets, live unrestricted addresses, private key files, or
|
|
131
|
+
raw verbose logs. Follow [SECURITY.md](SECURITY.md).
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# 100-iteration improvement ledger
|
|
2
2
|
|
|
3
|
+
> [!NOTE]
|
|
4
|
+
> This is the historical ledger for the initial Tailkitty implementation, not current product
|
|
5
|
+
> documentation. See `README.md`, `CHANGELOG.md`, and `docs/` for the current release. Version and
|
|
6
|
+
> platform statements below intentionally record what was true when each iteration was completed.
|
|
7
|
+
|
|
3
8
|
Each checkbox is a distinct implementation, hardening, verification, or documentation pass. Items
|
|
4
9
|
are marked only after their stated evidence exists.
|
|
5
10
|
|