Lybic Docs

执行长期异步任务

在 linux 沙箱中使用 systemd 运行长期后台任务

本篇文档适用于linux沙箱。如果你使用的是 Windows 沙箱,请参考 在 Windows 沙箱中运行长期任务

在 Lybic 沙箱中,你可能需要运行一些长期执行的任务,如模型训练、数据处理、Web 服务器等。由于 execSandboxProcess API 有超时限制且不支持后台运行,我们推荐使用 systemd --user run 来执行这类任务。

提示:对于快速执行的短命令(5 秒内完成),请使用 execSandboxProcess API

工作原理

systemd --user run 创建一个临时的 systemd 服务单元(transient unit),该服务在任务完成后自动清理。任务在 systemd 的管理下运行,与 API 调用解耦。

使用方法

步骤概览

  1. 准备脚本或程序:将要运行的脚本/程序传输到沙箱
  2. 启动任务:使用 systemd --user run 启动长期任务
  3. 查看状态:查询任务运行状态和日志

步骤 1:准备脚本或程序

有两种方式准备要运行的脚本或程序:

方式 A:通过文件拷贝

使用 文件传输 API 将脚本上传到沙箱:

# 首先,将脚本上传到对象存储(获取下载 URL)
# 然后,从 URL 下载到沙箱
curl -X POST "https://api.lybic.cn/api/orgs/{orgId}/sandboxes/{sandboxId}/file/copy" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "files": [
      {
        "id": "upload-training-script",
        "src": {
          "type": "httpGetLocation",
          "url": "https://storage.example.com/scripts/train.py"
        },
        "dest": {
          "type": "sandboxFileLocation",
          "path": "/home/agent/scripts/train.py"
        }
      }
    ]
  }'
import asyncio
from lybic import LybicClient, LybicAuth
from lybic.dto import (
    SandboxFileCopyRequestDto,
    FileCopyItem,
    SandboxFileLocation,
    HttpGetLocation
)

async def main():
    async with LybicClient(
        LybicAuth(
            org_id="ORG-xxxx",
            api_key="lysk-xxxxxxxxxxx",
            endpoint="https://api.lybic.cn/"
        )
    ) as client:
        # 上传脚本到沙箱
        await client.sandbox.copy_files(
            "SBX-xxxx",
            SandboxFileCopyRequestDto(files=[
                FileCopyItem(
                    id="upload-training-script",
                    src=HttpGetLocation(
                        url="https://storage.example.com/scripts/train.py"
                    ),
                    dest=SandboxFileLocation(
                        path="/home/agent/scripts/train.py"
                    )
                )
            ])
        )
        print("脚本上传成功")

if __name__ == "__main__":
    asyncio.run(main())
import { LybicClient } from '@lybic/core'

const lybic = new LybicClient({
  baseUrl: 'https://api.lybic.cn',
  orgId: 'ORG-xxxx',
  apiKey: 'lysk-your-api-key-here',
})

// 上传脚本到沙箱
await lybic.copyFilesWithSandbox('SBX-xxxx', {
  files: [
    {
      id: 'upload-training-script',
      src: {
        type: 'httpGetLocation',
        url: 'https://storage.example.com/scripts/train.py',
      },
      dest: {
        type: 'sandboxFileLocation',
        path: '/home/agent/scripts/train.py',
      },
    },
  ],
})

console.log('脚本上传成功')
package main

import (
    "context"
    "fmt"
    "github.com/lybic/lybic-sdk-go"
)

func main() {
    ctx := context.Background()
    client, _ := lybic.NewClient(nil)

    copyDto := lybic.SandboxFileCopyRequestDto{
        Files: []lybic.SandboxFileCopyRequestDtoFiles{
            {
                Id: "upload-training-script",
                Src: map[string]string{
                    "type": "httpGetLocation",
                    "url":  "https://storage.example.com/scripts/train.py",
                },
                Dest: map[string]any{
                    "type": "sandboxFileLocation",
                    "path": "/home/agent/scripts/train.py",
                },
            },
        },
    }

    _, err := client.CopyFilesWithSandbox(ctx, "SBX-xxxx", copyDto)
    if err != nil {
        fmt.Println("上传脚本出错:", err)
        return
    }
    fmt.Println("脚本上传成功")
}

