|
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 '' |
8 | 22 | ); |
9 | 23 |
|
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 '[]', |
19 | 43 | UNIQUE (user_id, slot_key) |
20 | 44 | ); |
21 | 45 |
|
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 |
30 | 59 | ); |
31 | 60 |
|
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, |
35 | 67 | 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), |
37 | 69 | UNIQUE (host_id, egress_ip_id) |
38 | 70 | ); |
39 | 71 |
|
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, |
48 | 83 | 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) |
51 | 149 | ); |
52 | 150 |
|
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 | + ) |
62 | 168 | ); |
63 | 169 |
|
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"}]'); |
0 commit comments