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

Rework file path organization and configuration #491

Merged
merged 29 commits into from
Oct 14, 2022

Conversation

Arksine
Copy link
Owner

@Arksine Arksine commented Aug 25, 2022

This pull request will significantly change how file paths are configured. There are two primary goals:

  1. Clean up the somewhat disjointed way Moonraker's file paths are organized
  2. Reduce "worst case scenarios" should an instance be compromised by a malicious actor

As things stand today, a compromised system could have their config and/or gcode paths reconfigured. This pull request organizes all of Moonraker's into a single data folder, named $HOME/printer_data by default. The folder is organized something like the following:

/home/pi/printer_data
├── backup
│   └── 20220822T202419Z
│       ├── config
│       │   └── moonraker.conf
│       └── service
│           └── moonraker.service
├── certs
│   ├── moonraker.cert (optional)
│   └── moonraker.key (optional)
├── config
│   ├── moonraker.conf
│   └── printer.cfg
├── database
│   ├── data.mdb
│   └── lock.mdb
├── gcodes
│   ├── test_gcode_one.gcode
│   └── test_gcode_two.gcode
├── logs
│   ├── klippy.log
│   └── moonraker.log
├── systemd
│   └── moonraker.env
└── moonraker.secrets (optional)

Each instance of Moonraker should have its own data path, however its acceptable for the files and folders within it to be symbolic links. A new command line option for Moonraker, -d <datapath>, has been introduced to configure this location. The -c and -l options still exist if a user desires custom names and/or locations for the log and config.

This change comes with an update to Moonraker's systemd service. The new file will look something like the following:

# systemd service file for moonraker
[Unit]
Description=API Server for Klipper SV1
Requires=network-online.target
After=network-online.target

[Install]
WantedBy=multi-user.target

[Service]
Type=simple
User=pi
SupplementaryGroups=moonraker-admin
RemainAfterExit=yes
WorkingDirectory=/home/pi/moonraker
EnvironmentFile=/home/pi/printer_data/systemd/moonraker.env
ExecStart=/home/pi/moonraker-env/bin/python $MOONRAKER_ARGS
Restart=always
RestartSec=10

There are two significant changes to the service:

  • The description is versioned SV1
  • The addition of the environment file

The environment file contains Moonraker's arguments, this will make possible to update the arguments in the future without updating the service itself. The version is for the InstallValidator covered below.

Having learned from the polkit experience, this pull request also includes an InstallValidator that attempts to automate the changes required to the service unit and configuration. There will be situations where this isn't possible (ie: instances running in containers). Options are available to disable either service or config validation in the machine section, its possible that containers can get away with only config validation.

The validator will first check the service version, if it does not match it will attempt to update the unit file. It will use the current unit file's name to determine the alias, and thus the new data folder location. If this is successful, Moonraker will move to config validation. It will look for the existing path options, remove them, and create symbolic links to them in the data folder.

Everything should go smoothly if Moonraker user has passwordless sudo, however this isn't the case on MainsailOS (not sure about FluiddPi). Thus I had to come up with a means to allow users to enter their linux user password. At this time I am leveraging announcements and Moonraker's landing page to handle this. APIs are available for front ends to handle this directly in the future if they choose to do so.

When Moonraker requests sudo access, users will get some warnings and the announcement:

Mainsail:
mainsail-root-announcement

Fluidd:
fluidd-root-warning
fluidd-root-announcement

Clicking through the link will take them to the landing page:
moonraker-landing-root-request

After entering the correct password, they'll get the following message:
moonraker-landing-update-complete

The data folder will look like the following after completion. Only items that are configured are linked, so if the user does not have ssl certs they won't be linked, etc :
update-tree

I have done quite a bit of testing with this, but it could certainly use more. Assuming there are no objections I plan to leave this up for a few days, make an announcement, then merge a few days later.

@Arksine
Copy link
Owner Author

Arksine commented Aug 25, 2022

Immediately I see something I don't like that I missed beforehand. The announcement shouldn't be titled Root Password Required, as we want the sudo password. I'll make that change and create some update screenshots.

@Arksine Arksine force-pushed the dev-config-rework-20220821 branch from cf656d2 to 8bf644a Compare August 25, 2022 12:20
@pedrolamas
Copy link
Contributor

Regarding the alias/service name, do we get that somewhere on the API so we know which Moonraker service is the one we are accessing when we get the known services state?

@Arksine
Copy link
Owner Author

Arksine commented Aug 29, 2022

I didn't make any changes to the API in this series, however it won't be a problem to add it. It should be possible to determine the service unit name for Klipper as well, however there is a touch more involved.

