Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

shell_command: Execute a linux command/script from within Klipper. #2173

Closed
wants to merge 1 commit into from

Conversation

Arksine
Copy link
Collaborator

@Arksine Arksine commented Nov 13, 2019

The shell_command module allows a user to run a linux command via gcode. The command is launched using subprocess.Popen, then Popen.poll() is checked every 50ms, this allows for the command's execution to yield cooperatively with the Reactor. If the command takes longer than the configured timeout to execute then the process is forcibly terminated.

If verbose is set to True the output from the command is forwarded to the terminal.

Example Command:

[shell_command say_hi]
command: echo hello!
timeout: 2.
verbose: True

Octoprint's Output:

Send: RUN_SHELL_COMMAND CMD=say_hi
Recv: // Running Command {say_hi}...:
Recv: // hello!
Recv: // Command {say_hi} finished
Recv: ok

Signed-off-by: Eric Callahan arksine.code@gmail.com

@KevinOConnor
Copy link
Collaborator

Thanks. My high-level feedback is that I'm a bit leery of doing this because of security and general stability concerns. The code itself looks fine to me. I'm not sure about the high-level implications though (eg, privilege escalation, system resource management, etc.).

Is there a particular use-case that this functionality is designed to address? Maybe there are alternative solutions.

-Kevin

@Arksine
Copy link
Collaborator Author

Arksine commented Nov 15, 2019

Hi Kevin, this was implemented at the request of a couple of Voron users. The examples they provided were things such as issuing push notifications and controlling LEDs from the Pi. These things could be implemented with extras modules, I think for them it was more an issue of flexibility.

I warned them that there could be security implications. I attempted was to make it as safe as possible by requiring the command to be configured in printer.cfg (rather than something like RUN_SHELL_COMMAND CMD='echo hi'). I hadn't considered system resources, but indeed that could be an issue if they do something wild.

@jperrin72
Copy link

Hi Kevin, this was implemented at the request of a couple of Voron users. The examples they provided were things such as issuing push notifications and controlling LEDs from the Pi. These things could be implemented with extras modules, I think for them it was more an issue of flexibility. ...

@Arksine @KevinOConnor I would love to have that feature so that I could shutdown properly my system through M81 macro (using Repetier Server)

Sent with GitHawk

@Stephan3
Copy link
Contributor

Stephan3 commented Dec 4, 2019

I use this for controlling my WLED Esp32. So probing does nightrider sequence, red fading while heatup, green light for print done etc.

Additionally I can ping my grafana dB for uptimes, filament usage, average speed, layertimes.

Maybe Someone moar more creative has other ideas.

@sbernier1
Copy link

I would like this too, to trigger GPIO pins (I use DWC instead of Octoprint)

@moozphat
Copy link

moozphat commented Apr 8, 2020

Ideally, it would be nice that arguments could be passed too. (I know this could be a greater security concern; sight...)

Raspberry Pi GPIOs is a good example. My use case is that I'm building an temperature controlled enclosure. I would like the slicer (Cura) to pass an enclosure temperature (that depends on the filament). This feature would allow me to send the value to an enclosure controller.

Ultimately, we need a way to send commands to the DWC host when the action cannot be done by the MCU... Those could be added to macros which, right now, my understanding can only control the MCU.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
@Arksine
Copy link
Collaborator Author

Arksine commented Apr 24, 2020

I have modified shell_command.py to behave as a dev utility rather than a prefix module. I modeled it after how gcode_macro creates templates. For example, if the developer wants to create a shell command, they could do the following:

shell_command = self.printer.try_load_module(config, 'shell_command')
self.reboot_cmd = shell_command.load_shell_command('sudo reboot now')
self.shutdown_cmd = shell_command.load_shell_command('sudo shutdown now')

Then to execute the command:

self.shutdown_cmd.run(timeout=None, verbose=False)

Setting the timeout to None makes it the command fire and forget, the process will be launched and run() will return. It is not possible for a fire and forget command to be "verbose", so regardless of the parameter passed verbose will be set to false.

Its also possible for a developer to set their own output callback via the shell command's set_output_callback method. This defaults to gcode.respond_info, however it might be useful for a module to capture that output and process it in some fashion. One possible application of this would be to launch Klippy in batch mode to simulate a print.

@KevinOConnor
Copy link
Collaborator

Thanks. Looks good to me. I think we should look to merge this along with the first set of code that utilizes it though.

Cheers,
-Kevin

@marcosscriven
Copy link

Thanks. Looks good to me. I think we should look to merge this along with the first set of code that utilizes it though.

Cheers,
-Kevin

Would a unit test not suffice here?

@lixxbox
Copy link

lixxbox commented Jun 28, 2020

I have modified shell_command.py to behave as a dev utility rather than a prefix module.

Would it still be possible for me - as a user - to define shell commands as gcode?
For example sending notifications via pushover or switching sonoff/tasmota devices.

edit: I have managed to write my own pushover host module. sonoff/tasmota should also be easy to implement.
To answer my question myself: no, in my opinion it is currently not possible to use the shell_command in a custom macro.

@changedsoul
Copy link

Has this been merged with the main branch yet?

@kakou-fr
Copy link
Contributor

This will be very helpfull for notification (domoticz ...) with execution of wget command

@kotyara75
Copy link

+1
I run my prints offline (no connectivity in my remote shed), it would be really helpful for implementation of graceful shutdown of RPI from the LCD menu.

@ru-ace
Copy link

ru-ace commented Oct 25, 2020

+1
i use Mainsail instead octoprint as light web interface, and it hasnt plugins that can do this functionality. So it will be very useful to have possibility run external scripts.

@abduct
Copy link