方式 B:通过 stdin 写入

使用 execSandboxProcess 将脚本内容通过管道写入文件:

# Base64 编码脚本内容
SCRIPT_CONTENT=$(cat << 'EOF' | base64 -w0
#!/usr/bin/env python3
import time
import sys

print("开始训练模型...")
for i in range(100):
    time.sleep(1)
    print(f"进度: {i+1}/100")
    sys.stdout.flush()
print("训练完成!")
EOF
)

# 写入文件
curl -X POST "https://api.lybic.cn/api/orgs/{orgId}/sandboxes/{sandboxId}/process" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "executable": "bash",
    "args": ["-c", "cat > /home/agent/scripts/train.py && chmod +x /home/agent/scripts/train.py"],
    "stdinBase64": "'$SCRIPT_CONTENT'"
  }'
import asyncio
import base64
from lybic import LybicClient, LybicAuth

async def main():
    async with LybicClient(
        LybicAuth(
            org_id="ORG-xxxx",
            api_key="lysk-xxxxxxxxxxx",
            endpoint="https://api.lybic.cn/"
        )
    ) as client:
        # 准备脚本内容
        script_content = """#!/usr/bin/env python3
import time
import sys

print("开始训练模型...")
for i in range(100):
    time.sleep(1)
    print(f"进度: {i+1}/100")
    sys.stdout.flush()
print("训练完成!")
"""
        
        # Base64 编码
        stdin_data = base64.b64encode(script_content.encode()).decode()
        
        # 写入文件并添加执行权限
        result = await client.sandbox.execute_process(
            "SBX-xxxx",
            executable="bash",
            args=["-c", "cat > /home/agent/scripts/train.py && chmod +x /home/agent/scripts/train.py"],
            stdinBase64=stdin_data
        )
        
        if result.exitCode == 0:
            print("脚本创建成功")
        else:
            print(f"创建失败: {base64.b64decode(result.stderrBase64 or '').decode()}")

if __name__ == "__main__":
    asyncio.run(main())
import { LybicClient } from '@lybic/core'

const lybic = new LybicClient({
  baseUrl: 'https://api.lybic.cn',
  orgId: 'ORG-xxxx',
  apiKey: 'lysk-your-api-key-here',
})

// 准备脚本内容
const scriptContent = `#!/usr/bin/env python3
import time
import sys

print("开始训练模型...")
for i in range(100):
    time.sleep(1)
    print(f"进度: {i+1}/100")
    sys.stdout.flush()
print("训练完成!")
`

// Base64 编码
const stdinBase64 = btoa(scriptContent)

// 写入文件并添加执行权限
const result = await lybic.execSandboxProcess('SBX-xxxx', {
  executable: 'bash',
  args: ['-c', 'cat > /home/agent/scripts/train.py && chmod +x /home/agent/scripts/train.py'],
  stdinBase64: stdinBase64,
})

if (result.data?.exitCode === 0) {
  console.log('脚本创建成功')
} else {
  console.log('创建失败:', atob(result.data?.stderrBase64 || ''))
}
package main

import (
    "context"
    "encoding/base64"
    "fmt"
    "github.com/lybic/lybic-sdk-go"
)

func main() {
    ctx := context.Background()
    client, _ := lybic.NewClient(nil)

    // 准备脚本内容
    scriptContent := `#!/usr/bin/env python3
import time
import sys

print("开始训练模型...")
for i in range(100):
    time.sleep(1)
    print(f"进度: {i+1}/100")
    sys.stdout.flush()
print("训练完成!")
`

    // Base64 编码
    stdinBase64 := base64.StdEncoding.EncodeToString([]byte(scriptContent))
    
    // 写入文件并添加执行权限
    cmd := "cat > /home/agent/scripts/train.py && chmod +x /home/agent/scripts/train.py"
    processDto := lybic.SandboxProcessRequestDto{
        Executable:  "bash",
        Args:        []string{"-c", cmd},
        StdinBase64: &stdinBase64,
    }

    result, _ := client.ExecSandboxProcess(ctx, "SBX-xxxx", processDto)
    
    if result.ExitCode == 0 {
        fmt.Println("脚本创建成功")
    } else {
        stderr, _ := base64.StdEncoding.DecodeString(result.StderrBase64)
        fmt.Printf("创建失败: %s\n", string(stderr))
    }
}

