Compare commits
10 Commits
6f2c79f985
...
30ac5dfae9
Author | SHA1 | Date |
---|---|---|
Eric Callahan | 30ac5dfae9 | |
Eric Callahan | 0a8590643f | |
Eric Callahan | 3a42dac02b | |
Eric Callahan | 863d0c1e4b | |
Eric Callahan | b40751ba9d | |
Eric Callahan | c2409e813e | |
Eric Callahan | 83371cf135 | |
Eric Callahan | 247640cc27 | |
Eric Callahan | 63578aefd0 | |
Eric Callahan | 8debbf8ba4 |
|
@ -11,3 +11,4 @@ start_moonraker
|
|||
.pdm-python
|
||||
build
|
||||
dist
|
||||
share
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: sync-requirements
|
||||
name: sync python requirements
|
||||
language: system
|
||||
entry: python3 scripts/sync_dependencies.py
|
||||
files: ^pyproject.toml$
|
||||
- id: sync-os-packages
|
||||
name: sync packages
|
||||
language: system
|
||||
entry: python3 scripts/sync_dependencies.py
|
||||
files: ^scripts/system-dependencies.json$
|
|
@ -6,6 +6,38 @@ The format is based on [Keep a Changelog].
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- **server**: Use `asyncio.run` to launch the server as recommended by the
|
||||
official Python documentation.
|
||||
- **announcements**: Look for xml files at `<data_path>/development/announcements`
|
||||
when `dev_mode` is set to True.
|
||||
|
||||
### Fixed
|
||||
- **confighelper**: Don't resolve symbolic links to the main configuration file.
|
||||
|
||||
|
||||
## [0.9.2] - 2024-07-30
|
||||
|
||||
### Added
|
||||
- **install**: Add support for installing Moonraker's python package via pip.
|
||||
- **scripts**: Add script to sync python and system dependencies from
|
||||
`pyproject.toml` and `system-dependencies.json` respectively.
|
||||
- **dev**: Add pre-commit hook to call `sync_dependencies.py`.
|
||||
|
||||
### Fixed
|
||||
- **build**: Build from sdist now correctly includes share data.
|
||||
- **build**: Remove stray `.gitignore` from Python Wheel.
|
||||
|
||||
### Changed
|
||||
- **install**: The `MOONRAKER_FORCE_DEFAULTS` environment variable has changed
|
||||
to `MOONRAKER_FORCE_SYSTEM_INSTALL`.
|
||||
|
||||
## [0.9.1] - 2024-07-25
|
||||
|
||||
### Fixed
|
||||
- **source_info**: Fixed `importlib.metadata` compatibility issues with python
|
||||
versions 3.9 or older.
|
||||
|
||||
## [0.9.0] - 2024-07-25
|
||||
|
||||
### Added
|
||||
|
@ -184,7 +216,9 @@ The format is based on [Keep a Changelog].
|
|||
[api_changes.md]: api_changes.md
|
||||
|
||||
<!-- Versions -->
|
||||
[unreleased]: https://github.com/Arksine/moonraker/compare/v0.9.0...HEAD
|
||||
[unreleased]: https://github.com/Arksine/moonraker/compare/v0.9.2...HEAD
|
||||
[0.9.2]: https://github.com/Arksine/moonraker/compare/v0.9.1...v0.9.2
|
||||
[0.9.1]: https://github.com/Arksine/moonraker/compare/v0.9.0...v0.9.1
|
||||
[0.9.0]: https://github.com/Arksine/moonraker/compare/v0.8.0...v0.9.0
|
||||
[0.8.0]: https://github.com/Arksine/moonraker/compare/v0.7.1...v0.8.0
|
||||
[0.7.1]: https://github.com/Arksine/moonraker/releases/tag/v0.7.1
|
|
@ -2222,7 +2222,7 @@ folder that provides supplemental information for the application. The
|
|||
Moonraker uses the [PDM backend](https://backend.pdm-project.org/) to perform
|
||||
its package builds. An example of a pdm build script that generates a
|
||||
`release_info` file may be found
|
||||
[here](https://github.com/Arksine/moonraker/blob/master/scripts/pdm_build_dist.py).
|
||||
[here](https://github.com/Arksine/moonraker/blob/master/pdm_build.py).
|
||||
|
||||
#### The System Dependencies File Format
|
||||
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
## Installation
|
||||
|
||||
This document provides a guide on how to install Moonraker on a Raspberry
|
||||
Pi running Raspian/Rasperry Pi OS. Other SBCs and/or linux distributions
|
||||
may work, however they may need a custom install script. Moonraker
|
||||
requires Python 3.7 or greater, verify that your distribution's
|
||||
Python 3 packages meet this requirement.
|
||||
This document provides a guide on how to install Moonraker on a Debian
|
||||
based Linux Distributions. Other linux distributions may work, however
|
||||
they may need a custom install script. Moonraker requires Python 3.7 or
|
||||
greater, verify that your distribution's Python 3 packages meet this
|
||||
requirement.
|
||||
|
||||
### Installing Klipper
|
||||
|
||||
Klipper should be installed prior to installing Moonraker. Please see
|
||||
[Klipper's Documention](https://klipper3d.com/Overview.html) for details.
|
||||
[Klipper's Documentation](https://klipper3d.com/Overview.html) for details.
|
||||
After installing Klipper you should make sure to add Moonraker's
|
||||
[configuration requirements](#klipper-configuration-requirements).
|
||||
|
||||
### Klipper Configuration Requirements
|
||||
#### Klipper Configuration Requirements
|
||||
|
||||
Moonraker depends on the following Klippy extras for full functionality:
|
||||
|
||||
|
@ -34,7 +34,7 @@ missing one or both, you can simply add the bare sections to `printer.cfg`:
|
|||
path: ~/printer_data/gcodes
|
||||
```
|
||||
|
||||
### Enabling Klipper's Unix Domain Socket Server
|
||||
#### Enabling Klipper's Unix Domain Socket Server
|
||||
|
||||
After Klipper is installed it may be necessary to modify its `defaults` file in
|
||||
order to enable the Unix Domain Socket. Begin by opening the file in your
|
||||
|
@ -53,7 +53,7 @@ KLIPPY_EXEC=/home/pi/klippy-env/bin/python
|
|||
KLIPPY_ARGS="/home/pi/klipper/klippy/klippy.py /home/pi/printer.cfg -l /tmp/klippy.log"
|
||||
```
|
||||
|
||||
Add `-a /tmp/klippy_uds` to KLIPPY_ARGS:
|
||||
Add `-a /home/pi/printer_data/comms/klippy.sock` to KLIPPY_ARGS:
|
||||
```
|
||||
# Configuration for /etc/init.d/klipper
|
||||
|
||||
|
@ -61,7 +61,7 @@ KLIPPY_USER=pi
|
|||
|
||||
KLIPPY_EXEC=/home/pi/klippy-env/bin/python
|
||||
|
||||
KLIPPY_ARGS="/home/pi/klipper/klippy/klippy.py /home/pi/printer.cfg -l /tmp/klippy.log -a /tmp/klippy_uds"
|
||||
KLIPPY_ARGS="/home/pi/klipper/klippy/klippy.py /home/pi/printer.cfg -l /tmp/klippy.log -a /home/pi/printer_data/comms/klippy.sock
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
@ -79,7 +79,7 @@ KLIPPY_USER=pi
|
|||
|
||||
KLIPPY_EXEC=/home/pi/klippy-env/bin/python
|
||||
|
||||
KLIPPY_ARGS="/home/pi/klipper/klippy/klippy.py /home/pi/printer_data/config/printer.cfg -l /home/pi/printer_data/logs/klippy.log -a /tmp/klippy_uds"
|
||||
KLIPPY_ARGS="/home/pi/klipper/klippy/klippy.py /home/pi/printer_data/config/printer.cfg -l /home/pi/printer_data/logs/klippy.log -a /home/pi/printer_data/comms/klippy.sock"
|
||||
```
|
||||
|
||||
Moonraker's install script will create the data folder, however you
|
||||
|
@ -94,13 +94,52 @@ mv printer.cfg ~/printer_data/config
|
|||
|
||||
### Installing Moonraker
|
||||
|
||||
Begin by cloning the git respository:
|
||||
Moonraker provides an install script that can be used to facilitate
|
||||
installation. The type of installation depends on where the install
|
||||
script is located on the host file system. If the install script is
|
||||
downloaded and run individually, the script will install Moonraker as
|
||||
a Python Package using pip. If the script is run from Moonraker's
|
||||
original source it will install Moonraker from source.
|
||||
|
||||
Prior to installation it is necessary to open a terminal on the host
|
||||
machine, or SSH into it. It is recommended to read this entire
|
||||
section before proceeding with the installation.
|
||||
|
||||
#### Installing the Moonraker Python package
|
||||
|
||||
The Python Package version of Moonraker will receive fewer updates, and
|
||||
should generally be more stable. This is intended for users that do
|
||||
not need to run unofficial 3rd party extensions, such as
|
||||
[Moonraker-Timelapse](https://github.com/mainsail-crew/moonraker-timelapse),
|
||||
and do not desire to run the "bleeding edge" version of Moonraker.
|
||||
|
||||
To perform this installation, download Moonraker's
|
||||
[install script](https://raw.githubusercontent.com/Arksine/moonraker/master/scripts/install-moonraker.sh)
|
||||
from Github, then run the installer:
|
||||
|
||||
```
|
||||
cd ~
|
||||
wget https://raw.githubusercontent.com/Arksine/moonraker/master/scripts/install-moonraker.sh
|
||||
./install-moonraker.sh
|
||||
```
|
||||
|
||||
#### Installing Moonraker from source
|
||||
|
||||
Moonraker can be run directly from source. This method of installation will
|
||||
clone Moonraker's git repository, and may receive frequent bleeding edge
|
||||
updates. Users who want to test Moonraker, run unofficial modifications, and
|
||||
do not mind SSHing into the host to correct issues should choose this option.
|
||||
|
||||
To install Moonraker from source, clone its git repository then run the installer:
|
||||
|
||||
```
|
||||
cd ~
|
||||
git clone https://github.com/Arksine/moonraker.git
|
||||
~/moonraker/scripts/install-moonraker.sh
|
||||
```
|
||||
|
||||
#### Customizing the installation
|
||||
|
||||
The install script will attempt to create a basic configuration if
|
||||
`moonraker.conf` does not exist at the expected location, however if you
|
||||
prefer to have Moonraker start with a robust configuration you may create
|
||||
|
@ -109,20 +148,17 @@ it now. By default the configuration file should be located at
|
|||
data path may be configured using the script's command line options.
|
||||
The [sample moonraker.conf](./moonraker.conf) may be used as a starting
|
||||
point, full details can be found in the
|
||||
[confguration documentation](./configuration.md).
|
||||
[configuration documentation](./configuration.md).
|
||||
|
||||
For a default installation run the following commands:
|
||||
```
|
||||
cd ~/moonraker/scripts
|
||||
./install-moonraker.sh
|
||||
```
|
||||
|
||||
The install script has a few command line options that may be useful,
|
||||
The install script has several command line options that may be useful,
|
||||
particularly for those upgrading:
|
||||
|
||||
- `-f`:
|
||||
Force an overwrite of Moonraker's systemd script. By default the
|
||||
the systemd script will not be modified if it exists.
|
||||
Force an overwrite of Moonraker's systemd script. In addition, a
|
||||
new `moonraker.env` file will be created, and the PolKit rules will
|
||||
be re-installed. By default these items will not be modified if
|
||||
they exist.
|
||||
- `-a <alias>`:
|
||||
The installer uses this option to determine the name of the service
|
||||
to install. If `-d` is not provided then this options will also be
|
||||
|
@ -133,7 +169,7 @@ particularly for those upgrading:
|
|||
files and directories used by moonraker. See the `Data Folder Structure`
|
||||
section for details. If omitted this defaults to `$HOME/printer_data`.
|
||||
- `-c <path to configuration file>`
|
||||
Specifies the path to Moonraker's configuation file. By default the
|
||||
Specifies the path to Moonraker's configuration file. By default the
|
||||
configuration is expected at `<data_folder>/config/moonraker.conf`. ie:
|
||||
`/home/pi/printer_data/config/moonraker.conf`.
|
||||
- `-l <path to log file>`
|
||||
|
@ -157,25 +193,26 @@ variables:
|
|||
|
||||
- `MOONRAKER_VENV`
|
||||
- `MOONRAKER_REBUILD_ENV`
|
||||
- `MOONRAKER_FORCE_DEFAULTS`
|
||||
- `MOONRAKER_FORCE_SYSTEM_INSTALL`
|
||||
- `MOONRAKER_DISABLE_SYSTEMCTL`
|
||||
- `MOONRAKER_SKIP_POLKIT`
|
||||
- `MOONRAKER_CONFIG_PATH`
|
||||
- `MOONAKER_LOG_PATH`
|
||||
- `MOONRAKER_LOG_PATH`
|
||||
- `MOONRAKER_DATA_PATH`
|
||||
- `MOONRAKER_SPEEDUPS`
|
||||
|
||||
When the script completes it should start both Moonraker and Klipper. In
|
||||
`klippy.log` you should find the following entry:
|
||||
When the script completes it should start the Moonraker system service. If Klipper
|
||||
is running and Moonraker is able to establish a connection the following log entry
|
||||
should be available in `klippy.log`:
|
||||
|
||||
`webhooks client <uid>: Client info {'program': 'Moonraker', 'version': '<version>'}`
|
||||
|
||||
Now you may install a client, such as
|
||||
Now you may wish to install a frontend, such as
|
||||
[Mainsail](https://github.com/mainsail-crew/mainsail) or
|
||||
[Fluidd](https://github.com/fluidd-core/fluidd).
|
||||
|
||||
!!! Note
|
||||
Moonraker's install script no longer includes the nginx dependency.
|
||||
Moonraker's installer does not include nginx as a dependency.
|
||||
If you want to install one of the above clients on the local machine,
|
||||
you may want to first install nginx (`sudo apt install nginx` on
|
||||
debian/ubuntu distros).
|
||||
|
@ -299,7 +336,7 @@ Following are some items to take note of:
|
|||
- The `EnvironmentFile` field contains Moonraker's arguments. See the
|
||||
[environment file section](#the-environment-file) for details.
|
||||
- The `ExecStart` field begins with the python executable, followed by
|
||||
by the enviroment variable `MOONRAKER_ARGS`. This variable is set in
|
||||
by the environment variable `MOONRAKER_ARGS`. This variable is set in
|
||||
the environment file.
|
||||
|
||||
|
||||
|
@ -362,7 +399,7 @@ If is necessary to run Moonraker without logging to a file the
|
|||
While moonraker will still log to stdout, all requests for support
|
||||
must be accompanied by `moonraker.log`.
|
||||
|
||||
Each command line argument has an associated enviroment variable that may
|
||||
Each command line argument has an associated environment variable that may
|
||||
be used to specify options in place of the command line.
|
||||
|
||||
- `MOONRAKER_DATA_PATH="<data path>"`: equivalent to `-d <data path>`
|
||||
|
@ -460,7 +497,7 @@ with the `sudo` prefix. This has significant downsides:
|
|||
wants to poll information about the system.
|
||||
|
||||
Moonraker now supports communicating with system services via D-Bus.
|
||||
Operations that require elevated privileges are authrorized through
|
||||
Operations that require elevated privileges are authorized through
|
||||
PolicyKit. On startup Moonraker will check for the necessary privileges
|
||||
and warn users if they are not available. Warnings are presented in
|
||||
`moonraker.log` and directly to the user through some clients.
|
||||
|
|
|
@ -422,9 +422,13 @@ class RssFeed:
|
|||
self.etag: Optional[str] = None
|
||||
self.dev_xml_path: Optional[pathlib.Path] = None
|
||||
if dev_mode:
|
||||
res_dir = pathlib.Path(__file__).parent.parent.parent.resolve()
|
||||
res_path = res_dir.joinpath(".devel/announcement_xml")
|
||||
self.dev_xml_path = res_path.joinpath(self.xml_file)
|
||||
data_path = pathlib.Path(self.server.get_app_arg("data_path"))
|
||||
dev_folder = data_path.joinpath("development/announcements")
|
||||
dev_folder.mkdir(parents=True, exist_ok=True)
|
||||
self.dev_xml_path = dev_folder.joinpath(self.xml_file)
|
||||
logging.info(
|
||||
f"Announcement Feed {name}: Dev XML path set to {self.dev_xml_path}"
|
||||
)
|
||||
|
||||
async def initialize(self) -> None:
|
||||
self.etag = await self.database.get_item(
|
||||
|
|
|
@ -531,7 +531,7 @@ class ConfigHelper:
|
|||
|
||||
def create_backup(self) -> None:
|
||||
cfg_path = self.server.get_app_args()["config_file"]
|
||||
cfg = pathlib.Path(cfg_path).expanduser().resolve()
|
||||
cfg = pathlib.Path(cfg_path).expanduser()
|
||||
backup = cfg.parent.joinpath(f".{cfg.name}.bkp")
|
||||
backup_fp: Optional[TextIO] = None
|
||||
try:
|
||||
|
@ -1108,7 +1108,10 @@ class FileSourceWrapper(ConfigSourceWrapper):
|
|||
def get_configuration(
|
||||
server: Server, app_args: Dict[str, Any]
|
||||
) -> ConfigHelper:
|
||||
start_path = pathlib.Path(app_args['config_file']).expanduser().resolve()
|
||||
cfg_file = app_args["config_file"]
|
||||
if app_args["is_backup_config"]:
|
||||
cfg_file = app_args["backup_config"]
|
||||
start_path = pathlib.Path(cfg_file).expanduser().absolute()
|
||||
source = FileSourceWrapper(server)
|
||||
source.read_file(start_path)
|
||||
if not source.config.has_section('server'):
|
||||
|
@ -1116,7 +1119,7 @@ def get_configuration(
|
|||
return ConfigHelper(server, source, 'server', {})
|
||||
|
||||
def find_config_backup(cfg_path: str) -> Optional[str]:
|
||||
cfg = pathlib.Path(cfg_path).expanduser().resolve()
|
||||
cfg = pathlib.Path(cfg_path).expanduser()
|
||||
backup = cfg.parent.joinpath(f".{cfg.name}.bkp")
|
||||
if backup.is_file():
|
||||
return str(backup)
|
||||
|
|
|
@ -27,7 +27,7 @@ _uvl_var = os.getenv("MOONRAKER_ENABLE_UVLOOP", "y").lower()
|
|||
_uvl_enabled = False
|
||||
if _uvl_var in ["y", "yes", "true"]:
|
||||
with contextlib.suppress(ImportError):
|
||||
import uvloop
|
||||
import uvloop # type: ignore
|
||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||
_uvl_enabled = True
|
||||
|
||||
|
@ -48,7 +48,7 @@ class EventLoop:
|
|||
return self.aioloop
|
||||
|
||||
def reset(self) -> None:
|
||||
self.aioloop = self._create_new_loop()
|
||||
self.aioloop = asyncio.get_running_loop()
|
||||
self.add_signal_handler = self.aioloop.add_signal_handler
|
||||
self.remove_signal_handler = self.aioloop.remove_signal_handler
|
||||
self.add_reader = self.aioloop.add_reader
|
||||
|
@ -126,7 +126,7 @@ class EventLoop:
|
|||
host, port, family=0, type=socket.SOCK_STREAM
|
||||
)
|
||||
for res in ainfo:
|
||||
af, socktype, proto, canonname, sa = res
|
||||
af, socktype, proto, _cannon_name, _sa = res
|
||||
sock = None
|
||||
try:
|
||||
sock = socket.socket(af, socktype, proto)
|
||||
|
@ -152,12 +152,6 @@ class EventLoop:
|
|||
else:
|
||||
raise socket.error("getaddrinfo returns an empty list")
|
||||
|
||||
def start(self):
|
||||
self.aioloop.run_forever()
|
||||
|
||||
def stop(self):
|
||||
self.aioloop.stop()
|
||||
|
||||
def close(self):
|
||||
self.aioloop.close()
|
||||
|
||||
|
|
|
@ -130,6 +130,7 @@ class LogManager:
|
|||
return eventloop.run_in_thread(self.file_hdlr.doRollover)
|
||||
|
||||
def stop_logging(self):
|
||||
if self.listener is not None:
|
||||
self.listener.stop()
|
||||
|
||||
async def _handle_log_rollover(
|
||||
|
|
|
@ -89,6 +89,7 @@ class Server:
|
|||
self.ssl_port: int = config.getint('ssl_port', 7130)
|
||||
self.exit_reason: str = ""
|
||||
self.server_running: bool = False
|
||||
self.app_running_evt = asyncio.Event()
|
||||
self.pip_recovery_attempted: bool = False
|
||||
|
||||
# Configure Debug Logging
|
||||
|
@ -198,10 +199,8 @@ class Server:
|
|||
await self.event_loop.run_in_thread(self.config.create_backup)
|
||||
|
||||
machine: Machine = self.lookup_component("machine")
|
||||
if await machine.validate_installation():
|
||||
return
|
||||
|
||||
if start_server:
|
||||
restarting = await machine.validate_installation()
|
||||
if not restarting and start_server:
|
||||
await self.start_server()
|
||||
|
||||
async def start_server(self, connect_to_klippy: bool = True) -> None:
|
||||
|
@ -218,6 +217,9 @@ class Server:
|
|||
if connect_to_klippy:
|
||||
self.klippy_connection.connect()
|
||||
|
||||
async def run_until_exit(self) -> None:
|
||||
await self.app_running_evt.wait()
|
||||
|
||||
def add_log_rollover_item(
|
||||
self, name: str, item: str, log: bool = True
|
||||
) -> None:
|
||||
|
@ -488,7 +490,7 @@ class Server:
|
|||
|
||||
self.exit_reason = exit_reason
|
||||
self.event_loop.remove_signal_handler(signal.SIGTERM)
|
||||
self.event_loop.stop()
|
||||
self.app_running_evt.set()
|
||||
|
||||
async def _handle_server_restart(self, web_request: WebRequest) -> str:
|
||||
self.event_loop.register_callback(self._stop_server)
|
||||
|
@ -540,6 +542,45 @@ class Server:
|
|||
'files': cfg_file_list
|
||||
}
|
||||
|
||||
async def launch_server(
|
||||
log_manager: LogManager, app_args: Dict[str, Any]
|
||||
) -> Optional[int]:
|
||||
eventloop = EventLoop()
|
||||
startup_warnings: List[str] = app_args["startup_warnings"]
|
||||
try:
|
||||
server = Server(app_args, log_manager, eventloop)
|
||||
server.load_components()
|
||||
except confighelper.ConfigError as e:
|
||||
logging.exception("Server Config Error")
|
||||
backup_cfg: Optional[str] = app_args["backup_config"]
|
||||
if app_args["is_backup_config"] or backup_cfg is None:
|
||||
return 1
|
||||
app_args["is_backup_config"] = True
|
||||
startup_warnings.append(
|
||||
f"Server configuration error: {e}\n"
|
||||
f"Loading most recent working configuration: '{backup_cfg}'\n"
|
||||
f"Please fix the issue in moonraker.conf and restart the server."
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
logging.exception("Moonraker Error")
|
||||
return 1
|
||||
try:
|
||||
await server.server_init()
|
||||
await server.run_until_exit()
|
||||
except Exception:
|
||||
logging.exception("Server Running Error")
|
||||
return 1
|
||||
if server.exit_reason == "terminate":
|
||||
return 0
|
||||
# Restore the original config and clear the warning
|
||||
# before the server restarts
|
||||
if app_args["is_backup_config"]:
|
||||
startup_warnings.pop()
|
||||
app_args["is_backup_config"] = False
|
||||
del server
|
||||
return None
|
||||
|
||||
def main(from_package: bool = True) -> None:
|
||||
def get_env_bool(key: str) -> bool:
|
||||
return os.getenv(key, "").lower() in ["y", "yes", "true"]
|
||||
|
@ -635,6 +676,7 @@ def main(from_package: bool = True) -> None:
|
|||
"data_path": str(data_path),
|
||||
"is_default_data_path": cmd_line_args.datapath is None,
|
||||
"config_file": cfg_file,
|
||||
"backup_config": confighelper.find_config_backup(cfg_file),
|
||||
"startup_warnings": startup_warnings,
|
||||
"verbose": cmd_line_args.verbose,
|
||||
"debug": cmd_line_args.debug,
|
||||
|
@ -661,60 +703,14 @@ def main(from_package: bool = True) -> None:
|
|||
log_manager = LogManager(app_args, startup_warnings)
|
||||
|
||||
# Start asyncio event loop and server
|
||||
event_loop = EventLoop()
|
||||
alt_config_loaded = False
|
||||
estatus = 0
|
||||
while True:
|
||||
try:
|
||||
server = Server(app_args, log_manager, event_loop)
|
||||
server.load_components()
|
||||
except confighelper.ConfigError as e:
|
||||
backup_cfg = confighelper.find_config_backup(cfg_file)
|
||||
logging.exception("Server Config Error")
|
||||
if alt_config_loaded or backup_cfg is None:
|
||||
estatus = 1
|
||||
estatus = asyncio.run(launch_server(log_manager, app_args))
|
||||
if estatus is not None:
|
||||
break
|
||||
app_args["config_file"] = backup_cfg
|
||||
app_args["is_backup_config"] = True
|
||||
warn_list = list(startup_warnings)
|
||||
app_args["startup_warnings"] = warn_list
|
||||
warn_list.append(
|
||||
f"Server configuration error: {e}\n"
|
||||
f"Loaded server from most recent working configuration:"
|
||||
f" '{app_args['config_file']}'\n"
|
||||
f"Please fix the issue in moonraker.conf and restart "
|
||||
f"the server."
|
||||
)
|
||||
alt_config_loaded = True
|
||||
continue
|
||||
except Exception:
|
||||
logging.exception("Moonraker Error")
|
||||
estatus = 1
|
||||
break
|
||||
try:
|
||||
event_loop.register_callback(server.server_init)
|
||||
event_loop.start()
|
||||
except Exception:
|
||||
logging.exception("Server Running Error")
|
||||
estatus = 1
|
||||
break
|
||||
if server.exit_reason == "terminate":
|
||||
break
|
||||
# Restore the original config and clear the warning
|
||||
# before the server restarts
|
||||
if alt_config_loaded:
|
||||
app_args["config_file"] = cfg_file
|
||||
app_args["startup_warnings"] = startup_warnings
|
||||
app_args["is_backup_config"] = False
|
||||
alt_config_loaded = False
|
||||
event_loop.close()
|
||||
# Since we are running outside of the the server
|
||||
# it is ok to use a blocking sleep here
|
||||
time.sleep(.5)
|
||||
logging.info("Attempting Server Restart...")
|
||||
del server
|
||||
event_loop.reset()
|
||||
event_loop.close()
|
||||
logging.info("Server Shutdown")
|
||||
log_manager.stop_logging()
|
||||
exit(estatus)
|
||||
|
|
|
@ -13,10 +13,7 @@ import re
|
|||
import json
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
if sys.version_info < (3, 8):
|
||||
from importlib_metadata import Distribution, PathDistribution, PackageMetadata
|
||||
else:
|
||||
from importlib.metadata import Distribution, PathDistribution, PackageMetadata
|
||||
from importlib_metadata import Distribution, PathDistribution, PackageMetadata
|
||||
from .exceptions import ServerError
|
||||
|
||||
# Annotation imports
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
# Wheel Setup Script for generating metadata
|
||||
#
|
||||
# Copyright (C) 2023 Eric Callahan <arksine.code@gmail.com>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license
|
||||
|
||||
from __future__ import annotations
|
||||
import pathlib
|
||||
import subprocess
|
||||
import shlex
|
||||
import json
|
||||
import shutil
|
||||
from datetime import datetime, timezone
|
||||
from typing import Dict, Any, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pdm.backend.hooks.base import Context
|
||||
|
||||
__package_name__ = "moonraker"
|
||||
__dependencies__ = "scripts/system-dependencies.json"
|
||||
|
||||
def _run_git_command(cmd: str) -> str:
|
||||
prog = shlex.split(cmd)
|
||||
process = subprocess.Popen(
|
||||
prog, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
ret, err = process.communicate()
|
||||
retcode = process.wait()
|
||||
if retcode == 0:
|
||||
return ret.strip().decode()
|
||||
return ""
|
||||
|
||||
def get_commit_sha(source_path: pathlib.Path) -> str:
|
||||
cmd = f"git -C {source_path} rev-parse HEAD"
|
||||
return _run_git_command(cmd)
|
||||
|
||||
def retrieve_git_version(source_path: pathlib.Path) -> str:
|
||||
cmd = f"git -C {source_path} describe --always --tags --long --dirty"
|
||||
return _run_git_command(cmd)
|
||||
|
||||
|
||||
def pdm_build_clean(context: Context) -> None:
|
||||
share_path: pathlib.Path = context.root.joinpath("share")
|
||||
if share_path.exists():
|
||||
shutil.rmtree(str(share_path))
|
||||
|
||||
def pdm_build_initialize(context: Context) -> None:
|
||||
context.ensure_build_dir()
|
||||
proj_name: str = context.config.metadata['name']
|
||||
build_dir = pathlib.Path(context.build_dir)
|
||||
data_path = context.root.joinpath(f"share/{proj_name}")
|
||||
pkg_path = build_dir.joinpath(__package_name__)
|
||||
pkg_path.mkdir(parents=True, exist_ok=True)
|
||||
rinfo_path: pathlib.Path = pkg_path.joinpath("release_info")
|
||||
rinfo_data: str = ""
|
||||
if context.root.joinpath(".git").exists():
|
||||
build_ver: str = context.config.metadata['version']
|
||||
build_time = datetime.now(timezone.utc)
|
||||
urls: Dict[str, str] = context.config.metadata['urls']
|
||||
release_info: Dict[str, Any] = {
|
||||
"project_name": proj_name,
|
||||
"package_name": __package_name__,
|
||||
"urls": {key.lower(): val for key, val in urls.items()},
|
||||
"package_version": build_ver,
|
||||
"git_version": retrieve_git_version(context.root),
|
||||
"commit_sha": get_commit_sha(context.root),
|
||||
"build_time": datetime.isoformat(build_time, timespec="seconds")
|
||||
}
|
||||
if __dependencies__:
|
||||
deps = pathlib.Path(context.root).joinpath(__dependencies__)
|
||||
if deps.is_file():
|
||||
dep_info: Dict[str, Any] = json.loads(deps.read_bytes())
|
||||
release_info["system_dependencies"] = dep_info
|
||||
# Write the release info to both the package and the data path
|
||||
rinfo_data = json.dumps(release_info, indent=4)
|
||||
rinfo_path.write_text(rinfo_data)
|
||||
else:
|
||||
rinfo_path = context.root.joinpath(f"{proj_name}/release_info")
|
||||
if rinfo_path.is_file():
|
||||
rinfo_data = rinfo_path.read_text()
|
||||
else:
|
||||
rinfo_data = ""
|
||||
data_path.mkdir(parents=True, exist_ok=True)
|
||||
if rinfo_data:
|
||||
data_path.joinpath("release_info").write_text(rinfo_data)
|
||||
scripts_path: pathlib.Path = context.root.joinpath("scripts")
|
||||
scripts_dest: pathlib.Path = data_path.joinpath("scripts")
|
||||
scripts_dest.mkdir()
|
||||
for item in scripts_path.iterdir():
|
||||
if item.name in ("__pycache__", "python_wheels"):
|
||||
continue
|
||||
if item.is_dir():
|
||||
shutil.copytree(str(item), str(scripts_dest.joinpath(item.name)))
|
||||
else:
|
||||
shutil.copy2(str(item), str(scripts_dest))
|
||||
git_ignore = build_dir.joinpath(".gitignore")
|
||||
if git_ignore.is_file():
|
||||
git_ignore.unlink()
|
||||
|
||||
def pdm_build_finalize(context: Context, artifact: pathlib.Path) -> None:
|
||||
share_path: pathlib.Path = context.root.joinpath("share")
|
||||
if share_path.exists():
|
||||
shutil.rmtree(str(share_path))
|
|
@ -25,7 +25,8 @@ dependencies = [
|
|||
"apprise==1.8.0",
|
||||
"ldap3==2.9.1",
|
||||
"python-periphery==2.4.1",
|
||||
"importlib_metadata==6.7.0 ; python_version=='3.7'"
|
||||
"importlib_metadata==6.7.0 ; python_version=='3.7'",
|
||||
"importlib_metadata==8.2.0 ; python_version>='3.8'"
|
||||
]
|
||||
requires-python = ">=3.7"
|
||||
readme = "README.md"
|
||||
|
@ -50,9 +51,13 @@ documentation = "https://moonraker.readthedocs.io"
|
|||
changelog = "https://moonraker.readthedocs.io/en/latest/changelog/"
|
||||
|
||||
[project.optional-dependencies]
|
||||
msgspec=["msgspec>=0.18.4 ; python_version>='3.8'"]
|
||||
uvloop=["uvloop>=0.17.0"]
|
||||
speedups = ["moonraker[msgspec,uvloop]"]
|
||||
msgspec = ["msgspec>=0.18.4 ; python_version>='3.8'"]
|
||||
uvloop = ["uvloop>=0.17.0"]
|
||||
speedups = [
|
||||
"msgspec>=0.18.4 ; python_version>='3.8'",
|
||||
"uvloop>=0.17.0"
|
||||
]
|
||||
dev = ["pre-commit"]
|
||||
|
||||
[tool.pdm.version]
|
||||
source = "scm"
|
||||
|
@ -62,8 +67,12 @@ write_template = "__version__ = '{}'\n"
|
|||
[tool.pdm.build]
|
||||
excludes = ["./**/.git", "moonraker/moonraker.py"]
|
||||
includes = ["moonraker"]
|
||||
source-includes = ["scripts"]
|
||||
editable-backend = "path"
|
||||
custom-hook = "scripts/pdm_build_dist.py"
|
||||
custom-hook = "pdm_build.py"
|
||||
|
||||
[tool.pdm.build.wheel-data]
|
||||
data = [{path = "share/moonraker/**/*", relative-to = "."}]
|
||||
|
||||
[project.scripts]
|
||||
moonraker = "moonraker.server:main"
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
#!/bin/bash
|
||||
# This script installs Moonraker on a Raspberry Pi machine running
|
||||
# Raspbian/Raspberry Pi OS based distributions.
|
||||
# This script installs Moonraker on Debian based Linux distros.
|
||||
|
||||
SUPPORTED_DISTROS="debian"
|
||||
PYTHONDIR="${MOONRAKER_VENV:-${HOME}/moonraker-env}"
|
||||
SYSTEMDDIR="/etc/systemd/system"
|
||||
REBUILD_ENV="${MOONRAKER_REBUILD_ENV:-n}"
|
||||
FORCE_DEFAULTS="${MOONRAKER_FORCE_DEFAULTS:-n}"
|
||||
FORCE_SYSTEM_INSTALL="${MOONRAKER_FORCE_SYSTEM_INSTALL:-n}"
|
||||
DISABLE_SYSTEMCTL="${MOONRAKER_DISABLE_SYSTEMCTL:-n}"
|
||||
SKIP_POLKIT="${MOONRAKER_SKIP_POLKIT:-n}"
|
||||
CONFIG_PATH="${MOONRAKER_CONFIG_PATH}"
|
||||
|
@ -14,17 +14,77 @@ DATA_PATH="${MOONRAKER_DATA_PATH}"
|
|||
INSTANCE_ALIAS="${MOONRAKER_ALIAS:-moonraker}"
|
||||
SPEEDUPS="${MOONRAKER_SPEEDUPS:-n}"
|
||||
SERVICE_VERSION="1"
|
||||
DISTRIBUTION=""
|
||||
IS_SRC_DIST="n"
|
||||
PACKAGES=""
|
||||
|
||||
package_decode_script=$( cat << EOF
|
||||
import sys
|
||||
import json
|
||||
try:
|
||||
ret = json.load(sys.stdin)
|
||||
except Exception:
|
||||
exit(0)
|
||||
sys.stdout.write(' '.join(ret['debian']))
|
||||
EOF
|
||||
)
|
||||
# Check deprecated FORCE_DEFAULTS environment variable
|
||||
if [ ! -z "${MOONRAKER_FORCE_DEFAULTS}" ]; then
|
||||
echo "Deprecated MOONRAKER_FORCE_DEFAULTS environment variable"
|
||||
echo -e "detected. Please use MOONRAKER_FORCE_SYSTEM_INSTALL\n"
|
||||
FORCE_SYSTEM_INSTALL=$MOONRAKER_FORCE_DEFAULTS
|
||||
fi
|
||||
|
||||
# Force script to exit if an error occurs
|
||||
set -e
|
||||
|
||||
# Find source director from the pathname of this script
|
||||
SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
|
||||
# Determine if Moonraker is to be installed from source
|
||||
if [ -f "${SRCDIR}/moonraker/__init__.py" ]; then
|
||||
echo "Installing from Moonraker source..."
|
||||
IS_SRC_DIST="y"
|
||||
fi
|
||||
|
||||
# Detect Current Distribution
|
||||
detect_distribution() {
|
||||
distro_list=""
|
||||
if [ -f "/etc/os-release" ]; then
|
||||
distro_list="$( grep -Po "^ID=\K.+" /etc/os-release || true )"
|
||||
like_str="$( grep -Po "^ID_LIKE=\K.+" /etc/os-release || true )"
|
||||
if [ ! -z "${like_str}" ]; then
|
||||
distro_list="${distro_list} ${like_str}"
|
||||
fi
|
||||
if [ ! -z "${distro_list}" ]; then
|
||||
echo "Found Linux distribution IDs: ${distro_list}"
|
||||
else
|
||||
echo "Unable to detect Linux Distribution."
|
||||
fi
|
||||
fi
|
||||
|
||||
distro_id=""
|
||||
while [ "$distro_list" != "$distro_id" ]; do
|
||||
distro_id="${distro_list%% *}"
|
||||
distro_list="${distro_list#$distro_id }"
|
||||
supported_dists=$SUPPORTED_DISTROS
|
||||
supported_id=""
|
||||
while [ "$supported_dists" != "$supported_id" ]; do
|
||||
supported_id="${supported_dists%% *}"
|
||||
supported_dists="${supported_dists#$supported_id }"
|
||||
if [ "$distro_id" = "$supported_id" ]; then
|
||||
DISTRIBUTION=$distro_id
|
||||
echo "Distribution detected: $DISTRIBUTION"
|
||||
break
|
||||
fi
|
||||
done
|
||||
[ ! -z "$DISTRIBUTION" ] && break
|
||||
done
|
||||
|
||||
if [ -z "$DISTRIBUTION" ] && [ -x "$( which apt-get || true )" ]; then
|
||||
# Fall back to debian if apt-get is deteted
|
||||
echo "Found apt-get, falling back to debian distribution"
|
||||
DISTRIBUTION="debian"
|
||||
fi
|
||||
|
||||
# *** AUTO GENERATED OS PACKAGE DEPENDENCES START ***
|
||||
if [ ${DISTRIBUTION} = "debian" ]; then
|
||||
PACKAGES="python3-virtualenv python3-dev libopenjp2-7 libsodium-dev zlib1g-dev"
|
||||
PACKAGES="${PACKAGES} libjpeg-dev packagekit wireless-tools curl"
|
||||
PACKAGES="${PACKAGES} build-essential"
|
||||
fi
|
||||
# *** AUTO GENERATED OS PACKAGE DEPENDENCES END ***
|
||||
}
|
||||
|
||||
# Step 2: Clean up legacy installation
|
||||
cleanup_legacy() {
|
||||
|
@ -41,30 +101,19 @@ cleanup_legacy() {
|
|||
# Step 3: Install packages
|
||||
install_packages()
|
||||
{
|
||||
if [ -z "${PACKAGES}" ]; then
|
||||
echo "Unsupported Linux Distribution ${DISTRIBUTION}. "
|
||||
echo "Bypassing system package installation."
|
||||
return
|
||||
fi
|
||||
# Update system package info
|
||||
report_status "Running apt-get update..."
|
||||
sudo apt-get update --allow-releaseinfo-change
|
||||
|
||||
system_deps="${SRCDIR}/scripts/system-dependencies.json"
|
||||
if [ -f "${system_deps}" ]; then
|
||||
if [ ! -x "$(command -v python3)" ]; then
|
||||
report_status "Installing python3 base package..."
|
||||
sudo apt-get install --yes python3
|
||||
fi
|
||||
PKGS="$( cat ${system_deps} | python3 -c "${package_decode_script}" )"
|
||||
|
||||
else
|
||||
echo "Error: system-dependencies.json not found, falling back to legacy pacakge list"
|
||||
PKGLIST="${PKGLIST} python3-virtualenv python3-dev"
|
||||
PKGLIST="${PKGLIST} libopenjp2-7 libsodium-dev zlib1g-dev libjpeg-dev"
|
||||
PKGLIST="${PKGLIST} packagekit wireless-tools curl"
|
||||
PKGS=${PKGLIST}
|
||||
fi
|
||||
|
||||
# Install desired packages
|
||||
report_status "Installing Moonraker Dependencies:"
|
||||
report_status "${PKGS}"
|
||||
sudo apt-get install --yes ${PKGS}
|
||||
report_status "${PACKAGES}"
|
||||
sudo apt-get install --yes ${PACKAGES}
|
||||
}
|
||||
|
||||
# Step 4: Create python virtual environment
|
||||
|
@ -88,12 +137,22 @@ create_virtualenv()
|
|||
|
||||
# Install/update dependencies
|
||||
export SKIP_CYTHON=1
|
||||
if [ $IS_SRC_DIST = "y" ]; then
|
||||
report_status "Installing Moonraker python dependencies..."
|
||||
${PYTHONDIR}/bin/pip install -r ${SRCDIR}/scripts/moonraker-requirements.txt
|
||||
|
||||
if [ ${SPEEDUPS} = "y" ]; then
|
||||
report_status "Installing Speedups..."
|
||||
${PYTHONDIR}/bin/pip install -r ${SRCDIR}/scripts/moonraker-speedups.txt
|
||||
fi
|
||||
else
|
||||
report_status "Installing Moonraker package via Pip..."
|
||||
if [ ${SPEEDUPS} = "y" ]; then
|
||||
${PYTHONDIR}/bin/pip install -U moonraker[speedups]
|
||||
else
|
||||
${PYTHONDIR}/bin/pip install -U moonraker
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Step 5: Initialize data folder
|
||||
|
@ -141,16 +200,16 @@ install_script()
|
|||
{
|
||||
# Create systemd service file
|
||||
ENV_FILE="${DATA_PATH}/systemd/moonraker.env"
|
||||
if [ ! -f $ENV_FILE ] || [ $FORCE_DEFAULTS = "y" ]; then
|
||||
if [ ! -f $ENV_FILE ] || [ $FORCE_SYSTEM_INSTALL = "y" ]; then
|
||||
rm -f $ENV_FILE
|
||||
env_vars="MOONRAKER_DATA_PATH=\"${DATA_PATH}\""
|
||||
[ -n "${CONFIG_PATH}" ] && env_vars="${env_vars}\nMOONRAKER_CONFIG_PATH=\"${CONFIG_PATH}\""
|
||||
[ -n "${LOG_PATH}" ] && env_vars="${env_vars}\nMOONRAKER_LOG_PATH=\"${LOG_PATH}\""
|
||||
env_vars="${env_vars}\nMOONRAKER_ARGS=\"-m moonraker\""
|
||||
env_vars="${env_vars}\nPYTHONPATH=\"${SRCDIR}\"\n"
|
||||
[ $IS_SRC_DIST = "y" ] && env_vars="${env_vars}\nPYTHONPATH=\"${SRCDIR}\"\n"
|
||||
echo -e $env_vars > $ENV_FILE
|
||||
fi
|
||||
[ -f $SERVICE_FILE ] && [ $FORCE_DEFAULTS = "n" ] && return
|
||||
[ -f $SERVICE_FILE ] && [ $FORCE_SYSTEM_INSTALL = "n" ] && return
|
||||
report_status "Installing system start script..."
|
||||
sudo groupadd -f moonraker-admin
|
||||
sudo /bin/sh -c "cat > ${SERVICE_FILE}" << EOF
|
||||
|
@ -184,32 +243,51 @@ EOF
|
|||
check_polkit_rules()
|
||||
{
|
||||
if [ ! -x "$(command -v pkaction || true)" ]; then
|
||||
echo "PolKit not installed"
|
||||
return
|
||||
fi
|
||||
if [ "${SKIP_POLKIT}" = "y" ]; then
|
||||
echo "Skipping PolKit rules installation"
|
||||
return
|
||||
fi
|
||||
POLKIT_VERSION="$( pkaction --version | grep -Po "(\d+\.?\d*)" )"
|
||||
NEED_POLKIT_INSTALL="n"
|
||||
if [ $FORCE_SYSTEM_INSTALL = "n" ]; then
|
||||
if [ "$POLKIT_VERSION" = "0.105" ]; then
|
||||
POLKIT_LEGACY_FILE="/etc/polkit-1/localauthority/50-local.d/10-moonraker.pkla"
|
||||
# legacy policykit rules don't give users other than root read access
|
||||
if sudo [ ! -f $POLKIT_LEGACY_FILE ]; then
|
||||
NEED_POLKIT_INSTALL="y"
|
||||
else
|
||||
echo "PolKit rules file found at ${POLKIT_LEGACY_FILE}"
|
||||
fi
|
||||
else
|
||||
POLKIT_FILE="/etc/polkit-1/rules.d/moonraker.rules"
|
||||
POLKIT_USR_FILE="/usr/share/polkit-1/rules.d/moonraker.rules"
|
||||
if [ ! -f $POLKIT_FILE ] && [ ! -f $POLKIT_USR_FILE ]; then
|
||||
if sudo [ -f $POLKIT_FILE ]; then
|
||||
echo "PolKit rules file found at ${POLKIT_FILE}"
|
||||
elif sudo [ -f $POLKIT_USR_FILE ]; then
|
||||
echo "PolKit rules file found at ${POLKIT_USR_FILE}"
|
||||
else
|
||||
NEED_POLKIT_INSTALL="y"
|
||||
fi
|
||||
fi
|
||||
if [ "${NEED_POLKIT_INSTALL}" = "y" ]; then
|
||||
if [ "${SKIP_POLKIT}" = "y" ]; then
|
||||
echo -e "\n*** No PolicyKit Rules detected, run 'set-policykit-rules.sh'"
|
||||
echo "*** if you wish to grant Moonraker authorization to manage"
|
||||
echo "*** system services, reboot/shutdown the system, and update"
|
||||
echo "*** packages."
|
||||
else
|
||||
NEED_POLKIT_INSTALL="y"
|
||||
fi
|
||||
if [ "${NEED_POLKIT_INSTALL}" = "y" ]; then
|
||||
report_status "Installing PolKit Rules"
|
||||
${SRCDIR}/scripts/set-policykit-rules.sh -z
|
||||
polkit_script="${SRCDIR}/scripts/set-policykit-rules.sh"
|
||||
if [ $IS_SRC_DIST != "y" ]; then
|
||||
polkit_script="${PYTHONDIR}/share/moonraker"
|
||||
polkit_script="${polkit_script}/scripts/set-policykit-rules.sh"
|
||||
fi
|
||||
if [ -f "$polkit_script" ]; then
|
||||
set +e
|
||||
$polkit_script -z
|
||||
set -e
|
||||
else
|
||||
echo "PolKit rule install script not found at $polkit_script"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
@ -235,17 +313,11 @@ verify_ready()
|
|||
fi
|
||||
}
|
||||
|
||||
# Force script to exit if an error occurs
|
||||
set -e
|
||||
|
||||
# Find SRCDIR from the pathname of this script
|
||||
SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
|
||||
# Parse command line arguments
|
||||
while getopts "rfzxsc:l:d:a:" arg; do
|
||||
case $arg in
|
||||
r) REBUILD_ENV="y";;
|
||||
f) FORCE_DEFAULTS="y";;
|
||||
f) FORCE_SYSTEM_INSTALL="y";;
|
||||
z) DISABLE_SYSTEMCTL="y";;
|
||||
x) SKIP_POLKIT="y";;
|
||||
s) SPEEDUPS="y";;
|
||||
|
@ -273,6 +345,7 @@ SERVICE_FILE="${SYSTEMDDIR}/${INSTANCE_ALIAS}.service"
|
|||
|
||||
# Run installation steps defined above
|
||||
verify_ready
|
||||
detect_distribution
|
||||
cleanup_legacy
|
||||
install_packages
|
||||
create_virtualenv
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
pre-commit
|
|
@ -20,3 +20,4 @@ apprise==1.8.0
|
|||
ldap3==2.9.1
|
||||
python-periphery==2.4.1
|
||||
importlib_metadata==6.7.0 ; python_version=='3.7'
|
||||
importlib_metadata==8.2.0 ; python_version>='3.8'
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
# Wheel Setup Script for generating metadata
|
||||
#
|
||||
# Copyright (C) 2023 Eric Callahan <arksine.code@gmail.com>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license
|
||||
|
||||
from __future__ import annotations
|
||||
import pathlib
|
||||
import subprocess
|
||||
import shlex
|
||||
import json
|
||||
import shutil
|
||||
from datetime import datetime, timezone
|
||||
from typing import Dict, Any, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pdm.backend.hooks.base import Context
|
||||
|
||||
__package_name__ = "moonraker"
|
||||
__dependencies__ = "scripts/system-dependencies.json"
|
||||
|
||||
def _run_git_command(cmd: str) -> str:
|
||||
prog = shlex.split(cmd)
|
||||
process = subprocess.Popen(
|
||||
prog, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
ret, err = process.communicate()
|
||||
retcode = process.wait()
|
||||
if retcode == 0:
|
||||
return ret.strip().decode()
|
||||
return ""
|
||||
|
||||
def get_commit_sha(source_path: pathlib.Path) -> str:
|
||||
cmd = f"git -C {source_path} rev-parse HEAD"
|
||||
return _run_git_command(cmd)
|
||||
|
||||
def retrieve_git_version(source_path: pathlib.Path) -> str:
|
||||
cmd = f"git -C {source_path} describe --always --tags --long --dirty"
|
||||
return _run_git_command(cmd)
|
||||
|
||||
def pdm_build_initialize(context: Context) -> None:
|
||||
context.ensure_build_dir()
|
||||
build_ver: str = context.config.metadata['version']
|
||||
proj_name: str = context.config.metadata['name']
|
||||
urls: Dict[str, str] = context.config.metadata['urls']
|
||||
build_dir = pathlib.Path(context.build_dir)
|
||||
rel_dpath = f"{__package_name__}-{build_ver}.data/data/share/{proj_name}"
|
||||
data_path = build_dir.joinpath(rel_dpath)
|
||||
pkg_path = build_dir.joinpath(__package_name__)
|
||||
build_time = datetime.now(timezone.utc)
|
||||
release_info: Dict[str, Any] = {
|
||||
"project_name": proj_name,
|
||||
"package_name": __package_name__,
|
||||
"urls": {key.lower(): val for key, val in urls.items()},
|
||||
"package_version": build_ver,
|
||||
"git_version": retrieve_git_version(context.root),
|
||||
"commit_sha": get_commit_sha(context.root),
|
||||
"build_time": datetime.isoformat(build_time, timespec="seconds")
|
||||
}
|
||||
if __dependencies__:
|
||||
deps = pathlib.Path(context.root).joinpath(__dependencies__)
|
||||
if deps.is_file():
|
||||
dep_info: Dict[str, Any] = json.loads(deps.read_bytes())
|
||||
release_info["system_dependencies"] = dep_info
|
||||
# Write the release info to both the package and the data path
|
||||
rinfo_data = json.dumps(release_info, indent=4)
|
||||
data_path.mkdir(parents=True, exist_ok=True)
|
||||
pkg_path.mkdir(parents=True, exist_ok=True)
|
||||
data_path.joinpath("release_info").write_text(rinfo_data)
|
||||
pkg_path.joinpath("release_info").write_text(rinfo_data)
|
||||
scripts_path = context.root.joinpath("scripts")
|
||||
scripts_dest = data_path.joinpath("scripts")
|
||||
scripts_dest.mkdir()
|
||||
for item in scripts_path.iterdir():
|
||||
if item.name == "__pycache__":
|
||||
continue
|
||||
if item.is_dir():
|
||||
shutil.copytree(str(item), str(scripts_dest.joinpath(item.name)))
|
||||
else:
|
||||
shutil.copy2(str(item), str(scripts_dest))
|
|
@ -0,0 +1,174 @@
|
|||
#! /usr/bin/python3
|
||||
# Script for syncing package dependencies and python reqs
|
||||
#
|
||||
# Copyright (C) 2024 Eric Callahan <arksine.code@gmail.com>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license
|
||||
|
||||
from __future__ import annotations
|
||||
import argparse
|
||||
import pathlib
|
||||
import tomllib
|
||||
import json
|
||||
import re
|
||||
from typing import Dict, List
|
||||
|
||||
MAX_LINE_LENGTH = 88
|
||||
SCRIPTS_PATH = pathlib.Path(__file__).parent
|
||||
INST_PKG_HEADER = "# *** AUTO GENERATED OS PACKAGE DEPENDENCES START ***"
|
||||
INST_PKG_FOOTER = "# *** AUTO GENERATED OS PACKAGE DEPENDENCES END ***"
|
||||
|
||||
def gen_multline_var(var_name: str, values: List[str], indent: int = 0) -> str:
|
||||
idt = " " * indent
|
||||
if not values:
|
||||
return f'{idt}{var_name}=""'
|
||||
line_list: List[str] = []
|
||||
current_line = f"{idt}{var_name}=\"{values.pop(0)}"
|
||||
for val in values:
|
||||
if len(current_line) + len(val) + 2 > MAX_LINE_LENGTH:
|
||||
line_list.append(f'{current_line}"')
|
||||
current_line = (f"{idt}{var_name}=\"${{{var_name}}} {val}")
|
||||
else:
|
||||
current_line += f" {val}"
|
||||
line_list.append(f'{current_line}"')
|
||||
return "\n".join(line_list)
|
||||
|
||||
def sync_packages() -> int:
|
||||
inst_script = SCRIPTS_PATH.joinpath("install-moonraker.sh")
|
||||
sys_deps_file = SCRIPTS_PATH.joinpath("system-dependencies.json")
|
||||
new_deps: Dict[str, List[str]] = json.loads(sys_deps_file.read_bytes())
|
||||
# Copy install script in memory.
|
||||
install_data: List[str] = []
|
||||
prev_deps: Dict[str, List[str]] = {}
|
||||
distro_name = ""
|
||||
skip_data = False
|
||||
with inst_script.open("r") as inst_file:
|
||||
for line in inst_file:
|
||||
cur_line = line.strip()
|
||||
if not skip_data:
|
||||
install_data.append(line)
|
||||
else:
|
||||
# parse current dependencies
|
||||
distro_match = re.match(
|
||||
r"(?:el)?if \[ \$\{DISTRIBUTION\} = \"([a-z0-9._-]+)\" \]; then",
|
||||
cur_line
|
||||
)
|
||||
if distro_match is not None:
|
||||
distro_name = distro_match.group(1)
|
||||
prev_deps[distro_name] = []
|
||||
elif cur_line.startswith("PACKAGES"):
|
||||
pkgs = cur_line.split("=", maxsplit=1)[1].strip('"')
|
||||
pkg_list = pkgs.split()
|
||||
if pkg_list and pkg_list[0] == "${PACKAGES}":
|
||||
pkg_list.pop(0)
|
||||
prev_deps[distro_name].extend(pkg_list)
|
||||
if cur_line == INST_PKG_HEADER:
|
||||
skip_data = True
|
||||
elif cur_line == INST_PKG_FOOTER:
|
||||
skip_data = False
|
||||
install_data.append(line)
|
||||
# Check if an update is necessary
|
||||
if set(prev_deps.keys()) == set(new_deps.keys()):
|
||||
for distro, pkg_list in prev_deps.items():
|
||||
new_pkgs = new_deps[distro]
|
||||
if set(pkg_list) != set(new_pkgs):
|
||||
break
|
||||
else:
|
||||
# Dependencies match, exit
|
||||
print("System package dependencies match")
|
||||
return 0
|
||||
print("Writing new system dependencies to install script...")
|
||||
with inst_script.open("w+") as inst_file:
|
||||
# Find and replace old package defs
|
||||
for line in install_data:
|
||||
inst_file.write(line)
|
||||
if line.strip() == INST_PKG_HEADER:
|
||||
indent_count = len(line) - len(line.lstrip())
|
||||
idt = " " * indent_count
|
||||
# Write Package data
|
||||
first_distro = True
|
||||
for distro, packages in new_deps.items():
|
||||
prefix = f"{idt}if" if first_distro else f"{idt}elif"
|
||||
first_distro = False
|
||||
inst_file.write(
|
||||
f'{prefix} [ ${{DISTRIBUTION}} = "{distro}" ]; then\n'
|
||||
)
|
||||
pkg_var = gen_multline_var("PACKAGES", packages, indent_count + 4)
|
||||
inst_file.write(pkg_var)
|
||||
inst_file.write("\n")
|
||||
inst_file.write(f"{idt}fi\n")
|
||||
return 1
|
||||
|
||||
def check_reqs_changed(reqs_file: pathlib.Path, new_reqs: List[str]) -> bool:
|
||||
req_list = []
|
||||
for requirement in reqs_file.read_text().splitlines():
|
||||
requirement = requirement.strip()
|
||||
if not requirement or requirement[0] in ("-", "#"):
|
||||
continue
|
||||
req_list.append(requirement)
|
||||
return set(new_reqs) != set(req_list)
|
||||
|
||||
def sync_requirements() -> int:
|
||||
ret: int = 0
|
||||
src_path = SCRIPTS_PATH.parent
|
||||
proj_file = src_path.joinpath("pyproject.toml")
|
||||
with proj_file.open("rb") as f:
|
||||
data = tomllib.load(f)
|
||||
python_deps = data["project"]["dependencies"]
|
||||
optional_deps = data["project"]["optional-dependencies"]
|
||||
reqs_path = SCRIPTS_PATH.joinpath("moonraker-requirements.txt")
|
||||
if check_reqs_changed(reqs_path, python_deps):
|
||||
print("Syncing Moonraker Python Requirements...")
|
||||
ret = 1
|
||||
with reqs_path.open("w+") as req_file:
|
||||
req_file.write("# Python dependencies for Moonraker\n")
|
||||
req_file.write("--find-links=python_wheels\n")
|
||||
for requirement in python_deps:
|
||||
req_file.write(f"{requirement}\n")
|
||||
else:
|
||||
print("Moonraker Python requirements match")
|
||||
# sync speedups
|
||||
speedups_path = SCRIPTS_PATH.joinpath("moonraker-speedups.txt")
|
||||
speedup_deps = optional_deps["speedups"]
|
||||
if check_reqs_changed(speedups_path, speedup_deps):
|
||||
print("Syncing speedup requirements...")
|
||||
ret = 1
|
||||
with speedups_path.open("w+") as req_file:
|
||||
for requirement in speedup_deps:
|
||||
req_file.write(f"{requirement}\n")
|
||||
else:
|
||||
print("Speedup sequirements match")
|
||||
# sync dev dependencies
|
||||
dev_reqs_path = SCRIPTS_PATH.joinpath("moonraker-dev-reqs.txt")
|
||||
dev_deps = optional_deps["dev"]
|
||||
if check_reqs_changed(dev_reqs_path, dev_deps):
|
||||
print("Syncing dev requirements")
|
||||
ret = 1
|
||||
with dev_reqs_path.open("r+") as req_file:
|
||||
for requirement in dev_deps:
|
||||
req_file.write(f"{requirement}\n")
|
||||
else:
|
||||
print("Dev requirements match")
|
||||
return ret
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"filename", default="", nargs="?",
|
||||
help="The name of the dependency file to sync"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
fname: str = args.filename
|
||||
if not fname:
|
||||
ret = sync_requirements()
|
||||
ret += sync_packages()
|
||||
return 1 if ret > 0 else 0
|
||||
elif fname == "pyproject.toml":
|
||||
return sync_requirements()
|
||||
elif fname == "scripts/system-dependencies.json":
|
||||
return sync_packages()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
|
@ -8,6 +8,7 @@
|
|||
"libjpeg-dev",
|
||||
"packagekit",
|
||||
"wireless-tools",
|
||||
"curl"
|
||||
"curl",
|
||||
"build-essential"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue