sql-harness 0.1.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. {sql_harness-0.1.0 → sql_harness-0.2.2}/AGENTS.md +4 -10
  2. {sql_harness-0.1.0 → sql_harness-0.2.2}/PKG-INFO +5 -56
  3. {sql_harness-0.1.0 → sql_harness-0.2.2}/README.md +4 -55
  4. sql_harness-0.2.2/SKILL.md +101 -0
  5. {sql_harness-0.1.0 → sql_harness-0.2.2}/agent-workspace/README.md +2 -2
  6. sql_harness-0.2.2/agent-workspace/skills/README.md +26 -0
  7. sql_harness-0.2.2/agent-workspace/skills/docker-deploy.md +85 -0
  8. sql_harness-0.2.2/agent-workspace/skills/pool.md +30 -0
  9. sql_harness-0.2.2/agent-workspace/skills/ssh.md +70 -0
  10. {sql_harness-0.1.0 → sql_harness-0.2.2}/agent-workspace/skills/workspace.md +4 -13
  11. sql_harness-0.2.2/docker-services/coturn/README.md +69 -0
  12. sql_harness-0.2.2/docker-services/coturn/docker-compose.yml +29 -0
  13. sql_harness-0.2.2/docker-services/coturn/turnserver.conf +36 -0
  14. sql_harness-0.2.2/docker-services/mysql/docker-compose.yml +35 -0
  15. sql_harness-0.2.2/docker-services/redis/docker-compose.yml +24 -0
  16. sql_harness-0.2.2/install.md +141 -0
  17. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/aggregations.md +3 -34
  18. sql_harness-0.2.2/interaction-skills/docker-via-ssh.md +137 -0
  19. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/indexes-and-explain.md +12 -8
  20. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/joins.md +8 -62
  21. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/recursive-ctes.md +3 -55
  22. sql_harness-0.2.2/interaction-skills/references/aggregations-advanced.md +64 -0
  23. sql_harness-0.2.2/interaction-skills/references/joins-advanced.md +60 -0
  24. sql_harness-0.2.2/interaction-skills/references/recursive-ctes-patterns.md +61 -0
  25. sql_harness-0.2.2/interaction-skills/references/window-functions-catalog.md +91 -0
  26. sql_harness-0.2.2/interaction-skills/save-run-cycle.md +76 -0
  27. sql_harness-0.2.2/interaction-skills/window-functions.md +38 -0
  28. sql_harness-0.2.2/interaction-skills/zone-skill-auto-surface.md +89 -0
  29. sql_harness-0.2.2/practice/pgexercises/README.md +63 -0
  30. {sql_harness-0.1.0 → sql_harness-0.2.2}/pyproject.toml +19 -1
  31. sql_harness-0.2.2/skills/sql-harness/SKILL.md +1 -0
  32. sql_harness-0.2.2/src/sql_harness/SKILL.md +1 -0
  33. {sql_harness-0.1.0 → sql_harness-0.2.2}/src/sql_harness/cli.py +108 -0
  34. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/integration/test_mysql.py +39 -39
  35. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/integration/test_pg_optimization.py +144 -144
  36. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/integration/test_pgexercises_practice.py +62 -62
  37. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/integration/test_postgres.py +67 -67
  38. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/unit/test_agent_loader.py +47 -47
  39. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/unit/test_cli.py +221 -221
  40. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/unit/test_config.py +141 -141
  41. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/unit/test_helpers.py +195 -195
  42. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/unit/test_manager.py +90 -90
  43. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/unit/test_paths.py +50 -50
  44. {sql_harness-0.1.0 → sql_harness-0.2.2}/uv.lock +70 -13
  45. sql_harness-0.1.0/SKILL.md +0 -61
  46. sql_harness-0.1.0/agent-workspace/skills/pool.md +0 -43
  47. sql_harness-0.1.0/agent-workspace/skills/ssh.md +0 -89
  48. sql_harness-0.1.0/install.md +0 -77
  49. sql_harness-0.1.0/interaction-skills/save-run-cycle.md +0 -138
  50. sql_harness-0.1.0/interaction-skills/window-functions.md +0 -104
  51. sql_harness-0.1.0/interaction-skills/zone-skill-auto-surface.md +0 -138
  52. sql_harness-0.1.0/skills/sql-harness/SKILL.md +0 -1
  53. sql_harness-0.1.0/src/sql_harness/SKILL.md +0 -61
  54. {sql_harness-0.1.0 → sql_harness-0.2.2}/.claude-plugin/marketplace.json +0 -0
  55. {sql_harness-0.1.0 → sql_harness-0.2.2}/.claude-plugin/plugin.json +0 -0
  56. {sql_harness-0.1.0 → sql_harness-0.2.2}/.env.example +0 -0
  57. {sql_harness-0.1.0 → sql_harness-0.2.2}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  58. {sql_harness-0.1.0 → sql_harness-0.2.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  59. {sql_harness-0.1.0 → sql_harness-0.2.2}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  60. {sql_harness-0.1.0 → sql_harness-0.2.2}/.gitignore +0 -0
  61. {sql_harness-0.1.0 → sql_harness-0.2.2}/agent-workspace/agent_helpers.example.py +0 -0
  62. {sql_harness-0.1.0 → sql_harness-0.2.2}/agent-workspace/agent_helpers.py +0 -0
  63. {sql_harness-0.1.0 → sql_harness-0.2.2}/agent-workspace/zones/example_pg/scripts/sh_demo_crud.py +0 -0
  64. {sql_harness-0.1.0 → sql_harness-0.2.2}/agent-workspace/zones/example_pg/skills/sh_demo-schema.md +0 -0
  65. {sql_harness-0.1.0 → sql_harness-0.2.2}/docs/connections-toml-example.toml +0 -0
  66. {sql_harness-0.1.0 → sql_harness-0.2.2}/docs/headless-notes.md +0 -0
  67. {sql_harness-0.1.0 → sql_harness-0.2.2}/docs/maintainer.md +0 -0
  68. {sql_harness-0.1.0 → sql_harness-0.2.2}/docs/standalone-install.md +0 -0
  69. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/encoding-and-charset.md +0 -0
  70. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/json-columns.md +0 -0
  71. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/large-result-sets.md +0 -0
  72. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/migrations.md +0 -0
  73. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/pooling.md +0 -0
  74. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/postgres/btree-indexes.md +0 -0
  75. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/postgres/plan-reading.md +0 -0
  76. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/postgres/slow-queries-joins.md +0 -0
  77. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/postgres/specialized-indexes.md +0 -0
  78. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/postgres/table-optimization.md +0 -0
  79. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/schema-introspection.md +0 -0
  80. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/ssh.md +0 -0
  81. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/timeouts-and-cancellation.md +0 -0
  82. {sql_harness-0.1.0 → sql_harness-0.2.2}/interaction-skills/transactions.md +0 -0
  83. {sql_harness-0.1.0 → sql_harness-0.2.2}/skills/sql-harness/README.txt +0 -0
  84. {sql_harness-0.1.0 → sql_harness-0.2.2}/skills/sql-harness/references/install.md +0 -0
  85. {sql_harness-0.1.0 → sql_harness-0.2.2}/sql-harness +0 -0
  86. {sql_harness-0.1.0 → sql_harness-0.2.2}/src/sql_harness/__init__.py +0 -0
  87. {sql_harness-0.1.0 → sql_harness-0.2.2}/src/sql_harness/agent_loader.py +0 -0
  88. {sql_harness-0.1.0 → sql_harness-0.2.2}/src/sql_harness/config.py +0 -0
  89. {sql_harness-0.1.0 → sql_harness-0.2.2}/src/sql_harness/drivers/__init__.py +0 -0
  90. {sql_harness-0.1.0 → sql_harness-0.2.2}/src/sql_harness/drivers/mysql.py +0 -0
  91. {sql_harness-0.1.0 → sql_harness-0.2.2}/src/sql_harness/drivers/postgres.py +0 -0
  92. {sql_harness-0.1.0 → sql_harness-0.2.2}/src/sql_harness/drivers/redis.py +0 -0
  93. {sql_harness-0.1.0 → sql_harness-0.2.2}/src/sql_harness/drivers/sqlite.py +0 -0
  94. {sql_harness-0.1.0 → sql_harness-0.2.2}/src/sql_harness/drivers/ssh.py +0 -0
  95. {sql_harness-0.1.0 → sql_harness-0.2.2}/src/sql_harness/helpers.py +0 -0
  96. {sql_harness-0.1.0 → sql_harness-0.2.2}/src/sql_harness/manager.py +0 -0
  97. {sql_harness-0.1.0 → sql_harness-0.2.2}/src/sql_harness/paths.py +0 -0
  98. {sql_harness-0.1.0 → sql_harness-0.2.2}/src/sql_harness/run.py +0 -0
  99. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/__init__.py +0 -0
  100. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/conftest.py +0 -0
  101. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/integration/__init__.py +0 -0
  102. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/integration/test_ssh.py +0 -0
  103. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/unit/__init__.py +0 -0
  104. {sql_harness-0.1.0 → sql_harness-0.2.2}/tests/unit/test_ssh.py +0 -0
@@ -8,13 +8,13 @@ sql-harness is the same idea for SQL — connects agents to relational databases
8
8
  - Versatility
9
9
 
10
10
  # Overview
11
- Core code lives in `lab/sql_harness/src/sql_harness/` (src-layout, mirrors browser-harness's `src/browser_harness/`):
11
+ Core code lives in `lab/sql_harness/src/sql_harness/`:
12
12
  - `config.py` — TOML schema + ConnectionConfig / ConnectionsConfig dataclasses
13
- - `paths.py` — XDG-style state directory resolution (mirrors browser-harness paths.py)
13
+ - `paths.py` — XDG-style state directory resolution
14
14
  - `drivers/` — backend abstractions (postgres, mysql, redis stub, sqlite for tests)
15
15
  - `manager.py` — SqlHarness class: holds engines, workspaces, skills registry
16
16
  - `helpers.py` — heredoc helpers (auto-imported)
17
- - `agent_loader.py` — loads `$BH_SQL_AGENT_WORKSPACE/agent_helpers.py` (mirrors browser-harness helpers.py:493-508)
17
+ - `agent_loader.py` — loads `$BH_SQL_AGENT_WORKSPACE/agent_helpers.py`
18
18
  - `run.py` — heredoc entry; delegates to CLI otherwise
19
19
  - `cli.py` — argparse subparser CLI (~10 subcommands)
20
20
 
@@ -28,10 +28,4 @@ An agent operating the harness only edits inside `agent-workspace/`:
28
28
  # Contributing
29
29
  Consider what is really needed. Prefer the smallest diff that fixes the bug.
30
30
 
31
- # Driver extension
32
- To add a new backend (Redis is the obvious next target):
33
- 1. Add the package to `pyproject.toml` via `uv add <pkg>`.
34
- 2. Create `drivers/<name>.py` implementing the `Driver` protocol (see `drivers/__init__.py`).
35
- 3. Register it in `drivers/__init__.py:get_driver()`.
36
- 4. Add a unit test in `tests/unit/test_<name>.py`.
37
- 5. Optionally add an integration test in `tests/integration/test_<name>.py` gated on `BH_<NAME>_URL`.
31
+ To add a backend: implement `drivers/<name>.py` against the Driver protocol, register in `drivers/__init__.py:get_driver()`, add `tests/unit/test_<name>.py` (see install.md §Drivers).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql-harness
3
- Version: 0.1.0
3
+ Version: 0.2.2
4
4
  Summary: Single-process SQL + SSH CLI for LLM agents. PostgreSQL/MySQL/SSH via SQLAlchemy + paramiko. Plaintext credentials in one TOML file. Helpers auto-injected into the heredoc namespace.
5
5
  Project-URL: Source, https://github.com/zhaoliuxue/much_bigpy/tree/master/lab/sql_harness
6
6
  Project-URL: Issues, https://github.com/zhaoliuxue/much_bigpy/issues
@@ -25,6 +25,8 @@ Description-Content-Type: text/markdown
25
25
 
26
26
  A thin, single-process SQL CLI for LLM agents. Mirrors [browser-harness](https://github.com/browser-use/browser-harness)'s structure but targets relational databases (Postgres, MySQL, Redis-soon).
27
27
 
28
+ Connection file format, env vars, and driver list: see `install.md`.
29
+
28
30
  ## Quickstart
29
31
 
30
32
  ```bash
@@ -53,7 +55,7 @@ PY
53
55
 
54
56
  - `install.md` — first-time install + first connection
55
57
  - `SKILL.md` — day-to-day usage
56
- - `lab/sql_harness/src/sql_harness/` — protected core package (src-layout, mirrors browser-harness)
58
+ - `lab/sql_harness/src/sql_harness/` — protected core package
57
59
  - `${XDG_CONFIG_HOME:-~/.config}/sql-harness/connections.toml` — plaintext credentials in ONE place
58
60
  - `${XDG_CONFIG_HOME:-~/.config}/sql-harness/agent-workspace/agent_helpers.py` — task-specific helpers
59
61
  - `${XDG_CONFIG_HOME:-~/.config}/sql-harness/agent-workspace/skills/` — per-task/per-table skills
@@ -72,59 +74,6 @@ PY
72
74
  - Not a connection-string parser (`pgcli`/`mycli` already exist for interactive REPL use; this is for *agents*).
73
75
  - Not a long-running daemon. SQL connections don't need one.
74
76
 
75
- ## Connection file format
76
-
77
- Default: `${XDG_CONFIG_HOME:-~/.config}/sql-harness/connections.toml`.
78
-
79
- ```toml
80
- default_workspace = "local_pg"
81
-
82
- [pool_defaults]
83
- size = 5
84
- recycle = 3600
85
- pre_ping = true
86
- echo = false
87
-
88
- [[connections]]
89
- name = "local_pg"
90
- driver = "postgres"
91
- url = "postgresql+psycopg://postgres:postgres@localhost:5432/postgres"
92
- description = "Local PostgreSQL for dev"
93
-
94
- [[connections]]
95
- name = "prod_pg"
96
- driver = "postgres"
97
- url = "${env:PROD_PG_URL}" # env-var indirection
98
- pool_size = 10
99
- ```
100
-
101
- Use `${env:VAR}` placeholders inside `url` for prod secrets — they're expanded at load time from the process environment.
102
-
103
- ## Configuration env vars
104
-
105
- | Var | Purpose |
106
- |---|---|
107
- | `BH_SQL_HOME` | Override state root (`~/.config/sql-harness` by default) |
108
- | `BH_SQL_CONFIG_FILE` | Override connections.toml path |
109
- | `BH_SQL_AGENT_WORKSPACE` | Override agent-workspace directory |
110
- | `BH_SQL_RUNTIME_DIR` | Override runtime state dir |
111
- | `BH_SQL_TMP_DIR` | Override temp dir |
112
- | `BH_PG_URL` | Used by integration tests (skipped if unset) |
113
- | `BH_MYSQL_URL` | Used by integration tests (skipped if unset) |
114
-
115
- ## Drivers
116
-
117
- | Driver | Backend | Package | Status |
118
- |---|---|---|---|
119
- | `postgres` | PostgreSQL | `psycopg[binary]>=3.2` | ✅ |
120
- | `mysql` | MySQL | `pymysql>=1.1` | ✅ |
121
- | `redis` | Redis | `redis>=5.0` (not yet added) | 🚧 stub |
122
- | `sqlite` | SQLite | stdlib (`sqlite3`) | ✅ (test-only) |
123
- | `ssh` / `ssh+password` / `ssh+key` | remote shell + SFTP (paramiko) | `paramiko>=3.4` | ✅ |
124
-
125
- To enable Redis: `uv add "redis>=5.0,<6"` then implement `drivers/redis.py`.
126
- To add another SSH host: append a `[[connections]]` block with `driver = "ssh"` and an `ssh://...` URL.
127
-
128
77
  ## Contributing
129
78
 
130
79
  PRs and improvements welcome. See `AGENTS.md` for code priorities.
@@ -134,4 +83,4 @@ PRs and improvements welcome. See `AGENTS.md` for code priorities.
134
83
 
135
84
  ## License
136
85
 
137
- MIT.
86
+ MIT.
@@ -2,6 +2,8 @@
2
2
 
3
3
  A thin, single-process SQL CLI for LLM agents. Mirrors [browser-harness](https://github.com/browser-use/browser-harness)'s structure but targets relational databases (Postgres, MySQL, Redis-soon).
4
4
 
5
+ Connection file format, env vars, and driver list: see `install.md`.
6
+
5
7
  ## Quickstart
6
8
 
7
9
  ```bash
@@ -30,7 +32,7 @@ PY
30
32
 
31
33
  - `install.md` — first-time install + first connection
32
34
  - `SKILL.md` — day-to-day usage
33
- - `lab/sql_harness/src/sql_harness/` — protected core package (src-layout, mirrors browser-harness)
35
+ - `lab/sql_harness/src/sql_harness/` — protected core package
34
36
  - `${XDG_CONFIG_HOME:-~/.config}/sql-harness/connections.toml` — plaintext credentials in ONE place
35
37
  - `${XDG_CONFIG_HOME:-~/.config}/sql-harness/agent-workspace/agent_helpers.py` — task-specific helpers
36
38
  - `${XDG_CONFIG_HOME:-~/.config}/sql-harness/agent-workspace/skills/` — per-task/per-table skills
@@ -49,59 +51,6 @@ PY
49
51
  - Not a connection-string parser (`pgcli`/`mycli` already exist for interactive REPL use; this is for *agents*).
50
52
  - Not a long-running daemon. SQL connections don't need one.
51
53
 
52
- ## Connection file format
53
-
54
- Default: `${XDG_CONFIG_HOME:-~/.config}/sql-harness/connections.toml`.
55
-
56
- ```toml
57
- default_workspace = "local_pg"
58
-
59
- [pool_defaults]
60
- size = 5
61
- recycle = 3600
62
- pre_ping = true
63
- echo = false
64
-
65
- [[connections]]
66
- name = "local_pg"
67
- driver = "postgres"
68
- url = "postgresql+psycopg://postgres:postgres@localhost:5432/postgres"
69
- description = "Local PostgreSQL for dev"
70
-
71
- [[connections]]
72
- name = "prod_pg"
73
- driver = "postgres"
74
- url = "${env:PROD_PG_URL}" # env-var indirection
75
- pool_size = 10
76
- ```
77
-
78
- Use `${env:VAR}` placeholders inside `url` for prod secrets — they're expanded at load time from the process environment.
79
-
80
- ## Configuration env vars
81
-
82
- | Var | Purpose |
83
- |---|---|
84
- | `BH_SQL_HOME` | Override state root (`~/.config/sql-harness` by default) |
85
- | `BH_SQL_CONFIG_FILE` | Override connections.toml path |
86
- | `BH_SQL_AGENT_WORKSPACE` | Override agent-workspace directory |
87
- | `BH_SQL_RUNTIME_DIR` | Override runtime state dir |
88
- | `BH_SQL_TMP_DIR` | Override temp dir |
89
- | `BH_PG_URL` | Used by integration tests (skipped if unset) |
90
- | `BH_MYSQL_URL` | Used by integration tests (skipped if unset) |
91
-
92
- ## Drivers
93
-
94
- | Driver | Backend | Package | Status |
95
- |---|---|---|---|
96
- | `postgres` | PostgreSQL | `psycopg[binary]>=3.2` | ✅ |
97
- | `mysql` | MySQL | `pymysql>=1.1` | ✅ |
98
- | `redis` | Redis | `redis>=5.0` (not yet added) | 🚧 stub |
99
- | `sqlite` | SQLite | stdlib (`sqlite3`) | ✅ (test-only) |
100
- | `ssh` / `ssh+password` / `ssh+key` | remote shell + SFTP (paramiko) | `paramiko>=3.4` | ✅ |
101
-
102
- To enable Redis: `uv add "redis>=5.0,<6"` then implement `drivers/redis.py`.
103
- To add another SSH host: append a `[[connections]]` block with `driver = "ssh"` and an `ssh://...` URL.
104
-
105
54
  ## Contributing
106
55
 
107
56
  PRs and improvements welcome. See `AGENTS.md` for code priorities.
@@ -111,4 +60,4 @@ PRs and improvements welcome. See `AGENTS.md` for code priorities.
111
60
 
112
61
  ## License
113
62
 
114
- MIT.
63
+ MIT.
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: sql-harness
3
+ description: "Always use sql-harness for any SQL work — querying, schema inspection, migrations, and cross-database workflows."
4
+ ---
5
+
6
+ # sql-harness
7
+
8
+ Direct SQL access via a thin Python heredoc CLI. Connections live in plaintext in a single TOML file (default: `~/.config/sql-harness/connections.toml`).
9
+
10
+ For setup or install problems, read `install.md`. For stuck-point mechanics, see `interaction-skills/` (cross-DB) and `interaction-skills/postgres/` — PG optimization depth.
11
+
12
+ ## Usage
13
+
14
+ ```bash
15
+ sql-harness --help
16
+ sql-harness list
17
+ sql-harness add <name> --driver postgres --url 'postgresql://...'
18
+ sql-harness test <name>
19
+
20
+ sql-harness <<'PY'
21
+ use_workspace("local_pg")
22
+ print(query("SELECT version()"))
23
+ print(list_tables())
24
+ PY
25
+ ```
26
+
27
+ - Helpers are pre-imported; you can call them by name in heredoc mode.
28
+ - **First call requires `use_workspace(name)`** — there is no implicit default.
29
+ - For task-specific helpers, drop them into `$BH_SQL_AGENT_WORKSPACE/agent_helpers.py` and they'll merge into the heredoc namespace.
30
+
31
+ ## Interaction Skills
32
+
33
+ Stuck-point mechanics — one file per topic, bare filenames only:
34
+
35
+ - `interaction-skills/aggregations.md`
36
+ - `interaction-skills/docker-via-ssh.md`
37
+ - `interaction-skills/encoding-and-charset.md`
38
+ - `interaction-skills/indexes-and-explain.md`
39
+ - `interaction-skills/joins.md`
40
+ - `interaction-skills/json-columns.md`
41
+ - `interaction-skills/large-result-sets.md`
42
+ - `interaction-skills/migrations.md`
43
+ - `interaction-skills/pooling.md`
44
+ - `interaction-skills/recursive-ctes.md`
45
+ - `interaction-skills/save-run-cycle.md`
46
+ - `interaction-skills/schema-introspection.md`
47
+ - `interaction-skills/ssh.md`
48
+ - `interaction-skills/timeouts-and-cancellation.md`
49
+ - `interaction-skills/transactions.md`
50
+ - `interaction-skills/window-functions.md`
51
+ - `interaction-skills/zone-skill-auto-surface.md`
52
+
53
+ PG optimization depth — `interaction-skills/postgres/`:
54
+
55
+ - `interaction-skills/postgres/btree-indexes.md`
56
+ - `interaction-skills/postgres/plan-reading.md`
57
+ - `interaction-skills/postgres/slow-queries-joins.md`
58
+ - `interaction-skills/postgres/specialized-indexes.md`
59
+ - `interaction-skills/postgres/table-optimization.md`
60
+
61
+ ## Workspace / strategy skills
62
+
63
+ Read these when working across multiple connections, tuning pool behavior, or operating remote hosts:
64
+
65
+ - `agent-workspace/skills/pool.md` — pool sizing, `pre_ping`, idle reuse.
66
+ - `agent-workspace/skills/workspace.md` — workspace isolation, multi-DB workflows.
67
+ - `agent-workspace/skills/ssh.md` — strategy for hitting remote DB hosts over SSH.
68
+ - `agent-workspace/skills/docker-deploy.md` — strategy: when to use docker-via-ssh, the `~/sql-harness/<svc>/` cohesion convention, service catalog (coturn/mysql/redis/...).
69
+
70
+ ## What actually works (field-tested)
71
+
72
+ - **Start with `list_tables` + `describe`, not `query`.** Before writing SQL on an unknown DB, run `list_tables(schema=None)` then `describe("table", schema=None)` to learn the columns. Skips the trial-and-error SELECT cycle.
73
+ - **Default to `with_transaction()` for any write with ≥ 2 statements** (insert + update, drop + create, …). Atomic + automatic rollback on exception.
74
+ - **For `insert` returning the new id**: psycopg supports `INSERT … RETURNING id`; MySQL has no `RETURNING` — use `execute(...)` and read `result.lastrowid`.
75
+ - **Streaming large result sets**: `query()` loads every row into memory. For >10k rows, use `ws.engine.connect().execution_options(stream_results=True)` and iterate manually.
76
+ - **Read-only "is this alive?" check**: `print(server_version())` — single round-trip, works on every DSN, no per-driver fudging.
77
+ - **Auth wall**: redirected to login → stop and ask the user. Don't type credentials from `page_info`/`describe`.
78
+ - **Credentials in TOML**: use `${env:VAR}` indirection for prod secrets. The connections.toml loader expands these at load time.
79
+ - **Cross-DB SQL**: prefer ANSI syntax (`'string'`, `LIMIT n OFFSET m`, `COALESCE`, `CURRENT_TIMESTAMP`). PG-specific: `::TYPE`, `JSONB`, `RETURNING`. MySQL-specific: `AUTO_INCREMENT`, backticks, `?` placeholders (sqlalchemy normalizes to `:name`).
80
+ - **Save the workflow**: every working block → `sql-harness save <name> -c <conn>`. Tomorrow's session re-runs with one command. See `interaction-skills/save-run-cycle.md`.
81
+ - **Zone skills surface automatically**: set `BH_SQL_ZONE_SKILLS=1` once; every `use_workspace(name)` then tells you what zone skills exist (see `interaction-skills/zone-skill-auto-surface.md`).
82
+
83
+ ## Design Constraints
84
+
85
+ - One connection = one workspace; never share engines across workspaces.
86
+ - Connection pool defaults: `size=5, recycle=3600, pre_ping=True` (per-connection overrides win).
87
+ - `with_transaction()` yields a `Connection`; use `conn.execute(text(...))` for raw control.
88
+ - `query()` returns `list[dict]`; use `execute()` for INSERT/UPDATE/DELETE.
89
+ - `list_tables()` and `describe()` are read-only schema introspection helpers.
90
+ - There is no Chrome and no remote "cloud" backend. SQL is single-process. All engines live in `SqlHarness`'s in-memory registry.
91
+
92
+ ## Gotchas
93
+
94
+ - Driver label in TOML must match the URL scheme (`postgres` ⇄ `postgresql://`, `mysql` ⇄ `mysql+pymysql://`).
95
+ - Passwords in TOML are plaintext; use `${env:VAR}` indirection for prod secrets.
96
+ - `query()` is for SELECTs only. For INSERTs, use `execute()`.
97
+ - For tables > 10k rows, use `engine.connect().execution_options(stream_results=True)` and iterate manually — `query()` loads everything into memory.
98
+
99
+ ## Domain / table skills
100
+
101
+ PG optimization practice: `practice/pgexercises/` (71 pgexercises problems, see its README). Per-table or per-schema skills: drop a markdown file into `agent-workspace/skills/<name>.md`, read with `apply_skill(name)`.
@@ -11,7 +11,7 @@ sessions. The live runtime copies live in `$BH_SQL_AGENT_WORKSPACE` (default
11
11
  - `scripts/sh_demo_crud.py` — full e-commerce CRUD walkthrough against a live
12
12
  PostgreSQL. Generated via `sql-harness save sh_demo_crud`. Idempotent (drops
13
13
  + recreates the `sh_demo` schema each run). Re-run with
14
- `sql-harness run sh_demo_crud`.
14
+ `sql-harness run sh_demo_crud`.
15
15
  - `agent_helpers.py` — reusable helpers (`seed_users`, `place_order`,
16
16
  `ship_pending_orders`, `list_orders`, `user_by_email`, `product_by_sku`)
17
17
  auto-imported into the heredoc/run namespace.
@@ -39,4 +39,4 @@ sql-harness run sh_demo_crud
39
39
  ```
40
40
 
41
41
  That last step — **executed code is saved and re-runnable** — is the essence
42
- this harness inherits from browser-harness.
42
+ this harness inherits from browser-harness.
@@ -0,0 +1,26 @@
1
+ # agent-workspace/skills — strategy layer
2
+
3
+ Strategy-altitude notes for sql-harness: **when** and **why** to reach for a
4
+ pattern. The **how** (helpers, commands, gotchas) lives one level down in
5
+ `interaction-skills/` — these docs point there, they don't duplicate it.
6
+
7
+ ## Files in this folder
8
+
9
+ - `pool.md` — ships the `[pool_defaults]` block + recycle-timing rule; points at the pooling mechanic for sizing
10
+ - `workspace.md` — one connection = one workspace; isolation, lifecycle, multi-DB, cross-workspace-transaction warning
11
+ - `ssh.md` — when to reach for `driver = "ssh"` vs a DB workspace; auth reference; host-key-trust decision
12
+ - `docker-deploy.md` — when to deploy docker-compose over SSH; service catalog; `~/sql-harness/<svc>/` convention; lifecycle decisions
13
+
14
+ ## When to read which
15
+
16
+ - Queries stall / connections go stale → `pool.md` (then the pooling mechanic)
17
+ - Juggling >1 database, or "can I share a connection?" → `workspace.md`
18
+ - Need to run shell / move files on a remote box → `ssh.md`
19
+ - Standing up mysql / redis / coturn / nginx on a remote host → `docker-deploy.md`
20
+
21
+ ## When to skip
22
+
23
+ Most pool / workspace / ssh / docker tuning is **mechanic work** — read
24
+ `interaction-skills/` first (`pooling.md`, `ssh.md`, `docker-via-ssh.md`,
25
+ `transactions.md`). Come here only when a mechanic doc points at strategy, or
26
+ when you're deciding *whether* to use a pattern rather than *how* to run it.
@@ -0,0 +1,85 @@
1
+ # Docker-via-SSH deployment — strategy
2
+
3
+ > **Strategy layer** for deploying docker-compose services to remote hosts via
4
+ > sql-harness SSH. For the **mechanic** (upload + `docker compose up` + verify),
5
+ > see `interaction-skills/docker-via-ssh.md`. This doc is about **when**, **why**,
6
+ > and **which service** — the convention, the catalog, the lifecycle decisions.
7
+
8
+ ## When to use this pattern
9
+
10
+ Use `docker-via-ssh` when:
11
+
12
+ - You have **1–N remote hosts** (Aliyun ECS, a VPS, a home lab box) reachable by SSH
13
+ - You want **standard services** (mysql / postgres / redis / coturn / nginx / grafana) running, self-contained
14
+ - You want **re-deployable / scriptable** deploys (not manual `docker run` you'll forget)
15
+ - You **don't** need k8s-scale orchestration (no auto-scaling, no multi-node clustering)
16
+
17
+ Don't use it when:
18
+
19
+ - You need **multi-node clustering** (Hadoop / HBase across N hosts) → k8s / Nomad
20
+ - The service needs **bare-metal perf** (GPU passthrough, hugepages) → systemd unit
21
+ - You're on a **managed platform** (Aliyun RDS, Redis Cloud) → use the managed one
22
+
23
+ ## The convention: `~/sql-harness/<service>/` on the remote
24
+
25
+ Every deployed service lives in its own dir under `~/sql-harness/<service>/` **on
26
+ the remote host** — compose + config + data + logs, all with relative mounts
27
+ (`./data`, not absolute paths). The mechanic doc shows the layout tree and the
28
+ relative-mount rationale in full; the strategy payoff is:
29
+
30
+ | Property | How the convention delivers it |
31
+ |---|---|
32
+ | **Cohesion** | A service's compose + config + data + logs are all in one dir |
33
+ | **Backup** | `tar czf coturn.tgz ~/sql-harness/coturn/` — one command, complete |
34
+ | **Teardown** | `docker compose down && rm -rf ~/sql-harness/<svc>` — clean |
35
+ | **Upgrade** | re-upload compose → `docker compose up -d` (rolling, keeps data) |
36
+ | **Discovery** | `ls ~/sql-harness/` shows what's deployed |
37
+ | **Isolation** | services don't share mounts/networks unless explicitly composed together |
38
+ | **Portability** | relative mounts follow the dir — move or migrate hosts without editing paths |
39
+
40
+ ## Service catalog (shipped templates in `docker-services/`)
41
+
42
+ | Service | Image | Ports | Use case |
43
+ |---|---|---|---|
44
+ | `coturn` | `coturn/coturn` | 3478, 5349, 49152-65535/udp | WebRTC TURN/STUN (P2P hole-punching) |
45
+ | `mysql` | `mysql:8.0` | 3306 | Relational DB (sql-harness `mysql` driver connects here) |
46
+ | `postgresql` | `postgres:16` | 5432 | Relational DB (sql-harness `postgres` driver connects here) |
47
+ | `redis` | `redis:7-alpine` | 6379 | Cache / KV (sql-harness `redis` driver — when implemented) |
48
+ | `nginx` | `nginx:alpine` | 80, 443 | Reverse proxy / static hosting |
49
+ | `rabbitmq` | `rabbitmq:3-management` | 5672, 15672 | Message queue |
50
+ | `cloud-grafana` | `grafana/grafana` + `prom/prometheus` | 3000, 9090 | Monitoring stack |
51
+
52
+ Each template in `docker-services/<svc>/` ships a `docker-compose.yml` + config
53
+ files, all with relative mounts. Pick one → the mechanic's 4-step loop uploads
54
+ and starts it. Pair a deployed DB service with a matching **DB workspace**
55
+ (`driver = "mysql"`/`postgres`) to run SQL against it — SSH-level container
56
+ management + DB-level queries on the same host is the typical diagnostic loop.
57
+
58
+ ## Lifecycle decisions
59
+
60
+ | Question | Answer |
61
+ |---|---|
62
+ | New service? | pick template → upload → `up -d` (4-step loop in mechanic doc) |
63
+ | Image update? | edit compose tag → re-upload → `up -d` (keeps data) |
64
+ | Config change? | edit conf → re-upload → `restart` (or `up -d` if compose changed) |
65
+ | Host migration? | `tar` the service dir → scp to new host → `up -d` |
66
+ | Out of disk? | `docker system prune -af` (careful) + check service `data/` growth |
67
+ | Service flapping? | `docker logs -f` → `docker compose ps` → healthcheck output |
68
+ | Stop paying for it? | `down -v` + `rm -rf ~/sql-harness/<svc>` + snapshot data first |
69
+
70
+ ## Gotchas (strategy-level)
71
+
72
+ - **One host = one `~/sql-harness/` root**. Multi-host deploys loop the deploy per
73
+ host — one sql-harness SSH workspace per host.
74
+ - **Don't mount the SAME host port from two services** — mysql + postgres coexist
75
+ fine on their defaults, but two mysql instances need port remapping.
76
+ - **Healthy ≠ reachable by the DB workspace**: if a container isn't healthy,
77
+ `sql-harness test <db-workspace>` fails confusingly — check `docker compose ps`
78
+ before assuming a sql-harness bug. (Per-service startup gotchas live with the
79
+ mechanic and `docker-services/<svc>/README.md`.)
80
+
81
+ ## See also
82
+
83
+ - `interaction-skills/docker-via-ssh.md` — the mechanic (layout tree, relative-mount
84
+ rationale, 4-step deploy loop, per-op lifecycle commands, deploy gotchas)
85
+ - `docker-services/<svc>/` — shipped templates (and `<svc>/README.md` service gotchas)
@@ -0,0 +1,30 @@
1
+ # Connection pooling — strategy
2
+
3
+ > **Strategy stub.** Sizing rules, `pre_ping`, `pool_recycle`, `NullPool` and pool
4
+ > gotchas all live in the mechanic: `interaction-skills/pooling.md`. Read that
5
+ > first. This file owns only the shipped default block below.
6
+
7
+ ## `[pool_defaults]`
8
+
9
+ Every workspace pool starts from these conservative defaults (`pool_size=5` means
10
+ 5 connections to *that* database, per workspace — not 5 total). Tune per workload
11
+ using the mechanic's sizing rules.
12
+
13
+ ```toml
14
+ [pool_defaults]
15
+ size = 5
16
+ recycle = 3600
17
+ pre_ping = true
18
+ echo = false
19
+ ```
20
+
21
+ **Recycle-timing rule:** when a load balancer / proxy kills idle connections
22
+ (AWS RDS Proxy = 1800s; PostgreSQL `idle_in_transaction_session_timeout`), set
23
+ `recycle` to (their timeout) × 0.8 so connections recycle *before* being killed
24
+ externally.
25
+
26
+ ## Read full guide
27
+
28
+ `interaction-skills/pooling.md` — when to tune `pool_size` (1 / 2-5 / 10+), when
29
+ `pre_ping` and `pool_recycle` matter, `NullPool` vs `QueuePool`, and pool-stat
30
+ gotchas.
@@ -0,0 +1,70 @@
1
+ # SSH workspaces — when to use the SSH driver
2
+
3
+ > **Strategy guide.** For the mechanics (helper calls, CLI subcommands, SFTP /
4
+ > timeout / quoting gotchas), see `interaction-skills/ssh.md`.
5
+
6
+ ## When to reach for `driver = "ssh"`
7
+
8
+ Use an SSH workspace to **operate on a remote host** (not a database). It reuses
9
+ the same `use_workspace("name")` abstraction as DB workspaces, but opens a
10
+ paramiko `SSHClient` + SFTP instead of a SQLAlchemy engine — so you get shell +
11
+ file ops with the same ergonomics.
12
+
13
+ Reach for it to: run ad-hoc shell commands (`ps`, `systemctl`, `cat /var/log/...`);
14
+ do a one-shot deploy / restart / log inspection without a full Ansible playbook;
15
+ upload-then-run a script (`ssh_run_script`); pull a log/artifact down for analysis;
16
+ or pair with a DB workspace (shell check, then `query(...)`).
17
+
18
+ Don't use it for: heavy interactive shells (no TUI/scrollback/resize — use
19
+ `term-cli` from `lab.subprocess`); long-running daemons (the channel is
20
+ short-lived — use systemd / supervisord); or anything that must survive the
21
+ sql-harness process exit (the connection closes on exit).
22
+
23
+ ## Auth reference
24
+
25
+ Configure in `connections.toml` (`driver = "ssh"`, `url = "ssh://user@host:port"`).
26
+ Pick an auth strategy:
27
+
28
+ | Strategy | When | Example URL |
29
+ |---|---|---|
30
+ | SSH key (preferred) | CI / dev boxes with `~/.ssh/id_*` | `ssh://user@host` (auto-resolves key) |
31
+ | Explicit key path | Specific key per connection | `ssh://user@host?key=/opt/keys/prod.pem` |
32
+ | Password | Quick lab use; NOT for prod | `ssh://user:pw@host` (works but key is better) |
33
+ | `ssh+password://` | Force password (no key lookup) | `ssh+password://user:pw@host` |
34
+ | `ssh+key://` | Force key-only auth (no password fallback) | `ssh+key://user@host?key=/path` |
35
+
36
+ Key resolution order (first hit wins): (1) `?key=...` in the URL, (2) `$BH_SSH_KEY`
37
+ env var, (3) `~/.ssh/id_ed25519` → `id_rsa` → `id_ecdsa`, (4) password from URL or
38
+ `$BH_SSH_PASSWORD`.
39
+
40
+ ## Workspace lifecycle parity
41
+
42
+ SSH workspaces follow the **same** lazy-open / cached / dispose pattern as DB
43
+ workspaces: `use_workspace("prod-app")` opens TCP+SSH+shell+SFTP and caches it;
44
+ re-opening returns the cached handle for free; process exit / `dispose()` /
45
+ `close_workspace(name)` tears it down.
46
+
47
+ ## Combining with DB workspaces
48
+
49
+ SSH and DB workspaces are independent — keep one of each open at once. The typical
50
+ diagnostic flow: shell-level process check, then in-process DB check.
51
+
52
+ ```python
53
+ ssh_use_workspace("prod-app")
54
+ print(ssh_exec("ps aux | grep postgres | head -3")["stdout"])
55
+
56
+ use_workspace("prod_pg")
57
+ print(query("SELECT count(*) FROM pg_stat_activity")["count"])
58
+ ```
59
+
60
+ ## Safety — the host-key-trust decision
61
+
62
+ The driver trusts host keys on first connect (`AutoAddPolicy()`, like
63
+ `StrictHostKeyChecking=accept-new`). Fine for trusted networks; for hostile ones,
64
+ harden this in a subclass. Also prefer a non-root user, and set an explicit
65
+ `timeout=` for long commands. (Connection pooling, shell-vs-exec, path handling,
66
+ non-ASCII decoding and other operational gotchas live in the mechanic doc.)
67
+
68
+ ## See also
69
+
70
+ - `interaction-skills/ssh.md` — the mechanic (helpers, CLI, SFTP/timeout/quoting gotchas)
@@ -37,19 +37,10 @@ Each `query()` call grabs its workspace's connection and returns it to the pool
37
37
 
38
38
  ## Transactions across workspaces
39
39
 
40
- Don't. A single transaction is bound to one connection. To move data between workspaces:
41
-
42
- ```python
43
- use_workspace("prod_pg")
44
- rows = query("SELECT * FROM users WHERE created_at > :t", {"t": "2024-01-01"})
45
-
46
- use_workspace("staging_pg")
47
- for row in rows:
48
- execute("INSERT INTO users (id, name, created_at) VALUES (:i, :n, :c)",
49
- {"i": row["id"], "n": row["name"], "c": row["created_at"]})
50
- ```
51
-
52
- This is two separate transactions — one per workspace — and is NOT atomic. If you need atomicity, you need a single DB.
40
+ Don't. A single transaction is bound to one connection, so moving data between
41
+ workspaces is two separate transactions (`query` from A → `execute` into B) — one
42
+ per workspace, and NOT atomic. If you need atomicity, you need a single DB. See
43
+ `interaction-skills/transactions.md` for the transaction mechanics.
53
44
 
54
45
  ## Gotchas
55
46
 
@@ -0,0 +1,69 @@
1
+ # coturn (TURN/STUN server) — docker-services template
2
+
3
+ Self-contained TURN/STUN relay for WebRTC clients. Deploy via
4
+ `../../interaction-skills/docker-via-ssh.md`. Service dir on the remote:
5
+ `~/sql-harness/coturn/`.
6
+
7
+ ## Key config knobs (`turnserver.conf`)
8
+
9
+ - `realm` — your domain / public hostname (clients see this in their URL).
10
+ - `external-ip` — the server's PUBLIC ip when behind NAT (Aliyun ECS is).
11
+ Set as `AUTO` in the shipped template; the deploy script substitutes the
12
+ real public ip before `up -d`.
13
+ - `listening-ip=0.0.0.0` — bind interface (private is fine; only `external-ip`
14
+ needs to be public).
15
+ - `listening-port=3478` (TURN/STUN), `tls-listening-port=5349` (TURNS over TLS).
16
+ - `min-port=49152` / `max-port=65535` — relay UDP range. Must match the firewall.
17
+ - `user=<u>:<p>` + `lt-cred-mech` — long-term credential mechanism.
18
+
19
+ Test creds after deploy:
20
+ ```bash
21
+ turnutils_uclient -u webrtc -w webrtc <server-ip> 3478
22
+ ```
23
+
24
+ ## Service-specific gotchas (from production)
25
+
26
+ - **`external-ip` behind NAT**: Aliyun ECS is behind NAT — coturn's
27
+ `turnserver.conf` `external-ip=` must be the **public** ip, not the private
28
+ one `listening-ip=0.0.0.0` binds. Templated as `AUTO`; substitute at deploy
29
+ time (`sed -i "s/external-ip=AUTO/external-ip=$PUBIP/"`).
30
+
31
+ - **Firewall / security group**: docker-publishes ports on the host, but Aliyun
32
+ security group + host firewall (ufw/firewalld) must ALSO allow them. The
33
+ coturn relay UDP range (49152-65535) is the easy one to forget. Open in
34
+ security group, NOT via docker `ports:`.
35
+
36
+ - **Don't publish the relay port range via `ports:`** — publishing coturn's
37
+ relay range `49152-65535` (16k ports) makes Docker create ~16k iptables
38
+ DNAT rules, which **OOM-kills the container start** (exit 137) on small
39
+ hosts (Aliyun ECS). Use `network_mode: host` instead — coturn binds the
40
+ range natively, zero iptables overhead. This is the official
41
+ coturn/docker recommendation, and the shipped `docker-compose.yml` already
42
+ does this.
43
+
44
+ - **`network_mode: host` disables `ports:`** — the container binds host ports
45
+ directly; don't also list `ports:` (compose rejects the combo). All port
46
+ management moves to the host firewall + cloud security group.
47
+
48
+ - **Config-as-dir gotcha**: if `./turnserver.conf` doesn't exist locally when
49
+ docker tries to mount it, Docker creates a **directory** at that path
50
+ (not a file) → coturn fails to read config. Always upload the file BEFORE
51
+ `up -d`, or pre-`touch` it on the remote.
52
+
53
+ ## Why host networking (rationale)
54
+
55
+ coturn's relay UDP range (49152-65535 = ~16k ports) must NOT be published via
56
+ docker `-p` — Docker would create ~16k iptables DNAT rules, OOM-killing the
57
+ daemon on small hosts (Aliyun ECS etc.). Host networking lets coturn bind
58
+ the range natively, zero iptables overhead. This is the official
59
+ coturn/docker recommendation.
60
+
61
+ Ports to open in the host firewall / cloud security group:
62
+ - 3478/tcp + 3478/udp (TURN/STUN)
63
+ - 5349/tcp + 5349/udp (TURNS over TLS)
64
+ - 49152-65535/udp (relay range — open in security group, NOT in docker)
65
+
66
+ ## Files in this template
67
+
68
+ - `docker-compose.yml` — host-networked coturn container.
69
+ - `turnserver.conf` — minimal config; edit `realm`/`external-ip`/`user` before deploy.
@@ -0,0 +1,29 @@
1
+ # coturn via docker-compose — HOST NETWORKING (production-correct)
2
+ #
3
+ # Why host networking: coturn's relay UDP range (49152-65535 = ~16k ports)
4
+ # must NOT be published via docker `-p` — Docker would create ~16k iptables
5
+ # DNAT rules, OOM-killing the daemon on small hosts (Aliyun ECS etc.).
6
+ # Host networking lets coturn bind the range natively, zero iptables overhead.
7
+ # This is the official coturn/docker recommendation.
8
+ #
9
+ # Ports to open in the host firewall / cloud security group:
10
+ # 3478/tcp + 3478/udp (TURN/STUN)
11
+ # 5349/tcp + 5349/udp (TURNS over TLS)
12
+ # 49152-65535/udp (relay range — open in security group, NOT in docker)
13
+
14
+ services:
15
+ coturn:
16
+ image: coturn/coturn:latest
17
+ container_name: coturn
18
+ restart: unless-stopped
19
+ network_mode: host # ← binds directly to host ports; no `ports:`
20
+ volumes:
21
+ # Relative to THIS dir (~/sql-harness/coturn/) — data + config cohesive.
22
+ - ./turnserver.conf:/etc/turnserver.conf:ro
23
+ - ./data:/var/lib/coturn
24
+ command: ["-c", "/etc/turnserver.conf"]
25
+ logging:
26
+ driver: json-file
27
+ options:
28
+ max-size: "10m"
29
+ max-file: "3"