Add app source and tests
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
125
lib/Peach/about/external/initial/SystemdSpec.dart
vendored
Normal file
125
lib/Peach/about/external/initial/SystemdSpec.dart
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
/// systemd service [Type].
|
||||
enum SystemdType { simple, exec, forking, oneshot, notify }
|
||||
|
||||
/// systemd Restart= policy.
|
||||
enum RestartPolicy { onFailure, always, no }
|
||||
|
||||
extension RestartPolicyX on RestartPolicy {
|
||||
String get value => switch (this) {
|
||||
RestartPolicy.onFailure => 'on-failure',
|
||||
RestartPolicy.always => 'always',
|
||||
RestartPolicy.no => 'no',
|
||||
};
|
||||
String get label => switch (this) {
|
||||
RestartPolicy.onFailure => 'On failure',
|
||||
RestartPolicy.always => 'Always',
|
||||
RestartPolicy.no => 'Never',
|
||||
};
|
||||
}
|
||||
|
||||
/// Everything needed to generate and register a `.service` unit file.
|
||||
class SystemdSpec {
|
||||
String unitName; // e.g. "orders" -> orders.service
|
||||
String description;
|
||||
SystemdType type;
|
||||
String user;
|
||||
String group;
|
||||
String workingDirectory;
|
||||
String execStart;
|
||||
RestartPolicy restart;
|
||||
int restartSec;
|
||||
|
||||
/// Raw "KEY=VALUE" lines.
|
||||
String environmentLines;
|
||||
|
||||
/// Extra `After=`/`Wants=` targets, e.g. mariadb.service.
|
||||
List<String> afterUnits;
|
||||
|
||||
/// Add `SuccessExitStatus=143` (SIGTERM) — recommended for JVM apps.
|
||||
bool javaSuccessExit;
|
||||
|
||||
bool enableOnBoot;
|
||||
bool startNow;
|
||||
|
||||
SystemdSpec({
|
||||
this.unitName = '',
|
||||
this.description = '',
|
||||
this.type = SystemdType.simple,
|
||||
this.user = 'root',
|
||||
this.group = '',
|
||||
this.workingDirectory = '',
|
||||
this.execStart = '',
|
||||
this.restart = RestartPolicy.onFailure,
|
||||
this.restartSec = 5,
|
||||
this.environmentLines = '',
|
||||
List<String>? afterUnits,
|
||||
this.javaSuccessExit = false,
|
||||
this.enableOnBoot = true,
|
||||
this.startNow = true,
|
||||
}) : afterUnits = afterUnits ?? ['network.target'];
|
||||
|
||||
/// Sanitized unit name (no spaces / .service suffix).
|
||||
String get safeName {
|
||||
var n = unitName.trim();
|
||||
if (n.toLowerCase().endsWith('.service')) {
|
||||
n = n.substring(0, n.length - '.service'.length);
|
||||
}
|
||||
return n.replaceAll(RegExp(r'[^A-Za-z0-9_.@-]'), '-');
|
||||
}
|
||||
|
||||
List<String> get envList => environmentLines
|
||||
.split('\n')
|
||||
.map((e) => e.trim())
|
||||
.where((e) => e.isNotEmpty && e.contains('='))
|
||||
.toList();
|
||||
|
||||
/// Renders the unit file contents.
|
||||
String render() {
|
||||
final b = StringBuffer();
|
||||
b.writeln('# Generated by PeachDeployments');
|
||||
b.writeln('[Unit]');
|
||||
b.writeln(
|
||||
'Description=${description.trim().isEmpty ? safeName : description.trim()}',
|
||||
);
|
||||
final afters = afterUnits.where((a) => a.trim().isNotEmpty).toList();
|
||||
if (afters.isNotEmpty) {
|
||||
b.writeln('After=${afters.join(' ')}');
|
||||
// Wants only the non-core targets (don't hard-require network.target).
|
||||
final wants = afters.where((a) => a != 'network.target').toList();
|
||||
if (wants.isNotEmpty) b.writeln('Wants=${wants.join(' ')}');
|
||||
}
|
||||
b.writeln();
|
||||
b.writeln('[Service]');
|
||||
b.writeln('Type=${type.name}');
|
||||
if (user.trim().isNotEmpty) b.writeln('User=${user.trim()}');
|
||||
if (group.trim().isNotEmpty) b.writeln('Group=${group.trim()}');
|
||||
if (workingDirectory.trim().isNotEmpty) {
|
||||
b.writeln('WorkingDirectory=${workingDirectory.trim()}');
|
||||
}
|
||||
for (final e in envList) {
|
||||
b.writeln('Environment="$e"');
|
||||
}
|
||||
b.writeln('ExecStart=${execStart.trim()}');
|
||||
b.writeln('Restart=${restart.value}');
|
||||
if (restart != RestartPolicy.no) b.writeln('RestartSec=$restartSec');
|
||||
if (javaSuccessExit) b.writeln('SuccessExitStatus=143');
|
||||
b.writeln();
|
||||
b.writeln('[Install]');
|
||||
b.writeln('WantedBy=multi-user.target');
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
/// Human-readable list of the commands the installer will run.
|
||||
List<String> plannedCommands({required bool sudo}) {
|
||||
final p = sudo ? 'sudo ' : '';
|
||||
final target = '/etc/systemd/system/$safeName.service';
|
||||
return [
|
||||
'write $target',
|
||||
'${p}chmod 644 $target',
|
||||
'${p}systemctl daemon-reload',
|
||||
if (enableOnBoot) '${p}systemctl enable $safeName',
|
||||
if (startNow) '${p}systemctl restart $safeName',
|
||||
'${p}systemctl status $safeName',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user