├── .gitattributes ├── .github └── workflows │ └── latest-tag.yml ├── FileSets ├── PatchSource │ ├── PageSettingsGeneral.qml │ ├── PageSettingsGeneral.qml.orig │ └── PageSettingsGeneral.qml.patch ├── VersionIndependent │ └── PageSettingsTailscaleGX.qml ├── fileListPatched └── fileListVersionIndependent ├── ReadMe.md ├── TailScaleReadMe.md ├── TailscaleGX-control.py ├── buildTailscaleForArm ├── changes ├── gitHubInfo ├── services ├── TailscaleGX-backend │ ├── down │ ├── log │ │ └── run │ └── run └── TailscaleGX-control │ ├── log │ └── run │ └── run ├── setup ├── tailscale ├── tailscale.combined ├── tailscaled └── version /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/latest-tag.yml: -------------------------------------------------------------------------------- 1 | name: Add latest tag to new release 2 | on: 3 | release: 4 | types: [published] 5 | workflow_dispatch: 6 | 7 | jobs: 8 | run: 9 | name: Run local action 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@master 14 | 15 | - name: Run latest-tag 16 | uses: EndBug/latest-tag@v1 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /FileSets/PatchSource/PageSettingsGeneral.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 1.1 2 | import com.victron.velib 1.0 3 | import "utils.js" as Utils 4 | 5 | MbPage 6 | { 7 | id: root 8 | property string bindPrefix: "com.victronenergy.settings" 9 | 10 | model: VisibleItemModel { 11 | MbItemOptions { 12 | id: accessLevelSelect 13 | description: qsTr("Access level") 14 | bind: Utils.path(bindPrefix, "/Settings/System/AccessLevel") 15 | magicKeys: true 16 | writeAccessLevel: User.AccessUser 17 | possibleValues: [ 18 | MbOption { description: qsTr("User"); value: User.AccessUser; password: "ZZZ" }, 19 | MbOption { description: qsTr("User & Installer"); value: User.AccessInstaller; password: "ZZZ" }, 20 | MbOption { description: qsTr("Superuser"); value: User.AccessSuperUser; readonly: true }, 21 | MbOption { description: qsTr("Service"); value: User.AccessService; readonly: true } 22 | ] 23 | 24 | // touch version to get super user 25 | property bool pulledDown: listview.contentY < -60 26 | Timer { 27 | running: accessLevelSelect.pulledDown 28 | interval: 5000 29 | onTriggered: if (user.accessLevel >= User.AccessInstaller) accessLevelSelect.item.setValue(User.AccessSuperUser) 30 | } 31 | 32 | // change to super user mode if the right button is pressed for a while 33 | property int repeatCount 34 | onFocusChanged: repeatCount = 0 35 | 36 | function open() { 37 | if (user.accessLevel >= User.AccessInstaller && ++repeatCount > 60) { 38 | if (accessLevelSelect.value !== User.AccessSuperUser) 39 | accessLevelSelect.item.setValue(User.AccessSuperUser) 40 | repeatCount = 0 41 | } 42 | } 43 | } 44 | 45 | MbEditBox { 46 | description: "Set root password" 47 | showAccessLevel: User.AccessSuperUser 48 | onEditDone: { 49 | if (newValue.length < 6) { 50 | toast.createToast("Please enter at least 6 characters") 51 | } else { 52 | toast.createToast(vePlatform.setRootPassword(newValue)) 53 | item.value = "" 54 | } 55 | } 56 | } 57 | 58 | MbSwitch { 59 | name: qsTr("SSH on LAN") 60 | showAccessLevel: User.AccessSuperUser 61 | bind: "com.victronenergy.settings/Settings/System/SSHLocal" 62 | } 63 | 64 | MbSwitch { 65 | id: remoteSupportOnOff 66 | name: qsTr("Remote support") 67 | bind: "com.victronenergy.settings/Settings/System/RemoteSupport" 68 | } 69 | 70 | MbItemValue { 71 | description: qsTr("Remote support tunnel") 72 | item.value: remotePort.item.valid && remotePort.item.value !== 0 ? qsTr("Online") : qsTr("Offline") 73 | show: remoteSupportOnOff.item.value 74 | } 75 | 76 | MbItemValue { 77 | id: remotePort 78 | description: qsTr("Remote support IP and port") 79 | item.bind: "com.victronenergy.settings/Settings/System/RemoteSupportIpAndPort" 80 | show: remoteSupportOnOff.item.value 81 | } 82 | 83 | ////// added for tailscale remote connections 84 | MbSubMenu 85 | { 86 | description: qsTr("Remote access via tailscale") 87 | subpage: Component { PageSettingsTailscaleGX {} } 88 | } 89 | 90 | MbOK { 91 | id: reboot 92 | description: qsTr("Reboot?") 93 | writeAccessLevel: User.AccessUser 94 | onClicked: { 95 | toast.createToast(qsTr("Rebooting..."), 10000, "icon-restart-active") 96 | vePlatform.reboot() 97 | } 98 | } 99 | 100 | MbSwitch { 101 | property VBusItem hasBuzzer: VBusItem {bind: "com.victronenergy.system/Buzzer/State"} 102 | name: qsTr("Audible alarm") 103 | bind: Utils.path(bindPrefix, "/Settings/Alarm/Audible") 104 | show: hasBuzzer.valid 105 | } 106 | 107 | MbSwitch { 108 | name: qsTr("Enable status LEDs") 109 | bind: Utils.path(bindPrefix, "/Settings/LEDs/Enable") 110 | show: item.valid 111 | } 112 | 113 | MbItemOptions { 114 | id: demoOnOff 115 | description: qsTr("Demo mode") 116 | bind: Utils.path(bindPrefix, "/Settings/Gui/DemoMode") 117 | possibleValues: [ 118 | MbOption { description: qsTr("Disabled"); value: 0 }, 119 | MbOption { description: qsTr("ESS demo"); value: 1 }, 120 | MbOption { description: qsTr("Boat/Motorhome demo 1"); value: 2 }, 121 | MbOption { description: qsTr("Boat/Motorhome demo 2"); value: 3 } 122 | ] 123 | } 124 | 125 | MbItemText { 126 | text: qsTr("Starting demo mode will change some settings and the user interface will be unresponsive for a moment.") 127 | wrapMode: Text.WordWrap 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /FileSets/PatchSource/PageSettingsGeneral.qml.orig: -------------------------------------------------------------------------------- 1 | import QtQuick 1.1 2 | import com.victron.velib 1.0 3 | import "utils.js" as Utils 4 | 5 | MbPage 6 | { 7 | id: root 8 | property string bindPrefix: "com.victronenergy.settings" 9 | 10 | model: VisibleItemModel { 11 | MbItemOptions { 12 | id: accessLevelSelect 13 | description: qsTr("Access level") 14 | bind: Utils.path(bindPrefix, "/Settings/System/AccessLevel") 15 | magicKeys: true 16 | writeAccessLevel: User.AccessUser 17 | possibleValues: [ 18 | MbOption { description: qsTr("User"); value: User.AccessUser; password: "ZZZ" }, 19 | MbOption { description: qsTr("User & Installer"); value: User.AccessInstaller; password: "ZZZ" }, 20 | MbOption { description: qsTr("Superuser"); value: User.AccessSuperUser; readonly: true }, 21 | MbOption { description: qsTr("Service"); value: User.AccessService; readonly: true } 22 | ] 23 | 24 | // touch version to get super user 25 | property bool pulledDown: listview.contentY < -60 26 | Timer { 27 | running: accessLevelSelect.pulledDown 28 | interval: 5000 29 | onTriggered: if (user.accessLevel >= User.AccessInstaller) accessLevelSelect.item.setValue(User.AccessSuperUser) 30 | } 31 | 32 | // change to super user mode if the right button is pressed for a while 33 | property int repeatCount 34 | onFocusChanged: repeatCount = 0 35 | 36 | function open() { 37 | if (user.accessLevel >= User.AccessInstaller && ++repeatCount > 60) { 38 | if (accessLevelSelect.value !== User.AccessSuperUser) 39 | accessLevelSelect.item.setValue(User.AccessSuperUser) 40 | repeatCount = 0 41 | } 42 | } 43 | } 44 | 45 | MbEditBox { 46 | description: "Set root password" 47 | showAccessLevel: User.AccessSuperUser 48 | onEditDone: { 49 | if (newValue.length < 6) { 50 | toast.createToast("Please enter at least 6 characters") 51 | } else { 52 | toast.createToast(vePlatform.setRootPassword(newValue)) 53 | item.value = "" 54 | } 55 | } 56 | } 57 | 58 | MbSwitch { 59 | name: qsTr("SSH on LAN") 60 | showAccessLevel: User.AccessSuperUser 61 | bind: "com.victronenergy.settings/Settings/System/SSHLocal" 62 | } 63 | 64 | MbSwitch { 65 | id: remoteSupportOnOff 66 | name: qsTr("Remote support") 67 | bind: "com.victronenergy.settings/Settings/System/RemoteSupport" 68 | } 69 | 70 | MbItemValue { 71 | description: qsTr("Remote support tunnel") 72 | item.value: remotePort.item.valid && remotePort.item.value !== 0 ? qsTr("Online") : qsTr("Offline") 73 | show: remoteSupportOnOff.item.value 74 | } 75 | 76 | MbItemValue { 77 | id: remotePort 78 | description: qsTr("Remote support IP and port") 79 | item.bind: "com.victronenergy.settings/Settings/System/RemoteSupportIpAndPort" 80 | show: remoteSupportOnOff.item.value 81 | } 82 | 83 | MbOK { 84 | id: reboot 85 | description: qsTr("Reboot?") 86 | writeAccessLevel: User.AccessUser 87 | onClicked: { 88 | toast.createToast(qsTr("Rebooting..."), 10000, "icon-restart-active") 89 | vePlatform.reboot() 90 | } 91 | } 92 | 93 | MbSwitch { 94 | property VBusItem hasBuzzer: VBusItem {bind: "com.victronenergy.system/Buzzer/State"} 95 | name: qsTr("Audible alarm") 96 | bind: Utils.path(bindPrefix, "/Settings/Alarm/Audible") 97 | show: hasBuzzer.valid 98 | } 99 | 100 | MbSwitch { 101 | name: qsTr("Enable status LEDs") 102 | bind: Utils.path(bindPrefix, "/Settings/LEDs/Enable") 103 | show: item.valid 104 | } 105 | 106 | MbItemOptions { 107 | id: demoOnOff 108 | description: qsTr("Demo mode") 109 | bind: Utils.path(bindPrefix, "/Settings/Gui/DemoMode") 110 | possibleValues: [ 111 | MbOption { description: qsTr("Disabled"); value: 0 }, 112 | MbOption { description: qsTr("ESS demo"); value: 1 }, 113 | MbOption { description: qsTr("Boat/Motorhome demo 1"); value: 2 }, 114 | MbOption { description: qsTr("Boat/Motorhome demo 2"); value: 3 } 115 | ] 116 | } 117 | 118 | MbItemText { 119 | text: qsTr("Starting demo mode will change some settings and the user interface will be unresponsive for a moment.") 120 | wrapMode: Text.WordWrap 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /FileSets/PatchSource/PageSettingsGeneral.qml.patch: -------------------------------------------------------------------------------- 1 | --- /Users/Kevin/GitHub/TailscaleGX.copy/FileSets/PatchSource/PageSettingsGeneral.qml.orig 2024-04-22 04:01:13 2 | +++ /Users/Kevin/GitHub/TailscaleGX.copy/FileSets/PatchSource/PageSettingsGeneral.qml 2024-05-02 17:31:26 3 | @@ -80,6 +80,13 @@ 4 | show: remoteSupportOnOff.item.value 5 | } 6 | 7 | +////// added for tailscale remote connections 8 | + MbSubMenu 9 | + { 10 | + description: qsTr("Remote access via tailscale") 11 | + subpage: Component { PageSettingsTailscaleGX {} } 12 | + } 13 | + 14 | MbOK { 15 | id: reboot 16 | description: qsTr("Reboot?") 17 | -------------------------------------------------------------------------------- /FileSets/VersionIndependent/PageSettingsTailscaleGX.qml: -------------------------------------------------------------------------------- 1 | /////// new menu for Tailscale GX 2 | 3 | import QtQuick 1.1 4 | import "utils.js" as Utils 5 | import com.victron.velib 1.0 6 | 7 | MbPage 8 | { 9 | property string servicePrefix: "com.victronenergy.tailscaleGX" 10 | property string settingsPrefix: "com.victronenergy.settings/Settings/Services/Tailscale" 11 | 12 | id: root 13 | title: qsTr("Remote access (tailscale) setup") 14 | VBusItem { id: stateItem; bind: Utils.path(servicePrefix, "/State") } 15 | VBusItem { id: loginItem; bind: Utils.path(servicePrefix, "/LoginLink") } 16 | VBusItem { id: ipV4Item; bind: Utils.path(servicePrefix, "/IPv4") } 17 | VBusItem { id: ipV6Item; bind: Utils.path(servicePrefix, "/IPv6") } 18 | VBusItem { id: hostNameItem; bind: Utils.path(servicePrefix, "/HostName") } 19 | VBusItem { id: commandItem; bind: Utils.path(servicePrefix, "/GuiCommand") } 20 | VBusItem { id: enabledItem; bind: Utils.path(settingsPrefix, "/Enabled") } 21 | VBusItem { id: customArgumentsItem; bind: Utils.path(settingsPrefix, "/CustomArguments") } 22 | 23 | property int connectState: stateItem.valid ? stateItem.value : 0 24 | property string ipV4: ipV4Item.valid ? ipV4Item.value : "" 25 | property string ipV6: ipV6Item.valid ? ipV6Item.value : "" 26 | property string hostName: hostNameItem.valid ? hostNameItem.value : "" 27 | property string loginLink: loginItem.valid ? loginItem.value : "" 28 | 29 | property bool isRunning: stateItem.valid 30 | property bool isEnabled: enable.checked && isRunning 31 | property bool isConnected: connectState == 100 && isEnabled 32 | 33 | VBusItem { id: authKeyItem; bind: Utils.path(settingsPrefix, "/AuthKey") } 34 | property string authKey: authKeyItem.valid ? authKeyItem.value : "" 35 | property string joinedKey: keyPt1.item.value + keyPt2.item.value + keyPt3.item.value 36 | 37 | function getState () 38 | { 39 | if ( ! isRunning ) 40 | return qsTr ( "TailscaleGX control not running" ) 41 | else if ( ! isEnabled ) 42 | return qsTr ( "remote connections not accepted\n (disabled above)" ) 43 | else if ( isConnected ) 44 | return ( qsTr ( "accepting remote connections at:\n") 45 | + hostName + "\n" + ipV4 + "\n" + ipV6 ) 46 | else if ( connectState == 0 ) 47 | return "" 48 | else if ( connectState == 1 ) 49 | return qsTr ("starting ...") 50 | else if ( connectState == 2 || connectState == 3) 51 | return qsTr ("tailscale starting ...") 52 | else if ( connectState == 4) 53 | return qsTr ("this GX device is logged out of tailscale") 54 | else if ( connectState == 5) 55 | return qsTr ("waiting for a response from tailscale ...") 56 | else if ( connectState == 6) 57 | return ( qsTr ("connect this GX device to your tailscale account at:\n\n") + loginLink ) 58 | else if ( connectState == 200 ) 59 | return ( qsTr ("login timeout - check auth key")) 60 | else 61 | return ( qsTr ( "unknown state " ) + connectState ) 62 | } 63 | 64 | model: VisibleItemModel 65 | { 66 | MbSwitch 67 | { 68 | id: enable 69 | name: qsTr("Allow secure remote connections via tailscale") 70 | bind: Utils.path( settingsPrefix, "/Enabled") 71 | writeAccessLevel: User.AccessInstaller 72 | show: isRunning 73 | } 74 | MbSwitch 75 | { 76 | id: ipForwardEnable 77 | name: qsTr("IP forwarding") 78 | bind: Utils.path( settingsPrefix, "/CustomArguments") 79 | valueTrue: "--advertise-exit-node=true" 80 | valueFalse: "" 81 | writeAccessLevel: User.AccessInstaller 82 | show: isEnabled 83 | } 84 | MbItemText 85 | { 86 | text: getState () 87 | wrapMode: Text.WordWrap 88 | horizontalAlignment: Text.AlignHCenter 89 | } 90 | MbOK 91 | { 92 | id: logoutButton 93 | description: qsTr("Disconnect from tailscale account") 94 | value: qsTr ("Logout") 95 | onClicked: commandItem.setValue ('logout') 96 | 97 | writeAccessLevel: User.AccessInstaller 98 | show: isConnected 99 | } 100 | MbItemText 101 | { 102 | text: qsTr ("Tailscale authorization key:\n") + joinedKey + qsTr ("\nenter below in up to three parts") 103 | wrapMode: Text.WrapAnywhere 104 | } 105 | 106 | MbEditBox 107 | { 108 | id: keyPt1 109 | description: "auth key part 1" 110 | showAccessLevel: User.AccessInstaller 111 | maximumLength: 25 112 | item.value: authKey.substring (0, 25) 113 | onEditDone: authKeyItem.setValue (joinedKey) 114 | } 115 | MbEditBox 116 | { 117 | id: keyPt2 118 | description: "auth key part 2" 119 | showAccessLevel: User.AccessInstaller 120 | maximumLength: 25 121 | item.value: authKey.substring (25, 50) 122 | onEditDone: authKeyItem.setValue (joinedKey) 123 | } 124 | MbEditBox 125 | { 126 | id: keyPt3 127 | description: "auth key part 3" 128 | showAccessLevel: User.AccessInstaller 129 | maximumLength: 25 130 | item.value: authKey.substring (50) 131 | onEditDone: authKeyItem.setValue (joinedKey) 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /FileSets/fileListPatched: -------------------------------------------------------------------------------- 1 | /opt/victronenergy/gui/qml/PageSettingsGeneral.qml 2 | -------------------------------------------------------------------------------- /FileSets/fileListVersionIndependent: -------------------------------------------------------------------------------- 1 | /opt/victronenergy/gui/qml/PageSettingsTailscaleGX.qml 2 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # TailscaleGX 2 | 3 | This package is a user interface for tailscale on Victron Energy GX devices. 4 | 5 | tailscale provides is a VPN-like connection for virtually any device. 6 | 7 | Victron VRM provides access to the GX device's GUI, 8 | but not a command line interface to the GX devie. 9 | TailscaleGX provides an ssh connection and also http access to all the GUIs available on the GX device 10 | Any web browser or ssh tool (ssh, scp, rsync, etc.) can be used to communicate with the GX device. 11 | However a tailscale account is required and the tailscale app must be installed on the computer, 12 | tablet or smart phone connecting to the GX device. 13 | 14 | The GX device must also be logged in to the SAME tailscale account. 15 | 16 | tailscale clients are available for Windows, Mac OS, iOS, Linux and Android. 17 | 18 | TailscaleGX is on GitHub at https://github.com/kwindrem/TailscaleGX 19 | 20 | And more information is available at: 21 | 22 | https://tailscale.com 23 | 24 | TailscaleReadMe.md file is also included in this package. 25 | 26 | # NOTE 27 | 28 | tailscale is being added to Venus OS. 29 | When a stock tailscale is detected, TailscaleGX will not run to avoid conflics 30 | The firmware version that will include tailscale has not been determined so the normal 31 | obsolete version mechamism is not used at the moment but will be added when the version is known. 32 | 33 | # NOTE 34 | Support for firmware prior to v3.10 has been dropped starting with TailScaleGX v1.6 35 | 36 | If you are running older versions, change the branch/tag to preV3.10support 37 | for any packages you wish to run on that firmware 38 | 39 | 40 | # Using 41 | 42 | ssh access must be enabled in Settings / General, and a root password set 43 | or any ssh tool will not be able to access the GX device. 44 | To do this refer to: 45 | 46 | https://www.victronenergy.com/live/ccgx:root_access 47 | 48 | After installing TailscaleGX, 49 | navigate to __Settings / General / Remote access via tailscale__ 50 | 51 | and turn on __Allow remote connections__ 52 | 53 | After tailscale starts up you will be presented a message reading: 54 | 55 | >__connect this GX devices to your account at:__ 56 | 57 | >__https://login.tailscale.com/x/xxxxxxxxxxxxx__ 58 | 59 | On a computer, tablet or smart phone with the tailscale app installed, 60 | enter the URL exactly as it is shown on the screen. 61 | 62 | You will be asked login to your tailscale account. 63 | 64 | Press the __Connect__ button. 65 | 66 | On the GX devive, the message should change to: 67 | 68 | >__accepting remote connections at:__ 69 | 70 | >__xxx.xxx.xxx.xxx__ 71 | 72 | >__xxxx:xxxx:xxxx::xxxx:xxxx__ 73 | 74 | (IPv4 and IPv6 addresses) 75 | 76 | You can then connect to the GX device from any computer, etc logged in to your tailscale account. 77 | 78 | Any tool for ssh, scp, etc or any web browser should work, 79 | however you must have the tailscale app enabled and logged in to your account. 80 | 81 | You can disable tailscale by turning __Allow remote connections__ off. 82 | Turning it on again you will reconnect to tailscale without logging in again. 83 | The same IP addresses will be used until you logout the GX device. 84 | 85 | If you wish to disconnect the GX device from the existing tailscale account, 86 | press the __Logout__ button. You can then log into a different account. 87 | 88 | # IP Forwarding 89 | 90 | You may optionally share the tailnet connection with other devices on your local network. 91 | 92 | To do so, turn on IP forwarding in the Tailscale GX setup menu. 93 | 94 | Note that IP forwarding will impact CPU performance so use with caution. 95 | 96 | # Tailscale authorization key 97 | 98 | An alternate way to connect the GX device to your tailnet is to use an authorization key. 99 | 100 | This key is generated under settings in your tailscale admin console. 101 | It then must be entered into the GX device. 102 | 103 | The complete key is longer than supported by the GUI edit box 104 | so it is split into up to three separate pieces for entry. 105 | The complete code is shown above the three editable parts. 106 | Each part is limited to 25 characters. 107 | 108 | If you have console access to the GX device, it is far easier to use 109 | dbus-spy to enter the key into 110 | 111 | > com.victronenergy.settings /Settings/Services/Tailscale/AuthKey 112 | 113 | Or use the command line interface: 114 | 115 | > dbus -y com.victronenergy.settings /Settings/Services/Tailscale/AuthKey SetValue [key] 116 | 117 | # Installing 118 | 119 | TailscaleGX can be installed from Package manager. 120 | 121 | In __Inactive packages__ 122 | 123 | If TailscaleGX is already in the list, select it and tap __Proceed__ 124 | 125 | If not in the list, select __new__ and fill in the details: 126 | 127 | Packagename: TailscaleGX 128 | 129 | GitHub user: kwindrem 130 | 131 | GitHub branch or tag: latest 132 | 133 | then tap __Proceed__ 134 | 135 | # Security 136 | 137 | Only a computer, tablet or smart phone running the tailscale app 138 | AND logged into the same account used when connecting the GX device 139 | to tailscale can access the GX device. 140 | 141 | There is information on the tailscale web site that discusses the security issues. 142 | 143 | The GX device will not allow tailscale connections 144 | when __Allow remote connections__ is turned off. 145 | 146 | # TailscaleGX details 147 | 148 | The tailscale included in TailscaleGX is an "extra-small" build of v1.70. 149 | This build is about 25 MB compared to about 50 MB for the pre-built binairies. 150 | 151 | tailscale runs as a daemon (tailscaled). 152 | 153 | In Venus OS, tailscaled is run as a daemontools service: __TailscaleGX-backend__ 154 | 155 | In addition a command-line application (tailscale) controls tailscaled. 156 | 157 | The daemon only runs when __Allow remote connections__ is turned on. 158 | 159 | A second service __TailscaleGX-control__: 160 | 161 | - starts and stops TailscaleGX-backend 162 | - manages bringing up the GX to tailscale server link 163 | - collects login and connection status from tailscale 164 | - provides this status to the GUI 165 | - prompts the user for necessary steps to establish a connection 166 | -------------------------------------------------------------------------------- /TailScaleReadMe.md: -------------------------------------------------------------------------------- 1 | # Tailscale 2 | 3 | https://tailscale.com 4 | 5 | Private WireGuard® networks made easy 6 | 7 | ## Overview 8 | 9 | This repository contains the majority of Tailscale's open source code. 10 | Notably, it includes the `tailscaled` daemon and 11 | the `tailscale` CLI tool. The `tailscaled` daemon runs on Linux, Windows, 12 | [macOS](https://tailscale.com/kb/1065/macos-variants/), and to varying degrees 13 | on FreeBSD and OpenBSD. The Tailscale iOS and Android apps use this repo's 14 | code, but this repo doesn't contain the mobile GUI code. 15 | 16 | Other [Tailscale repos](https://github.com/orgs/tailscale/repositories) of note: 17 | 18 | * the Android app is at https://github.com/tailscale/tailscale-android 19 | * the Synology package is at https://github.com/tailscale/tailscale-synology 20 | * the QNAP package is at https://github.com/tailscale/tailscale-qpkg 21 | * the Chocolatey packaging is at https://github.com/tailscale/tailscale-chocolatey 22 | 23 | For background on which parts of Tailscale are open source and why, 24 | see [https://tailscale.com/opensource/](https://tailscale.com/opensource/). 25 | 26 | ## Using 27 | 28 | We serve packages for a variety of distros and platforms at 29 | [https://pkgs.tailscale.com](https://pkgs.tailscale.com/). 30 | 31 | ## Other clients 32 | 33 | The [macOS, iOS, and Windows clients](https://tailscale.com/download) 34 | use the code in this repository but additionally include small GUI 35 | wrappers. The GUI wrappers on non-open source platforms are themselves 36 | not open source. 37 | 38 | ## Building 39 | 40 | We always require the latest Go release, currently Go 1.22. (While we build 41 | releases with our [Go fork](https://github.com/tailscale/go/), its use is not 42 | required.) 43 | 44 | ``` 45 | go install tailscale.com/cmd/tailscale{,d} 46 | ``` 47 | 48 | If you're packaging Tailscale for distribution, use `build_dist.sh` 49 | instead, to burn commit IDs and version info into the binaries: 50 | 51 | ``` 52 | ./build_dist.sh tailscale.com/cmd/tailscale 53 | ./build_dist.sh tailscale.com/cmd/tailscaled 54 | ``` 55 | 56 | If your distro has conventions that preclude the use of 57 | `build_dist.sh`, please do the equivalent of what it does in your 58 | distro's way, so that bug reports contain useful version information. 59 | 60 | ## Bugs 61 | 62 | Please file any issues about this code or the hosted service on 63 | [the issue tracker](https://github.com/tailscale/tailscale/issues). 64 | 65 | ## Contributing 66 | 67 | PRs welcome! But please file bugs. Commit messages should [reference 68 | bugs](https://docs.github.com/en/github/writing-on-github/autolinked-references-and-urls). 69 | 70 | We require [Developer Certificate of 71 | Origin](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin) 72 | `Signed-off-by` lines in commits. 73 | 74 | See `git log` for our commit message style. It's basically the same as 75 | [Go's style](https://github.com/golang/go/wiki/CommitMessage). 76 | 77 | ## About Us 78 | 79 | [Tailscale](https://tailscale.com/) is primarily developed by the 80 | people at https://github.com/orgs/tailscale/people. For other contributors, 81 | see: 82 | 83 | * https://github.com/tailscale/tailscale/graphs/contributors 84 | * https://github.com/tailscale/tailscale-android/graphs/contributors 85 | 86 | ## Legal 87 | 88 | WireGuard is a registered trademark of Jason A. Donenfeld. 89 | -------------------------------------------------------------------------------- /TailscaleGX-control.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # TailscaleGX-control.py 4 | # Kevin Windrem 5 | # 6 | # This program controls remote access to a Victron Energy 7 | # It is based on tailscale which is based on WireGauard. 8 | # 9 | # This runs as a daemon tools service at /service/TailscaleGx-control 10 | # 11 | # ssh and html (others TBD) connections can be made via 12 | # the IP address(s) supplied by the tailscale broker. 13 | # 14 | # Persistent storage for TailscaleGX is stored in dbus Settings: 15 | # 16 | # com.victronenergy.Settings parameters: 17 | # /Settings/TailscaleGX/Enabled 18 | # controls wheter remote access is enabled or disabled 19 | # /Settings/TailscaleGX/IpForwarding 20 | # controls whether the GX device is set to forward IP traffic to other nodes 21 | # 22 | # Operational parameters are provided by: 23 | # com.victronenergy.tailscaleGX 24 | # /State 25 | # /IPv4 IP v4 remote access IP address 26 | # /IPv6 as above for IP v6 27 | # /HostName as above but as a host name 28 | # /LoginLink temorary URL for connecting to tailscale 29 | # for initiating a connection 30 | # /AuthKey tailscale authorization key (optional connection mechanism) 31 | # /GuiCommand GUI writes string here to request an action: 32 | # logout 33 | # 34 | # together, the above settings and dbus service provide the condiut to the GUI 35 | # 36 | # On startup the dbus settings and service are created 37 | # control then passes to mainLoop which gets scheduled once per second: 38 | # starts / stops the TailscaleGX-backend based on /Enabled 39 | # IP forwarding is also set during starting and stopping 40 | # scans status from tailscale link 41 | # scans status from tailscale lin 42 | # provides status and prompting to the GUI during this process 43 | # in the end providing the user the IP address they must use 44 | # to connect to the GX device. 45 | # 46 | # Note: tailscale will be integrated into stock firmware 47 | # when this happens, TailscaleGX will not run 48 | 49 | import platform 50 | import argparse 51 | import logging 52 | import sys 53 | import subprocess 54 | import threading 55 | import os 56 | import shutil 57 | import dbus 58 | import time 59 | import re 60 | from gi.repository import GLib 61 | # add the path to our own packages for import 62 | sys.path.insert(1, "/data/SetupHelper/velib_python") 63 | from vedbus import VeDbusService 64 | from settingsdevice import SettingsDevice 65 | 66 | 67 | # sends a unix command 68 | # eg sendCommand ( [ 'svc', '-u' , serviceName ] ) 69 | # 70 | # stdout, stderr and the exit code are returned as a list to the caller 71 | 72 | def sendCommand ( command=None, hostName=None, authKey=None ): 73 | if command == None: 74 | logging.error ( "sendCommand: no command specified" ) 75 | return None, None, None 76 | 77 | if hostName != None and hostName != "": 78 | command += [ "--hostname=" + hostName ] 79 | if authKey != None and authKey != "": 80 | command += [ "--auth-key=" + authKey ] 81 | 82 | try: 83 | proc = subprocess.Popen ( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 84 | except: 85 | logging.error ("sendCommand: " + command + " failed") 86 | return None, None, None 87 | else: 88 | out, err = proc.communicate () 89 | stdout = out.decode ().strip () 90 | stderr = err.decode ().strip () 91 | return stdout, stderr, proc.returncode 92 | 93 | 94 | tsControlCmd = '/data/TailscaleGX/tailscale' 95 | 96 | 97 | # static variables for main and mainLoop 98 | DbusSettings = None 99 | DbusService = None 100 | 101 | # state values 102 | UNKNOWN_STATE = 0 103 | BACKEND_STARTING = 1 104 | NOT_RUNNING = 2 105 | STOPPED = 3 106 | LOGGED_OUT = 4 107 | WAIT_FOR_RESPONSE = 5 108 | CONNECT_WAIT = 6 109 | CONNECTED = 100 110 | CHECK_AUTH_KEY = 200 111 | 112 | global previousState 113 | global state 114 | global systemNameObj 115 | global systemName 116 | global hostName 117 | global ipV4 118 | global lastIpForwardingEnabled 119 | 120 | previousState = UNKNOWN_STATE 121 | state = UNKNOWN_STATE 122 | systemNameObj = None 123 | systemName = None 124 | hostName = None 125 | ipV4 = "" 126 | lastIpForwardingEnabled = False 127 | authKey = "" 128 | lastResponseTime = 0 129 | checkAuthKey = False 130 | 131 | def mainLoop (): 132 | global DbusSettings 133 | global DbusService 134 | global previousState 135 | global state 136 | global systemName 137 | global hostName 138 | global ipV4 139 | global lastIpForwardingEnabled 140 | global authKey 141 | global lastResponseTime 142 | global checkAuthKey 143 | 144 | startTime = time.time () 145 | 146 | backendRunning = None 147 | tailscaleEnabled = False 148 | ipForwardingEnabled = False 149 | thisHostName = None 150 | 151 | loginInfo = "" 152 | 153 | if systemNameObj == None: 154 | systemName = None 155 | hostName = "" 156 | else: 157 | name = systemNameObj.GetValue () 158 | if name != systemName: 159 | systemName = name 160 | if name == None or name == "": 161 | hostName = "" 162 | logging.warning ("no system name so no host name" ) 163 | else: 164 | # some characters permitted for the GX system name aren't valid as a URL name 165 | # so replace them with '-' 166 | name = re.sub("[!@#$%^&*()\[\]{};:,./<>?\|`'~=_+ ]", "-", name) 167 | name = name.replace ('\\', '-') 168 | # host name must start with a letter or number 169 | name = name.strip(' -').lower () 170 | hostName = name 171 | logging.info ("system name changed to " + systemName) 172 | logging.info ("new host name " + hostName + " will be used on NEXT login" ) 173 | 174 | # see if backend is running 175 | stdout, stderr, exitCode = sendCommand ( [ 'svstat', "/service/TailscaleGX-backend" ] ) 176 | if stdout == None: 177 | logging.warning ("TailscaleGX-backend not in services") 178 | backendRunning = None 179 | elif stderr == None or "does not exist" in stderr: 180 | logging.warning ("TailscaleGX-backend not in services") 181 | backendRunning = None 182 | elif stdout != None and ": up" in stdout: 183 | backendRunning = True 184 | else: 185 | backendRunning = False 186 | 187 | tailscaleEnabled = DbusSettings ['enabled'] == 1 188 | if tailscaleEnabled and state == CONNECTED: 189 | ipForwardingEnabled = DbusSettings ['customArguements'] == "--advertise-exit-node=true" 190 | else: 191 | ipForwardingEnabled = "" 192 | 193 | # update IP forwarding and exit-node enable 194 | if ipForwardingEnabled != lastIpForwardingEnabled: 195 | lastIpForwardingEnabled = ipForwardingEnabled 196 | if ipForwardingEnabled: 197 | logging.info ("IP forwarding enabled") 198 | enabled = '1' 199 | enabled2 = "true" 200 | else: 201 | logging.info ("IP forwarding disabled") 202 | enabled = '0' 203 | enabled2 = "false" 204 | _, _, exitCode = sendCommand ( [ 'sysctl', '-w', "net.ipv4.ip_forward=" + enabled ] ) 205 | if exitCode != 0: 206 | logging.error ( "could not change IP v4 forwarding state to " + enabled + " " + str (exitCode) ) 207 | _, _, exitCode = sendCommand ( [ 'sysctl', '-w', "net.ipv6.conf.all.forwarding=" + enabled ] ) 208 | if exitCode != 0: 209 | logging.error ( "could not change IP v6 forwarding state to " + enabled + " " + str (exitCode) ) 210 | _, _, exitCode = sendCommand ( [ tsControlCmd, 'set', "--advertise-exit-node=" + enabled2 ] ) 211 | if exitCode != 0: 212 | logging.error ( "could not change tailscale exit-node setting to " + enabled2 + " " + str (exitCode) ) 213 | 214 | # start backend 215 | if tailscaleEnabled and backendRunning == False: 216 | logging.info ("starting TailscaleGX-backend") 217 | _, _, exitCode = sendCommand ( [ 'svc', '-u', "/service/TailscaleGX-backend"] ) 218 | if exitCode != 0: 219 | logging.error ( "start TailscaleGX failed " + str (exitCode) ) 220 | state = BACKEND_STARTING 221 | # stop backend 222 | elif not tailscaleEnabled and backendRunning == True: 223 | logging.info ("stopping TailscaleGX-backend") 224 | _, _, exitCode = sendCommand ( [ 'svc', '-d', "/service/TailscaleGX-backend"] ) 225 | if exitCode != 0: 226 | logging.error ( "stop TailscaleGX failed " + str (exitCode) ) 227 | backendRunning = False 228 | 229 | if backendRunning: 230 | resetConnection = False 231 | 232 | # check for GUI commands and act on them 233 | guiCommand = DbusService['/GuiCommand'] 234 | if guiCommand != "": 235 | # acknowledge receipt of command so another can be sent 236 | DbusService['/GuiCommand'] = "" 237 | if guiCommand == 'logout': 238 | logging.info ("logout command received") 239 | resetConnection = True 240 | lastResponseTime = startTime 241 | 242 | newAuthKey = DbusSettings ['authKey'] 243 | if newAuthKey == None: 244 | newAuthKey = "" 245 | if newAuthKey != authKey and newAuthKey != None and newAuthKey != "": 246 | logging.info ("new auth key detected") 247 | resetConnection = True 248 | checkAuthKey = False 249 | authKey = newAuthKey 250 | 251 | # get current status from tailscale and update state 252 | stdout, stderr, exitCode = sendCommand ( [ tsControlCmd, 'status' ] ) 253 | # don't update state if we don't get a response 254 | if stdout == None or stderr == None: 255 | logging.error ("no response to status command") 256 | checkAuthKey = False 257 | pass 258 | elif "failed to connect" in stderr: 259 | state = NOT_RUNNING 260 | checkAuthKey = False 261 | elif "Tailscale is stopped" in stdout: 262 | state = STOPPED 263 | elif "Log in at" in stdout and authKey == "": 264 | state = CONNECT_WAIT 265 | lines = stdout.splitlines () 266 | loginInfo = lines[1].replace ("Log in at: ", "") 267 | elif "Logged out" in stdout: 268 | # can get back to this condition while loggin in 269 | # so wait for another condition to update state 270 | if previousState != WAIT_FOR_RESPONSE: 271 | state = LOGGED_OUT 272 | elif exitCode == 0: 273 | state = CONNECTED 274 | checkAuthKey = False 275 | # extract this host's name from status message 276 | if ipV4 != "": 277 | for line in stdout.splitlines (): 278 | if ipV4 in line: 279 | thisHostName = line.split()[1] 280 | 281 | # don't update state if we don't recognize the response 282 | else: 283 | pass 284 | 285 | # response timeout indicates no internet connection to tailscale server 286 | # or possibly bad auth key 287 | if state == WAIT_FOR_RESPONSE and authKey != "": 288 | if lastResponseTime != 0 and startTime - lastResponseTime > 30: 289 | logging.error ("timeout waiting for response from tailscale - check auth key") 290 | resetConnection = True 291 | checkAuthKey = True 292 | else: 293 | lastResponseTime = startTime 294 | 295 | 296 | # make changes necessary to bring connection up 297 | # up will fully connect if login had succeeded 298 | # or ask for login if not 299 | # next get syatus pass will indicate that 300 | # call is made with a short timeout so we can monitor status 301 | # but need to defer future tailscale commands until 302 | # tailscale has processed the first one 303 | # ALMOST any state change will signal the wait is over 304 | # (status not included) 305 | 306 | # resetConnection logs out of tailscale 307 | # so that a new connection can be made 308 | # this will occur automatically if an auth key is set 309 | # otherwise, a message to manually connect via tailscale admin console is displayed 310 | if resetConnection: 311 | if authKey == "": 312 | logging.info ( "resetting connetion for manual connection" ) 313 | else: 314 | logging.info ( "resetting connetion for new auth key: " + authKey) 315 | # logout takes time and can't specify a timeout so provide feedback first 316 | DbusService['/State'] = WAIT_FOR_RESPONSE 317 | state = WAIT_FOR_RESPONSE 318 | _, stderr, exitCode = sendCommand ( [ tsControlCmd, 'logout' ] ) 319 | if exitCode != 0: 320 | logging.error ( "tailscale logout failed " + str (exitCode) ) 321 | logging.error (stderr) 322 | else: 323 | state = LOGGED_OUT 324 | elif state == STOPPED: 325 | logging.info ("starting tailscale " + hostName + " " + authKey) 326 | _, stderr, exitCode = sendCommand ( [ tsControlCmd, 'up', 327 | '--timeout=0.1s' ], hostName=hostName, authKey=authKey ) 328 | if exitCode != 0 and not "timeout" in stderr: 329 | logging.error ( "tailscale up failed " + str (exitCode) ) 330 | logging.error (stderr) 331 | else: 332 | state = WAIT_FOR_RESPONSE 333 | elif state == LOGGED_OUT: 334 | logging.info ("logging in to tailscale " + hostName + " " + authKey) 335 | _, stderr, exitCode = sendCommand ( [ tsControlCmd, 'login', 336 | '--timeout=0.1s' ], hostName=hostName, authKey=authKey ) 337 | if exitCode != 0 and not "timeout" in stderr: 338 | logging.error ( "tailscale login failed " + str (exitCode) ) 339 | logging.error (stderr) 340 | else: 341 | state = WAIT_FOR_RESPONSE 342 | 343 | # show IP addresses only if connected 344 | if state == CONNECTED: 345 | if previousState != CONNECTED: 346 | logging.info ("connection successful") 347 | stdout, stderr, exitCode = sendCommand ( [ tsControlCmd, 'ip' ] ) 348 | if exitCode != 0: 349 | logging.error ( "tailscale ip failed " + str (exitCode) ) 350 | logging.error (stderr) 351 | if stdout != None and stdout != "": 352 | ipV4, ipV6 = stdout.splitlines () 353 | DbusService['/IPv4'] = ipV4 354 | DbusService['/IPv6'] = ipV6 355 | else: 356 | DbusService['/IPv4'] = "?" 357 | DbusService['/IPv6'] = "?" 358 | DbusService['/HostName'] = thisHostName 359 | else: 360 | DbusService['/IPv4'] = "" 361 | DbusService['/IPv6'] = "" 362 | DbusService['/HostName'] = "" 363 | else: 364 | state = NOT_RUNNING 365 | checkAuthKey = False 366 | 367 | # update dbus values regardless of state of the link 368 | if checkAuthKey: 369 | DbusService['/State'] = CHECK_AUTH_KEY 370 | else: 371 | DbusService['/State'] = state 372 | DbusService['/LoginLink'] = loginInfo 373 | 374 | previousState = state 375 | #### TODO: enable for testing 376 | endTime = time.time () 377 | ####print ("main loop time %3.1f mS" % ( (endTime - startTime) * 1000 )) 378 | return True 379 | 380 | def main(): 381 | global DbusSettings 382 | global DbusService 383 | global systemNameObj 384 | 385 | # fetch installed version 386 | installedVersionFile = "/etc/venus/installedVersion-TailscaleGX" 387 | try: 388 | versionFile = open (installedVersionFile, 'r') 389 | except: 390 | installedVersion = "(version unknown)" 391 | else: 392 | installedVersion = versionFile.readline().strip() 393 | versionFile.close() 394 | # if file is empty, an unknown version is installed 395 | if installedVersion == "": 396 | installedVersion = "(version unknown)" 397 | 398 | # set logging level to include info level entries 399 | logging.basicConfig( format='%(levelname)s:%(message)s', level=logging.INFO ) 400 | 401 | logging.info (">>>> TailscaleGX-control" + installedVersion + " starting") 402 | 403 | # Have a mainloop, so we can send/receive asynchronous calls to and from dbus 404 | from dbus.mainloop.glib import DBusGMainLoop 405 | DBusGMainLoop(set_as_default=True) 406 | 407 | theBus = dbus.SystemBus() 408 | dbusSettingsPath = "com.victronenergy.settings" 409 | 410 | settingsList = { 'enabled': [ '/Settings/Services/Tailscale/Enabled', 0, 0, 1 ], 411 | 'customArguements': [ '/Settings/Services/Tailscale/CustomArguments', "", 0, 0 ], 412 | 'authKey' : [ '/Settings/Services/Tailscale/AuthKey', "", 0, 0 ] 413 | } 414 | DbusSettings = SettingsDevice(bus=theBus, supportedSettings=settingsList, 415 | timeout = 30, eventCallback=None ) 416 | 417 | # migrate settings and tailscale state directory 418 | removeSettings = False 419 | try: 420 | oldSettingObj = theBus.get_object (dbusSettingsPath, "/Settings/TailscaleGX/Enabled") 421 | oldSetting=oldSettingObj.GetValue() 422 | logging.warning ( "moving enabled setting to new location and removing old loction" ) 423 | DbusSettings['enabled'] = oldSetting 424 | removeSettings = True 425 | except: 426 | pass 427 | try: 428 | oldSettingObj = theBus.get_object (dbusSettingsPath, "/Settings/TailscaleGX/IpForwarding") 429 | oldSetting=oldSettingObj.GetValue() 430 | logging.warning ( "moving IP forwarding setting to new location and removing old loction" ) 431 | if oldSetting: 432 | DbusSettings['customArguements'] = "--advertise-exit-node=true" 433 | else: 434 | DbusSettings['customArguements'] = "" 435 | removeSettings = True 436 | except: 437 | pass 438 | 439 | # remove old settings 440 | if removeSettings: 441 | settingsToRemove = '%[ "' + "/Settings/TailscaleGX/Enabled" + '" , "' + "/Settings/TailscaleGX/IpForwarding" + '" ]' 442 | sendCommand ( ['dbus', '-y', 'com.victronenergy.settings', '/', 'RemoveSettings', settingsToRemove ] ) 443 | 444 | stockTailscaleStateDir = "/data/conf/tailscale" 445 | oldStateDir = "/data/setupOptions/TailscaleGX/state" 446 | if os.path.exists ( oldStateDir ) and not os.path.exists ( stockTailscaleStateDir ): 447 | logging.warning ( "moving tailscale state to new location" ) 448 | shutil.move ( oldStateDir, stockTailscaleStateDir ) 449 | 450 | if os.path.exists ("/opt/victronenergy/tailscale"): 451 | logging.warning ("tailscale is now part of stock firmware - TailscaleGX-control no longer used - exiting") 452 | sendCommand ( [ 'svc', '-d' , "/service/TailscaleGX-control" ] ) 453 | exit () 454 | 455 | DbusService = VeDbusService ('com.victronenergy.tailscaleGX', bus = dbus.SystemBus(), register=False) 456 | DbusService.add_mandatory_paths ( 457 | processname = 'TailscaleGX-control', processversion = 1.0, connection = 'none', 458 | deviceinstance = 0, productid = 1, 459 | productname = 'TailscaleGX-control', 460 | firmwareversion = 1, hardwareversion = 0, connected = 1) 461 | 462 | DbusService.add_path ( '/State', "" ) 463 | DbusService.add_path ( '/IPv4', "" ) 464 | DbusService.add_path ( '/IPv6', "" ) 465 | DbusService.add_path ( '/HostName', "" ) 466 | DbusService.add_path ( '/LoginLink', "" ) 467 | 468 | DbusService.add_path ( '/GuiCommand', "", writeable = True ) 469 | 470 | DbusService.register () 471 | 472 | 473 | systemNameObj = theBus.get_object (dbusSettingsPath, "/Settings/SystemSetup/SystemName") 474 | 475 | # call the main loop - every 1 second 476 | # this section of code loops until mainloop quits 477 | GLib.timeout_add(1000, mainLoop) 478 | mainloop = GLib.MainLoop() 479 | mainloop.run() 480 | 481 | logging.critical ("TailscaleGX-control exiting") 482 | 483 | main() 484 | -------------------------------------------------------------------------------- /buildTailscaleForArm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # script to build tailscale.combined extra-small executable 4 | # for Linux/ARM suitable for Venus OS 5 | # 6 | # result is placed in the current directory 7 | 8 | GOOS=linux 9 | export GOOS 10 | GOARCH=arm 11 | export GOARCH 12 | 13 | targetDir="/users/Kevin/GitHub/TailscaleGX" 14 | sourceDir="/users/Kevin/GitHub/tailscale" 15 | 16 | targetFile=tailscale.combined 17 | targetPath="$targetDir/$targetFile" 18 | 19 | rm -f $targetFile 20 | 21 | cd $sourceDir 22 | ./build_dist.sh --extra-small -o $targetPath -tags ts_include_cli ./cmd/tailscaled 23 | 24 | cd $targetDir 25 | 26 | # create links so executable will run as the control or daemon 27 | ln -sf ./tailscale.combined tailscale 28 | ln -sf ./tailscale.combined tailscaled 29 | 30 | -------------------------------------------------------------------------------- /changes: -------------------------------------------------------------------------------- 1 | v2.0: 2 | add tailscale auth key option 3 | 4 | v1.12: 5 | v1.10 failed to update properly on GitHub 6 | v1.11: 7 | update to tailscale v1.82 8 | 9 | v1.9/1.10: 10 | switch to stock tailscale settings and state values 11 | eventually tailscale will be included in Venus OS 12 | and TailscaleGX will be obsolete and will not run to avoid conflicts 13 | 14 | v1.7/1.8: 15 | add IP forwarding when tailscale is enabled 16 | update to tailscale v1.70 17 | 18 | v1.6: 19 | dropping support for firmware earlier than v3.10 20 | moved velib_python in SetupHelper to a single version 21 | 22 | v1.5: 23 | update to tailscale v1.68 24 | use velib_python to the one in SetupHelper (if it exists) 25 | 26 | v1.4: 27 | update tailscale to v1.66 28 | remove local copy of HelperResources 29 | 30 | v1.2/v1.3: 31 | update to tailscale 1.64.2 32 | 33 | 34 | v1.1: 35 | fixed: some GX names don't translate to valid URL names 36 | which results in a host name of 'custom' in admin console 37 | added host name to accepting connections at ... 38 | 39 | v1.0: 40 | initial release 41 | -------------------------------------------------------------------------------- /gitHubInfo: -------------------------------------------------------------------------------- 1 | kwindrem:latest 2 | -------------------------------------------------------------------------------- /services/TailscaleGX-backend/down: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwindrem/TailscaleGX/f7b546f572e0dfeee7894a7a17216b3c4fc61bcf/services/TailscaleGX-backend/down -------------------------------------------------------------------------------- /services/TailscaleGX-backend/log/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec multilog t s25000 n4 /var/log/TailscaleGX-backend 3 | 4 | -------------------------------------------------------------------------------- /services/TailscaleGX-backend/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # this service runs tailscaled which provides actual tailscale connection 4 | # 5 | # TailscaleGX-control.py starts and stops this service 6 | 7 | exec 2>&1 8 | 9 | exec /data/TailscaleGX/tailscaled -no-logs-no-support -statedir /data/conf/tailscale 10 | 11 | -------------------------------------------------------------------------------- /services/TailscaleGX-control/log/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec multilog t s25000 n4 /var/log/TailscaleGX-control 3 | 4 | -------------------------------------------------------------------------------- /services/TailscaleGX-control/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # this service runs TailscaleGX-control.py 4 | # which provides the user interface for controlling tailscale 5 | 6 | exec 2>&1 7 | 8 | exec /data/TailscaleGX/TailscaleGX-control.py 9 | -------------------------------------------------------------------------------- /setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # setup for Tailscale GX 4 | # 5 | # provides secure access to a GX device from any remote location 6 | # via tailscale and WireGard 7 | 8 | # Note: Venus OS is being updated to include tailscale 9 | # when this happens, code below will prevent TailscaleGX from installing 10 | # or will trigger an uninstall if already installed 11 | 12 | #### following line incorporates helper resources into this script 13 | source "/data/SetupHelper/HelperResources/IncludeHelpers" 14 | #### end of lines to include helper resources 15 | 16 | if [ $scriptAction == 'NONE' ] ; then 17 | standardActionPrompt 18 | fi 19 | 20 | if [ -e "/opt/victronenergy/tailscale" ]; then 21 | if [ "$scriptAction" != "UNINSTALL" ]; then 22 | setInstallFailed $EXIT_PACKAGE_CONFLICT "tailscale is now part of stock firmware - TailscaleGX can not be installed" 23 | fi 24 | fi 25 | 26 | endScript 'INSTALL_FILES' 'INSTALL_SERVICES' 27 | -------------------------------------------------------------------------------- /tailscale: -------------------------------------------------------------------------------- 1 | ./tailscale.combined -------------------------------------------------------------------------------- /tailscale.combined: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwindrem/TailscaleGX/f7b546f572e0dfeee7894a7a17216b3c4fc61bcf/tailscale.combined -------------------------------------------------------------------------------- /tailscaled: -------------------------------------------------------------------------------- 1 | ./tailscale.combined -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | v2.0 2 | --------------------------------------------------------------------------------