步骤 2:使用 systemd --user run 启动任务

使用 systemd-run --user 启动长期任务。该命令会立即返回,任务在后台继续运行。

# 启动长期任务
curl -X POST "https://api.lybic.cn/api/orgs/{orgId}/sandboxes/{sandboxId}/process" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "executable": "systemd-run",
    "args": [
      "--user",
      "--unit=my-training-task",
      "--description=Model Training Task",
      "python3",
      "/home/agent/scripts/train.py"
    ]
  }'
import asyncio
import base64
from lybic import LybicClient, LybicAuth

async def main():
    async with LybicClient(
        LybicAuth(
            org_id="ORG-xxxx",
            api_key="lysk-xxxxxxxxxxx",
            endpoint="https://api.lybic.cn/"
        )
    ) as client:
        # 启动 systemd 任务
        result = await client.sandbox.execute_process(
            "SBX-xxxx",
            executable="systemd-run",
            args=[
                "--user",
                "--unit=my-training-task",
                "--description=Model Training Task",
                "python3",
                "/home/agent/scripts/train.py"
            ]
        )
        
        output = base64.b64decode(result.stdoutBase64 or '').decode()
        print(f"任务已启动: {output}")
        print(f"退出代码: {result.exitCode}")

if __name__ == "__main__":
    asyncio.run(main())
import { LybicClient } from '@lybic/core'

const lybic = new LybicClient({
  baseUrl: 'https://api.lybic.cn',
  orgId: 'ORG-xxxx',
  apiKey: 'lysk-your-api-key-here',
})

// 启动 systemd 任务
const result = await lybic.execSandboxProcess('SBX-xxxx', {
  executable: 'systemd-run',
  args: [
    '--user',
    '--unit=my-training-task',
    '--description=Model Training Task',
    'python3',
    '/home/agent/scripts/train.py',
  ],
})

const output = atob(result.data?.stdoutBase64 || '')
console.log('任务已启动:', output)
console.log('退出代码:', result.data?.exitCode)
package main

import (
    "context"
    "encoding/base64"
    "fmt"
    "github.com/lybic/lybic-sdk-go"
)

func main() {
    ctx := context.Background()
    client, _ := lybic.NewClient(nil)

    // 启动 systemd 任务
    processDto := lybic.SandboxProcessRequestDto{
        Executable: "systemd-run",
        Args: []string{
            "--user",
            "--unit=my-training-task",
            "--description=Model Training Task",
            "python3",
            "/home/agent/scripts/train.py",
        },
    }

    result, err := client.ExecSandboxProcess(ctx, "SBX-xxxx", processDto)
    if err != nil {
        fmt.Println("启动任务出错:", err)
        return
    }

    stdout, _ := base64.StdEncoding.DecodeString(result.StdoutBase64)
    fmt.Printf("任务已启动: %s\n", string(stdout))
    fmt.Printf("退出代码: %d\n", result.ExitCode)
}

重要参数说明:

  • --user:在用户模式下运行(必需)
  • --unit=<名称>:指定单元名称,用于后续查询状态和日志
  • --description=<描述>:任务描述(可选)
  • 后续参数是要执行的实际命令

步骤 3:查看任务状态

3.1 查看任务运行状态

使用 systemctl --user status 查看任务运行状态:

# 查看任务状态
curl -X POST "https://api.lybic.cn/api/orgs/{orgId}/sandboxes/{sandboxId}/process" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "executable": "systemctl",
    "args": ["--user", "status", "my-training-task"]
  }'
import asyncio
import base64
from lybic import LybicClient, LybicAuth

