Contribution Guide
Thanks for considering contributing to Cloudy Pad! This guide will help you get started contributing to various parts of the project.
Here’s a typical workflow outline for contributions:
- Understand project architecture
- Fork Cloudy Pad on GitHub or clone project locally
- Setup your development environment
- Development environment setup is fully automated! Don’t worry about tooling installation and setup
- Write code
- Use of AI is encouraged, we provide contextual AI
AGENTS.mdand Cursor rules files. - You can also pass this contribution guide directly to AI context
- Make sure to review and test your code before going forward though. Unsupervised IA contribution are unlikely to pass review.
- Use of AI is encouraged, we provide contextual AI
- Run tests and review
- A simple
task test-unitand human is enough for most situations - Detailed testing and debugging instructions are provided for more complex situation
- A simple
- Submit Pull Request
Table of Content
- Getting started: setup development environment, build and run tests
- Architecture Overview
- Using AI is encouraged - with caveats
- Where and how to contribute ?
- Updating a provider (AWS, Azure, GCP, etc.)
cloudypadCLI: fixing bugs, adding arguments, etc.- Instance configuration: NVIDIA driver install, Sunshine / Wolf deployment, etc. via Ansible Playbook
- Documentation, troubleshooting guide and other non-code related tasks
- Cloudy Pad desktop and Sunshine server container image (Docker)
- Running / writing tests and verifying your changes locally
- Debugging
- Create a Pull Request with your changes
- Useful commands
- Get help on Discord !
Getting started: setup development environment, build and run tests
A fully automated development environment is provided by Nix Flake (see flake.nix at project root). It will setup the required tools with desired versions in a kind of virtual development environment which will not impact or change your own tool version anywhere else on your machine.
You only need to install:
Start a Nix development shell: it will download packages and setup your development environment. The first run may take some time - don’t worry subsequent runs are almost instantaneous:
nix develop
You can then run commands:
# Build Node CLI package
task build-pnpm
# Run unit tests
task test-unit
Most commands are wrapped as a task - See Taskfile.yml at project root.
You’re ready to contribute !
Architecture Overview
Using AI is encouraged - with caveats
You are encouraged to use AI to contribute to Cloudy Pad. AGENTS.md-like files are present under .cursor/rules - make sure to include them. They describe expected best practices and context for AI to better understand the project.
Please note:
- Even though AI use is encouraged, please review your code and follow best practices
- Do not contribute a PR without reviewing and testing your changes. A PR with low-effort and/or broken AI-generated code will slow down the merging process
Where and how to contribute ?
Depending on your contribution, here are various guides to get started: where is code located, guidelines to get started in code and various tips.
Make sure to read Architecture overview first 😉
Updating a provider (AWS, Azure, GCP, etc.)
Providers are located under src/providers/<provider> with a similar structure. You might want to
- Update infrastructure used for provider in
pulumi.ts - Improve CLI arguments and prompting for a provider in
cli.ts
See Architecture and Provider implementation to get a better understanding of Providers.
cloudypad CLI: fixing bugs, adding arguments, etc.
CLI entrypoint is src/cli/main.ts. CLI is constructed by:
- Building
Commandsobjects based on common arguments (as often same arguments are present in multiple commands) program.tsdescribes various commands and available optionsinitializer.tsandupdater.tsare wrappers around their Core equivalents to prompt users for specific details duringcloudypad createandcloudypad update
On running CLI:
- CLI args are parsed into a
CliArgsinterface CliArgsis transformed into a Core interfaceInstanceInputsdescribing inputs (~configurations) for an instance byprompter.tscliArgsIntoPartialInput()
To add or update CLI arguments you can:
- Add/update arguments in main program
- You may also need to update provider-specific
cli.ts, e.g.src/providers/aws/cli.tsfor AWS. These files define CLI arguments specific to each provider.
- You may also need to update provider-specific
- Update
prompter.tsto pass new arguments intoInstanceInputs - If needed, update
prompter.ts:completeCliInput()to prompt the user for input if missing from CLI args - From this point on,
InstanceInputsare passed through to internal components. You can now change code to use your changes.
Instance configuration: NVIDIA driver install, Sunshine / Wolf deployment, etc. via Ansible Playbook
Ansible (an Infrastructure as Code tool) is used to configure instances via playbooks in ansible/.
You can update playbook to:
- Change how NVIDIA drivers are installed
- Update Sunshine server setup
- Update Wolf server setup
- Customize per-provider behavior (e.g. package installation, add exceptions for some providers)
- Manage auto-stop mechanism (detect inactivity to automatically stop instance, avoiding unwanted Cloud cost)
Documentation, troubleshooting guide and other non-code related tasks
Documentation at https://docs.cloudypad.gg is auto-generated from Markdown files under docs/src.
- Updating existing files: on merge, the documentation will be updated
- Adding new files: make sure to reference them in
docs/src/SUMMARY.mdto have them included
Run a local doc server to see the result of your updates via http://localhost:3000:
task mdbook-server
Cloudy Pad desktop and Sunshine server container image (Docker)
The Sunshine streaming server or desktop container is what you see when you connect to Cloudy Pad via Moonlight (using Sunshine streaming server). Source code is located at containers/sunshine
Container is roughly built by:
- Installing a desktop environment on an Ubuntu base image
- Installing game launchers: Steam, Heroic, etc.
- Installing various tools and libraries (Proton, text editor, etc.)
- Adding an overlay with various scripts and default config (Sunshine server config template, desktop environment config, etc.)
On startup, container starts Sunshine, the desktop and all required components.
Running / writing tests and verifying your changes locally
You’ll want to test your changes and verify them along the way. Typical ways include:
Running tests
Running unit tests:
task test-unit
Building application (plain node build or Docker build):
# Node build
task build-pnpm
# CLI Docker container build
task build-core-container-local
Running the CLI directly from your code - e.g. deploy an instance to verify your changes are applied correctly:
# Equivalent of `cloudypad --help` for development
npx tsx src/cli/main.ts --help
# Create a test instance
npx tsx src/cli/main.ts create aws my-test-instance [...]
See examples of fully automated instance deployment via CLI in test/integ/unstable/cli-full-lifecycle
Adding and writing tests
Cloudy Pad has several layer of tests - most of them run on CI:
Unit tests
Run unit tests via task test-unit. Unit tests are under test/unit and follow these global coding practices:
- Use Mocha for testing
Teststructure matchessrcstructure, e.g.src/core/manager.tsis tested viatest/unit/core/manager.spec.ts- Each test is self-contained. A test should not change or update data, variable or context which is also used by another test. If test needs setup (such as an existing instance State file), test setup should be done either in the
it()function or in a before hook. - Avoid using
beforein a spec file to set a global variable used by all tests. Prefer generating data in each test. - Do not hesitate to look at similar tests to match their structure
About side effects:
- You must avoid side effects with unit tests (e.g. don’t contact an external service or a database, etc.). Writing local files is accepted, but the method must guarantee proper reproducibility and debugging analysis (e.g. don’t write a temporary test that can’t be verified if something happens)
- If the component under tests relies on side effect, use Sinon to stub calls causing side effect
- See
test/unit/hooks.tsfor globally defined stubs, e.g. for Pulumi or AWS clients
Unit tests have a few utility functions in test/unit/utils.ts to help:
createDummyState()- Create dummy instance State for testing with optional overridesinitializeDummyInstanceState()- Initialize a dummy instance state and return itDEFAULT_COMMON_INPUT- Instance inputs to use during test require Inputs or StateDEFAULT_COMMON_CLI_ARGS- CLI argument matching DEFAULT_COMMON_INPUTcreateTempTestDir(prefix)- Create temporary directory for testinggetUnitTestCoreClient()- Create a Core client suitable for testing with local data backendgetUnitTestCoreConfig()- Create CoreConfig with local state backend for testinggetUnitTestDummyProviderClient()- Create a dummy ProviderClient for testing
Integration tests
Stable integration tests are under test/integ/stable - they create real Cloud infrastructure to test instance lifecycles. They are not currently run on CI, only locally using your own credentials.
⚠️ Make sure to understand what these tests are doing before running them.
Example:
# Run all integration tests
task test-integ-stable
# Run a specific provider integration tests
test-integ-provider-scaleway
Other tests
Various tests on CI also:
- Check documentation and dead links
- Verify build, compilation and linting
- Verify installation script
You can run them as task locally if CI reports issues.
Debugging
Various debugging methods:
CLI log level
Set environment variable export CLOUDYPAD_LOG_LEVEL=2 (DEBUG) or 3 (INFO) to show more information
Ansible playbook
Ansible playbook is written locally under a temp directory at runtime. Ansible playbook path is shown at DEBUG log level. To debug configuration more easily:
- Run a deployment or configuration command, e.g.
CLOUDYPAD_LOG_LEVEL=2 npx tsx src/cli/main.ts configure my-instance - Logs will show something like
2025-10-09 14:14:18.448 DEBUG src/tools/ansible.ts:15 AnsibleClient Ansible command: ansible-playbook ["-i","/tmp/nix-shell.iuM9BJ/cloudypad-qBivGz/inventory.yml","/home/me/cloudypad/ansible/sunshine.yml","-e","'ansible_ssh_common_args=\"-o StrictHostKeyChecking=no\"'"]
- Use or edit Ansible playbook directly and run:
ansible-playbook -i /tmp/nix-shell.iuM9BJ/cloudypad-qBivGz/inventory.yml ansible/sunshine.yml
Local Pulumi stack manipulation
Nix development shell automatically sets PULUMI_BACKEND_URL and PULUMI_CONFIG_PASSPHRASE environment variables, allowing you to manipulate Pulumi stacks locally.
# List stacks
pulumi stack ls -a
# Show stack resources
pulumi stack -s <organization/CloudyPad-XXX/STACK> --show-ids
# Destroy stack
pulumi destroy -s <organization/CloudyPad-XXX/STACK>
Local Virtual Machine as Cloudy Pad instance
A local debug Virtual Machine (VM) can be set up with Vagrant and Vagrantfile. This machine matches a real-world Cloudy Pad instance except it does not use provisioning and may not have an available GPU (Cloudy Pad still works using CPU encoding).
Local VM goals:
- Test changes impacting configuration, typically:
- Changes to Ansible Playbook in
ansible/ - Changes to Sunshine container image (Docker image) in
containers/
Local VM setup
Debug VM can be setup with:
# Create VM
vagrant up
# Run configuration from Ansible playbook
task dev-ansible-config
# Might fail as :local tag doesn't exist
# Push local container image to VM
task dev-docker-sunshine-to-vm
Using local VM
Instance is reachable via 192.168.56.43.
To pair, connect to the Sunshine web UI and use Moonlight manually:
# A TLS certificate warning is shown as certificate is self-signed
# login: sunshine
# password: @!/:,?!*#'€`_\µ$="foo
# or 'sunshine'
https://192.168.56.43:47990/
Debug container and Dockerfile changes
During Sunshine container development, it’s possible to use a Vagrant-specific Docker Compose which will mount folders from the host machine directly in the Cloudy Pad container.
For example, if you change containers/sunshine/overlay/cloudy/conf/sunshine/sunshine.conf.template locally, you don’t need to rebuild the container image, you can mount this file directly in the VM container for faster testing:
- Update
test/resources/docker-compose.vagrant.ymlto mount desired files, for example:
volumes:
# [...]
#
# Mount local containers/sunshine/overlay/cloudy/conf/sunshine/sunshine.conf.template
# Local project is mounted at /vagrant, hence using /vagrant/<project/path>
#
- "/vagrant/containers/sunshine/overlay/cloudy/conf/sunshine/sunshine.conf.template:/cloudy/conf/sunshine/sunshine.conf.template"
- Update container with custom mounts
vagrant ssh
$ docker compose -f /vagrant/test/resources/docker-compose.vagrant.yml -p sunshine up -d --force-recreate
Connect to Cloudy Pad VM and container
To connect via SSH on instance, see: Connect via SSH
Debug Proton / Wine and game run
To run directly Proton or Wine and see how it behaves, first run the game in a standard way and re-use the same command to reproduce issue or situation.
Identify process running game, then get their env variables:
ps -ef
GAME_PID=1234 cat proc/$GAME_PID$/environ | tr '\0' '\n'
Steam specifics: variables we want to re-use:
export STEAM_COMPAT_CLIENT_INSTALL_PATH="/cloudy/data/Steam"
export STEAM_COMPAT_DATA_PATH="/cloudy/data/Steam/steamapps/compatdata/1903340"
Then run game manually (re-use actual command), eg.:
python3 "/cloudy/data/Steam/steamapps/common/Proton 10.0/proton" waitforexitandrun "C:\\windows\\system32\\cmd.exe" /c "path/to/game.exe"
Example for Steam with Expedition 33:
python3 /cloudy/data/Steam/steamapps/common/Proton 10.0/proton waitforexitandrun /cloudy/data/Steam/steamapps/common/Expedition 33/Expedition33_Steam.exe
Debug process CPU usage or affinity
Specific instructions to debug CPU usage, especially linked to CPU affinity. (eg. for #335)
# Show all process
ps -ef
# Show process using NVIDIA GPU
nvidia-smi
# Show process CPU affinity
taskset -pc PID
Debug process environment variable
cat /proc/5096/environ | tr '\0' '\n'
Enable debug log with Steam and Proton
Connect on instance and docker exec -it -u cloudy cloudy bash to run a shell in container as cloudy user, then run Steam directly:
STEAM_LINUX_RUNTIME_LOG=1 STEAM_LINUX_RUNTIME_VERBOSE=1 PRESSURE_VESSEL_VERBOSE=1 PROTON_LOG=1 PROTON_LOG_DIR=/tmp steam
Possibly also add (as per Steam debug logging instructions ):
CAPSULE_DEBUG=all
G_MESSAGES_DEBUG=all
Create a Pull Request with your changes
When your changes are ready, you can create a Pull Request. Please make sure to:
- Follow conventional commits
- Eg. commit messages like
feat: added a nice feature - See Git history as example
- Eg. commit messages like
- Verify your code: test, compilation, review, etc.
- Explain your changes
Review process
Once your PR is created you should get an update within 3 days. If your PR is not acknowledged, please ping Pierre.
While we’ll do our best to merge your code ASAP, review process may take a bit of time and typically involves:
- Questions about your code
- Fixing a few things like design, refactoring, or adding tests
Useful commands
Taskfile contains lots of commands available in development (see comments on tasks). Run with task xxx, e.g.:
task test-unit
Get help on Discord !
Get in touch on Discord to get help for contributing - we’ll happily guide you through !