Skip to content

Commit caee3e1

Browse files
committed
chore: finalize v4.2.1 SQLite migration — consolidate migrations, remove pgx cruft
1 parent 0d0e2e6 commit caee3e1

28 files changed

Lines changed: 295 additions & 529 deletions

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# SQLite
2-
DATABASE_URL=file:/data/cloud-cli-proxy.db
2+
DATABASE_URL=file:/data/cloud-cli-proxy.db?_texttotime=true
33

44
# Control Plane
55
CONTROL_PLANE_ADDR=:8080

docker-compose.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ services:
44
pull_policy: always
55
restart: unless-stopped
66
environment:
7-
DATABASE_URL: file:/data/cloud-cli-proxy.db
7+
DATABASE_URL: file:/data/cloud-cli-proxy.db?_texttotime=true
88
CONTROL_PLANE_ADDR: ":8080"
99
HOST_AGENT_MODE: embedded
1010
ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
1111
ADMIN_PASSWORD: ${ADMIN_PASSWORD:?Set ADMIN_PASSWORD in .env}
1212
ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET:?Set ADMIN_JWT_SECRET in .env}
1313
LOG_FORMAT: ${LOG_FORMAT:-json}
1414
LOG_LEVEL: ${LOG_LEVEL:-info}
15+
SKIP_EGRESS_VERIFY: ${SKIP_EGRESS_VERIFY:-}
1516
CONTAINER_REGISTRY: ${CONTAINER_REGISTRY:-}
1617
ports:
1718
- "${SSH_PROXY_PORT:-2222}:2222"

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ require (
4848
github.com/inconshreveable/mousetrap v1.1.0 // indirect
4949
github.com/jackc/pgpassfile v1.0.0 // indirect
5050
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
51+
github.com/jackc/puddle/v2 v2.2.2 // indirect
5152
github.com/klauspost/compress v1.18.5 // indirect
5253
github.com/kr/fs v0.1.0 // indirect
5354
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect

internal/network/provider.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package network
33
import (
44
"context"
55
"log/slog"
6+
"os"
67
"runtime"
78
)
89

@@ -41,12 +42,16 @@ type NetworkVerifier interface {
4142
}
4243

4344
// NewProvider 返回平台适配的 Provider 实现。
44-
// 内部根据 runtime.GOOS 选择 NetworkVerifier:
45-
// - Linux:DockerVerifier,通过 docker exec 进入容器做真实网络验证
46-
// - 非 Linux:NopVerifier,返回 safe-pass 结果不阻塞开发
45+
//
46+
// Verifier 选择规则(按优先级):
47+
// 1. SKIP_EGRESS_VERIFY=true → NopVerifier(Windows/macOS Docker Desktop 开发环境)
48+
// 2. runtime.GOOS != "linux" → NopVerifier(非 Linux 原生环境)
49+
// 3. 默认 → DockerVerifier(Linux 生产环境,通过 docker exec 做真实网络验证)
4750
func NewProvider(logger *slog.Logger) Provider {
4851
var verifier NetworkVerifier
49-
if runtime.GOOS == "linux" {
52+
if os.Getenv("SKIP_EGRESS_VERIFY") == "true" {
53+
verifier = &NopVerifier{}
54+
} else if runtime.GOOS == "linux" {
5055
verifier = &DockerVerifier{}
5156
} else {
5257
verifier = &NopVerifier{}
Lines changed: 257 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,273 @@
1-
CREATE TABLE users (
2-
id TEXT PRIMARY KEY,
3-
username TEXT NOT NULL UNIQUE,
4-
password_hash TEXT,
5-
status TEXT NOT NULL DEFAULT 'active',
6-
created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
7-
updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP)
1+
-- 0001_initial.sql
2+
-- Cloud CLI Proxy 初始 schema(SQLite)
3+
-- 将所有增量迁移合并为单文件,直接反映最终表结构。
4+
5+
-- ============================================================
6+
-- users
7+
-- ============================================================
8+
CREATE TABLE IF NOT EXISTS users (
9+
id TEXT PRIMARY KEY,
10+
username TEXT NOT NULL UNIQUE,
11+
password_hash TEXT,
12+
status TEXT NOT NULL DEFAULT 'active',
13+
created_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
14+
updated_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
15+
expires_at DATETIME,
16+
short_id TEXT UNIQUE,
17+
entry_password TEXT NOT NULL DEFAULT '',
18+
role TEXT NOT NULL DEFAULT 'user',
19+
ssh_public_key TEXT DEFAULT '',
20+
ssh_private_key TEXT DEFAULT '',
21+
ssh_key_type TEXT DEFAULT ''
822
);
923

10-
CREATE TABLE hosts (
11-
id TEXT PRIMARY KEY,
12-
user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
13-
status TEXT NOT NULL DEFAULT 'pending',
14-
template_image_ref TEXT NOT NULL,
15-
home_volume_name TEXT NOT NULL,
16-
slot_key TEXT NOT NULL,
17-
created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
18-
updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
24+
-- ============================================================
25+
-- hosts
26+
-- ============================================================
27+
CREATE TABLE IF NOT EXISTS hosts (
28+
id TEXT PRIMARY KEY,
29+
user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
30+
status TEXT NOT NULL DEFAULT 'pending',
31+
template_image_ref TEXT NOT NULL,
32+
home_volume_name TEXT NOT NULL,
33+
slot_key TEXT NOT NULL,
34+
created_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
35+
updated_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
36+
timezone TEXT NOT NULL DEFAULT 'America/Los_Angeles',
37+
hostname TEXT NOT NULL DEFAULT '',
38+
short_id TEXT UNIQUE,
39+
memory_limit_mb INTEGER,
40+
cpu_limit REAL,
41+
disk_limit_gb INTEGER,
42+
host_mounts TEXT NOT NULL DEFAULT '[]',
1943
UNIQUE (user_id, slot_key)
2044
);
2145

22-
CREATE TABLE egress_ips (
23-
id TEXT PRIMARY KEY,
24-
label TEXT NOT NULL,
25-
ip_address TEXT NOT NULL UNIQUE,
26-
provider TEXT NOT NULL DEFAULT 'manual',
27-
status TEXT NOT NULL DEFAULT 'available',
28-
created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
29-
updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP)
46+
-- ============================================================
47+
-- egress_ips
48+
-- ============================================================
49+
CREATE TABLE IF NOT EXISTS egress_ips (
50+
id TEXT PRIMARY KEY,
51+
label TEXT NOT NULL,
52+
ip_address TEXT NOT NULL UNIQUE,
53+
provider TEXT NOT NULL DEFAULT 'manual',
54+
status TEXT NOT NULL DEFAULT 'available',
55+
created_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
56+
updated_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
57+
proxy_config TEXT,
58+
detected_ip_address TEXT
3059
);
3160

32-
CREATE TABLE host_egress_bindings (
33-
id TEXT PRIMARY KEY,
34-
host_id TEXT NOT NULL REFERENCES hosts (id) ON DELETE CASCADE,
61+
-- ============================================================
62+
-- host_egress_bindings
63+
-- ============================================================
64+
CREATE TABLE IF NOT EXISTS host_egress_bindings (
65+
id TEXT PRIMARY KEY,
66+
host_id TEXT NOT NULL REFERENCES hosts (id) ON DELETE CASCADE,
3567
egress_ip_id TEXT NOT NULL REFERENCES egress_ips (id) ON DELETE RESTRICT,
36-
created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
68+
created_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
3769
UNIQUE (host_id, egress_ip_id)
3870
);
3971

40-
CREATE TABLE tasks (
41-
id TEXT PRIMARY KEY,
42-
host_id TEXT REFERENCES hosts (id) ON DELETE SET NULL,
43-
kind TEXT NOT NULL,
44-
status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'succeeded', 'failed', 'canceled')),
45-
requested_by TEXT NOT NULL,
46-
error_code TEXT,
47-
error_message TEXT,
72+
-- ============================================================
73+
-- tasks
74+
-- ============================================================
75+
CREATE TABLE IF NOT EXISTS tasks (
76+
id TEXT PRIMARY KEY,
77+
host_id TEXT REFERENCES hosts (id) ON DELETE SET NULL,
78+
kind TEXT NOT NULL,
79+
status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'succeeded', 'failed', 'canceled')),
80+
requested_by TEXT NOT NULL,
81+
error_code TEXT,
82+
error_message TEXT,
4883
last_error_summary TEXT,
49-
created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
50-
updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP)
84+
created_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
85+
updated_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
86+
progress_percent INTEGER NOT NULL DEFAULT 0,
87+
progress_message TEXT NOT NULL DEFAULT ''
88+
);
89+
90+
-- ============================================================
91+
-- events
92+
-- ============================================================
93+
CREATE TABLE IF NOT EXISTS events (
94+
id TEXT PRIMARY KEY,
95+
task_id TEXT REFERENCES tasks (id) ON DELETE SET NULL,
96+
host_id TEXT REFERENCES hosts (id) ON DELETE SET NULL,
97+
level TEXT NOT NULL DEFAULT 'info',
98+
type TEXT NOT NULL,
99+
message TEXT NOT NULL,
100+
metadata TEXT NOT NULL DEFAULT '{}',
101+
created_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
102+
user_id TEXT REFERENCES users (id) ON DELETE SET NULL
103+
);
104+
105+
-- ============================================================
106+
-- claude_accounts
107+
-- ============================================================
108+
CREATE TABLE IF NOT EXISTS claude_accounts (
109+
id TEXT PRIMARY KEY,
110+
user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
111+
host_id TEXT REFERENCES hosts (id) ON DELETE SET NULL,
112+
email TEXT NOT NULL,
113+
display_name TEXT NOT NULL DEFAULT '',
114+
status TEXT NOT NULL DEFAULT 'active',
115+
created_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
116+
updated_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
117+
persistent_volume_name TEXT
118+
);
119+
120+
-- ============================================================
121+
-- ssh_keys
122+
-- ============================================================
123+
CREATE TABLE IF NOT EXISTS ssh_keys (
124+
id TEXT PRIMARY KEY,
125+
user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
126+
purpose TEXT NOT NULL CHECK (purpose IN ('inbound', 'outbound')),
127+
label TEXT NOT NULL DEFAULT '',
128+
public_key TEXT NOT NULL,
129+
private_key TEXT NOT NULL DEFAULT '',
130+
key_type TEXT NOT NULL DEFAULT 'ed25519',
131+
fingerprint TEXT NOT NULL DEFAULT '',
132+
created_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP)
133+
);
134+
135+
-- ============================================================
136+
-- host_bypass_presets
137+
-- ============================================================
138+
CREATE TABLE IF NOT EXISTS host_bypass_presets (
139+
id TEXT PRIMARY KEY,
140+
slug TEXT NOT NULL UNIQUE,
141+
name TEXT NOT NULL,
142+
description TEXT,
143+
is_system INTEGER NOT NULL DEFAULT 0,
144+
is_force_on INTEGER NOT NULL DEFAULT 0,
145+
is_active INTEGER NOT NULL DEFAULT 1,
146+
rules TEXT NOT NULL DEFAULT '[]',
147+
created_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
148+
updated_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP)
51149
);
52150