@Arksine
Copy link
Owner Author

Arksine commented Aug 31, 2022

After some initial conversations I have concluded that Moonraker does not need the alias option, as each instance will have a unique data path. The install script will retain an alias option to help with installing additional instances, however it won't be passed to Moonraker. I plan to update the docs and push them shortly.

I changed the default data folder name to ~/printer_data, however this is a placeholder. I'm open to feedback and recommendations as to what the default naming scheme for the data folder should be.

@miklschmidt
Copy link
Contributor

This looks like a good change, and I applaud you for attempting to automate it as much as possible. I do have a request though (and I completely understand if you decline), but is there any possibility that you might delay merging this by a week? I just noticed this today (those announcements are such a great feature), and I'm leaving for vacation tomorrow, so I won't be able to test it ahead of time / make necessary changes or help RatOS users get through it on the 7th and the following 4 days. Of course that's my problem, not yours :)

Thanks in advance, as always, your work is much appreciated!

@Arksine
Copy link
Owner Author

Arksine commented Oct 3, 2022

There is no harm in delaying a week, I will change to the date to the 14th. The more chance for testing the better.

@miklschmidt
Copy link
Contributor

Excellent! Thank you!

@jbeima
Copy link

jbeima commented Oct 4, 2022

Just an observation, since I have seen this before.

Calling it "printer_data", might not be the best name.

What is this install manages 10 printers?

When we install multiple instances of Klipper and so on the naming convention does not flow or is consistent with a single printer.

If we are taking steps to "tidy" things up, could we not possibly consider a more congruent naming convention for 1, 6, or 12 printers under a single install? Please?

@Arksine
Copy link
Owner Author

Arksine commented Oct 4, 2022

When multiple instances are involved the folders will be printer_1_data, printer_2_data and so on. That said, there is no hard requirement for it to be named this way. Multiple instances are an advanced user option and the data folder can be named whatever you want it to be.

@Arksine Arksine force-pushed the dev-config-rework-20220821 branch from fcacb9a to 7ed03a5 Compare October 6, 2022 20:42
Prepare to move away from configurable paths.  This will
resolve potential security vulnerabilities in the event that
a user's access is compromised.

Signed-off-by:  Eric Callahan <[email protected]>
The config and logs paths are no longer configurable,
they all exist as folders or symbolic links within the primary
data folder.  The gcode path no longer relies on Klipper to
specify the location, instead Klipper's virtual_sdcard path
shold be configured to the location of the "gcodes" folder
in the data path.

Signed-off-by:  Eric Callahan <[email protected]>
Deprecate the "database_path" option.  If the database
does not exist, however the "database_path" does, it
will be used as a fallback.

Signed-off-by:  Eric Callahan <[email protected]>
The secrets module will now look for "moonraker.secrets"
in the data folder.  If the file does not exist the deprecated
"secrets_path" option will be used as a fallback.

Signed-off-by:  Eric Callahan <[email protected]>
Allow components to register reserved paths, then perform reserved
path validation it upon request.  Reserved paths may be registered as
read-only or no access.  Any request to modify an file/folder that is
either reserved or a child of a reserved path is rejected.

Signed-off-by:  Eric Callahan <[email protected]>
Handle exceptions raised when adding a new watch.  Warn
the user and skip adding the node to the watched tree.

Signed-off-by: Eric Callahan <[email protected]>
Signed-off-by:  Eric Callahan <[email protected]>
Additionally, rework the systemd unit so it is not necessary to
overwrite and reload systemd when changes are made to Moonraker's
arguments.  Use a symbolic link for the executable and an environment
flle to supply the arguments.

Signed-off-by:  Eric Callahan <[email protected]>
Add documentation for the new validation options, and document
changes to the sudo APIs.

Signed-off-by:  Eric Callahan <[email protected]>
Report the unit names for both Moonarker and Klipper.

Signed-off-by:  Eric Callahan <[email protected]>
Differentiate instances based on the data path provided.

Signed-off-by:  Eric Callahan <[email protected]>
This prevents an upgrade from unintentionally populating
the config directory when a legacy install exists.

Signed-off-by:  Eric Callahan <[email protected]>
This script provides a method for users to complete an upgrade
that requires elevated privileges via ssh.

Signed-off-by:  Eric Callahan <[email protected]>
@Arksine Arksine force-pushed the dev-config-rework-20220821 branch from 7ed03a5 to d964cf2 Compare October 12, 2022 23:45
@Arksine
Copy link
Owner Author

Arksine commented Oct 13, 2022

Update:

