Files
octo-funnel/octoprint_tailscale_funnel/__init__.py

230 lines
9.2 KiB
Python

# coding=utf-8
from __future__ import absolute_import
import octoprint.plugin
from .tailscale_interface import TailscaleInterface, TailscaleError, TailscaleNotInstalledError, TailscaleNotRunningError
from .status_monitor import StatusMonitor
class TailscaleFunnelPlugin(octoprint.plugin.StartupPlugin,
octoprint.plugin.SettingsPlugin,
octoprint.plugin.AssetPlugin,
octoprint.plugin.TemplatePlugin,
octoprint.plugin.BlueprintPlugin):
def __init__(self):
self._logger = None
self.tailscale_interface = None
self.status_monitor = None
##~~ StartupPlugin mixin
def on_after_startup(self):
self._logger = self._plugin_manager.get_logger("octoprint_tailscale_funnel")
self._logger.info("Tailscale Funnel Plugin started")
self.tailscale_interface = TailscaleInterface(self._logger)
self.status_monitor = StatusMonitor(self)
self.status_monitor.start()
def on_shutdown(self):
if self.status_monitor:
self.status_monitor.stop()
##~~ SettingsPlugin mixin
def get_settings_defaults(self):
return dict(
enabled=False,
port=80,
confirm_enable=True
)
def get_settings_version(self):
return 1
def on_settings_migrate(self, target, current):
if current is None:
# Migrate from settings version 0 to 1
if self._settings.has(["funnel_enabled"]):
self._settings.set_boolean(["enabled"], self._settings.get_boolean(["funnel_enabled"]))
self._settings.remove(["funnel_enabled"])
def on_settings_save(self, data):
# Save the settings
octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
# If enabled setting changed, update funnel status
if "enabled" in data:
enabled = self._settings.get_boolean(["enabled"])
port = self._settings.get_int(["port"])
try:
if enabled:
# Enable funnel
if self.tailscale_interface:
success = self.tailscale_interface.enable_funnel(port)
if success:
self._logger.info("Tailscale Funnel enabled on port {}".format(port))
else:
self._logger.error("Failed to enable Tailscale Funnel on port {}".format(port))
else:
# Disable funnel
if self.tailscale_interface:
success = self.tailscale_interface.disable_funnel(port)
if success:
self._logger.info("Tailscale Funnel disabled on port {}".format(port))
else:
self._logger.error("Failed to disable Tailscale Funnel on port {}".format(port))
except TailscaleError as e:
self._logger.error("Tailscale error: {}".format(str(e)))
except Exception as e:
self._logger.error("Unexpected error: {}".format(str(e)))
##~~ AssetPlugin mixin
def get_assets(self):
return dict(
js=["js/tailscale_funnel.js"],
css=["css/tailscale_funnel.css"],
less=["less/tailscale_funnel.less"]
)
##~~ TemplatePlugin mixin
def get_template_configs(self):
return [
dict(type="settings", custom_bindings=False)
]
##~~ BlueprintPlugin mixin
@octoprint.plugin.BlueprintPlugin.route("/status", methods=["GET"])
def get_status(self):
if not self.tailscale_interface:
return {"status": "error", "message": "Tailscale interface not initialized"}, 500
try:
# Check if tailscale is installed
if not self.tailscale_interface.is_tailscale_installed():
return {"status": "error", "message": "Tailscale is not installed"}, 500
# Check if tailscale is running
if not self.tailscale_interface.is_tailscale_running():
return {"status": "error", "message": "Tailscale is not running"}, 500
# Get funnel status
funnel_enabled = self.tailscale_interface.is_funnel_enabled()
public_url = self.tailscale_interface.get_public_url() if funnel_enabled else None
return {
"status": "success",
"data": {
"funnel_enabled": funnel_enabled,
"public_url": public_url,
"tailscale_installed": True,
"tailscale_running": True
}
}
except TailscaleNotInstalledError as e:
return {"status": "error", "message": "Tailscale is not installed: {}".format(str(e))}, 500
except TailscaleNotRunningError as e:
return {"status": "error", "message": "Tailscale is not running: {}".format(str(e))}, 500
except TailscaleError as e:
return {"status": "error", "message": "Tailscale error: {}".format(str(e))}, 500
except Exception as e:
self._logger.error("Unexpected error in get_status: {}".format(str(e)))
return {"status": "error", "message": "Unexpected error: {}".format(str(e))}, 500
@octoprint.plugin.BlueprintPlugin.route("/enable", methods=["POST"])
def enable_funnel(self):
if not self.tailscale_interface:
return {"status": "error", "message": "Tailscale interface not initialized"}, 500
try:
port = self._settings.get_int(["port"])
success = self.tailscale_interface.enable_funnel(port)
if success:
# Update settings
self._settings.set_boolean(["enabled"], True)
self._settings.save()
public_url = self.tailscale_interface.get_public_url()
return {
"status": "success",
"message": "Tailscale Funnel enabled successfully",
"data": {
"public_url": public_url
}
}
else:
return {"status": "error", "message": "Failed to enable Tailscale Funnel"}, 500
except TailscaleNotInstalledError as e:
return {"status": "error", "message": "Tailscale is not installed: {}".format(str(e))}, 500
except TailscaleError as e:
return {"status": "error", "message": "Tailscale error: {}".format(str(e))}, 500
except Exception as e:
self._logger.error("Unexpected error in enable_funnel: {}".format(str(e)))
return {"status": "error", "message": "Unexpected error: {}".format(str(e))}, 500
@octoprint.plugin.BlueprintPlugin.route("/disable", methods=["POST"])
def disable_funnel(self):
if not self.tailscale_interface:
return {"status": "error", "message": "Tailscale interface not initialized"}, 500
try:
port = self._settings.get_int(["port"])
success = self.tailscale_interface.disable_funnel(port)
if success:
# Update settings
self._settings.set_boolean(["enabled"], False)
self._settings.save()
return {
"status": "success",
"message": "Tailscale Funnel disabled successfully"
}
else:
return {"status": "error", "message": "Failed to disable Tailscale Funnel"}, 500
except TailscaleNotInstalledError as e:
return {"status": "error", "message": "Tailscale is not installed: {}".format(str(e))}, 500
except TailscaleError as e:
return {"status": "error", "message": "Tailscale error: {}".format(str(e))}, 500
except Exception as e:
self._logger.error("Unexpected error in disable_funnel: {}".format(str(e)))
return {"status": "error", "message": "Unexpected error: {}".format(str(e))}, 500
##~~ Softwareupdate hook
def get_update_information(self):
return dict(
tailscale_funnel=dict(
displayName="Tailscale Funnel Plugin",
displayVersion=self._plugin_version,
# version check: github repository
type="github_release",
user="markus",
repo="OctoPrint-Tailscale-Funnel",
current=self._plugin_version,
# update method: pip
pip="https://github.com/markus/OctoPrint-Tailscale-Funnel/archive/{target_version}.zip"
)
)
# If you want your plugin to be registered within OctoPrint under its own name,
# define it here.
__plugin_name__ = "Tailscale Funnel Plugin"
__plugin_pythoncompat__ = ">=3.7,<4" # python 3.7+
def __plugin_load__():
global __plugin_implementation__
__plugin_implementation__ = TailscaleFunnelPlugin()
global __plugin_hooks__
__plugin_hooks__ = {
"octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information
}