Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
crysxd committed Nov 28, 2023
2 parents 435ee90 + 9c64076 commit cbc5de7
Show file tree
Hide file tree
Showing 18 changed files with 658 additions and 135 deletions.
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"bootstraper",
"boundarydonotcross",
"brotli",
"Buildroot",
"certifi",
"checkin",
"classicwebcam",
Expand All @@ -27,6 +28,7 @@
"crypo",
"Damerell",
"deps",
"devel",
"devs",
"DGRAM",
"didnt",
Expand All @@ -48,6 +50,7 @@
"gcode",
"geteuid",
"getpwnam",
"Guilouz",
"hacky",
"handshakesyn",
"hostcommon",
Expand Down Expand Up @@ -116,6 +119,7 @@
"octowebstreamhttphelperimpl",
"octowebstreamimpl",
"octowebstreamwshelper",
"openwrt",
"opkg",
"oprint",
"ostype",
Expand Down
114 changes: 85 additions & 29 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,37 @@
# Set this to terminate on error.
set -e

# Set if we are running the Creality OS or not.
# We use the presence of opkg as they key
IS_CREALITY_OS=false
if command -v opkg &> /dev/null

#
# First things first, we need to detect what kind of OS we are running on. The script works by default with all
# Debian OSs, but some printers with embedded computers run strange embedded OSs, that have a lot of restrictions.
# These must stay in sync with update.sh and uninstall.sh!
#

# The K1 and K1 Max run an OS called Buildroot. We detect that by looking at the os-release file.
# Quick note about bash vars, to support all OSs, we use the most compilable var version. This means we use ints
# where 1 is true and 0 is false, and we use comparisons like this [[ $IS_K1_OS -eq 1 ]]
IS_K1_OS=0
if grep -Fqs "ID=buildroot" /etc/os-release
then
IS_K1_OS=1
# On the K1, we always want the path to be /usr/data
# /usr/share has very limited space, so we don't want to use it.
# This is also where the github script installs moonraker and everything.
HOME="/usr/data"
fi

# Next, we try to detect if this OS is the Sonic Pad OS.
# The Sonic Pad runs openwrt. We detect that by looking at the os-release file.
IS_SONIC_PAD_OS=0
if grep -Fqs "sonic" /etc/openwrt_release
then
IS_CREALITY_OS=true
# We install everything at this path, which is a fixed path where moonraker and klipper are also installed.
IS_SONIC_PAD_OS=1
# On the K1, we always want the path to be /usr/share, this is where the rest of the klipper stuff is.
HOME="/usr/share"
fi


# Get the root path of the repo, aka, where this script is executing
OCTOAPP_REPO_DIR=$(readlink -f $(dirname "$0"))

Expand All @@ -54,7 +75,8 @@ OCTOAPP_ENV="${HOME}/octoapp-env"
PKGLIST="python3 python3-pip virtualenv curl"
# For the Creality OS, we only need to install these.
# We don't override the default name, since that's used by the Moonraker installer
CREALITY_PKGLIST="python3 python3-pip"
# Note that we DON'T want to use the same name as above (not even in this comment) because some parsers might find it.
SONIC_PAD_DEP_LIST="python3 python3-pip"


#
Expand Down Expand Up @@ -89,38 +111,52 @@ log_info()
echo -e "${c_green}$1${c_default}"
}

log_blue()
{
echo -e "${c_cyan}$1${c_default}"
}

log_blank()
{
echo ""
}

