Skip to content

Commit 406e1cb

Browse files
committed
feat(systemd-service-manager): 支持目标类型自动推断
当用户省略目标类型(service或timer)时,系统现在可以自动推断目标类型。 如果服务或定时器配置文件只存在其中一种,则自动使用该类型; 如果两者都存在则报错提示用户明确指定类型;如果两者都不存在则报错。 同时更新了相关命令如install、start等以支持这种新的目标解析机制, 并在CLI帮助文档中添加了相应的语法说明和示例。
1 parent 3f1827e commit 406e1cb

6 files changed

Lines changed: 129 additions & 17 deletions

File tree

scripts/bash/systemd-service-manager/commands/install.sh

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,25 @@ SSM_CMD_INSTALL_LOADED=1
99
ssm_cmd_install() {
1010
local target_kind="${1:-}"
1111
local target_name="${2:-}"
12-
[[ -n "${target_kind}" ]] || ssm_die "Missing install target kind"
13-
[[ -n "${target_name}" ]] || ssm_die "Missing install target name"
12+
[[ -n "${target_kind}" ]] || ssm_die "Missing install target"
1413

1514
local project_dir
1615
project_dir="$(ssm_find_project_dir "${SSM_CLI_PROJECT_DIR:-}")"
16+
ssm_resolve_target_spec "${project_dir}" "${target_kind}" "${target_name}"
1717
local render_dir
1818
render_dir="$(mktemp -d)"
1919
trap 'rm -rf '"'"${render_dir}"'"'' RETURN
2020

2121
local source_file=""
2222
local scope="system"
2323

24-
case "${target_kind}" in
24+
case "${SSM_RESOLVED_TARGET_KIND}" in
2525
service)
26-
ssm_parse_service_config "${project_dir}" "${target_name}"
26+
ssm_parse_service_config "${project_dir}" "${SSM_RESOLVED_TARGET_NAME}"
2727
ssm_require_safe_name "UNIT_PREFIX" "${UNIT_PREFIX}"
28-
source_file="$(ssm_service_config_path "${project_dir}" "${target_name}")"
28+
source_file="$(ssm_service_config_path "${project_dir}" "${SSM_RESOLVED_TARGET_NAME}")"
2929
scope="${SSM_SERVICE_SCOPE}"
30-
local service_unit_file="${render_dir}/$(ssm_service_unit_name "${target_name}")"
30+
local service_unit_file="${render_dir}/$(ssm_service_unit_name "${SSM_RESOLVED_TARGET_NAME}")"
3131
ssm_render_service_unit "${source_file}" >"${service_unit_file}"
3232
ssm_verify_unit_file "${service_unit_file}" || ssm_die "systemd-analyze verify failed for ${service_unit_file}"
3333
if [[ "${SSM_CLI_DRY_RUN}" == "1" ]]; then
@@ -39,16 +39,16 @@ ssm_cmd_install() {
3939
ssm_daemon_reload "${scope}"
4040
;;
4141
timer)
42-
ssm_parse_timer_config "${project_dir}" "${target_name}"
42+
ssm_parse_timer_config "${project_dir}" "${SSM_RESOLVED_TARGET_NAME}"
4343
ssm_require_safe_name "UNIT_PREFIX" "${UNIT_PREFIX}"
44-
source_file="$(ssm_timer_config_path "${project_dir}" "${target_name}")"
44+
source_file="$(ssm_timer_config_path "${project_dir}" "${SSM_RESOLVED_TARGET_NAME}")"
4545
scope="${SSM_TIMER_SCOPE}"
4646
local schedule_block
4747
schedule_block="$(ssm_resolve_schedule "${SCHEDULE}")"
4848
local task_unit_name
49-
task_unit_name="$(ssm_timer_task_unit_name "${target_name}")"
49+
task_unit_name="$(ssm_timer_task_unit_name "${SSM_RESOLVED_TARGET_NAME}")"
5050
local task_unit_file="${render_dir}/${task_unit_name}"
51-
local timer_unit_file="${render_dir}/$(ssm_timer_unit_name "${target_name}")"
51+
local timer_unit_file="${render_dir}/$(ssm_timer_unit_name "${SSM_RESOLVED_TARGET_NAME}")"
5252
local task_exec_command=""
5353

