Repository: dougcalobrisi/linuxcnc-grpc
/ # Go module at repo root
├── go.mod # module github.com/dougcalobrisi/linuxcnc-grpc
├── *.pb.go # Generated Go code
├── proto/ # Proto source files (canonical location)
│ ├── linuxcnc.proto
│ └── hal.proto
├── src/linuxcnc_grpc/ # Python package (PyPI: linuxcnc-grpc)
│ ├── _generated/ # Re-exports from linuxcnc_pb (backwards compatibility)
│ └── *.py # Server implementation
├── packages/
│ ├── python/linuxcnc_pb/ # Python generated protobuf code
│ ├── node/ # npm package (linuxcnc-grpc)
│ │ ├── package.json
│ │ └── src/*.ts # Generated TypeScript
│ └── rust/ # Rust crate (linuxcnc-grpc)
│ ├── Cargo.toml
│ ├── proto/ # Proto files (copied for crate packaging)
│ └── src/ # Rust source (uses tonic for codegen)
├── client.go # Go gRPC client wrapper
├── client_test.go # Go client tests
├── scripts/ # Build, publish, and utility scripts
│ ├── generate-protos.sh # Generate proto code for all languages
│ ├── build-*.sh # Build scripts per language
│ ├── publish-*.sh # Publish scripts per registry
│ ├── sync-versions.sh # Version management
│ └── wait-for-linuxcnc.py # Poll LinuxCNC readiness (e2e CI)
├── tests/ # Python test suite
│ ├── test_e2e.py # E2E tests against real LinuxCNC simulator
│ ├── test_integration.py # Integration tests with mock server
│ ├── test_*_mapper.py # Unit tests for proto mappers
│ ├── test_*_service.py # Unit tests for gRPC services
│ ├── mock_server.py # Mock gRPC server for integration tests
│ └── conftest.py # Shared fixtures
├── docs/ # Hugo documentation site
│ ├── hugo.toml # Hugo configuration
│ ├── README.md # Docs development guide
│ ├── content/ # Markdown documentation pages
│ │ ├── _index.md # Documentation index
│ │ ├── getting-started.md # Installation and quickstart
│ │ ├── server.md # Server configuration
│ │ ├── api-reference.md # Complete API documentation
│ │ ├── examples.md # Examples guide
│ │ ├── tutorial.md # Step-by-step tutorial
│ │ └── e2e-testing.md # E2E testing guide
│ └── static/ # Static assets
└── examples/ # Multi-language client examples
├── python/ # Python examples
├── go/ # Go examples (in cmd/ subdirectories)
├── node/ # Node.js/TypeScript examples
└── rust/ # Rust examples (cargo binaries)
examples/ - Client examples in all supported languages:
Each language directory contains equivalent implementations:
get_status- Basic status pollingstream_status- Real-time status streamingjog_axis- Continuous and incremental joggingmdi_command- MDI G-code execution with interactive modehal_query- HAL pin/signal/parameter queryingupload_file- Upload, list, and delete G-code files
Run examples:
# Python
cd examples/python && python get_status.py
# Go
cd examples/go && go run ./cmd/get_status
# Node.js/TypeScript
cd examples/node && npm install && npx tsx get_status.ts
# Rust
cd examples/rust && cargo run --bin get_statusAll packages use consistent naming across registries:
| Registry | Package Name |
|---|---|
| PyPI | linuxcnc-grpc |
| npm | linuxcnc-grpc |
| crates.io | linuxcnc-grpc |
| Go | github.com/dougcalobrisi/linuxcnc-grpc |
Thread Safety: LinuxCNCServiceServicer uses a threading.RLock to protect all
accesses to self._stat, self._command, and self._error_channel. Streaming RPCs
(StreamStatus, StreamErrors) hold the lock only during poll/map operations, releasing
it before sleep and yield to avoid blocking other threads.
Command Dispatch: SendCommand uses an explicit _command_handlers dict mapping
command type strings to handler methods (no dynamic getattr dispatch).
Input Validation: Handler methods validate indices before dispatching to LinuxCNC:
_validate_joint_index(index, is_joint)checks bounds againststat.joint/stat.axis; index -1 is only valid for joint operations (home/unhome all)._validate_spindle_index(index)checks bounds againststat.spindle._validate_nc_path(filename)resolves paths relative to the NC files directory and rejects path traversal, null bytes, and empty filenames. Used by both_handle_program_cmdand the file management RPCs.- MDI commands reject empty strings and null bytes, and log the full command at WARNING level for audit.
File Management RPCs: UploadFile, ListFiles, and DeleteFile provide remote file
management for the NC files directory (/home/linuxcnc/linuxcnc/nc_files or LINUXCNC_NC_FILES env var).
These RPCs do not require the LinuxCNC lock (no stat/command access) and use _validate_nc_path
for security. Upload max size is 10 MB.
Requires uv for Python dependency management:
make setup # Install all dev + build deps (creates .venv)
make test # Run tests
make test-cov # Run tests with coverage report
make lint # Check Python syntaxAll Python commands use uv run to execute within the managed .venv.
On a LinuxCNC machine, use make install which creates the venv with
--system-site-packages so the linuxcnc Python module is accessible.
make proto # Python only (default)
make proto-go # Python + Go
make proto-rust # Python + Rust
make proto-node # Python + Node.js/TypeScript
make proto-all # All languagesGo prerequisites:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latestRust prerequisites:
cargo install protoc-gen-prost protoc-gen-tonicNode.js/TypeScript prerequisites:
cd packages/node && npm install
# or globally: npm install -g ts-protoAll generated code is committed so users don't need protoc:
- Python:
packages/python/linuxcnc_pb/(re-exported viasrc/linuxcnc_grpc/_generated/for backwards compatibility) - Go:
*.pb.goat repo root - Rust: Generated at build time via
build.rs(proto files inpackages/rust/proto/) - Node.js:
packages/node/src/linuxcnc.ts,packages/node/src/hal.ts
packages/node/README.md and packages/rust/README.md are auto-generated from the root README.md
by scripts/generate-package-readmes.sh (or make readme). These are what npm and crates.io display on
their package pages. CI checks they are up-to-date via the readme-check job.
The root README.md uses absolute GitHub URLs for all links so it renders correctly on PyPI and pkg.go.dev.
pip install linuxcnc-grpcgo get github.com/dougcalobrisi/linuxcnc-grpcimport linuxcnc "github.com/dougcalobrisi/linuxcnc-grpc"npm install linuxcnc-grpcimport { LinuxCNCServiceClient, GetStatusRequest } from 'linuxcnc-grpc';[dependencies]
linuxcnc-grpc = "1.0"use linuxcnc_grpc::linuxcnc::linux_cnc_service_client::LinuxCncServiceClient;All automation scripts are in the scripts/ directory:
| Script | Purpose |
|---|---|
generate-protos.sh |
Generate protobuf code for all languages |
generate-package-readmes.sh |
Generate package READMEs for npm/crates.io from root README |
build-python.sh |
Build Python wheel and sdist |
build-node.sh |
Build Node.js/TypeScript package |
build-rust.sh |
Build Rust crate |
build-all.sh |
Build all packages |
publish-python.sh |
Publish to PyPI |
publish-node.sh |
Publish to npm |
publish-rust.sh |
Publish to crates.io |
publish-all.sh |
Publish all packages (with confirmation) |
sync-versions.sh |
Synchronize version across all packages and doc examples |
common.sh |
Shared helper functions sourced by other scripts |
wait-for-linuxcnc.py |
Poll LinuxCNC readiness (used by e2e CI) |
make build-all # Build all packages
make build-python # Build Python only
make build-node # Build Node.js only
make build-rust # Build Rust onlymake publish-all # Publish all (with confirmation)
make publish-dry-run # Test publish without uploadingmake sync-version VERSION=0.6.0 # Stable release
make sync-version VERSION=0.6.0-beta.1 # Pre-release (also accepts 0.6.0b1)
./scripts/sync-versions.sh 0.6.0 --commit --tag # With git commit and tagPre-release versions are automatically converted per ecosystem:
- Python (PEP 440):
0.6.0b1,0.6.0a1,0.6.0rc1 - npm/Rust (semver):
0.6.0-beta.1,0.6.0-alpha.1,0.6.0-rc.1
Either format can be passed as input; the script converts to the correct format for each package.
The script also auto-discovers and updates Rust dependency version strings (major.minor) in all .md files containing linuxcnc-grpc = "X.Y".
GitHub Actions workflows:
- ci.yml: Runs on push/PR - tests all languages, checks proto freshness, checks package README freshness
- e2e.yml: Runs on push/PR - e2e tests against a real LinuxCNC simulator (builds from source on Ubuntu 24.04)
- release.yml: Unified release workflow (manual dispatch) - publishes to PyPI, npm, and crates.io
- version-bump.yml: Creates a version bump PR (manual dispatch) - updates all package versions via
sync-versions.sh
proto-check ─────────────────────────────────────────────────────┐
readme-check │
lint ──► test-python ──► test-node ──► examples-node │
├──► test-rust ──► examples-rust │
└──► test-go ───► examples-go │
└──► examples-python │
The CI workflow uses aggressive caching to minimize build times:
| Cache | Key | Jobs |
|---|---|---|
| protoc binary | protoc-{VERSION}-linux |
proto-check, test-rust, examples-rust |
| pip packages | pyproject.toml hash |
All jobs with Python |
| npm packages | package-lock.json hash |
test-node, examples-node |
| Go modules | Built-in (setup-go@v5) | test-go, examples-go |
| Cargo (Rust) | Swatinem/rust-cache with shared-key: rust-ci |
test-rust, examples-rust |
| Cargo bin plugins | cargo-bin-proto-plugins-{OS}-v1 |
proto-check |
- Update versions:
make sync-version VERSION=x.y.z - Commit and push to main
- Go to GitHub Actions > Release > Run workflow
dry_run: Test without publishingcreate_tag: Create git tag and GitHub Release after publish
The release workflow:
- Validates version consistency across all packages (normalizes PEP 440 vs semver)
- Runs full CI suite before publishing
- Verifies build artifacts match expected versions before uploading
- Publishes Python, Node.js, and Rust in parallel
- Auto-detects npm
--tagfor pre-release versions (beta, alpha, rc) - Publishes npm packages with
--provenancefor supply chain attestation - Marks GitHub Releases as pre-release when version contains a pre-release suffix
- Creates git tag and GitHub Release after all succeed
Authentication (all use OIDC trusted publishing):
pypienvironment: OIDC viapypa/gh-action-pypi-publishnpmenvironment: OIDC trusted publishing (configured on npmjs.com, requires npm >= 11.5.1)cratesenvironment: OIDC viarust-lang/crates-io-auth-action(configured on crates.io)
User-facing documentation is a Hugo static site in the docs/ directory, deployed to GitHub Pages.
Published site: https://dougcalobrisi.github.io/linuxcnc-grpc/
| Document | Description |
|---|---|
| docs/content/_index.md | Documentation index and overview |
| docs/content/getting-started.md | Installation and quickstart |
| docs/content/server.md | Server configuration and setup |
| docs/content/api-reference.md | Complete API documentation |
| docs/content/examples.md | Examples guide and walkthroughs |
| docs/content/tutorial.md | Step-by-step tutorial |
| docs/content/e2e-testing.md | E2E testing with LinuxCNC simulator |
make docs-serve # Downloads theme + starts live-reload server at localhost:1313
make docs-build # Production build to docs/public/The Hugo theme (hugo-book) is downloaded at build time and gitignored. The GitHub Actions workflow (.github/workflows/docs.yml) builds and deploys on push to main when docs/** changes.