cloudaxe-opencode/packages/opencode/src/service/linux.ts

84 lines
2.8 KiB
TypeScript
Raw Normal View History

2026-04-19 00:02:49 +00:00
import { Effect, Layer } from "effect"
import { ChildProcessSpawner } from "effect/unstable/process"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import * as ServicePlatform from "./platform"
import {
buildUnixLauncher,
chmodFile,
quoteSystemd,
UNIX_LAUNCHER,
writeTextFile,
ServiceError,
} from "./shared"
import path from "path"
import { Global } from "@/global"
import { ChildProcess } from "effect/unstable/process"
import { Stream } from "effect"
const SYSTEMD_SERVICE_NAME = "opencode.service"
const systemdUnitFile = path.join(path.dirname(Global.Path.config), "systemd", "user", SYSTEMD_SERVICE_NAME)
function buildLinuxUnit() {
return [
"[Unit]",
"Description=OpenCode background server",
"",
"[Service]",
"Type=simple",
`ExecStart=${quoteSystemd(UNIX_LAUNCHER)}`,
"WorkingDirectory=%h",
"Restart=on-failure",
"RestartSec=5",
"",
"[Install]",
"WantedBy=default.target",
"",
].join("\n")
}
export const layer: Layer.Layer<
ServicePlatform.Service,
never,
ChildProcessSpawner.ChildProcessSpawner | AppFileSystem.Service
> = Layer.effect(
ServicePlatform.Service,
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
const run = Effect.fnUntraced(
function* (command: string[]) {
const handle = yield* spawner.spawn(
ChildProcess.make(command[0], command.slice(1), { extendEnv: true, stdin: "ignore" }),
)
const [stdout, stderr] = yield* Effect.all(
[Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))],
{ concurrency: 2 },
)
const code = yield* handle.exitCode
if (code === 0) return { stdout, stderr }
return yield* new ServiceError({
message: stderr.trim() || stdout.trim() || `Command failed: ${command.join(" ")}`,
})
},
Effect.scoped,
Effect.catch((cause) => {
if (cause instanceof ServiceError) return Effect.fail(cause)
return Effect.fail(new ServiceError({ message: "Failed to execute service manager command", cause }))
}),
)
const install = Effect.fn("ServiceLinux.install")(function* (input) {
yield* writeTextFile(fs, UNIX_LAUNCHER, buildUnixLauncher(input.command, input.password), 0o700)
yield* chmodFile(fs, UNIX_LAUNCHER, 0o700)
yield* writeTextFile(fs, systemdUnitFile, buildLinuxUnit())
yield* run(["systemctl", "--user", "daemon-reload"])
yield* run(["systemctl", "--user", "enable", "--now", SYSTEMD_SERVICE_NAME])
yield* run(["systemctl", "--user", "restart", SYSTEMD_SERVICE_NAME])
return { target: systemdUnitFile }
})
return ServicePlatform.Service.of({ install })
}),
)