├── test ├── __init__.py └── main_test.py ├── src └── main.py ├── LICENSE ├── .github └── workflows │ ├── hello-world.yml │ ├── python-test.yml │ └── python-test-in-cloud.yml ├── .gitignore └── README.md /test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | if __name__=="__main__": 2 | print("Hello from main.py!") 3 | -------------------------------------------------------------------------------- /test/main_test.py: -------------------------------------------------------------------------------- 1 | class TestTruth: 2 | def test_math(self): 3 | assert(2 + 2) == 4 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Public.Law 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/hello-world.yml: -------------------------------------------------------------------------------- 1 | name: Hello World on self-hosted 2 | 3 | # Controls when the action will run. Triggers the workflow on push or pull request 4 | # events but only for the main branch 5 | on: 6 | push: 7 | branches: [ main ] 8 | pull_request: 9 | branches: [ main ] 10 | 11 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 12 | jobs: 13 | # This workflow contains a single job called "build" 14 | build: 15 | # The type of runner that the job will run on 16 | runs-on: self-hosted # ubuntu-latest 17 | 18 | # Steps represent a sequence of tasks that will be executed as part of the job 19 | steps: 20 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 21 | - uses: actions/checkout@v2 22 | 23 | # Runs a single command using the runners shell 24 | - name: Run a one-line script 25 | run: echo Hello, world! 26 | 27 | # Runs a set of commands using the runners shell 28 | - name: Run a multi-line script 29 | run: | 30 | echo Add other actions to build, 31 | echo test, and deploy your project. 32 | -------------------------------------------------------------------------------- /.github/workflows/python-test.yml: -------------------------------------------------------------------------------- 1 | name: Python application on self-hosted 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: self-hosted 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Set up Python in a virtual env 18 | run: | 19 | if [[ `uname` != "Darwin" ]]; then sudo apt -y install python3-venv; fi 20 | python3 -m venv .env 21 | 22 | - name: Install dependencies 23 | run: | 24 | source .env/bin/activate 25 | python -m pip install --upgrade pip 26 | pip install flake8 pytest 27 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 28 | 29 | - name: Lint with flake8 30 | run: | 31 | source .env/bin/activate 32 | # stop the build if there are Python syntax errors or undefined names 33 | flake8 src/ --count --select=E9,F63,F7,F82 --show-source --statistics 34 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 35 | flake8 src/ --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 36 | 37 | - name: Test with pytest 38 | run: | 39 | source .env/bin/activate 40 | pytest 41 | -------------------------------------------------------------------------------- /.github/workflows/python-test-in-cloud.yml: -------------------------------------------------------------------------------- 1 | name: Python application in the Cloud 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Set up Python in a virtual env 18 | run: | 19 | if [[ `uname` != "Darwin" ]]; then sudo apt -y install python3-venv; fi 20 | python3 -m venv .env 21 | 22 | - name: Install dependencies 23 | run: | 24 | source .env/bin/activate 25 | python -m pip install --upgrade pip 26 | pip install flake8 pytest 27 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 28 | 29 | - name: Lint with flake8 30 | run: | 31 | source .env/bin/activate 32 | # stop the build if there are Python syntax errors or undefined names 33 | flake8 src/ --count --select=E9,F63,F7,F82 --show-source --statistics 34 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 35 | flake8 src/ --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 36 | 37 | - name: Test with pytest 38 | run: | 39 | source .env/bin/activate 40 | pytest 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # VS Code 132 | settings.json 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Python self-hosted GitHub Runner for the Raspberry Pi 2 | 3 | ![Python application on self-hosted Raspberry Pi](https://github.com/dogweather/raspberry-pi-python-github-runner/workflows/Python%20application%20on%20self-hosted/badge.svg) 4 | 5 | 6 | Raspberry Pi's can be used as low-configuration [self-hosted GitHub Runners](https://docs.github.com/en/free-pro-team@latest/actions/hosting-your-own-runners/about-self-hosted-runners). They can do CI builds and tests, and also run [periodic jobs using cron syntax](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#onschedule): 7 | 8 | ```bash 9 | pi@pi1 ~/actions-runner> ./run.sh 10 | 11 | √ Connected to GitHub 12 | 13 | 2020-10-06 23:24:36Z: Listening for Jobs 14 | ``` 15 | 16 | This repo solves a problem: GitHub's default Python setup installs 17 | X86 binaries and doesn't work on ARM (Raspberry Pi). So, in the 18 | process of setting up a new Python app, I extracted this configuration. 19 | 20 | Here's a typical [Raspberry Pi runner log](https://github.com/dogweather/raspberry-pi-python-github-runner/runs/1212774604?check_suite_focus=true) 21 | for this repo. Check out how much detail you get vs. running a simple cron job! 22 | 23 | This repo is a minimal Python template application which runs several workflows: 24 | 25 | * [One - the whole point of this repo](https://github.com/dogweather/raspberry-pi-python-github-runner/blob/main/.github/workflows/python-test.yml) which runs on a self-hosted Raspberry Pi 26 | * [Another](https://github.com/dogweather/raspberry-pi-python-github-runner/blob/main/.github/workflows/python-test-in-cloud.yml) which runs on GitHub's cloud servers 27 | * [Another simple one](https://github.com/dogweather/raspberry-pi-python-github-runner/blob/main/.github/workflows/hello-world.yml), a Hello World shell script which also runs self-hosted 28 | 29 | It's also still compatible with MacOS and GitHub's cloud servers. 30 | 31 | ## How to run this yourself 32 | 33 | 1. Fork this repo 34 | 2. In your new repo's **Settings/Actions**, click **Add runner** and add your Raspberry Pi as a Linux / ARM runner. [GitHub's Docs for this](https://docs.github.com/en/free-pro-team@latest/actions/hosting-your-own-runners/adding-self-hosted-runners) 35 | 3. Once the runner is listening for new tasks, make some kind of small commit in your forked repo. You should see tasks sent down to the Raspberry Pi both in the Runner's output and GitHub's Actions tab. 36 | 37 | ## Compatible Pi's 38 | 39 | | Known working | Known not working | 40 | | ------------- | ----------------- | 41 | | Pi 4 Model B 4G RAM w/ Raspberry Pi OS | Pi Zero W | 42 | 43 | These were tested on a **Raspberry Pi 4 Model B** with 4G RAM, running Raspberry Pi OS. 44 | I ran two runners on it simultaneously, and there was **tons** of RAM leftover. I suspect it'd 45 | do fine on a Pi with just 1G RAM. 46 | 47 | But for some reason, GitHub's runner client won't run on a Pi Zero W (which has 512MB RAM). 48 | The `config` and `run.sh` scripts crash with `Segmentation fault` on startup. 49 | The error seems to be around the `bin/Runner.Listener` invocation. When I try that 50 | command on its own, I get `“bin/Runner.Listener” terminated by signal SIGSEGV (Address boundary error)`. 51 | I suspect that low memory is the issue. RAM is also the only material difference between 52 | the Zero W and 4. 53 | --------------------------------------------------------------------------------