mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-18 15:54:35 +01:00
Merge pull request #282 from PaperCube/main
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:dartssh2/dartssh2.dart';
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:toolbox/core/extension/context/dialog.dart';
|
import 'package:toolbox/core/extension/context/dialog.dart';
|
||||||
import 'package:toolbox/core/extension/stringx.dart';
|
import 'package:toolbox/core/extension/stringx.dart';
|
||||||
@@ -85,4 +85,42 @@ extension SSHClientX on SSHClient {
|
|||||||
stdin: stdin,
|
stdin: stdin,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Uint8List> runWithSessionAction(
|
||||||
|
String command, {
|
||||||
|
bool runInPty = false,
|
||||||
|
bool stdout = true,
|
||||||
|
bool stderr = true,
|
||||||
|
Map<String, String>? environment,
|
||||||
|
Future<void> Function(SSHSession)? action,
|
||||||
|
}) async {
|
||||||
|
final session = await execute(
|
||||||
|
command,
|
||||||
|
pty: runInPty ? const SSHPtyConfig() : null,
|
||||||
|
environment: environment,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = BytesBuilder(copy: false);
|
||||||
|
final stdoutDone = Completer<void>();
|
||||||
|
final stderrDone = Completer<void>();
|
||||||
|
|
||||||
|
session.stdout.listen(
|
||||||
|
stdout ? result.add : (_) {},
|
||||||
|
onDone: stdoutDone.complete,
|
||||||
|
onError: stderrDone.completeError,
|
||||||
|
);
|
||||||
|
|
||||||
|
session.stderr.listen(
|
||||||
|
stderr ? result.add : (_) {},
|
||||||
|
onDone: stderrDone.complete,
|
||||||
|
onError: stderrDone.completeError,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (action != null) await action(session);
|
||||||
|
|
||||||
|
await stdoutDone.future;
|
||||||
|
await stderrDone.future;
|
||||||
|
|
||||||
|
return result.takeBytes();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,13 +33,17 @@ enum ShellFunc {
|
|||||||
|
|
||||||
/// Issue #168
|
/// Issue #168
|
||||||
/// Use `sh` for compatibility
|
/// Use `sh` for compatibility
|
||||||
static final installShellCmd = """
|
// static final installShellCmd = """
|
||||||
mkdir -p $_homeVar/$_srvBoxDir
|
// mkdir -p $_homeVar/$_srvBoxDir
|
||||||
cat << 'EOF' > $_installShellPath
|
// cat << 'EOF' > $_installShellPath
|
||||||
${ShellFunc.allScript}
|
// ${ShellFunc.allScript}
|
||||||
EOF
|
// EOF
|
||||||
chmod +x $_installShellPath
|
// chmod +x $_installShellPath
|
||||||
""";
|
// """;
|
||||||
|
|
||||||
|
static const installerMkdirs = "mkdir -p $_homeVar/$_srvBoxDir";
|
||||||
|
static const installerShellWriter = "cat > $_installShellPath";
|
||||||
|
static const installerPermissionModifier = "chmod +x $_installShellPath";
|
||||||
|
|
||||||
String get flag {
|
String get flag {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ import 'package:toolbox/data/model/container/type.dart';
|
|||||||
import 'package:toolbox/data/model/container/version.dart';
|
import 'package:toolbox/data/model/container/version.dart';
|
||||||
import 'package:toolbox/data/res/logger.dart';
|
import 'package:toolbox/data/res/logger.dart';
|
||||||
import 'package:toolbox/data/res/store.dart';
|
import 'package:toolbox/data/res/store.dart';
|
||||||
|
import 'package:toolbox/core/extension/uint8list.dart';
|
||||||
|
|
||||||
final _dockerNotFound = RegExp(r'command not found|Unknown command');
|
final _dockerNotFound =
|
||||||
|
RegExp(r"command not found|Unknown command|Command '\w+' not found");
|
||||||
|
|
||||||
class ContainerProvider extends ChangeNotifier {
|
class ContainerProvider extends ChangeNotifier {
|
||||||
SSHClient? client;
|
SSHClient? client;
|
||||||
@@ -43,15 +45,55 @@ class ContainerProvider extends ChangeNotifier {
|
|||||||
await refresh();
|
await refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> _checkDockerInstalled(SSHClient client) async {
|
||||||
|
final session = await client.execute("docker");
|
||||||
|
await session.done;
|
||||||
|
// debugPrint('docker code: ${session.exitCode}');
|
||||||
|
return session.exitCode == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _removeSudoPrompts(String value) {
|
||||||
|
final regex = RegExp(r"\[sudo\] password for \w+:");
|
||||||
|
if (value.startsWith(regex)) {
|
||||||
|
return value.replaceFirstMapped(regex, (match) => "");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _requiresSudo() async {
|
||||||
|
final psResult = await client?.run(_wrap(ContainerCmdType.ps.exec(type)));
|
||||||
|
if (psResult == null) return true;
|
||||||
|
if (psResult.string.toLowerCase().contains("permission denied")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
var raw = '';
|
var raw = '';
|
||||||
|
var rawErr = '';
|
||||||
|
debugPrint('exec: ${_wrap(ContainerCmdType.execAll(type))}');
|
||||||
|
|
||||||
|
final sudo = await _requiresSudo();
|
||||||
|
|
||||||
await client?.execWithPwd(
|
await client?.execWithPwd(
|
||||||
_wrap(ContainerCmdType.execAll(type)),
|
_wrap(ContainerCmdType.execAll(type, sudo: sudo)),
|
||||||
context: context,
|
context: context,
|
||||||
onStdout: (data, _) => raw = '$raw$data',
|
onStdout: (data, _) => raw = '$raw$data',
|
||||||
|
onStderr: (data, _) => raw = '$rawErr$data',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (raw.contains(_dockerNotFound)) {
|
raw = _removeSudoPrompts(raw);
|
||||||
|
rawErr = _removeSudoPrompts(rawErr);
|
||||||
|
|
||||||
|
debugPrint('result raw [$raw, $rawErr]');
|
||||||
|
|
||||||
|
final dockerInstalled = await _checkDockerInstalled(client!);
|
||||||
|
// debugPrint("docker installed = $dockerInstalled");
|
||||||
|
|
||||||
|
if (!dockerInstalled ||
|
||||||
|
raw.contains(_dockerNotFound) ||
|
||||||
|
rawErr.contains(_dockerNotFound)) {
|
||||||
error = ContainerErr(type: ContainerErrType.notInstalled);
|
error = ContainerErr(type: ContainerErrType.notInstalled);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return;
|
return;
|
||||||
@@ -71,6 +113,7 @@ class ContainerProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
// Parse docker version
|
// Parse docker version
|
||||||
final verRaw = ContainerCmdType.version.find(segments);
|
final verRaw = ContainerCmdType.version.find(segments);
|
||||||
|
debugPrint('version raw = $verRaw\n');
|
||||||
try {
|
try {
|
||||||
final containerVersion = Containerd.fromRawJson(verRaw);
|
final containerVersion = Containerd.fromRawJson(verRaw);
|
||||||
version = containerVersion.client.version;
|
version = containerVersion.client.version;
|
||||||
@@ -196,8 +239,8 @@ enum ContainerCmdType {
|
|||||||
images,
|
images,
|
||||||
;
|
;
|
||||||
|
|
||||||
String exec(ContainerType type) {
|
String exec(ContainerType type, {bool sudo = false}) {
|
||||||
final prefix = type.name;
|
final prefix = sudo ? 'sudo -S ${type.name}' : type.name;
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
ContainerCmdType.version => '$prefix version $_jsonFmt',
|
ContainerCmdType.version => '$prefix version $_jsonFmt',
|
||||||
ContainerCmdType.ps => '$prefix ps -a $_jsonFmt',
|
ContainerCmdType.ps => '$prefix ps -a $_jsonFmt',
|
||||||
@@ -206,6 +249,7 @@ enum ContainerCmdType {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static String execAll(ContainerType type) =>
|
static String execAll(ContainerType type, {bool sudo = false}) => values
|
||||||
values.map((e) => e.exec(type)).join(' && echo $seperator && ');
|
.map((e) => e.exec(type, sudo: sudo))
|
||||||
|
.join(' && echo $seperator && ');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:computer/computer.dart';
|
import 'package:computer/computer.dart';
|
||||||
import 'package:dartssh2/dartssh2.dart';
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:toolbox/core/extension/ssh_client.dart';
|
||||||
import 'package:toolbox/core/utils/platform/path.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';
|
||||||
@@ -256,6 +258,29 @@ class ServerProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _writeInstallerScript(Server s) async {
|
||||||
|
void ensure(String? writeResult) {
|
||||||
|
if (writeResult == null || writeResult.isNotEmpty) {
|
||||||
|
throw Exception("Failed to write installer script: $writeResult");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final client = s.client;
|
||||||
|
if (client == null) {
|
||||||
|
throw Exception("Invalid state: s.client cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure(await client.run(ShellFunc.installerMkdirs).string);
|
||||||
|
|
||||||
|
ensure(await client.runWithSessionAction(ShellFunc.installerShellWriter,
|
||||||
|
action: (session) async {
|
||||||
|
session.stdin.add(utf8.encode(ShellFunc.allScript));
|
||||||
|
})
|
||||||
|
.string);
|
||||||
|
|
||||||
|
ensure(await client.run(ShellFunc.installerPermissionModifier).string);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _getData(ServerPrivateInfo spi) async {
|
Future<void> _getData(ServerPrivateInfo spi) async {
|
||||||
final sid = spi.id;
|
final sid = spi.id;
|
||||||
final s = _servers[sid];
|
final s = _servers[sid];
|
||||||
@@ -304,11 +329,7 @@ class ServerProvider extends ChangeNotifier {
|
|||||||
// Write script to server
|
// Write script to server
|
||||||
// by ssh
|
// by ssh
|
||||||
try {
|
try {
|
||||||
final writeResult =
|
await _writeInstallerScript(s);
|
||||||
await s.client?.run(ShellFunc.installShellCmd).string;
|
|
||||||
if (writeResult == null || writeResult.isNotEmpty) {
|
|
||||||
throw Exception('$writeResult');
|
|
||||||
}
|
|
||||||
} on SSHAuthAbortError catch (e) {
|
} on SSHAuthAbortError catch (e) {
|
||||||
TryLimiter.inc(sid);
|
TryLimiter.inc(sid);
|
||||||
s.status.err = e.toString();
|
s.status.err = e.toString();
|
||||||
|
|||||||
Reference in New Issue
Block a user