async def main():
    async with LybicClient(
        LybicAuth(
            org_id="ORG-xxxx",
            api_key="lysk-xxxxxxxxxxx",
            endpoint="https://api.lybic.cn/"
        )
    ) as client:
        # 查看任务状态
        result = await client.sandbox.execute_process(
            "SBX-xxxx",
            executable="systemctl",
            args=["--user", "status", "my-training-task"]
        )
        
        status = base64.b64decode(result.stdoutBase64 or '').decode()
        print("任务状态:")
        print(status)

if __name__ == "__main__":
    asyncio.run(main())
import { LybicClient } from '@lybic/core'

const lybic = new LybicClient({
  baseUrl: 'https://api.lybic.cn',
  orgId: 'ORG-xxxx',
  apiKey: 'lysk-your-api-key-here',
})

// 查看任务状态
const result = await lybic.execSandboxProcess('SBX-xxxx', {
  executable: 'systemctl',
  args: ['--user', 'status', 'my-training-task'],
})

const status = atob(result.data?.stdoutBase64 || '')
console.log('任务状态:')
console.log(status)
package main

import (
    "context"
    "encoding/base64"
    "fmt"
    "github.com/lybic/lybic-sdk-go"
)

func main() {
    ctx := context.Background()
    client, _ := lybic.NewClient(nil)

    // 查看任务状态
    processDto := lybic.SandboxProcessRequestDto{
        Executable: "systemctl",
        Args:       []string{"--user", "status", "my-training-task"},
    }

    result, _ := client.ExecSandboxProcess(ctx, "SBX-xxxx", processDto)
    
    stdout, _ := base64.StdEncoding.DecodeString(result.StdoutBase64)
    fmt.Println("任务状态:")
    fmt.Println(string(stdout))
}

状态输出示例:

● my-training-task.service - Model Training Task
     Loaded: loaded (/run/user/1000/systemd/transient/my-training-task.service; transient)
  Transient: yes
     Active: active (running) since Mon 2024-01-15 10:30:00 UTC; 5min ago
   Main PID: 12345 (python3)
      Tasks: 1 (limit: 4915)
     Memory: 45.2M
        CPU: 2.5s
     CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/my-training-task.service
             └─12345 python3 /home/agent/scripts/train.py

3.2 查看任务日志

使用 journalctl --user 查看任务的输出日志:

# 查看任务日志(最新 100 行)
curl -X POST "https://api.lybic.cn/api/orgs/{orgId}/sandboxes/{sandboxId}/process" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "executable": "journalctl",
    "args": [
      "--user",
      "--unit=my-training-task",
      "-n", "100",
      "--no-pager"
    ]
  }'
import asyncio
import base64
from lybic import LybicClient, LybicAuth

async def main():
    async with LybicClient(
        LybicAuth(
            org_id="ORG-xxxx",
            api_key="lysk-xxxxxxxxxxx",
            endpoint="https://api.lybic.cn/"
        )
    ) as client:
        # 查看任务日志
        result = await client.sandbox.execute_process(
            "SBX-xxxx",
            executable="journalctl",
            args=[
                "--user",
                "--unit=my-training-task",
                "-n", "100",
                "--no-pager"
            ]
        )
        
        logs = base64.b64decode(result.stdoutBase64 or '').decode()
        print("任务日志:")
        print(logs)

if __name__ == "__main__":
    asyncio.run(main())
import { LybicClient } from '@lybic/core'

const lybic = new LybicClient({
  baseUrl: 'https://api.lybic.cn',
  orgId: 'ORG-xxxx',
  apiKey: 'lysk-your-api-key-here',
})

// 查看任务日志
const result = await lybic.execSandboxProcess('SBX-xxxx', {
  executable: 'journalctl',
  args: [
    '--user',
    '--unit=my-training-task',
    '-n', '100',
    '--no-pager',
  ],
})

const logs = atob(result.data?.stdoutBase64 || '')
console.log('任务日志:')
console.log(logs)
package main

import (
    "context"
    "encoding/base64"
    "fmt"
    "github.com/lybic/lybic-sdk-go"
)

func main() {
    ctx := context.Background()
    client, _ := lybic.NewClient(nil)

    // 查看任务日志
    processDto := lybic.SandboxProcessRequestDto{
        Executable: "journalctl",
        Args: []string{
            "--user",
            "--unit=my-training-task",
            "-n", "100",
            "--no-pager",
        },
    }

    result, _ := client.ExecSandboxProcess(ctx, "SBX-xxxx", processDto)
    
    stdout, _ := base64.StdEncoding.DecodeString(result.StdoutBase64)
    fmt.Println("任务日志:")
    fmt.Println(string(stdout))
}

日志输出示例:

Jan 28 06:20:37 agent-machine systemd[877]: Started my-training-task.service - Model Training Task.
Jan 28 06:20:38 agent-machine python3[17083]: 开始训练模型...
Jan 28 06:20:38 agent-machine python3[17083]: 进度: 1/100
Jan 28 06:20:39 agent-machine python3[17083]: 进度: 2/100
Jan 28 06:20:40 agent-machine python3[17083]: 进度: 3/100
Jan 28 06:20:41 agent-machine python3[17083]: 进度: 4/100
Jan 28 06:20:42 agent-machine python3[17083]: 进度: 5/100
...

常用 journalctl 参数:

  • -n <行数>:显示最新的 N 行日志
  • -f:实时跟踪日志(不推荐,因为 API 有超时限制)
  • --no-pager:直接输出,不使用分页器(必需)
  • --since "5 minutes ago":显示最近 5 分钟的日志
  • -o json:以 JSON 格式输出

3.3 检查任务是否仍在运行

使用 systemctl --user is-active 快速检查任务状态:

curl -X POST "https://api.lybic.cn/api/orgs/{orgId}/sandboxes/{sandboxId}/process" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "executable": "systemctl",
    "args": ["--user", "is-active", "my-training-task"]
  }'
import asyncio
import base64
from lybic import LybicClient, LybicAuth

async def main():
    async with LybicClient(
        LybicAuth(
            org_id="ORG-xxxx",
            api_key="lysk-xxxxxxxxxxx",
            endpoint="https://api.lybic.cn/"
        )
    ) as client:
        result = await client.sandbox.execute_process(
            "SBX-xxxx",
            executable="systemctl",
            args=["--user", "is-active", "my-training-task"]
        )
        
        status = base64.b64decode(result.stdoutBase64 or '').decode().strip()
        
        if status == "active":
            print("✓ 任务正在运行")
        elif status == "inactive":
            print("✗ 任务已停止")
        else:
            print(f"任务状态: {status}")

if __name__ == "__main__":
    asyncio.run(main())
import { LybicClient } from '@lybic/core'

const lybic = new LybicClient({
  baseUrl: 'https://api.lybic.cn',
  orgId: 'ORG-xxxx',
  apiKey: 'lysk-your-api-key-here',
})

const result = await lybic.execSandboxProcess('SBX-xxxx', {
  executable: 'systemctl',
  args: ['--user', 'is-active', 'my-training-task'],
})

const status = atob(result.data?.stdoutBase64 || '').trim()

if (status === 'active') {
  console.log('✓ 任务正在运行')
} else if (status === 'inactive') {
  console.log('✗ 任务已停止')
} else {
  console.log('任务状态:', status)
}
package main

import (
    "context"
    "encoding/base64"
    "fmt"
    "strings"
    "github.com/lybic/lybic-sdk-go"
)

func main() {
    ctx := context.Background()
    client, _ := lybic.NewClient(nil)

    processDto := lybic.SandboxProcessRequestDto{
        Executable: "systemctl",
        Args:       []string{"--user", "is-active", "my-training-task"},
    }

    result, _ := client.ExecSandboxProcess(ctx, "SBX-xxxx", processDto)
    
    stdout, _ := base64.StdEncoding.DecodeString(result.StdoutBase64)
    status := strings.TrimSpace(string(stdout))
    
    if status == "active" {
        fmt.Println("✓ 任务正在运行")
    } else if status == "inactive" {
        fmt.Println("✗ 任务已停止")
    } else {
        fmt.Printf("任务状态: %s\n", status)
    }
}

返回值:

  • active:任务正在运行
  • inactive:任务已停止
  • failed:任务失败

完整示例:运行 Web 服务器

以下是一个完整的示例,展示如何在沙箱中运行一个长期的 Python Web 服务器:

import asyncio
import base64
from lybic import LybicClient, LybicAuth

async def run_web_server():
    async with LybicClient(
        LybicAuth(
            org_id="ORG-xxxx",
            api_key="lysk-xxxxxxxxxxx",
            endpoint="https://api.lybic.cn/"
        )
    ) as client:
        sandbox_id = "SBX-xxxx"
        
        # 1. 创建 Web 服务器脚本
        server_code = """#!/usr/bin/env python3
from http.server import HTTPServer, BaseHTTPRequestHandler
import json

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        response = {'status': 'ok', 'message': 'Hello from Lybic!'}
        self.wfile.write(json.dumps(response).encode())
    
    def log_message(self, format, *args):
        print(f"{self.address_string()} - {format % args}")

if __name__ == '__main__':
    server = HTTPServer(('0.0.0.0', 8080), Handler)
    print("服务器启动在 http://0.0.0.0:8080")
    server.serve_forever()
"""
        
        stdin_data = base64.b64encode(server_code.encode()).decode()
        
        result = await client.sandbox.execute_process(
            sandbox_id,
            executable="bash",
            args=["-c", "mkdir -p /home/agent/server && cat > /home/agent/server/app.py && chmod +x /home/agent/server/app.py"],
            stdinBase64=stdin_data
        )
        
        if result.exitCode != 0:
            print("创建服务器脚本失败")
            return
        
        print("✓ 服务器脚本创建成功")
        
        # 2. 使用 systemd 启动服务器
        result = await client.sandbox.execute_process(
            sandbox_id,
            executable="systemd-run",
            args=[
                "--user",
                "--unit=web-server",
                "--description=Python Web Server",
                "python3",
                "/home/agent/server/app.py"
            ]
        )
        
        output = base64.b64decode(result.stdoutBase64 or '').decode()
        print(f"✓ Web 服务器已启动: {output}")
        
        # 3. 等待几秒让服务器启动
        await asyncio.sleep(3)
        
        # 4. 检查服务器状态
        result = await client.sandbox.execute_process(
            sandbox_id,
            executable="systemctl",
            args=["--user", "is-active", "web-server"]
        )
        
        status = base64.b64decode(result.stdoutBase64 or '').decode().strip()
        print(f"✓ 服务器状态: {status}")
        
        # 5. 查看日志
        result = await client.sandbox.execute_process(
            sandbox_id,
            executable="journalctl",
            args=["--user", "--unit=web-server", "-n", "10", "--no-pager"]
        )
        
        logs = base64.b64decode(result.stdoutBase64 or '').decode()
        print("✓ 服务器日志:")
        print(logs)
        
        # 6. 测试服务器
        result = await client.sandbox.execute_process(
            sandbox_id,
            executable="curl",
            args=["http://localhost:8080"]
        )
        
        response = base64.b64decode(result.stdoutBase64 or '').decode()
        print(f"✓ 服务器响应: {response}")

if __name__ == "__main__":
    asyncio.run(run_web_server())
import { LybicClient } from '@lybic/core'

const lybic = new LybicClient({
  baseUrl: 'https://api.lybic.cn',
  orgId: 'ORG-xxxx',
  apiKey: 'lysk-your-api-key-here',
})

async function runWebServer() {
  const sandboxId = 'SBX-xxxx'
  
  // 1. 创建 Web 服务器脚本
  const serverCode = `#!/usr/bin/env python3
from http.server import HTTPServer, BaseHTTPRequestHandler
import json

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        response = {'status': 'ok', 'message': 'Hello from Lybic!'}
        self.wfile.write(json.dumps(response).encode())
    
    def log_message(self, format, *args):
        print(f"{self.address_string()} - {format % args}")

if __name__ == '__main__':
    server = HTTPServer(('0.0.0.0', 8080), Handler)
    print("服务器启动在 http://0.0.0.0:8080")
    server.serve_forever()
`
  
  const stdinBase64 = btoa(serverCode)
  
  let result = await lybic.execSandboxProcess(sandboxId, {
    executable: 'bash',
    args: ['-c', 'mkdir -p /home/agent/server && cat > /home/agent/server/app.py && chmod +x /home/agent/server/app.py'],
    stdinBase64: stdinBase64,
  })
  
  if (result.data?.exitCode !== 0) {
    console.log('创建服务器脚本失败')
    return
  }
  
  console.log('✓ 服务器脚本创建成功')
  
  // 2. 使用 systemd 启动服务器
  result = await lybic.execSandboxProcess(sandboxId, {
    executable: 'systemd-run',
    args: [
      '--user',
      '--unit=web-server',
      '--description=Python Web Server',
      'python3',
      '/home/agent/server/app.py',
    ],
  })
  
  const output = atob(result.data?.stdoutBase64 || '')
  console.log('✓ Web 服务器已启动:', output)
  
  // 3. 等待几秒让服务器启动
  await new Promise(resolve => setTimeout(resolve, 3000))
  
  // 4. 检查服务器状态
  result = await lybic.execSandboxProcess(sandboxId, {
    executable: 'systemctl',
    args: ['--user', 'is-active', 'web-server'],
  })
  
  const status = atob(result.data?.stdoutBase64 || '').trim()
  console.log('✓ 服务器状态:', status)
  
  // 5. 查看日志
  result = await lybic.execSandboxProcess(sandboxId, {
    executable: 'journalctl',
    args: ['--user', '--unit=web-server', '-n', '10', '--no-pager'],
  })
  
  const logs = atob(result.data?.stdoutBase64 || '')
  console.log('✓ 服务器日志:')
  console.log(logs)
  
  // 6. 测试服务器
  result = await lybic.execSandboxProcess(sandboxId, {
    executable: 'curl',
    args: ['http://localhost:8080'],
  })
  
  const response = atob(result.data?.stdoutBase64 || '')
  console.log('✓ 服务器响应:', response)
}

runWebServer()

高级用法

设置资源限制

可以为任务设置 CPU 和内存限制:

systemd-run --user \
  --unit=resource-limited-task \
  --property=CPUQuota=50% \
  --property=MemoryMax=512M \
  python3 /home/agent/scripts/train.py

设置环境变量

systemd-run --user \
  --unit=task-with-env \
  --setenv=API_KEY=your-key \
  --setenv=DEBUG=true \
  python3 /home/agent/scripts/app.py

设置工作目录

systemd-run --user \
  --unit=task-with-workdir \
  --working-directory=/home/agent/project \
  python3 train.py

停止任务

如果需要停止正在运行的任务:

systemctl --user stop my-training-task

列出所有用户任务

systemctl --user list-units --type=service

最佳实践

  1. 使用有意义的单元名称:使用描述性的 --unit 名称,便于后续管理
  2. 添加任务描述:使用 --description 参数添加任务说明
  3. 定期检查日志:使用 journalctl 监控任务输出,及时发现问题
  4. 资源限制:为长期任务设置合理的资源限制,避免影响其他任务
  5. 错误处理:在脚本中添加错误处理和日志记录
  6. 清理任务:完成后及时停止不需要的任务,释放资源

常见问题

Q: 任务启动后如何知道它何时完成?

定期使用 systemctl --user is-active 检查任务状态。当返回 inactive 时,任务已完成。

Q: 如何查看任务的退出代码?

使用 systemctl --user show my-training-task 查看详细信息,包括 ExecMainStatus 字段。

Q: 沙箱重启后任务还会运行吗?

使用 systemd-run 创建的临时单元在沙箱重启后不会自动启动。如需持久化,需要创建永久的 systemd 服务文件。

Q: 可以同时运行多个任务吗?

可以,只需为每个任务使用不同的 --unit 名称即可。

Q: 如何获取实时日志?

由于 API 超时限制,不建议使用 journalctl -f。建议定期调用 API 获取最新日志。


相关文档

本页内容