Skip to content

Added plugin bitbang#1443

Open
richlegrand wants to merge 10 commits into
OctoPrint:gh-pagesfrom
richlegrand:add-bitbang
Open

Added plugin bitbang#1443
richlegrand wants to merge 10 commits into
OctoPrint:gh-pagesfrom
richlegrand:add-bitbang

Conversation

@richlegrand

Copy link
Copy Markdown
  • [x ] You have read the "Registering a new Plugin" guide.
  • You want to and are able to maintain the plugin you are registering, long-term.
  • You understand why the plugin you are registering works.
  • You have read and acknowledge the Code of Conduct.

What is the name of your plugin?

OctoPrint-BitBang

What does your plugin do?

Provides full remote access to an OctoPrint instance -- including live H.264 video -- over a single secure HTTPS shareable link. It uses BitBang to broker a WebRTC peer-to-peer connection, so no account, subscription, port forwarding, tunnel or VPN is required. Video is hardware-encoded on Pi 4 (V4L2 M2M) and software-encoded on Pi 5 / other Linux hosts. Integrates with OctoPrint's WebcamProviderPlugin for snapshots and timelapse.

Where can we find the source code of your plugin?

https://github.com/richlegrand/OctoPrint-BitBang

Was any kind of genAI (ChatGPT, Copilot etc) involved in creating this plugin?

Yes -- some assistance for refactoring. Plugin is not vibe-coded; I designed it and can maintain it. BitBang is where much of the code is and it is fully written by me.

Is your plugin commercial in nature?

No

Does your plugin rely on some cloud services?

Yes -- the bitba.ng signaling service brokers the WebRTC peer connection. Once peers are connected, all media and HTTP traffic flows directly between the browser and OctoPrint over an encrypted WebRTC channel (DTLS-SRTP); bitba.ng only sees the public key, a derived UID, and connection metadata. If a direct peer-to-peer path can't be established, bitba.ng may relay the encrypted stream via TURN — even then, the relay sees ciphertext only. No account, no telemetry. Privacy policy: https://github.com/richlegrand/OctoPrint-BitBang/blob/main/PRIVACY.md

Further notes

The cloud attribute is declared in the registration YAML; __plugin_privacypolicy__ is set in the plugin source and points at the same policy URL.

Comment thread _plugins/bitbang.md Outdated
@github-project-automation github-project-automation Bot moved this to In Progress in OctoPrint Backlog May 25, 2026
@jneilliii

Copy link
Copy Markdown
Contributor

I'd recommend including instructions for OctoPi installs (majority use case for OctoPrint) on how to disable the webcam stream services...I have similar instructions in my go2rtc gist for reference.

https://gist.github.com/jneilliii/93b412cb0bbf6b7bfd76f7e10d612f24?permalink_comment_id=4993013#gistcomment-4993013

@jneilliii

Copy link
Copy Markdown
Contributor

One other thing I just remembered is that you might want to add your plugin_identifier to the list of plugins for remote access.

https://github.com/OctoPrint/plugins.octoprint.org/blob/gh-pages/_topics/remote_access.md

@richlegrand

richlegrand commented May 26, 2026

Copy link
Copy Markdown
Author

One other thing I just remembered is that you might want to add your plugin_identifier to the list of plugins for remote access.

Just added -- thanks :)
And I added instructions for stopping stream services to the BitBang README.md.

@richlegrand richlegrand requested a review from jneilliii May 28, 2026 02:26

@jneilliii jneilliii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one small correction needed for this PR noted in remote_access.md.

relative to the plugin itself, just a quick scan with octoscanner brings up a couple of security concerns relative to the use of BlueprintPlugin.csrf_exempt.

+---------------------------------------------------- OctoScanner ----------------------------------------------------+
|                                                                                                                     |
| +- Plugin OctoPrint-BitBang --------------------------------------------------------------------------------------+ |
| | +---------------------------------------- Summary (2 total findings) -----------------------------------------+ | |
| | | 2 security                                                                                                  | | |
| | +-------------------------------------------------------------------------------------------------------------+ | |
| | +------------------------------------------- Security (2 findings) -------------------------------------------+ | |
| | | SEC-0006                                                                                                    | | |
| | | Endpoint exempted from CSRF protection via BlueprintPlugin.csrf_exempt(). Ensure that the exemption is      | | |
| | | necessary and that the endpoint uses an alternative anti-CSRF mechanism (e.g. token in header).             | | |
| | | File: octoprint_bitbang\__init__.py:155                                                                     | | |
| | |      154 |         @octoprint.plugin.BlueprintPlugin.route("/offer", methods=["POST"])                      | | |
| | |   >  155 |         @octoprint.plugin.BlueprintPlugin.csrf_exempt()                                          | | |
| | |      156 |         def local_offer(self):                                                                   | | |
| | | Suggestion: Remove the decorator if not strictly required. Otherwise, document why the endpoint cannot use  | | |
| | | CSRF protection and ensure another anti-CSRF mechanism is in place.                                         | | |
| | |                                                                                                             | | |
| | | SEC-0006                                                                                                    | | |
| | | Endpoint exempted from CSRF protection via BlueprintPlugin.csrf_exempt(). Ensure that the exemption is      | | |
| | | necessary and that the endpoint uses an alternative anti-CSRF mechanism (e.g. token in header).             | | |
| | | File: octoprint_bitbang\__init__.py:250                                                                     | | |
| | |      249 |         @octoprint.plugin.BlueprintPlugin.route("/camera/brightness", methods=["POST"])          | | |
| | |   >  250 |         @octoprint.plugin.BlueprintPlugin.csrf_exempt()                                          | | |
| | |      251 |         def set_brightness(self):                                                                | | |
| | | Suggestion: Remove the decorator if not strictly required. Otherwise, document why the endpoint cannot use  | | |
| | | CSRF protection and ensure another anti-CSRF mechanism is in place.                                         | | |
| | +-------------------------------------------------------------------------------------------------------------+ | |
| +-----------------------------------------------------------------------------------------------------------------+ |
|                                                                                                                     |
| +--------------------------- Rule Statistics (1 rules matched 2 times on 1/1 plugins) ----------------------------+ |
| | Matches  Rule ID   Message                                                                                      | |
| |       2  SEC-0006  Endpoint exempted from CSRF protection via BlueprintPlugin.csrf_exempt(). Ensure that the    | |
| |                    exemption is necessary and that the endpoint uses an alternative anti-CSRF mechanism (e.g.   | |
| |                    token in header).                                                                            | |
| +-----------------------------------------------------------------------------------------------------------------+ |
|                                                                                                                     |
+---------------------------------------------------------------------------------------------------------------------+

Comment thread _topics/remote_access.md Outdated
@jneilliii

Copy link
Copy Markdown
Contributor

I'm also not 100% sure why, but I've installed this plugin on 3 different instances and it only actually loads the settings/navbar in 1. I initially thought it was because of the pi zero 2w not being supported, but it also didn't load on my pi 4 with USB camera attached. The working install I don't have a camera attached to it but remote access did work fine. Would appreciate some more eyes on this plugin review due to the nature of it being remote access.

@richlegrand

Copy link
Copy Markdown
Author

I'm also not 100% sure why, but I've installed this plugin on 3 different instances and it only actually loads the settings/navbar in 1.

Hi jneilliii,
To try to decouple things, if it doesn't show up in the navbar, please look in settings -> plugins -> bitbang. The URL should be listed there also. If you try the URL and appears good, it's good info for tracking down the issue.

thanks!
--rich

@richlegrand

Copy link
Copy Markdown
Author

relative to the plugin itself, just a quick scan with octoscanner brings up a couple of security concerns relative to the use of BlueprintPlugin.csrf_exempt.