I have decided to deprecate the debug config options and move them to the command line. This will be part of tomorrow's merge. The reasoning is the same, it reduces the worst case scenario on compromised/exposed systems. There are 3 new command line options:

-v, --verbose        Enables debug logging
-g, --debug          Enables debug features
-o, --asyncio-debug  Enables the asyncio debug flag

These can easily be be added to moonraker.env, no need to modify the service file and reload the daemon (one reason why I chose to do this). Developers can simply ssh into the sbc, add arguments to moonraker.env, and restart the moonraker service.

The -g option is the one front end devs will be most interested in. It enables "repo debug"and debug endpoints". Right now there are 4 debug endpoints, they bring back the ability to read and write to protected namespaces in the database.

Unlike the config changes, these changes aren't fully automated. The deprecated options will be removed, however the install validator won't attempt to add -g to moonraker.env due to exposure.

The -v option is available to allow debug logging for testers without enabling other debug features that expose the system. I considered keeping this option in the config, but ultimately decided against it. Its possible that some debug logging could contain credentials. Moonraker will sanitize endpoints known to accept a password, however given that endpoints are customizable through Klipper the possibility of logging sensitive information exists. When I implement support for 3rd party extensions the possibility of logging sensitive info increases.

@Arksine Arksine force-pushed the dev-config-rework-20220821 branch from d964cf2 to 148dbd8 Compare October 13, 2022 12:20
Add a debug option to the command line to enable
debug features.

Signed-off-by:  Eric Callahan <[email protected]>
Allow full database access to registered debug endpoints.

Signed-off-by:  Eric Callahan <[email protected]>
Use the "debug" command line option to enable debug
features for the update manager.

Signed-off-by:  Eric Callahan <[email protected]>
Signed-off-by:  Eric Callahan <[email protected]>
@Arksine Arksine force-pushed the dev-config-rework-20220821 branch from 148dbd8 to 9deb905 Compare October 13, 2022 18:21
@Arksine
Copy link
Owner Author

Arksine commented Oct 13, 2022

Small update, I have decoupled verbose logging and the debug features. So to get both Moonraker needs -v and -g.

@miklschmidt
Copy link
Contributor

miklschmidt commented Oct 13, 2022

What's the best way to test this? Was hoping to switch to the branch, reset to an early commit and run the moonraker update, but of course that won't work without some way to tell moonraker that it's now syncing against another branch. I checked out the branch and restarted moonraker but absolutely nothing happened, it shows the new branch, but everything is working like before, no notifications, no modifications to any files.

Update: it did create the printer_data folder and symlinked the items.

Update 2: It did everything it was supposed to do, i'm just super blind. Seems like it "just works"! I'm gonna give it more of a challenge now, i've got most of the config hidden away in an include (including the [file_manager] section), we'll see what that does.

@miklschmidt
Copy link
Contributor

miklschmidt commented Oct 13, 2022

Okay so, when the [file_manager] section resides in an included file, moonraker won't symlink the printer_data dirs, that would be nice to have, is that possible? Of course, it won't modify the included file and I wouldn't expect it to (that would actually be a bit of a problem for RatOS :))

@Arksine
Copy link
Owner Author

Arksine commented Oct 14, 2022

It should work with included files. In my tests it both creates the symlinks and removes the deprecated options from included files.

When testing, make sure you start in a pristine state. Moonraker tracks validation in the database, so that key must be removed before performing a new test. I have a script I run when I want to reset:

sudo service moonraker stop
FM_KEY="{\"gcode_path\":\"/home/pi/gcode_files\",\"metadata_version\":3}"
~/moonraker-env/bin/python -mlmdb -e ~/.moonraker_database/ -d moonraker edit --delete=validate_install
~/moonraker-env/bin/python -mlmdb -e ~/.moonraker_database/ -d moonraker edit --set file_manager=${FM_KEY}
cp ~/klipper_config/moonraker.conf.bak ~/klipper_config/moonraker.conf
rm -rf ~/printer_data
sudo cp ~/klipper_config/moonraker.service /etc/systemd/system/moonraker.service
sudo systemctl daemon-reload

This script does the following:

  • Restores the gcode_path database key. This prevents Moonraker from clearing and re-extracting gcode metadata
  • Removes the validate_install database key. As mentioned above this key must be removed for Moonraker to perform the validation checks.
  • Restores a backup of the "old" moonraker.conf.
  • Removes ~/printer_data. This is required, if the install validator detects that the symbolic links are already there it will not create new links.
  • Restores a backup of the legacy version of the system service.

After making these changes, moonraker can be restarted to re-test validation under different conditions.

@Arksine
Copy link
Owner Author