abduct commented Oct 26, 2020

+1
This would be nice to include. Alot of functionality can be obtained by such a feature set such as notification systems for various platforms (irc, discord, telegram, growl, etc). Currently the only way (that I am aware of) to interface with klipper is to write a moonraker service that polls the api periodically checking on status.

The concerns about security and priv esc are valid, but lets face it, the printers should not be public facing anyways and should only be accessible via secure gateways (ssh, vpn, reverse proxy with authentication, etc).

@toskium
Copy link

toskium commented Nov 8, 2020

Would love to to see this happening.
😀

@onshorechet
Copy link

This should go back so that it lets users defined shell_commands in the config file again because the functions to run shell commands internally in klippy is trivial. The real power is letting users define their own cmd to call. Eric if you do not mind I would like to add template support and a filter extension to Jinja2 for safe quoting of shell args similar to Ansible. I can create a competing pull request starting from the commit above if that is not stepping on your toes.

Ansible example of a shell quote filter for Jinja2 ~ line 111:
https://github.com/ansible/ansible/blob/252685092cacdd0f8b485ed6f105ec7acc29c7a4/lib/ansible/plugins/filter/core.py

The from a security point of view because Klipper's config is not accessible to run time changes that makes it akin to defining functions in code and recompiling. Users should be conditioned to secure their config files anyways because of the nature of the machines they run and the harm it could cause if misconfigured. What is the problem is other projects exposing the config file via a web interface which may or may not be protected with a user accounts, encryption, etc.

To mitigate the above we could add a cli arg to enable this feature and that arg would usually to be provided in "/etc/default/klipper" so it isn't web accessible. Then if shell_commands are enabled, klipper could refuse to start if the main config file isn't owned by a certain user with more restrictive permissions. Something like chown klipper:klipper printer.cfg; chmod 664 printer.cfg. Then the pi user or a general web server user could read the file for display in the UI but not write changes. Users would need to ssh and sudo editor or cp a new versions and restart klippy. Openvpn, Ssh and Postgresql are a few projects I know of that have similar protections for config files and runtime function definitions(Postgresql especially).

These checks would only apply if shell_commands are enabled leaving the default behavior in tact. Other less related protections could be added to secure the /etc/default/klipper file as well.

I can roll all of the above into a new PR but it will take some time:

  1. Enable shell_commads definitions from the config file
  2. Add templating support for shell_commands so runtime variables can be used as args in shell_commands
  3. Add a shell safe quote filter to Jinja2
  4. Add a flag to the main klippy exec to enable this feature
  5. If enabled ensure the provided config file is owned by the user starting the command and only writable by that user
  6. Changes to documentation encouraging the setup of a secure printer.cfg

@onshorechet
Copy link

  1. Enable shell_commads definitions from the config file
  2. Add templating support for shell_commands so runtime variables can be used as args in shell_commands
  3. Add a shell safe quote filter to Jinja2
  4. Add a flag to the main klippy exec to enable this feature
  5. If enabled ensure the provided config file is owned by the user starting the command and only writable by that user
  6. Changes to documentation encouraging the setup of a secure printer.cfg

I'm not quite ready for a full PR but here is a WIP of the above:
https://github.com/chet0xhenry/klipper/tree/feature/shell_exec

Item number 4 was skipped in favor of just detecting the existence of a shell_exec section and protecting the config files based on that.

@Arksine
Copy link
Collaborator Author

Arksine commented May 3, 2021

As there doesn't seem to be a need for this PR I'm going to go ahead and close it. Using the built in subprocess module seems to be adequate for the time being.

@Arksine Arksine closed this May 3, 2021
@auhopu
Copy link

auhopu commented May 8, 2021

Sorry to reshuffle a closed issue, but it's not clear to me what the workaround is.

In my case, having the 3d printer in the basement, I keep the light off. I then use an app on my phone to turn an IP camera's IR light on before heading to Fluidd where I get the stream via MJPEG. It would be nice to have a couple of macros assigned to a couple of wget's turning the IR light on/off without having to leave Fluidd.

@Arksine Arksine deleted the work-shell_command branch May 8, 2021 19:22
@Exeu
Copy link

Exeu commented Jun 25, 2021

Basically this means that we have to either fork this or just put that single shell file into the extras folder.
Then we can start using this....

@auhopu
Copy link

auhopu commented Jun 25, 2021

I have been using this for a while now and it's very helpful. I have defined 6 IP camera API calls via curl under 6 gcode_shell_command sections which I then group in 2 macros.

The first turns the IR led on, points the camera to the printer, zooms in.
The second zooms out, points the camera to the original position, turns the IR led off

[gcode_macro CAM_INIT]
gcode:
    RUN_SHELL_COMMAND CMD=led_on
    G4 P4000
    RUN_SHELL_COMMAND CMD=preset_5
    G4 P2000
    RUN_SHELL_COMMAND CMD=zoom_in

[gcode_macro CAM_RESTORE]
gcode:
    RUN_SHELL_COMMAND CMD=zoom_out
    G4 P4000
    RUN_SHELL_COMMAND CMD=preset_1
    G4 P2000
    RUN_SHELL_COMMAND CMD=led_off

@rizwansarwar
Copy link

@auhopu Do you have a copy of the code? @Arksine seems to have trashed it.

@auhopu
Copy link

auhopu commented Jul 26, 2021

Here's the script:
https://github.com/th33xitus/kiauh/blob/master/resources/gcode_shell_command.py

Just place it under your
~/klipper/klippy/extras/

Here's a sample config:
https://github.com/th33xitus/kiauh/blob/master/resources/shell_command.cfg

@github-actions github-actions bot locked and limited conversation to collaborators Oct 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.