CSRF exemptions removed in v0.1.3; the JS now reads csrf_token from the cookie and sends it as X-CSRF-Token. Re-scan should be clean. It might require a hard reload.

thanks!

@richlegrand richlegrand requested a review from jneilliii May 28, 2026 13:07
@jneilliii

Copy link
Copy Markdown
Contributor

I'm also not 100% sure why, but I've installed this plugin on 3 different instances and it only actually loads the settings/navbar in 1.

Hi jneilliii, To try to decouple things, if it doesn't show up in the navbar, please look in settings -> plugins -> bitbang. The URL should be listed there also. If you try the URL and appears good, it's good info for tracking down the issue.

thanks! --rich

That was the weird part, there were no UI elements loading, not even settings, although the plugin was shown as being installed in plugin manager. I attempted reinstall and force refresh of the page. What I did notice was that the instances that did not work, the restart of OctoPrint was not triggered like it should have been.

@jneilliii

Copy link
Copy Markdown
Contributor

the JS now reads csrf_token from the cookie and sends it as X-CSRF-Token

I think it would be better served to use the OctoPrintClient.getRequestHeaders function available in the OctoPrint js client rather than messing with the cookies directly. Alternatively, I think you could use the get/post methods of the OctoPrint js client instead of fetch and those headers will be added for you automatically (would need verification of that).

@richlegrand

Copy link
Copy Markdown
Author

Hi jneilliii,
Switched to OctoPrint.getRequestHeaders per your suggestion. Verified against client/base.js:194-230 -- that path also adds X-Api-Key for API-key callers, which is a nice bonus. Pushed in v0.1.4.

@richlegrand

Copy link
Copy Markdown
Author

I just posted v0.1.5:

  • CSRF: switched to OctoPrint.getRequestHeaders per your suggestion. Verified against client/base.js:194-230 -- that path also adds X-Api-Key for API-key callers
  • Plugin loads on 1 of 3 instances: tracked the silent disappearance to a bare except ImportError: pass at the bottom of init.py that was swallowing any load failure (intended for standalone-CLI use but too broad). Now logs the failure to octoprint.log. Also replaced the print() calls in octoprint_adapter.py with the plugin logger, so camera-open failures land in
    the same place instead of stdout/journald.

When you have a moment to re-test on a failing instance, the log line to look for is "BitBang plugin not loaded: " -- that should name the missing dependency directly. Happy to dig in from there.

Thanks for the thoughtful review.

@jacopotediosi jacopotediosi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@richlegrand Thanks for the contribution! I finally found some spare time to review the plugin; my findings are below.

  1. In setup.py, you should pass license="MIT" to setuptools.setup() for compatibility with setuptools<77, as shown in the OctoPrint cookiecutter template.
  2. In pyproject.toml the following dependencies are never used and can be removed: numpy, Pillow, aiohttp.
  3. I don't think it's necessary to override several plugin attributes in __init__.y, e.g. __plugin_version__, since the plugin version is already declared in pyproject.toml. I'd suggest minimizing unnecessary overrides to reduce the risk of forgetting to update the value in both places.
  4. In both templates you do some weird JavaScript things, like fetching /api/settings. The correct approach is to use the viewmodels provided by OctoPrint, knockout bindings, and template vars.

In addition, I wanted to check how the PIN verification was done, but I couldn't because it seems to happen inside bitbangproxy, which however is included in the python library bitbang only as a precompiled binary. Is there a reason why the proxy's source code is not available?

@jacopotediosi

Copy link
Copy Markdown
Member

Errata corrige: it appears that the PIN is checked by the two binaries contained in the bin folder of the plugin. Are sources available for them too?

@richlegrand

Copy link
Copy Markdown
Author

Ah, sorry! I took a detour with the code and it's in transition. I was thinking that no one was looking, and I've just been checking into main. Please give me a a few days and I'll give an "all clear" :) I'll address your findings also-- many thanks for those.  

