This commit is contained in:
lollipopkit
2023-09-22 10:36:30 -06:00
parent 09576285c9
commit f2981c5b15
3 changed files with 87 additions and 55 deletions

View File

@@ -10,9 +10,9 @@ const _serverBoxDir = r'$HOME/.config/server_box';
/// Issue #159 /// Issue #159
/// Use script commit count as version of shell script. /// Use script commit count as version of shell script.
/// So different version of app can run at the same time. /// 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, status,
docker, docker,
process, process,
@@ -22,48 +22,48 @@ enum AppShellFuncType {
String get flag { String get flag {
switch (this) { switch (this) {
case AppShellFuncType.status: case ShellFunc.status:
return 's'; return 's';
case AppShellFuncType.docker: case ShellFunc.docker:
return 'd'; return 'd';
case AppShellFuncType.process: case ShellFunc.process:
return 'p'; return 'p';
case AppShellFuncType.shutdown: case ShellFunc.shutdown:
return 'sd'; return 'sd';
case AppShellFuncType.reboot: case ShellFunc.reboot:
return 'r'; return 'r';
} }
} }
String get exec => 'sh $_shellPath -$flag'; String get exec => 'sh $installShellPath -$flag';
String get name { String get name {
switch (this) { switch (this) {
case AppShellFuncType.status: case ShellFunc.status:
return 'status'; return 'status';
case AppShellFuncType.docker: case ShellFunc.docker:
// `dockeR` -> avoid conflict with `docker` command // `dockeR` -> avoid conflict with `docker` command
// 以防止循环递归 // 以防止循环递归
return 'dockeR'; return 'dockeR';
case AppShellFuncType.process: case ShellFunc.process:
return 'process'; return 'process';
case AppShellFuncType.shutdown: case ShellFunc.shutdown:
return 'ShutDown'; return 'ShutDown';
case AppShellFuncType.reboot: case ShellFunc.reboot:
return 'Reboot'; return 'Reboot';
} }
} }
String get cmd { String get cmd {
switch (this) { switch (this) {
case AppShellFuncType.status: case ShellFunc.status:
return ''' return '''
if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then
\t${_statusCmds.join(_cmdDivider)} \t${_statusCmds.join(_cmdDivider)}
else else
\t${_bsdStatusCmd.join(_cmdDivider)} \t${_bsdStatusCmd.join(_cmdDivider)}
fi'''; fi''';
case AppShellFuncType.docker: case ShellFunc.docker:
return ''' return '''
result=\$(docker version 2>&1 | grep "permission denied") result=\$(docker version 2>&1 | grep "permission denied")
if [ "\$result" != "" ]; then if [ "\$result" != "" ]; then
@@ -71,7 +71,7 @@ if [ "\$result" != "" ]; then
else else
\t${_dockerCmds.map((e) => "sudo -S $e").join(_cmdDivider)} \t${_dockerCmds.map((e) => "sudo -S $e").join(_cmdDivider)}
fi'''; fi''';
case AppShellFuncType.process: case ShellFunc.process:
return ''' return '''
if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then
\tif [ "\$isBusybox" != "" ]; then \tif [ "\$isBusybox" != "" ]; then
@@ -83,14 +83,14 @@ else
\tps -ax \tps -ax
fi fi
'''; ''';
case AppShellFuncType.shutdown: case ShellFunc.shutdown:
return ''' return '''
if [ "\$userId" = "0" ]; then if [ "\$userId" = "0" ]; then
\tshutdown -h now \tshutdown -h now
else else
\tsudo -S shutdown -h now \tsudo -S shutdown -h now
fi'''; fi''';
case AppShellFuncType.reboot: case ShellFunc.reboot:
return ''' return '''
if [ "\$userId" = "0" ]; then if [ "\$userId" = "0" ]; then
\treboot \treboot
@@ -100,8 +100,25 @@ fi''';
} }
} }
static final String shellScript = () { static final String allScript = () {
final sb = StringBuffer(); 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 // Write each func
for (final func in values) { for (final func in values) {
sb.write(''' sb.write('''
@@ -212,31 +229,12 @@ const _bsdStatusCmd = [
'hostname', '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 /// Issue #168
/// Use `sh` for compatibility /// Use `sh` for compatibility
final installShellCmd = """ final installShellCmd = """
mkdir -p $_serverBoxDir mkdir -p $_serverBoxDir
sh -c cat << 'EOF' > $_shellPath cat << 'EOF' > $installShellPath
$_shellCmd ${ShellFunc.allScript}
EOF EOF
chmod +x $_shellPath chmod +x $installShellPath
"""; """;

View File

@@ -70,7 +70,7 @@ class ServerPrivateInfo {
return data; return data;
} }
Server? get findServer => Providers.server.servers[id]; Server? get server => Providers.server.pick(spi: this);
bool shouldReconnect(ServerPrivateInfo old) { bool shouldReconnect(ServerPrivateInfo old) {
return id != old.id || return id != old.id ||

View File

@@ -1,9 +1,14 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart'; 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/app/shell_func.dart';
import 'package:toolbox/data/model/server/system.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/logger.dart';
import 'package:toolbox/data/res/path.dart';
import 'package:toolbox/data/res/provider.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:toolbox/data/res/store.dart';
import '../../core/extension/order.dart'; import '../../core/extension/order.dart';
@@ -20,7 +25,7 @@ typedef ServersMap = Map<String, Server>;
class ServerProvider extends ChangeNotifier { class ServerProvider extends ChangeNotifier {
final ServersMap _servers = {}; final ServersMap _servers = {};
ServersMap get servers => _servers; Iterable<Server> get servers => _servers.values;
final Order<String> _serverOrder = []; final Order<String> _serverOrder = [];
Order<String> get serverOrder => _serverOrder; Order<String> get serverOrder => _serverOrder;
final List<String> _tags = []; final List<String> _tags = [];
@@ -58,6 +63,19 @@ class ServerProvider extends ChangeNotifier {
notifyListeners(); 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() { void _updateTags() {
_tags.clear(); _tags.clear();
for (final s in _servers.values) { for (final s in _servers.values) {
@@ -264,27 +282,43 @@ class ServerProvider extends ChangeNotifier {
try { try {
final writeResult = await s.client?.run(installShellCmd).string; final writeResult = await s.client?.run(installShellCmd).string;
if (writeResult == null || writeResult.isNotEmpty) { if (writeResult == null || writeResult.isNotEmpty) {
_limiter.inc(sid); throw Exception('$writeResult');
s.status.failedInfo = writeResult;
_setServerState(s, ServerState.failed);
return;
} }
} catch (e) { } catch (e) {
_limiter.inc(sid); var sftpFailed = false;
s.status.failedInfo = e.toString(); try {
_setServerState(s, ServerState.failed); Loggers.app.warning('Using SFTP to write script to ${spi.name}');
Loggers.app.warning('Write script to ${spi.name} failed', e); final localPath = joinPath(await Paths.doc, 'install.sh');
return; 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) { if (s.state != ServerState.finished) {
_setServerState(s, ServerState.loading); _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(); final segments = raw?.split(seperator).map((e) => e.trim()).toList();
if (raw == null || raw.isEmpty || segments == null || segments.isEmpty) { if (raw == null || raw.isEmpty || segments == null || segments.isEmpty) {
_limiter.inc(sid); _limiter.inc(sid);