Initial commit: Tailscale Funnel plugin for OctoPrint with build documentation
This commit is contained in:
230
octoprint_tailscale_funnel/__init__.py
Normal file
230
octoprint_tailscale_funnel/__init__.py
Normal file
@@ -0,0 +1,230 @@
|
||||
# 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
|
||||
}
|
Reference in New Issue
Block a user