The previous code was all python and it had some speed issues running on the Pi 3, so I decided to do the proxy over the Go binary (bitbangproxy, which isn't public yet).  This code will be fully open source as well. 
The development of this has taken a lot more time than anticipated, but I'm excited to say that it runs really well with the Go code. And it works well on 32-bit OS (quick note to @jneilliii) I'll be doing some more QA on 32 and 64 bit OS before asking you for another look.
 
thanks!

@jacopotediosi

Copy link
Copy Markdown
Member

Gotcha, so just two quick suggestions about Python and Go interactions.

I'd suggest avoiding the use of subprocessing and looking into whether they can interact through bindings (Foreign Function Interface) instead. For example, you can do this by compiling Go as a C shared library and calling it from Python with ctypes or cffi. You might also want to take a look at gopy.

Regarding compilation, I'd suggest keeping only the Go source code in the repository instead of the compiled binaries, and adding a GitHub Action (or similar) that compiles them and bundles them into the Python library's build artifact.

@richlegrand

Copy link
Copy Markdown
Author

Hi @jacopotediosi and @jneilliii -- thanks for your patience while I finished the rework. I just tagged v0.2.1, which should close out the open items. Here's the "all clear" :)

The Go proxy is now fully open source. What was the private bitbangproxy is public: https://github.com/richlegrand/bitbang-cli (MIT). The PIN check and all signaling/transport logic are in the source there.

Binaries are no longer committed -- they're built from source in CI. Per @jacopotediosi's suggestion, the octoprint_bitbang/bin/ binaries are now gitignored and out of the repo. A GitHub Action (publish.yml) checks out the bitbang-cli source, cross-compiles the arm64 + armv7 proxy (CGO_ENABLED=0, statically linked), bundles them into the wheel, and asserts they're present before publishing to PyPI -- so the shipped binary is reproducible from public source.

Some notes on FFI vs. subprocess -- I looked into ctypes/cffi and gopy. I ended up keeping the subprocess boundary on purpose: the proxy is a long-lived process running its own WebRTC/networking event loop, and I wanted hard crash-isolation from the OctoPrint process plus a clean lifecycle (it's the same binary users can run standalone as bitbang). Folding a cgo network event loop into the Python process-- GIL/threading interaction, shared-memory failure modes-- felt like more risk than the subprocess gives up, especially for remote-access code; the FD/socketpair handoff keeps the IPC surface small. Happy to revisit, but that was the reasoning.

bare except ImportError: pass at the bottom of __init__.py that was silently swallowing load failures (fixed in v0.1.5 --it now logs "BitBang plugin not loaded: "). That also explains the missing auto-restart younoticed: the import was failing before the plugin registered. v0.2.1 keeps the explicit logging, so a re-test on a previously-failing pi should name the cause directly-- happy to chase it from there.

Remote-access security posture-- since you wanted more eyes given the nature of the plugin: traffic is peer-to-peer WebRTC/DTLS (end-to-end encrypted; the signaling server only brokers the handshake, and a TURN relay (if one is needed) carries ciphertext only), there's an optional PIN, no account, and the earlier CSRF exemptions are gone (now via OctoPrint.getRequestHeaders). A fresh octoscanner run should come back clean.

I've QA'd on both 32-bit and 64-bit OctoPi. Please take look when you have a moment-- thanks!

@jneilliii

Copy link
Copy Markdown
Contributor

I'm testing out your new instructions on my failed instances, but then realized something about your plugin that should be modified. The DOM replacement of the default classic webcam is not the way to go for webcam providers. Instead, you should generate your own webcam jinja2 template. This way you aren't stepping on the toes of other plugins like the classic webcam and providing your own. An example of this can be found in the simple https://github.com/OctoPrint/OctoPrint-Testpicture

@richlegrand

Copy link
Copy Markdown
Author

you should generate your own webcam jinja2 template

I made this modification to v0.2.3.

@jneilliii

Copy link
Copy Markdown
Contributor

So after getting through the fixes for bookworm install of aiortc as described here the plugin is functional, but I wonder if there would be a better approach for initial install given the CI based binary changes. The install now requires a PyPi based install of OctoPrint-BitBang to be fully functional (after doing the other system level prerequisites) instead of a source distribution zip URL. The only other approach I can think of is bundling the compiled binaries into a source+binaries release asset zip on GitHub rather than pushing to PyPi. I know the WS281x plugin does this, albeit without the compiled binaries part.

That being said, I think the plugin listing should include a prerequisites section documenting the necessary steps prior to attempting install of the plugin as you have on the PyPi description until either @jacopotediosi or @foosel are able to chime in on possible alternatives.

@richlegrand

Copy link
Copy Markdown
Author

I didn't realize the registry installs from a zip archive, not pip/PyPI. That reframes it-- thanks for the nudge and the WS281x pointer.

Happy to make the change-- I'll have CI bundle the proxy binaries into a release.zip attached to the GitHub release, and point the listing's archive: at releases/latest/download/release.zip. I'll also add a prerequisites section covering the system-level steps. PyPI stays available for anyone who prefers pip install (but I could remove if it's confusing -- let me know)

I'll try to get this set up today before travel. Let me know if there's anything else.

@jneilliii

Copy link
Copy Markdown
Contributor

If you go the release zip route make sure to update the software update hook as well.

@foosel

foosel commented Jun 11, 2026

Copy link
Copy Markdown
Member

I'm currently quite busy with 2.0.0rc3 and some other stuff in the background of OctoPrint, so I cannot look closer at the discussion here, but just to clarify:

I didn't realize the registry installs from a zip archive, not pip/PyPI.

This basically just depends on how you register your plugin & your software update hook. In theory it should also work perfectly fine to set a PyPI package name as the archive URL. However, if you provide/stay with the default of the GitHub branch zip, it will go with that and not whatever you might or might not also have uploaded to PyPI.

Check the docs on the pypi_release check type.. you'd basically go with something like

plugin_id:
  type: pypi_release
  package: your_package
  pip: "your_package=={target_version}"

for the software update hook, and have just your_package as the archive URL on the plugin repo.

That should work. However, I have to admit that this would make your plugin the first plugin to ever test this, and also remove the option to support release channels further down the road (those require github releases/branches).

If you need to support multiple architectures though with your combined bits, this is probably the best way to go, and at some point this really should also be tested.

@jneilliii

Copy link
Copy Markdown
Contributor

for the software update hook, and have just your_package as the archive URL on the plugin repo.

I thought that this might work that way, but wasn't completely sure since I don't remember any other plugins using this approach.

@foosel

foosel commented Jun 11, 2026

Copy link
Copy Markdown
Member

Afaik no other plugins do it this way. But it should work 🤔

@richlegrand

Copy link
Copy Markdown
Author

Thanks both, really helpful-- @jneilliii you're right about the hook: mine currently points pip at the /archive/{version}.zip source zip, the binary-less path that kicked this off, so it needs fixing regardless.

pypi_release looks like the natural fit -- my wheel is py3-none-any with all three arch binaries baked in, so pip install OctoPrint-BitBang=={target_version} already works on every arch from one artifact. Before I commit to being the first to rely on it, I'd like to actually run an install + update through Plugin Manage -- and I'm traveling a few days, so I'll test it and report back shortly after. I've got a release.zip asset wired up in CI as a proven fallback either way.

@jneilliii

jneilliii commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Software Update may be able to handle it but not Plugin Manager. I just tested by modifying config.yaml to pull from an offline copy of the plugins.json and plugin manager attempts to install by downloading the archive field first it seems.

2026-06-11 20:52:30,040 - octoprint.plugins.pluginmanager - ERROR - Unexpected error while trying to install plugin from None
Traceback (most recent call last):
  File "C:\Program Files\OctoPrint\WPy64-31700\python\Lib\site-packages\octoprint\plugins\pluginmanager\__init__.py", line 940, in command_install
    path = download_file(url, folder.name)
  File "C:\Program Files\OctoPrint\WPy64-31700\python\Lib\site-packages\octoprint\util\net.py", line 327, in download_file
    with requests.get(url, stream=True, timeout=(connect_timeout, read_timeout)) as r:
         ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\OctoPrint\WPy64-31700\python\Lib\site-packages\requests\api.py", line 73, in get
    return request("get", url, params=params, **kwargs)
  File "C:\Program Files\OctoPrint\WPy64-31700\python\Lib\site-packages\requests\api.py", line 59, in request
    return session.request(method=method, url=url, **kwargs)
           ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\OctoPrint\WPy64-31700\python\Lib\site-packages\requests\sessions.py", line 578, in request
    prep = self.prepare_request(req)
  File "C:\Program Files\OctoPrint\WPy64-31700\python\Lib\site-packages\requests\sessions.py", line 487, in prepare_request
    p.prepare(
    ~~~~~~~~~^
        method=request.method.upper(),
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<10 lines>...
        hooks=merge_hooks(request.hooks, self.hooks),
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Program Files\OctoPrint\WPy64-31700\python\Lib\site-packages\requests\models.py", line 369, in prepare
    self.prepare_url(url, params)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
  File "C:\Program Files\OctoPrint\WPy64-31700\python\Lib\site-packages\requests\models.py", line 440, in prepare_url
    raise MissingSchema(
    ...<2 lines>...
    )
requests.exceptions.MissingSchema: Invalid URL 'OctoPrint-BitBang': No scheme supplied. Perhaps you meant https://OctoPrint-BitBang?

Looking at Plugin Manager code it seems there are only json, python file, and download of known extension types (zip, gzip, etc.). So tested again with https://files.pythonhosted.org/packages/ad/b3/4f0335f6109e391e973f70fe6cbe7efdc30aeec72cc1cd729d99fbcedea2/octoprint_bitbang-0.2.4-py3-none-any.whl and that does work.

image

However, because this can't be patterned to use a "latest" version URL I think the publish to release asset on GitHub is still the better option.

@jneilliii

Copy link
Copy Markdown
Contributor

If you want to attempt your own installs with plugin manager you can update config.yaml to point to your own plugins.json file like I did for testing. Just modify the repository parameter to point to your own copy of the json file.

plugins:
  pluginmanager:
    repository: https://jneilliii.github.io/test/plugins.json

@jneilliii jneilliii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a pre-requisite section in the description describing any OS level application dependency installs.

Comment thread _plugins/bitbang.md Outdated

@jneilliii jneilliii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all my concerns have been addressed. Will request re-review by @jacopotediosi.

@jneilliii jneilliii requested a review from jacopotediosi June 17, 2026 16:00
@richlegrand

Copy link
Copy Markdown
Author

quickie note -- I'm back from travel and I'm doing a refactor to address the code in bitbang_navbar.jinja2 that fetches /api/settings that @jacopotediosi brought up. I hope to have it up tonight :)

@richlegrand

Copy link
Copy Markdown
Author

@jacopotediosi @jneilliii -- all open items addressed, ready for another look:

  1. setup.py passes license="MIT"
  2. dropped unused deps (numpy, Pillow, aiohttp)
  3. version no longer duplicated-- init.py reads it from package metadata
  4. navbar + settings now use a Knockout viewmodel (settingsViewModel dep) -- no more fetch('/api/settings')

Proxy source has been public since v0.2.1 at bitbang-cli (PIN check + transport there; CI builds the binaries from it).

@jneilliii -- added the Prerequisites section and set the title to BitBang.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

4 participants