5454
if [[ "${TARGET_TYPE}" == "service" ]]; then

scripts/bash/systemd-service-manager/commands/status.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ ssm_cmd_status() {
1616
enabled_state="$(ssm_systemctl "${SSM_ACTIVE_SCOPE}" is-enabled "${SSM_ACTIVE_UNIT}" 2>/dev/null || true)"
1717
active_state="$(ssm_systemctl "${SSM_ACTIVE_SCOPE}" is-active "${SSM_ACTIVE_UNIT}" 2>/dev/null || true)"
1818

19-
printf 'name=%s\n' "${target_name}"
19+
printf 'name=%s\n' "${SSM_ACTIVE_NAME}"
2020
printf 'unit=%s\n' "${SSM_ACTIVE_UNIT}"
2121
printf 'scope=%s\n' "${SSM_ACTIVE_SCOPE}"
2222
printf 'installed=%s\n' "$(ssm_is_unit_installed "${SSM_ACTIVE_SCOPE}" "${SSM_ACTIVE_UNIT}")"

scripts/bash/systemd-service-manager/lib/cli.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,17 @@ Common options:
2929
--dry-run 只预览将执行的操作,不实际写入 unit
3030
--follow 配合 logs 使用,持续跟随日志输出
3131
32+
Target syntax:
33+
<command> <service|timer> <name> 显式指定目标类型
34+
<command> <name> 当名字只在 service 或 timer 中命中一个时自动推断类型
35+
3236
Examples:
3337
systemd-service-manager init
3438
systemd-service-manager list --project /path/to/app
3539
systemd-service-manager install service api --project /path/to/app
40+
systemd-service-manager install api --project /path/to/app
3641
systemd-service-manager install timer cleanup --project /path/to/app --dry-run
42+
systemd-service-manager start api --project /path/to/app
3743
systemd-service-manager logs service api --project /path/to/app --follow
3844
EOF
3945
}

scripts/bash/systemd-service-manager/lib/systemd.sh

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,25 +68,76 @@ ssm_is_unit_installed() {
6868
}
6969

7070
# 把 service/timer 目标解析成 scope 与最终 unit 名,供 lifecycle 命令复用。
71+
ssm_resolve_target_spec() {
72+
local project_dir="$1"
73+
local target_kind="$2"
74+
local target_name="$3"
75+
76+
if [[ -z "${target_kind}" ]]; then
77+
ssm_die "Missing target name"
78+
fi
79+
80+
case "${target_kind}" in
81+
service | timer)
82+
[[ -n "${target_name}" ]] || ssm_die "Missing target name"
83+
SSM_RESOLVED_TARGET_KIND="${target_kind}"
84+
SSM_RESOLVED_TARGET_NAME="${target_name}"
85+
return 0
86+
;;
87+
esac
88+
89+
if [[ -n "${target_name}" ]]; then
90+
ssm_die "Unknown target kind: ${target_kind}"
91+
fi
92+
93+
local inferred_name="${target_kind}"
94+
local service_exists="false"
95+
local timer_exists="false"
96+
97+
[[ -f "$(ssm_service_config_path "${project_dir}" "${inferred_name}")" ]] && service_exists="true"
98+
[[ -f "$(ssm_timer_config_path "${project_dir}" "${inferred_name}")" ]] && timer_exists="true"
99+
100+
case "${service_exists}:${timer_exists}" in
101+
true:false)
102+
SSM_RESOLVED_TARGET_KIND="service"
103+
SSM_RESOLVED_TARGET_NAME="${inferred_name}"
104+
;;
105+
false:true)
106+
SSM_RESOLVED_TARGET_KIND="timer"
107+
SSM_RESOLVED_TARGET_NAME="${inferred_name}"
108+
;;
109+
true:true)
110+
ssm_die "Ambiguous target name: ${inferred_name}. Use 'service ${inferred_name}' or 'timer ${inferred_name}'"
111+
;;
112+
false:false)
113+
ssm_die "Cannot infer target kind for ${inferred_name}. Use 'service ${inferred_name}' or 'timer ${inferred_name}'"
114+
;;
115+
esac
116+
}
117+
71118
ssm_load_target_context() {
72119
local target_kind="$1"
73120
local target_name="$2"
74121
local project_dir
75122
project_dir="$(ssm_find_project_dir "${SSM_CLI_PROJECT_DIR:-}")"
76123

77-
case "${target_kind}" in
124+
ssm_resolve_target_spec "${project_dir}" "${target_kind}" "${target_name}"
125+
126+
case "${SSM_RESOLVED_TARGET_KIND}" in
78127
service)
79-
ssm_parse_service_config "${project_dir}" "${target_name}"
128+
ssm_parse_service_config "${project_dir}" "${SSM_RESOLVED_TARGET_NAME}"
129+
SSM_ACTIVE_NAME="${SSM_RESOLVED_TARGET_NAME}"
80130
SSM_ACTIVE_SCOPE="${SSM_SERVICE_SCOPE}"
81-
SSM_ACTIVE_UNIT="$(ssm_service_unit_name "${target_name}")"
131+
SSM_ACTIVE_UNIT="$(ssm_service_unit_name "${SSM_RESOLVED_TARGET_NAME}")"
82132
;;
83133
timer)
84-
ssm_parse_timer_config "${project_dir}" "${target_name}"
134+
ssm_parse_timer_config "${project_dir}" "${SSM_RESOLVED_TARGET_NAME}"
135+
SSM_ACTIVE_NAME="${SSM_RESOLVED_TARGET_NAME}"
85136
SSM_ACTIVE_SCOPE="${SSM_TIMER_SCOPE}"
86-
SSM_ACTIVE_UNIT="$(ssm_timer_unit_name "${target_name}")"
137+
SSM_ACTIVE_UNIT="$(ssm_timer_unit_name "${SSM_RESOLVED_TARGET_NAME}")"
87138
;;
88139
*)
89-
ssm_die "Unknown target kind: ${target_kind}"
140+
ssm_die "Unknown target kind: ${SSM_RESOLVED_TARGET_KIND}"
90141
;;
91142
esac
92143
}