#
# It's important for consistency that the repo root is in /usr/share on Creality OS.
# It's important for consistency that the repo root is in set $HOME for the K1 and Sonic Pad
# To enforce that, we will move the repo where it should be.
#
ensure_creality_os_right_repo_path()
{
if $IS_CREALITY_OS
# TODO - re-enable this for the || [[ $IS_K1_OS -eq 1 ]] after the github script updates.
if [[ $IS_SONIC_PAD_OS -eq 1 ]]
then
EXPECT='/usr/share/'
if [[ "$OCTOAPP_REPO_DIR" != *"$EXPECT"* ]]; then
log_error "For the Creality OS this repo must be cloned into /usr/share/octoapp."
# Due to the K1 shell, we have to use grep rather than any bash string contains syntax.
if echo $OCTOAPP_REPO_DIR |grep "$HOME" - > /dev/null
then
return
else
log_info "Current path $OCTOAPP_REPO_DIR"
log_error "For the Creality devices the OctoEverywhere repo must be cloned into $HOME/octoapp"
log_important "Moving the repo and running the install again..."
cd /usr/share
cd $HOME
# Send errors to null, if the folder already exists this will fail.
git clone https://github.com/QuinnDamerell/OctoPrint-OctoApp octoapp 2>/dev/null || true
cd /usr/share/octoapp
git clone https://github.com/crysxd/OctoApp-Plugin octoapp 2>/dev/null || true
cd $HOME/octoapp
# Ensure state
git reset --hard
git checkout master
git pull
# Run the install, if it fails, still do the clean-up of this repo.
./install.sh "$@" || true
if [[ $IS_K1_OS -eq 1 ]]
then
sh ./install.sh "$@" || true
else
./install.sh "$@" || true
fi
installExit=$?
# Delete this folder.
rm -fr $OCTOAPP_REPO_DIR
# Take the user back to the new install folder.
cd /usr/share/
cd $HOME
# Exit.
exit $installExit
fi
Expand All @@ -133,9 +169,10 @@ ensure_creality_os_right_repo_path()
ensure_py_venv()
{
log_header "Checking Python Virtual Environment For OctoApp..."
# If the service is already running, we can't recreate the virtual env
# so if it exists, don't try to create it.
if [ -d $OCTOAPP_ENV ]; then
# If the service is already running, we can't recreate the virtual env so if it exists, don't try to create it.
# Note that we check the bin folder exists in the path, since we mkdir the folder below but virtualenv might fail and leave it empty.
OCTOAPP_BIN_PATH="$OCTOAPP_ENV/bin"
if [ -d $OCTOAPP_BIN_PATH ]; then
# This virtual env refresh fails on some devices when the service is already running, so skip it for now.
# This only refreshes the virtual environment package anyways, so it's not super needed.
#log_info "Virtual environment found, updating to the latest version of python."
Expand All @@ -145,7 +182,14 @@ ensure_py_venv()

log_info "No virtual environment found, creating one now."
mkdir -p "${OCTOAPP_ENV}"
virtualenv -p /usr/bin/python3 --system-site-packages "${OCTOAPP_ENV}"
if [[ $IS_K1_OS -eq 1 ]]
then
# The K1 requires we setup the virtualenv like this.
python3 /usr/lib/python3.8/site-packages/virtualenv.py -p /usr/bin/python3 --system-site-packages "${OCTOAPP_ENV}"
else
# Everything else can use this more modern style command.
virtualenv -p /usr/bin/python3 --system-site-packages "${OCTOAPP_ENV}"
fi
}

#
Expand All @@ -155,10 +199,17 @@ install_or_update_system_dependencies()
{
log_header "Checking required system packages are installed..."

if $IS_CREALITY_OS
if [[ $IS_K1_OS -eq 1 ]]
then
# The K1 by default doesn't have any package manager. In some cases
# the user might install opkg via the 3rd party moonraker installer script.
# But in general, PY will already be installed, so there's no need to try.
# On the K1, the only we thing we ensure is that virtualenv is installed via pip.
pip3 install virtualenv
elif [[ $IS_SONIC_PAD_OS -eq 1 ]]
then
# On the Creality OS, we only need to run these installers
opkg install ${CREALITY_PKGLIST}
# The sonic pad always has opkg installed, so we can make sure these packages are installed.
opkg install ${SONIC_PAD_DEP_LIST}
pip3 install virtualenv
else
log_important "You might be asked for your system password - this is required to install the required system packages."
Expand Down Expand Up @@ -211,7 +262,7 @@ install_or_update_python_env()
#
check_for_octoprint()
{
if $IS_CREALITY_OS
if [[ $IS_SONIC_PAD_OS -eq 1 ]] || [[ $IS_K1_OS -eq 1 ]]
then
# Skip, there's no need and we don't have curl.
return
Expand Down Expand Up @@ -316,9 +367,14 @@ log_header " Based on the OctoEverywhere Companion"
log_blank
log_blank

if $IS_CREALITY_OS
# These are helpful for debugging.
if [[ $IS_SONIC_PAD_OS -eq 1 ]]
then
echo "Running in Sonic Pad OS mode"
fi
if [[ $IS_K1_OS -eq 1 ]]
then
echo "Running in Creality OS mode"
echo "Running in K1 and K1 Max OS mode"
fi

# Before anything, make sure this repo is cloned into the correct path on Creality OS devices.
Expand Down Expand Up @@ -357,7 +413,7 @@ cd ${OCTOAPP_REPO_DIR} > /dev/null
# Disable the PY cache files (-B), since they will be written as sudo, since that's what we launch the PY
# installer as. The PY installer must be sudo to write the service files, but we don't want the
# complied files to stay in the repo with sudo permissions.
if $IS_CREALITY_OS
if [[ $IS_SONIC_PAD_OS -eq 1 ]] || [[ $IS_K1_OS -eq 1 ]]
then
# Creality OS only has a root user and we can't use sudo.
${OCTOAPP_ENV}/bin/python3 -B -m moonraker_installer ${PY_LAUNCH_JSON}
Expand Down
25 changes: 17 additions & 8 deletions moonraker_installer/Configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# The goal of this class is the take the context object from the Discovery Gen2 phase to the Phase 3.
class Configure:

# This is the common service prefix we use for all of our service file names.
# This is the common service prefix (or word used in the file name) we use for all of our service file names.
# This MUST be used for all instances running on this device, both local plugins and companions.
# This also MUST NOT CHANGE, as it's used by the Updater logic to find all of the locally running services.
c_ServiceCommonNamePrefix = "octoapp"
Expand All @@ -28,11 +28,14 @@ def Run(self, context:Context):
# For observers, we use the observer id, with a unique prefix to separate it from any possible local moonraker installs
# Note that the moonraker service suffix can be numbers or letters, so we use the same rules.
serviceSuffixStr = f"-companion{context.ObserverInstanceId}"
elif context.IsCrealityOs():
# For Creality OS, we know the format of the service file is a bit different.
# It is moonraker_service or moonraker_service.<number>
elif context.OsType == OsTypes.SonicPad:
# For Sonic Pad, we know the format of the service file is a bit different.
# For the SonicIt is moonraker_service or moonraker_service.<number>
if "." in context.MoonrakerServiceFileName:
serviceSuffixStr = context.MoonrakerServiceFileName.split(".")[1]
elif context.OsType == OsTypes.K1:
# For the k1, there's only every one moonraker instance, so this isn't needed.
pass
else:
# Now we need to figure out the instance suffix we need to use.
# To keep with Kiauh style installs, each moonraker instances will be named moonraker-<number or name>.service.
Expand Down Expand Up @@ -70,26 +73,32 @@ def Run(self, context:Context):
# For now we assume the folder structure is the standard Klipper folder config,
# thus the full moonraker config path will be .../something_data/config/moonraker.conf
# Based on that, we will define the config folder and the printer data root folder.
# Note that the K1 uses this standard folder layout as well.
context.PrinterDataConfigFolder = Util.GetParentDirectory(context.MoonrakerConfigFilePath)
context.PrinterDataFolder = Util.GetParentDirectory(context.PrinterDataConfigFolder)
Logger.Debug("Printer data folder: "+context.PrinterDataFolder)


# This is the name of our service we create. If the port is the default port, use the default name.
# Otherwise, add the port to keep services unique.
if context.IsCrealityOs():
# For creality os, since the service is setup differently, follow the conventions of it.
if context.OsType == OsTypes.SonicPad:
# For Sonic Pad, since the service is setup differently, follow the conventions of it.
# Both the service name and the service file name must match.
# The format is <name>_service<number>
# NOTE! For the Update class to work, the name must start with Configure.c_ServiceCommonNamePrefix
context.ServiceName = Configure.c_ServiceCommonNamePrefix + "_service"
context.ServiceName = Configure.c_ServiceCommonName + "_service"
if len(serviceSuffixStr) != 0:
context.ServiceName= context.ServiceName + "." + serviceSuffixStr
context.ServiceFilePath = os.path.join(Paths.CrealityOsServiceFilePath, context.ServiceName)
elif context.OsType == OsTypes.K1:
# For the k1, there's only ever one moonraker and we know the exact service naming convention.
# Note we use 66 to ensure we start after moonraker.
context.ServiceName = f"S66{Configure.c_ServiceCommonName}_service"
context.ServiceFilePath = os.path.join(Paths.CrealityOsServiceFilePath, context.ServiceName)
else:
# For normal setups, use the convention that Klipper users
# NOTE! For the Update class to work, the name must start with Configure.c_ServiceCommonNamePrefix
context.ServiceName = Configure.c_ServiceCommonNamePrefix + serviceSuffixStr
context.ServiceName = Configure.c_ServiceCommonName + serviceSuffixStr
context.ServiceFilePath = os.path.join(Paths.SystemdServiceFilePath, context.ServiceName+".service")

# Since the moonraker config folder is unique to the moonraker instance, we will put our storage in it.
Expand Down
48 changes: 32 additions & 16 deletions moonraker_installer/Context.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import os
import json
import subprocess
from enum import Enum

from .Logging import Logger
Expand All @@ -11,7 +10,7 @@
class OsTypes(Enum):
Debian = 1
SonicPad = 2
K1 = 2 # Both the K1 and K1 Max
K1 = 3 # Both the K1 and K1 Max


# This class holds the context of the installer, meaning all of the target vars and paths
Expand Down Expand Up @@ -66,6 +65,9 @@ def __init__(self) -> None:
# Parsed from the command line args, if set, the plugin install should be in update mode.
self.IsUpdateMode:bool = False

# Parsed from the command line args, if set, the plugin install should be in uninstall mode.
self.IsUninstallMode:bool = False


#
# Generation 2
Expand Down Expand Up @@ -230,6 +232,9 @@ def ParseCmdLineArgs(self):
elif rawArg.lower() == "update":
Logger.Info("Setup running in update mode.")
self.IsUpdateMode = True
elif rawArg.lower() == "uninstall":
Logger.Info("Setup running in uninstall mode.")
self.IsUninstallMode = True
else:
raise Exception("Unknown argument found. Use install.sh -help for options.")

Expand Down Expand Up @@ -257,21 +262,32 @@ def _ValidateString(self, s:str, error:str):

def DetectOsType(self):
#
# Note! This should closely resemble the ostype.py class in the plugin.
# Note! This should closely resemble the ostype.py class in the plugin and the logic in the ./install.sh script!
#
# We use the presence of opkg to figure out if we are running no Creality OS
# This is the same thing we do in the install and update scripts.
result = subprocess.run("command -v opkg", check=False, shell=True, capture_output=True, text=True)
if result.returncode == 0:
# This is a Creality OS.
# Now we need to detect if it's a Sonic Pad or a K1
if os.path.exists(Paths.CrealityOsUserDataPath_SonicPad):
self.OsType = OsTypes.SonicPad
return
if os.path.exists(Paths.CrealityOsUserDataPath_K1):
self.OsType = OsTypes.K1
return
raise Exception("We detected a Creality OS, but can't determine the device type. Please contact support.")

# For the k1 and k1 max, we look for the "buildroot" OS.
if os.path.exists("/etc/os-release"):
with open("/etc/os-release", "r", encoding="utf-8") as osInfo:
lines = osInfo.readlines()
for l in lines:
if "ID=buildroot" in l:
# If we find it, make sure the user data path is where we expect it to be, and we are good.
if os.path.exists(Paths.CrealityOsUserDataPath_K1):
self.OsType = OsTypes.K1
return
raise Exception("We detected a K1 or K1 Max OS, but can't determine the data path. Please contact support.")

# For the Sonic Pad, we look for the openwrt os
if os.path.exists("/etc/openwrt_release"):
with open("/etc/openwrt_release", "r", encoding="utf-8") as osInfo:
lines = osInfo.readlines()
for l in lines:
if "sonic" in l:
# If we find it, make sure the user data path is where we expect it to be, and we are good.
if os.path.exists(Paths.CrealityOsUserDataPath_SonicPad):
self.OsType = OsTypes.SonicPad
return
raise Exception("We detected a Sonic Pad, but can't determine the data path. Please contact support.")

# The OS is debian
self.OsType = OsTypes.Debian
Expand Down
Loading

0 comments on commit cbc5de7

Please sign in to comment.