From f2981c5b15b310f68636f6319508f7fce88f190d Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Fri, 22 Sep 2023 10:36:30 -0600 Subject: [PATCH] fix #168 --- lib/data/model/app/shell_func.dart | 80 +++++++++---------- .../model/server/server_private_info.dart | 2 +- lib/data/provider/server.dart | 60 +++++++++++--- 3 files changed, 87 insertions(+), 55 deletions(-) diff --git a/lib/data/model/app/shell_func.dart b/lib/data/model/app/shell_func.dart index d5f76f8e..162885b9 100644 --- a/lib/data/model/app/shell_func.dart +++ b/lib/data/model/app/shell_func.dart @@ -10,9 +10,9 @@ const _serverBoxDir = r'$HOME/.config/server_box'; /// Issue #159 /// Use script commit count as version of shell script. /// So different version of app can run at the same time. -const _shellPath = '$_serverBoxDir/mobile_v${BuildData.script}.sh'; +const installShellPath = '$_serverBoxDir/mobile_v${BuildData.script}.sh'; -enum AppShellFuncType { +enum ShellFunc { status, docker, process, @@ -22,48 +22,48 @@ enum AppShellFuncType { String get flag { switch (this) { - case AppShellFuncType.status: + case ShellFunc.status: return 's'; - case AppShellFuncType.docker: + case ShellFunc.docker: return 'd'; - case AppShellFuncType.process: + case ShellFunc.process: return 'p'; - case AppShellFuncType.shutdown: + case ShellFunc.shutdown: return 'sd'; - case AppShellFuncType.reboot: + case ShellFunc.reboot: return 'r'; } } - String get exec => 'sh $_shellPath -$flag'; + String get exec => 'sh $installShellPath -$flag'; String get name { switch (this) { - case AppShellFuncType.status: + case ShellFunc.status: return 'status'; - case AppShellFuncType.docker: + case ShellFunc.docker: // `dockeR` -> avoid conflict with `docker` command // 以防止循环递归 return 'dockeR'; - case AppShellFuncType.process: + case ShellFunc.process: return 'process'; - case AppShellFuncType.shutdown: + case ShellFunc.shutdown: return 'ShutDown'; - case AppShellFuncType.reboot: + case ShellFunc.reboot: return 'Reboot'; } } String get cmd { switch (this) { - case AppShellFuncType.status: + case ShellFunc.status: return ''' if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then \t${_statusCmds.join(_cmdDivider)} else \t${_bsdStatusCmd.join(_cmdDivider)} fi'''; - case AppShellFuncType.docker: + case ShellFunc.docker: return ''' result=\$(docker version 2>&1 | grep "permission denied") if [ "\$result" != "" ]; then @@ -71,7 +71,7 @@ if [ "\$result" != "" ]; then else \t${_dockerCmds.map((e) => "sudo -S $e").join(_cmdDivider)} fi'''; - case AppShellFuncType.process: + case ShellFunc.process: return ''' if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then \tif [ "\$isBusybox" != "" ]; then @@ -83,14 +83,14 @@ else \tps -ax fi '''; - case AppShellFuncType.shutdown: + case ShellFunc.shutdown: return ''' if [ "\$userId" = "0" ]; then \tshutdown -h now else \tsudo -S shutdown -h now fi'''; - case AppShellFuncType.reboot: + case ShellFunc.reboot: return ''' if [ "\$userId" = "0" ]; then \treboot @@ -100,8 +100,25 @@ fi'''; } } - static final String shellScript = () { + static final String allScript = () { final sb = StringBuffer(); + sb.write(''' +#!/bin/sh +# Script for ServerBox app v1.0.${BuildData.build} +# DO NOT delete this file while app is running + +export LANG=en_US.UTF-8 + +# If macSign & bsdSign are both empty, then it's linux +macSign=\$(uname 2>&1 | grep "Darwin") +bsdSign=\$(uname 2>&1 | grep "BSD") + +# Link /bin/sh to busybox? +isBusybox=\$(ls -l /bin/sh | grep "busybox") + +userId=\$(id -u) + +'''); // Write each func for (final func in values) { sb.write(''' @@ -212,31 +229,12 @@ const _bsdStatusCmd = [ 'hostname', ]; -final _shellCmd = """ -#!/bin/sh -# Script for ServerBox app v1.0.${BuildData.build} -# DO NOT delete this file while app is running - -export LANG=en_US.UTF-8 - -# If macSign & bsdSign are both empty, then it's linux -macSign=\$(uname 2>&1 | grep "Darwin") -bsdSign=\$(uname 2>&1 | grep "BSD") - -# Link /bin/sh to busybox? -isBusybox=\$(ls -l /bin/sh | grep "busybox") - -userId=\$(id -u) - -${AppShellFuncType.shellScript} -"""; - /// Issue #168 /// Use `sh` for compatibility final installShellCmd = """ mkdir -p $_serverBoxDir -sh -c cat << 'EOF' > $_shellPath -$_shellCmd +cat << 'EOF' > $installShellPath +${ShellFunc.allScript} EOF -chmod +x $_shellPath +chmod +x $installShellPath """; diff --git a/lib/data/model/server/server_private_info.dart b/lib/data/model/server/server_private_info.dart index ecc093ca..59ed2cf1 100644 --- a/lib/data/model/server/server_private_info.dart +++ b/lib/data/model/server/server_private_info.dart @@ -70,7 +70,7 @@ class ServerPrivateInfo { return data; } - Server? get findServer => Providers.server.servers[id]; + Server? get server => Providers.server.pick(spi: this); bool shouldReconnect(ServerPrivateInfo old) { return id != old.id || diff --git a/lib/data/provider/server.dart b/lib/data/provider/server.dart index 4ff26e6b..a092e5d9 100644 --- a/lib/data/provider/server.dart +++ b/lib/data/provider/server.dart @@ -1,9 +1,14 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/foundation.dart'; +import 'package:toolbox/core/utils/platform/path.dart'; import 'package:toolbox/data/model/app/shell_func.dart'; import 'package:toolbox/data/model/server/system.dart'; +import 'package:toolbox/data/model/sftp/req.dart'; import 'package:toolbox/data/res/logger.dart'; +import 'package:toolbox/data/res/path.dart'; +import 'package:toolbox/data/res/provider.dart'; import 'package:toolbox/data/res/store.dart'; import '../../core/extension/order.dart'; @@ -20,7 +25,7 @@ typedef ServersMap = Map; class ServerProvider extends ChangeNotifier { final ServersMap _servers = {}; - ServersMap get servers => _servers; + Iterable get servers => _servers.values; final Order _serverOrder = []; Order get serverOrder => _serverOrder; final List _tags = []; @@ -58,6 +63,19 @@ class ServerProvider extends ChangeNotifier { notifyListeners(); } + /// Get a [Server] by [spi] or [id]. + /// + /// Priority: [spi] > [id] + Server? pick({ServerPrivateInfo? spi, String? id}) { + if (spi != null) { + return _servers[spi.id]; + } + if (id != null) { + return _servers[id]; + } + return null; + } + void _updateTags() { _tags.clear(); for (final s in _servers.values) { @@ -264,27 +282,43 @@ class ServerProvider extends ChangeNotifier { try { final writeResult = await s.client?.run(installShellCmd).string; if (writeResult == null || writeResult.isNotEmpty) { - _limiter.inc(sid); - s.status.failedInfo = writeResult; - _setServerState(s, ServerState.failed); - return; + throw Exception('$writeResult'); } } catch (e) { - _limiter.inc(sid); - s.status.failedInfo = e.toString(); - _setServerState(s, ServerState.failed); - Loggers.app.warning('Write script to ${spi.name} failed', e); - return; + var sftpFailed = false; + try { + Loggers.app.warning('Using SFTP to write script to ${spi.name}'); + final localPath = joinPath(await Paths.doc, 'install.sh'); + final file = File(localPath); + file.writeAsString(ShellFunc.allScript); + final sftp = Providers.sftp; + final completer = Completer(); + sftp.add( + SftpReq(spi, installShellPath, localPath, SftpReqType.upload), + completer: completer, + ); + await completer.future; + await file.delete(); + } catch (_) { + sftpFailed = true; + } + if (sftpFailed) { + _limiter.inc(sid); + s.status.failedInfo = e.toString(); + _setServerState(s, ServerState.failed); + Loggers.app.warning('Write script to ${spi.name} failed', e); + return; + } } } - if (s.client == null) return; - + /// Keep [finished] state, or the UI will be refreshed to [loading] state + /// instead of the 'Temp & Uptime'. if (s.state != ServerState.finished) { _setServerState(s, ServerState.loading); } - final raw = await s.client?.run(AppShellFuncType.status.exec).string; + final raw = await s.client?.run(ShellFunc.status.exec).string; final segments = raw?.split(seperator).map((e) => e.trim()).toList(); if (raw == null || raw.isEmpty || segments == null || segments.isEmpty) { _limiter.inc(sid);