scripts/bash/systemd-service-manager/tests/install.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,33 @@ describe('install command', () => {
9494
fs.readFileSync(path.join(workspace.root, 'systemctl.log'), 'utf8'),
9595
).toContain('daemon-reload')
9696
})
97+
98+
it('infers service kind when omitted for install dry-run', async () => {
99+
const workspace = createWorkspace()
100+
workspaces.push(workspace)
101+
102+
installMockCommand(
103+
workspace,
104+
'systemd-analyze',
105+
'#!/usr/bin/env bash\nexit 0\n',
106+
)
107+
108+
const projectRoot = path.join(
109+
workspace.managerHome,
110+
'tests',
111+
'fixtures',
112+
'project-basic',
113+
)
114+
115+
const result = await runSource(workspace, [
116+
'install',
117+
'api',
118+
'--project',
119+
projectRoot,
120+
'--dry-run',
121+
])
122+
123+
expect(result.exitCode).toBe(0)
124+
expect(result.stdout).toContain('myapp-api.service')
125+
})
97126
})

scripts/bash/systemd-service-manager/tests/lifecycle.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,30 @@ describe('lifecycle commands', () => {
109109
'--user -u myapp-user-agent.service -f',
110110
)
111111
})
112+
113+
it('infers service kind when omitted for start', async () => {
114+
const workspace = createWorkspace()
115+
workspaces.push(workspace)
116+
117+
const systemctlLog = path.join(workspace.root, 'systemctl.log')
118+
installMockCommand(
119+
workspace,
120+
'systemctl',
121+
'#!/usr/bin/env bash\nprintf "%s\\n" "$*" >>"${SSM_SYSTEMCTL_LOG}"\nexit 0\n',
122+
)
123+
124+
const projectRoot = path.join(
125+
workspace.managerHome,
126+
'tests',
127+
'fixtures',
128+
'project-basic',
129+
)
130+
131+
const result = await runSource(workspace, ['start', 'api', '--project', projectRoot], {
132+
SSM_SYSTEMCTL_LOG: systemctlLog,
133+
})
134+
135+
expect(result.exitCode).toBe(0)
136+
expect(fs.readFileSync(systemctlLog, 'utf8')).toContain('start myapp-api.service')
137+
})
112138
})

0 commit comments

Comments
 (0)