53-
CREATE TABLE events (
54-
id TEXT PRIMARY KEY,
55-
task_id TEXT REFERENCES tasks (id) ON DELETE SET NULL,
56-
host_id TEXT REFERENCES hosts (id) ON DELETE SET NULL,
57-
level TEXT NOT NULL DEFAULT 'info',
58-
type TEXT NOT NULL,
59-
message TEXT NOT NULL,
60-
metadata TEXT NOT NULL DEFAULT '{}',
61-
created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP)
151+
-- ============================================================
152+
-- host_bypass_rules
153+
-- ============================================================
154+
CREATE TABLE IF NOT EXISTS host_bypass_rules (
155+
id TEXT PRIMARY KEY,
156+
scope TEXT NOT NULL CHECK (scope IN ('global', 'host')),
157+
host_id TEXT REFERENCES hosts (id) ON DELETE CASCADE,
158+
rule_type TEXT NOT NULL CHECK (rule_type IN ('ip','cidr','domain','domain_suffix','domain_keyword','port')),
159+
value TEXT NOT NULL,
160+
note TEXT,
161+
is_risky INTEGER NOT NULL DEFAULT 0,
162+
created_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
163+
updated_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
164+
CONSTRAINT chk_bypass_rule_scope CHECK (
165+
(scope = 'global' AND host_id IS NULL) OR
166+
(scope = 'host' AND host_id IS NOT NULL)
167+
)
62168
);
63169

64-
CREATE INDEX idx_tasks_status_updated_at ON tasks (status, updated_at DESC);
65-
CREATE INDEX idx_hosts_user_id ON hosts (user_id);
66-
CREATE INDEX idx_host_egress_bindings_host_id ON host_egress_bindings (host_id);
170+
-- ============================================================
171+
-- host_bypass_bindings
172+
-- ============================================================
173+
CREATE TABLE IF NOT EXISTS host_bypass_bindings (
174+
id TEXT PRIMARY KEY,
175+
host_id TEXT NOT NULL REFERENCES hosts (id) ON DELETE CASCADE,
176+
preset_id TEXT REFERENCES host_bypass_presets (id) ON DELETE RESTRICT,
177+
rule_id TEXT REFERENCES host_bypass_rules (id) ON DELETE CASCADE,
178+
enabled INTEGER NOT NULL DEFAULT 1,
179+
source TEXT NOT NULL DEFAULT 'admin' CHECK (source IN ('admin','system')),
180+
created_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
181+
CONSTRAINT chk_bypass_binding_xor CHECK (
182+
(preset_id IS NOT NULL AND rule_id IS NULL) OR
183+
(preset_id IS NULL AND rule_id IS NOT NULL)
184+
)
185+
);
186+
187+
-- ============================================================
188+
-- host_bypass_snapshots
189+
-- ============================================================
190+
CREATE TABLE IF NOT EXISTS host_bypass_snapshots (
191+
id TEXT PRIMARY KEY,
192+
host_id TEXT NOT NULL REFERENCES hosts (id) ON DELETE CASCADE,
193+
version INTEGER NOT NULL,
194+
config_hash TEXT NOT NULL,
195+
whitelist_cidrs_json TEXT NOT NULL DEFAULT '{"version":3,"rules":[]}',
196+
whitelist_domains_json TEXT NOT NULL DEFAULT '{"version":3,"rules":[]}',
197+
applied_status TEXT NOT NULL DEFAULT 'pending'
198+
CHECK (applied_status IN ('pending','applied','failed','rolled_back')),
199+
source TEXT NOT NULL DEFAULT 'apply',
200+
created_by TEXT,
201+
created_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP),
202+
UNIQUE (host_id, config_hash)
203+
);
204+
205+
-- ============================================================
206+
-- host_bypass_audit_log
207+
-- ============================================================
208+
CREATE TABLE IF NOT EXISTS host_bypass_audit_log (
209+
id TEXT PRIMARY KEY,
210+
actor_id TEXT,
211+
actor_ip TEXT,
212+
action TEXT NOT NULL,
213+
target_kind TEXT NOT NULL,
214+
target_id TEXT,
215+
before TEXT,
216+
after TEXT,
217+
note TEXT,
218+
created_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP)
219+
);
220+
221+
-- ============================================================
222+
-- 索引
223+
-- ============================================================
224+
225+
-- tasks
226+
CREATE INDEX IF NOT EXISTS idx_tasks_status_updated_at ON tasks (status, updated_at DESC);
227+
CREATE INDEX IF NOT EXISTS idx_tasks_stale ON tasks (status, updated_at) WHERE status IN ('pending', 'running');
228+
229+
-- hosts
230+
CREATE INDEX IF NOT EXISTS idx_hosts_user_id ON hosts (user_id);
231+
CREATE INDEX IF NOT EXISTS idx_hosts_status_running ON hosts (status) WHERE status = 'running';
232+
CREATE UNIQUE INDEX IF NOT EXISTS idx_hosts_user_active
233+
ON hosts (user_id)
234+
WHERE status NOT IN ('deleted', 'archived');
235+
236+
-- host_egress_bindings
237+
CREATE INDEX IF NOT EXISTS idx_host_egress_bindings_host_id ON host_egress_bindings (host_id);
238+
239+
-- events
240+
CREATE INDEX IF NOT EXISTS idx_events_created_at ON events (created_at DESC);
241+
CREATE INDEX IF NOT EXISTS idx_events_type_created_at ON events (type, created_at DESC);
242+
CREATE INDEX IF NOT EXISTS idx_events_user_id_created_at ON events (user_id, created_at DESC);
243+
CREATE INDEX IF NOT EXISTS idx_events_host_id_created_at ON events (host_id, created_at DESC);
244+
245+
-- claude_accounts
246+
CREATE INDEX IF NOT EXISTS idx_claude_accounts_user_id ON claude_accounts (user_id);
247+
CREATE INDEX IF NOT EXISTS idx_claude_accounts_host_id ON claude_accounts (host_id);
248+
CREATE UNIQUE INDEX IF NOT EXISTS idx_claude_accounts_email ON claude_accounts (email);
249+
250+
-- ssh_keys
251+
CREATE INDEX IF NOT EXISTS idx_ssh_keys_user_id ON ssh_keys (user_id);
252+
CREATE INDEX IF NOT EXISTS idx_ssh_keys_user_purpose ON ssh_keys (user_id, purpose);
253+
254+
-- bypass tables
255+
CREATE INDEX IF NOT EXISTS idx_bypass_rules_host ON host_bypass_rules (host_id) WHERE host_id IS NOT NULL;
256+
CREATE INDEX IF NOT EXISTS idx_bypass_bindings_host ON host_bypass_bindings (host_id);
257+
CREATE INDEX IF NOT EXISTS idx_bypass_snapshots_host_version ON host_bypass_snapshots (host_id, version DESC);
258+
CREATE INDEX IF NOT EXISTS idx_bypass_audit_target ON host_bypass_audit_log (target_kind, target_id);
259+
CREATE INDEX IF NOT EXISTS idx_bypass_audit_created ON host_bypass_audit_log (created_at DESC);
260+
261+
-- ============================================================
262+
-- 系统预设 seed
263+
-- ============================================================
264+
INSERT OR IGNORE INTO host_bypass_presets (id, slug, name, description, is_system, is_force_on, is_active, rules)
265+
VALUES
266+
(hex(randomblob(16)), 'loopback', '本机回环',
267+
'127.0.0.0/8 与 169.254.0.0/16(链路本地),强制开启不可关闭。',
268+
1, 1, 1,
269+
'[{"rule_type":"cidr","value":"127.0.0.0/8"},{"rule_type":"cidr","value":"169.254.0.0/16"}]'),
270+
(hex(randomblob(16)), 'lan', '局域网',
271+
'RFC1918(10/8、172.16/12、192.168/16)+ CGNAT 100.64/10。',
272+
1, 0, 1,
273+
'[{"rule_type":"cidr","value":"10.0.0.0/8"},{"rule_type":"cidr","value":"172.16.0.0/12"},{"rule_type":"cidr","value":"192.168.0.0/16"},{"rule_type":"cidr","value":"100.64.0.0/10"}]');

internal/store/migrations/0002_egress_tunnel.sql

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)