Arksine commented Oct 14, 2022

I thought I would provide a bit more context on how the the install validator works. It does not rely on the update_manager, validation is performed post update. This allows it to work if Moonraker is updated via KIAUH or just a git pull.

When Moonraker finishes loading all components it will check to see if install validation is necessary. The database stores a key to indicate if validation has been performed. If the key does not exist, Moonraker will first validate if the service file needs to be updated. It will then proceed to validate the configuration. When validating the configuration, it will fetch the value of deprecated path options and create symbolic links IF the folder/link does not exist in the data path. It will then remove the option from the configuration.

Moonraker's configuration parser tracks the file location of each section/option. When a section and/or option is removed it will remove it from all files. If this is a problem for RatOS we'll need to discuss how to proceed, if possible it would be good if this can be resolved on your end. This functionality will be used in the near future by configuration APIs. Front ends will be able to use these APIs to present a UI for modifying the config.

@miklschmidt
Copy link
Contributor

miklschmidt commented Oct 14, 2022

Moonraker tracks validation in the database, so that key must be removed before performing a new test. I have a script I run when I want to reset:

Yeah that'll do it! I'll try again :)

If this is a problem for RatOS we'll need to discuss how to proceed, if possible it would be good if this can be resolved on your end.

I expected I'd have to do something, which is okay. It's a bit of a chicken and egg problem though, I can't change the included config before moonraker has been updated and when moonraker has been updated the RatOS configuration would have a dirty working copy. I could read from the moonraker database and check the validate_install key, then if that has been set i can swap out the include in the user's moonraker.conf. Is there something similar to ~/moonraker-env/bin/python -mlmdb -e ~/.moonraker_database/ -d moonraker edit --delete=validate_install for reading? like ~/moonraker-env/bin/python -mlmdb -e ~/.moonraker_database/ -d moonraker read --key=validate_install or something similar?

EDIT: seems like ~/moonraker-env/bin/python -mlmdb -e ~/.moonraker_database/ -d moonraker get validate_install is what i want. Now i just have to figure out what to do about the dirty working copy..

EDIT2: Just remembered i have a moonraker post-merge hook. That would allow me to checkout the old ratos-configuration moonraker.conf include in case it has been modified, and then I should be good. Sorry for using this PR as a notepad, lol.

EDIT3: the post-merge hook runs too early (before moonraker is restarted and makes the changes) :(

@Arksine
Copy link
Owner Author

Arksine commented Oct 14, 2022

I see, I wasn't sure what the issue is but suspected it might be that the config exists in a git repo.

One possibility would be for the post-merge hook to first check for the "validate_install" key, and if it exists do nothing (suggests that the update has already occured). If it doesn't exist it can write out a temporary script and schedule it as a cron job to run every minute. Once the script begins execution I believe it should be able to remove itself from cron, then periodically check for the validate_install key. Once its detected it can reset the branch or do whatever is necessary.

@Arksine
Copy link
Owner Author

Arksine commented Oct 14, 2022

Maybe there is another option. You could update the config in your repo to remove the deprecated options, then use the post merge hook to add them back. Presuming Moonraker does the right thing, when it removes the options the repo should be pristine again.

@miklschmidt
Copy link
Contributor

miklschmidt commented Oct 14, 2022

Both of these are viable solutions, I already considered the cronjob, but decided against it because I was hesitant to add yet another management mechanism. The last suggestion is pretty clever though, let me think about that some more. Currently the plan was to merge the included file into the user's moonraker.conf (just replace the include section), and then later on delete hose sections and make them managed again, while creating a backup. But your solution seems more robust. I'll talk it over with the team, thank you for taking the time!

@miklschmidt
Copy link
Contributor

Went ahead with you last suggestion, it's implemented, and it looks like it does the job! Fantastic! Thank you for that suggestion that made things a whole lot easier. There's the case of a dirty working copy that'll have to be reset in case moonraker is run first, but that's a minor issue and we already condition users to update RatOS first, so it's fine. I'll merge and push as soon as you merge this PR. You have a go from me! :)

@Arksine
Copy link
Owner Author

Arksine commented Oct 14, 2022

@miklschmidt Glad it worked out in tests. I will go ahead and merge this now.

@Arksine Arksine merged commit 2603cc2 into master Oct 14, 2022
@Arksine Arksine deleted the dev-config-rework-20220821 branch October 14, 2022 19:17
mkuf added a commit to mkuf/prind that referenced this pull request Oct 18, 2022
kennethjiang pushed a commit to TheSpaghettiDetective/moonraker-obico that referenced this pull request Oct 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants