rms-devremote 3.0.0
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.
- package/README.md +154 -0
- package/dist/commands/attach.d.ts +2 -0
- package/dist/commands/attach.js +10 -0
- package/dist/commands/check.d.ts +2 -0
- package/dist/commands/check.js +210 -0
- package/dist/commands/clean.d.ts +2 -0
- package/dist/commands/clean.js +177 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.js +57 -0
- package/dist/commands/link.d.ts +2 -0
- package/dist/commands/link.js +112 -0
- package/dist/commands/ping.d.ts +2 -0
- package/dist/commands/ping.js +21 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +54 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +65 -0
- package/dist/commands/unlink.d.ts +2 -0
- package/dist/commands/unlink.js +53 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +55 -0
- package/dist/server/auth.d.ts +6 -0
- package/dist/server/auth.js +32 -0
- package/dist/server/frontend.d.ts +4 -0
- package/dist/server/frontend.js +886 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +283 -0
- package/dist/server/terminal.d.ts +14 -0
- package/dist/server/terminal.js +43 -0
- package/dist/services/battery-worker.d.ts +1 -0
- package/dist/services/battery-worker.js +2 -0
- package/dist/services/battery.d.ts +27 -0
- package/dist/services/battery.js +152 -0
- package/dist/services/config.d.ts +63 -0
- package/dist/services/config.js +84 -0
- package/dist/services/docker.d.ts +25 -0
- package/dist/services/docker.js +75 -0
- package/dist/services/hooks.d.ts +15 -0
- package/dist/services/hooks.js +111 -0
- package/dist/services/ntfy.d.ts +19 -0
- package/dist/services/ntfy.js +63 -0
- package/dist/services/process.d.ts +30 -0
- package/dist/services/process.js +90 -0
- package/dist/services/proxy-worker.d.ts +1 -0
- package/dist/services/proxy-worker.js +12 -0
- package/dist/services/proxy.d.ts +4 -0
- package/dist/services/proxy.js +195 -0
- package/dist/services/shell.d.ts +22 -0
- package/dist/services/shell.js +47 -0
- package/dist/services/tmux.d.ts +30 -0
- package/dist/services/tmux.js +74 -0
- package/dist/services/ttyd.d.ts +28 -0
- package/dist/services/ttyd.js +71 -0
- package/dist/setup-server/routes.d.ts +4 -0
- package/dist/setup-server/routes.js +177 -0
- package/dist/setup-server/server.d.ts +4 -0
- package/dist/setup-server/server.js +32 -0
- package/docker/docker-compose.yml +24 -0
- package/docker/ntfy/server.yml +6 -0
- package/package.json +61 -0
- package/scripts/claude-remote.sh +583 -0
- package/scripts/hooks/notify.sh +68 -0
- package/scripts/notify.sh +54 -0
- package/scripts/startup.sh +29 -0
- package/scripts/update-check.sh +25 -0
- package/src/setup-server/public/index.html +21 -0
- package/src/setup-server/public/setup.css +475 -0
- package/src/setup-server/public/setup.js +687 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# claude-remote -- CLI for managing Claude Remote
|
|
3
|
+
# Data lives in ~/.claude-remote/ (config, .env, projects.conf)
|
|
4
|
+
# Source files live in the repo (Dockerfile, docker-compose, scripts)
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
CLAUDE_REMOTE_HOME="${HOME}/.claude-remote"
|
|
9
|
+
# REPO_DIR is where the source files are (Dockerfile, docker-compose, etc.)
|
|
10
|
+
# Resolve symlink to find the real repo location
|
|
11
|
+
SCRIPT_REAL_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
|
|
12
|
+
REPO_DIR="$(cd "$(dirname "$SCRIPT_REAL_PATH")/.." && pwd)"
|
|
13
|
+
|
|
14
|
+
# Colors
|
|
15
|
+
RED='\033[0;31m'
|
|
16
|
+
GREEN='\033[0;32m'
|
|
17
|
+
YELLOW='\033[1;33m'
|
|
18
|
+
NC='\033[0m'
|
|
19
|
+
|
|
20
|
+
info() { echo -e "${GREEN}[claude-remote]${NC} $1"; }
|
|
21
|
+
warn() { echo -e "${YELLOW}[claude-remote]${NC} $1"; }
|
|
22
|
+
error() { echo -e "${RED}[claude-remote]${NC} $1" >&2; }
|
|
23
|
+
|
|
24
|
+
# ── Init check ───────────────────────────────────
|
|
25
|
+
cmd_init() {
|
|
26
|
+
if [ -f "${CLAUDE_REMOTE_HOME}/.env" ]; then
|
|
27
|
+
info "Claude Remote est deja installe"
|
|
28
|
+
echo ""
|
|
29
|
+
source "${CLAUDE_REMOTE_HOME}/.env"
|
|
30
|
+
echo " Domaine VS Code : https://${CLAUDE_DOMAIN:-non configure}"
|
|
31
|
+
echo " Domaine notifications : https://${NOTIFY_DOMAIN:-non configure}"
|
|
32
|
+
echo " Dossier config : ${CLAUDE_REMOTE_HOME}"
|
|
33
|
+
echo " Dossier source : ${REPO_DIR}"
|
|
34
|
+
echo ""
|
|
35
|
+
|
|
36
|
+
# Show projects
|
|
37
|
+
if [ -f "${CLAUDE_REMOTE_HOME}/projects.conf" ] && [ -s "${CLAUDE_REMOTE_HOME}/projects.conf" ]; then
|
|
38
|
+
echo " Projets :"
|
|
39
|
+
while IFS='|' read -r name path; do
|
|
40
|
+
[ -z "$name" ] && continue
|
|
41
|
+
echo " - ${name} -> ${path}"
|
|
42
|
+
done < "${CLAUDE_REMOTE_HOME}/projects.conf"
|
|
43
|
+
else
|
|
44
|
+
echo " Aucun projet (lance 'claude-remote add /chemin/projet')"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
echo ""
|
|
48
|
+
# Check if running
|
|
49
|
+
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "claude-remote"; then
|
|
50
|
+
info "Status : en cours d'execution"
|
|
51
|
+
else
|
|
52
|
+
warn "Status : arrete (lance 'claude-remote up')"
|
|
53
|
+
fi
|
|
54
|
+
else
|
|
55
|
+
info "Claude Remote n'est pas encore installe"
|
|
56
|
+
echo ""
|
|
57
|
+
echo " Lance 'claude-remote setup' pour commencer l'installation."
|
|
58
|
+
fi
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
generate_compose() {
|
|
62
|
+
# Read projects.conf and generate docker-compose.override.yml with project volume mounts
|
|
63
|
+
local OVERRIDE="${CLAUDE_REMOTE_HOME}/docker-compose.override.yml"
|
|
64
|
+
local PROJECTS_CONF="${CLAUDE_REMOTE_HOME}/projects.conf"
|
|
65
|
+
|
|
66
|
+
if [ ! -f "$PROJECTS_CONF" ] || [ ! -s "$PROJECTS_CONF" ]; then
|
|
67
|
+
# No projects, remove override if exists
|
|
68
|
+
rm -f "$OVERRIDE"
|
|
69
|
+
return
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# Build volume list from projects.conf
|
|
73
|
+
local VOLUMES=""
|
|
74
|
+
while IFS='|' read -r name path; do
|
|
75
|
+
[ -z "$name" ] && continue
|
|
76
|
+
VOLUMES="${VOLUMES} - ${path}:/home/coder/projects/${name}\n"
|
|
77
|
+
done < "$PROJECTS_CONF"
|
|
78
|
+
|
|
79
|
+
if [ -z "$VOLUMES" ]; then
|
|
80
|
+
rm -f "$OVERRIDE"
|
|
81
|
+
return
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# Write override file
|
|
85
|
+
printf "services:\n code-server:\n volumes:\n${VOLUMES}" > "$OVERRIDE"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
cmd_up() {
|
|
89
|
+
if [ ! -f "${CLAUDE_REMOTE_HOME}/.env" ]; then
|
|
90
|
+
error "Claude Remote n'est pas installe. Lance 'claude-remote setup' d'abord."
|
|
91
|
+
exit 1
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Sync source files from repo to data dir
|
|
95
|
+
sync_files
|
|
96
|
+
|
|
97
|
+
generate_compose
|
|
98
|
+
info "Starting Claude Remote..."
|
|
99
|
+
|
|
100
|
+
local COMPOSE_CMD="docker compose -f ${CLAUDE_REMOTE_HOME}/docker-compose.yml"
|
|
101
|
+
if [ -f "${CLAUDE_REMOTE_HOME}/docker-compose.override.yml" ]; then
|
|
102
|
+
COMPOSE_CMD="${COMPOSE_CMD} -f ${CLAUDE_REMOTE_HOME}/docker-compose.override.yml"
|
|
103
|
+
fi
|
|
104
|
+
${COMPOSE_CMD} up -d --build
|
|
105
|
+
info "Claude Remote is running"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
sync_files() {
|
|
109
|
+
# Copy source files from repo to ~/.claude-remote/
|
|
110
|
+
# This ensures updates to the repo are reflected without re-setup
|
|
111
|
+
cp "${REPO_DIR}/docker-compose.yml" "${CLAUDE_REMOTE_HOME}/"
|
|
112
|
+
cp "${REPO_DIR}/Dockerfile" "${CLAUDE_REMOTE_HOME}/"
|
|
113
|
+
cp "${REPO_DIR}/.dockerignore" "${CLAUDE_REMOTE_HOME}/"
|
|
114
|
+
mkdir -p "${CLAUDE_REMOTE_HOME}/scripts/hooks"
|
|
115
|
+
cp "${REPO_DIR}/scripts/update-check.sh" "${CLAUDE_REMOTE_HOME}/scripts/"
|
|
116
|
+
cp "${REPO_DIR}/scripts/startup.sh" "${CLAUDE_REMOTE_HOME}/scripts/"
|
|
117
|
+
cp "${REPO_DIR}/scripts/hooks/notify.sh" "${CLAUDE_REMOTE_HOME}/scripts/hooks/"
|
|
118
|
+
chmod +x "${CLAUDE_REMOTE_HOME}/scripts/update-check.sh"
|
|
119
|
+
chmod +x "${CLAUDE_REMOTE_HOME}/scripts/startup.sh"
|
|
120
|
+
chmod +x "${CLAUDE_REMOTE_HOME}/scripts/hooks/notify.sh"
|
|
121
|
+
# Copy hooks.json if it exists in repo
|
|
122
|
+
if [ -f "${REPO_DIR}/hooks.json" ]; then
|
|
123
|
+
cp "${REPO_DIR}/hooks.json" "${CLAUDE_REMOTE_HOME}/hooks.json"
|
|
124
|
+
fi
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
cmd_down() {
|
|
128
|
+
info "Stopping Claude Remote..."
|
|
129
|
+
docker compose -f "${CLAUDE_REMOTE_HOME}/docker-compose.yml" down
|
|
130
|
+
info "Claude Remote stopped"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
cmd_status() {
|
|
134
|
+
docker compose -f "${CLAUDE_REMOTE_HOME}/docker-compose.yml" ps
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
cmd_logs() {
|
|
138
|
+
docker compose -f "${CLAUDE_REMOTE_HOME}/docker-compose.yml" logs -f code-server
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
cmd_add() {
|
|
142
|
+
local project_path="$1"
|
|
143
|
+
|
|
144
|
+
if [ ! -d "$project_path" ]; then
|
|
145
|
+
error "Directory not found: $project_path"
|
|
146
|
+
exit 1
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
# Resolve to absolute path
|
|
150
|
+
project_path="$(cd "$project_path" && pwd)"
|
|
151
|
+
local project_name
|
|
152
|
+
project_name="$(basename "$project_path")"
|
|
153
|
+
|
|
154
|
+
local PROJECTS_CONF="${CLAUDE_REMOTE_HOME}/projects.conf"
|
|
155
|
+
touch "$PROJECTS_CONF"
|
|
156
|
+
|
|
157
|
+
# Check if already exists
|
|
158
|
+
if grep -q "^${project_name}|" "$PROJECTS_CONF" 2>/dev/null; then
|
|
159
|
+
warn "Project '${project_name}' already exists, updating..."
|
|
160
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
161
|
+
sed -i '' "/^${project_name}|/d" "$PROJECTS_CONF"
|
|
162
|
+
else
|
|
163
|
+
sed -i "/^${project_name}|/d" "$PROJECTS_CONF"
|
|
164
|
+
fi
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
echo "${project_name}|${project_path}" >> "$PROJECTS_CONF"
|
|
168
|
+
info "Project '${project_name}' added -> ${project_path}"
|
|
169
|
+
cmd_up
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
cmd_remove() {
|
|
173
|
+
local project_name="$1"
|
|
174
|
+
local PROJECTS_CONF="${CLAUDE_REMOTE_HOME}/projects.conf"
|
|
175
|
+
|
|
176
|
+
if ! grep -q "^${project_name}|" "$PROJECTS_CONF" 2>/dev/null; then
|
|
177
|
+
error "Project '${project_name}' not found"
|
|
178
|
+
exit 1
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
182
|
+
sed -i '' "/^${project_name}|/d" "$PROJECTS_CONF"
|
|
183
|
+
else
|
|
184
|
+
sed -i "/^${project_name}|/d" "$PROJECTS_CONF"
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
info "Project '${project_name}' removed"
|
|
188
|
+
cmd_up
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
cmd_setup() {
|
|
192
|
+
echo ""
|
|
193
|
+
echo "╔══════════════════════════════════════════╗"
|
|
194
|
+
echo "║ CLAUDE REMOTE - Setup v1 ║"
|
|
195
|
+
echo "║ Accede a Claude Code depuis ton phone ║"
|
|
196
|
+
echo "╚══════════════════════════════════════════╝"
|
|
197
|
+
echo ""
|
|
198
|
+
|
|
199
|
+
# ── Prerequis ──────────────────────────────────
|
|
200
|
+
echo "── Verification des prerequis ──"
|
|
201
|
+
echo ""
|
|
202
|
+
|
|
203
|
+
if ! command -v docker &> /dev/null; then
|
|
204
|
+
error "Docker non installe. Installe-le : https://docs.docker.com/get-docker/"
|
|
205
|
+
exit 1
|
|
206
|
+
fi
|
|
207
|
+
info "Docker ✓"
|
|
208
|
+
|
|
209
|
+
if ! docker compose version &> /dev/null; then
|
|
210
|
+
error "Docker Compose non disponible. Mets Docker a jour."
|
|
211
|
+
exit 1
|
|
212
|
+
fi
|
|
213
|
+
info "Docker Compose ✓"
|
|
214
|
+
|
|
215
|
+
if [ ! -d "${HOME}/.claude" ]; then
|
|
216
|
+
error "Session Claude Code non trouvee (~/.claude/)"
|
|
217
|
+
echo " Lance 'claude login' d'abord, puis relance ce setup."
|
|
218
|
+
exit 1
|
|
219
|
+
fi
|
|
220
|
+
info "Claude Code ✓"
|
|
221
|
+
|
|
222
|
+
mkdir -p "${CLAUDE_REMOTE_HOME}/ntfy"
|
|
223
|
+
mkdir -p "${CLAUDE_REMOTE_HOME}/scripts/hooks"
|
|
224
|
+
|
|
225
|
+
# ── Domaines ───────────────────────────────────
|
|
226
|
+
echo ""
|
|
227
|
+
echo "── Configuration des domaines ──"
|
|
228
|
+
echo ""
|
|
229
|
+
read -rp " Domaine VS Code (ex: claude.mondomaine.com): " CLAUDE_DOMAIN
|
|
230
|
+
read -rp " Domaine notifications (ex: notify.mondomaine.com): " NOTIFY_DOMAIN
|
|
231
|
+
|
|
232
|
+
# ── Cloudflare Tunnel ────────────────────────
|
|
233
|
+
echo ""
|
|
234
|
+
echo "── Configuration Cloudflare Tunnel ──"
|
|
235
|
+
echo ""
|
|
236
|
+
echo " Si tu as DEJA configure ton tunnel, passe au token."
|
|
237
|
+
echo " Sinon suis ces etapes :"
|
|
238
|
+
echo ""
|
|
239
|
+
echo " 1. Ouvre https://dash.cloudflare.com"
|
|
240
|
+
echo " → Networking → Tunnels → '+ Create a tunnel'"
|
|
241
|
+
echo ""
|
|
242
|
+
echo " 2. Selectionne 'Cloudflared' → Next"
|
|
243
|
+
echo " Nomme le tunnel (ex: claude-remote) → Save tunnel"
|
|
244
|
+
echo ""
|
|
245
|
+
echo " 3. Choisis l'environnement 'Docker'"
|
|
246
|
+
echo " Tu verras une commande :"
|
|
247
|
+
echo " docker run cloudflare/cloudflared ... --token eyJhb..."
|
|
248
|
+
echo " → Copie le token (la longue chaine apres --token)"
|
|
249
|
+
echo ""
|
|
250
|
+
read -rp " Token du tunnel: " CLOUDFLARE_TUNNEL_TOKEN
|
|
251
|
+
|
|
252
|
+
echo ""
|
|
253
|
+
echo " 4. Clique Next → Configure la 1ere route :"
|
|
254
|
+
echo " Subdomain : $(echo "${CLAUDE_DOMAIN}" | cut -d. -f1)"
|
|
255
|
+
echo " Domain : $(echo "${CLAUDE_DOMAIN}" | cut -d. -f2-)"
|
|
256
|
+
echo " Path : (vide)"
|
|
257
|
+
echo " Type : HTTP"
|
|
258
|
+
echo " URL : code-server:8443"
|
|
259
|
+
echo " → Sauvegarde"
|
|
260
|
+
echo ""
|
|
261
|
+
echo " 5. Ajoute la 2eme route :"
|
|
262
|
+
echo " → Clique sur ton tunnel → onglet 'Published application routes'"
|
|
263
|
+
echo " → '+ Add a published application route'"
|
|
264
|
+
echo " Subdomain : $(echo "${NOTIFY_DOMAIN}" | cut -d. -f1)"
|
|
265
|
+
echo " Domain : $(echo "${NOTIFY_DOMAIN}" | cut -d. -f2-)"
|
|
266
|
+
echo " Path : (vide)"
|
|
267
|
+
echo " Type : HTTP"
|
|
268
|
+
echo " URL : ntfy:80"
|
|
269
|
+
echo ""
|
|
270
|
+
read -rp " Appuie sur Entree quand les 2 routes sont ajoutees..."
|
|
271
|
+
|
|
272
|
+
# ── Cloudflare Access ──────────────────────────
|
|
273
|
+
echo ""
|
|
274
|
+
echo "── Protection Cloudflare Access ──"
|
|
275
|
+
echo ""
|
|
276
|
+
echo " 1. Va dans Zero Trust → Access → Applications → Add"
|
|
277
|
+
echo " 2. Type : Self-hosted"
|
|
278
|
+
echo " 3. Application name : claude-remote"
|
|
279
|
+
echo " Session Duration : 24 hours"
|
|
280
|
+
echo " → '+ Add public hostname' → ${CLAUDE_DOMAIN}"
|
|
281
|
+
echo " → NE PAS ajouter ${NOTIFY_DOMAIN}"
|
|
282
|
+
echo " 4. Clique 'Add a policy' :"
|
|
283
|
+
echo " Policy name : allow-me"
|
|
284
|
+
echo " Action : Allow"
|
|
285
|
+
echo " Selector : Emails → ton email"
|
|
286
|
+
echo " 5. Sauvegarde"
|
|
287
|
+
echo ""
|
|
288
|
+
read -rp " Appuie sur Entree quand c'est fait..."
|
|
289
|
+
info "Cloudflare ✓"
|
|
290
|
+
|
|
291
|
+
# ── Mot de passe & config ──────────────────────
|
|
292
|
+
echo ""
|
|
293
|
+
echo "── Configuration locale ──"
|
|
294
|
+
echo ""
|
|
295
|
+
read -rs -p " Mot de passe code-server: " CODE_SERVER_PASSWORD
|
|
296
|
+
echo ""
|
|
297
|
+
|
|
298
|
+
local DEFAULT_TZ="Europe/Paris"
|
|
299
|
+
read -rp " Fuseau horaire [${DEFAULT_TZ}]: " TZ_INPUT
|
|
300
|
+
TZ="${TZ_INPUT:-$DEFAULT_TZ}"
|
|
301
|
+
|
|
302
|
+
NTFY_TOPIC="claude-remote-$(openssl rand -hex 6)"
|
|
303
|
+
|
|
304
|
+
# ── Installation des fichiers ────────────────
|
|
305
|
+
echo ""
|
|
306
|
+
echo "── Installation ──"
|
|
307
|
+
echo ""
|
|
308
|
+
|
|
309
|
+
sync_files
|
|
310
|
+
info "Fichiers copies ✓"
|
|
311
|
+
|
|
312
|
+
# Write ntfy server.yml with user's domain
|
|
313
|
+
cat > "${CLAUDE_REMOTE_HOME}/ntfy/server.yml" << NTFYEOF
|
|
314
|
+
# ntfy server configuration -- generated by claude-remote setup
|
|
315
|
+
base-url: https://${NOTIFY_DOMAIN}
|
|
316
|
+
listen-http: ":80"
|
|
317
|
+
auth-default-access: "deny-all"
|
|
318
|
+
behind-proxy: true
|
|
319
|
+
cache-file: "/var/cache/ntfy/cache.db"
|
|
320
|
+
auth-file: "/var/lib/ntfy/user.db"
|
|
321
|
+
enable-web: false
|
|
322
|
+
NTFYEOF
|
|
323
|
+
|
|
324
|
+
# Step 12: Write .env
|
|
325
|
+
cat > "${CLAUDE_REMOTE_HOME}/.env" << ENVEOF
|
|
326
|
+
# CLAUDE-REMOTE -- Generated by setup
|
|
327
|
+
CODE_SERVER_PASSWORD=${CODE_SERVER_PASSWORD}
|
|
328
|
+
CLOUDFLARE_TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
|
|
329
|
+
CLAUDE_DOMAIN=${CLAUDE_DOMAIN}
|
|
330
|
+
NOTIFY_DOMAIN=${NOTIFY_DOMAIN}
|
|
331
|
+
NTFY_TOPIC=${NTFY_TOPIC}
|
|
332
|
+
NTFY_TOKEN=placeholder
|
|
333
|
+
TZ=${TZ}
|
|
334
|
+
ENVEOF
|
|
335
|
+
|
|
336
|
+
info ".env ✓"
|
|
337
|
+
|
|
338
|
+
# ── Build Docker ───────────────────────────────
|
|
339
|
+
echo ""
|
|
340
|
+
echo "── Build Docker (premiere fois, ca peut prendre quelques minutes) ──"
|
|
341
|
+
echo ""
|
|
342
|
+
docker compose -f "${CLAUDE_REMOTE_HOME}/docker-compose.yml" up -d --build
|
|
343
|
+
|
|
344
|
+
# Step 14: Create ntfy user and token
|
|
345
|
+
info "Configuration auth ntfy..."
|
|
346
|
+
sleep 5
|
|
347
|
+
|
|
348
|
+
# Create admin user with a readable password
|
|
349
|
+
local NTFY_ADMIN_PASS
|
|
350
|
+
NTFY_ADMIN_PASS="$(openssl rand -hex 8)"
|
|
351
|
+
docker exec -e NTFY_PASSWORD="${NTFY_ADMIN_PASS}" claude-ntfy ntfy user add --role=admin --ignore-exists admin
|
|
352
|
+
# If user already existed, update its password
|
|
353
|
+
docker exec -e NTFY_PASSWORD="${NTFY_ADMIN_PASS}" claude-ntfy ntfy user change-pass admin 2>/dev/null || true
|
|
354
|
+
|
|
355
|
+
# Create access token and capture the real token value
|
|
356
|
+
local NTFY_TOKEN_OUTPUT
|
|
357
|
+
NTFY_TOKEN_OUTPUT=$(docker exec claude-ntfy ntfy token add admin 2>&1)
|
|
358
|
+
NTFY_TOKEN=$(echo "$NTFY_TOKEN_OUTPUT" | grep -oE 'tk_[a-zA-Z0-9]+' | head -1)
|
|
359
|
+
|
|
360
|
+
if [ -z "$NTFY_TOKEN" ]; then
|
|
361
|
+
error "Failed to create ntfy token. Output: ${NTFY_TOKEN_OUTPUT}"
|
|
362
|
+
exit 1
|
|
363
|
+
fi
|
|
364
|
+
|
|
365
|
+
# Update .env with real token and admin password
|
|
366
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
367
|
+
sed -i '' "s|NTFY_TOKEN=placeholder|NTFY_TOKEN=${NTFY_TOKEN}|" "${CLAUDE_REMOTE_HOME}/.env"
|
|
368
|
+
else
|
|
369
|
+
sed -i "s|NTFY_TOKEN=placeholder|NTFY_TOKEN=${NTFY_TOKEN}|" "${CLAUDE_REMOTE_HOME}/.env"
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
# Save admin password to .env for reference
|
|
373
|
+
echo "NTFY_ADMIN_PASSWORD=${NTFY_ADMIN_PASS}" >> "${CLAUDE_REMOTE_HOME}/.env"
|
|
374
|
+
|
|
375
|
+
info "Token ntfy ✓"
|
|
376
|
+
|
|
377
|
+
# Restart code-server to pick up the real token
|
|
378
|
+
docker compose -f "${CLAUDE_REMOTE_HOME}/docker-compose.yml" up -d code-server
|
|
379
|
+
|
|
380
|
+
# ── Hooks Claude Code ──────────────────────────
|
|
381
|
+
echo ""
|
|
382
|
+
echo "── Configuration des hooks Claude Code ──"
|
|
383
|
+
echo ""
|
|
384
|
+
|
|
385
|
+
# Write hooks config as a separate file inside ~/.claude-remote/
|
|
386
|
+
# The startup.sh script will copy this into the container's settings
|
|
387
|
+
cat > "${CLAUDE_REMOTE_HOME}/hooks.json" << 'HOOKSEOF'
|
|
388
|
+
{
|
|
389
|
+
"hooks": {
|
|
390
|
+
"Notification": [
|
|
391
|
+
{
|
|
392
|
+
"matcher": "",
|
|
393
|
+
"hooks": [
|
|
394
|
+
{
|
|
395
|
+
"type": "command",
|
|
396
|
+
"command": "/home/coder/scripts/hooks/notify.sh notification \"$CLAUDE_NOTIFICATION\""
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
}
|
|
400
|
+
],
|
|
401
|
+
"Stop": [
|
|
402
|
+
{
|
|
403
|
+
"matcher": "",
|
|
404
|
+
"hooks": [
|
|
405
|
+
{
|
|
406
|
+
"type": "command",
|
|
407
|
+
"command": "/home/coder/scripts/hooks/notify.sh stop $EXIT_CODE"
|
|
408
|
+
}
|
|
409
|
+
]
|
|
410
|
+
}
|
|
411
|
+
]
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
HOOKSEOF
|
|
415
|
+
|
|
416
|
+
info "Hooks ✓"
|
|
417
|
+
|
|
418
|
+
# ── Configuration du phone ─────────────────────
|
|
419
|
+
echo ""
|
|
420
|
+
echo "── Configuration de ton phone ──"
|
|
421
|
+
echo ""
|
|
422
|
+
echo " 1. Installe l'app 'ntfy' depuis le Play Store / App Store"
|
|
423
|
+
echo ""
|
|
424
|
+
echo " 2. Configure le compte (AVANT de s'abonner au topic) :"
|
|
425
|
+
echo " - Ouvre l'app -> menu (3 points en haut) -> Settings -> Users"
|
|
426
|
+
echo " - Clique '+' pour ajouter un utilisateur"
|
|
427
|
+
echo " - Service URL : https://${NOTIFY_DOMAIN}"
|
|
428
|
+
echo " - Username : admin"
|
|
429
|
+
echo " - Password : ${NTFY_ADMIN_PASS}"
|
|
430
|
+
echo " - Sauvegarde"
|
|
431
|
+
echo ""
|
|
432
|
+
echo " 3. Abonne-toi au topic :"
|
|
433
|
+
echo " - Retour a l'ecran principal -> appuie sur '+'"
|
|
434
|
+
echo " - Topic name : ${NTFY_TOPIC}"
|
|
435
|
+
echo " - Coche 'Use another server'"
|
|
436
|
+
echo " - Server URL : https://${NOTIFY_DOMAIN}"
|
|
437
|
+
echo " - Coche 'Instant delivery in doze mode'"
|
|
438
|
+
echo " - Appuie sur SUBSCRIBE"
|
|
439
|
+
echo ""
|
|
440
|
+
read -rp "Appuie sur Entree quand c'est fait..."
|
|
441
|
+
|
|
442
|
+
info "Envoi notification de test..."
|
|
443
|
+
docker exec claude-remote sh -c "
|
|
444
|
+
curl -s \
|
|
445
|
+
-H 'Authorization: Bearer ${NTFY_TOKEN}' \
|
|
446
|
+
-H 'Title: Claude Remote' \
|
|
447
|
+
-d 'Test Claude Remote -- les notifications marchent !' \
|
|
448
|
+
http://ntfy:80/${NTFY_TOPIC}
|
|
449
|
+
"
|
|
450
|
+
|
|
451
|
+
echo ""
|
|
452
|
+
read -rp "Tu as recu 'Test Claude Remote' sur ton phone ? (oui/non): " TEST_RESULT
|
|
453
|
+
|
|
454
|
+
if [ "$TEST_RESULT" != "oui" ]; then
|
|
455
|
+
warn "Verifie que :"
|
|
456
|
+
echo " - L'app ntfy.sh est installee"
|
|
457
|
+
echo " - Le serveur est https://${NOTIFY_DOMAIN}"
|
|
458
|
+
echo " - Le topic est ${NTFY_TOPIC}"
|
|
459
|
+
echo " - Le token est saisi dans l'app"
|
|
460
|
+
echo " - Ton phone a du reseau"
|
|
461
|
+
else
|
|
462
|
+
info "Notifications OK"
|
|
463
|
+
fi
|
|
464
|
+
|
|
465
|
+
# Step 17: Install CLI globally (symlink to REPO, not to ~/.claude-remote)
|
|
466
|
+
echo ""
|
|
467
|
+
info "Installation de la commande claude-remote..."
|
|
468
|
+
local CLI_TARGET="${REPO_DIR}/scripts/claude-remote.sh"
|
|
469
|
+
if [ -w "/usr/local/bin" ]; then
|
|
470
|
+
ln -sf "${CLI_TARGET}" /usr/local/bin/claude-remote
|
|
471
|
+
else
|
|
472
|
+
sudo ln -sf "${CLI_TARGET}" /usr/local/bin/claude-remote
|
|
473
|
+
fi
|
|
474
|
+
info "Commande 'claude-remote' installee globalement ✓"
|
|
475
|
+
|
|
476
|
+
echo ""
|
|
477
|
+
info "C'est pret ! Ouvre https://${CLAUDE_DOMAIN}"
|
|
478
|
+
echo ""
|
|
479
|
+
echo "Commandes utiles :"
|
|
480
|
+
echo " claude-remote up # Demarrer"
|
|
481
|
+
echo " claude-remote down # Arreter"
|
|
482
|
+
echo " claude-remote add /chemin/projet # Ajouter un projet"
|
|
483
|
+
echo " claude-remote status # Etat"
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
cmd_ping() {
|
|
487
|
+
info "Test de connexion..."
|
|
488
|
+
echo ""
|
|
489
|
+
|
|
490
|
+
# Check containers are running
|
|
491
|
+
local ALL_OK=true
|
|
492
|
+
|
|
493
|
+
if docker ps --format '{{.Names}}' | grep -q "claude-remote"; then
|
|
494
|
+
info "code-server : OK (running)"
|
|
495
|
+
else
|
|
496
|
+
error "code-server : DOWN"
|
|
497
|
+
ALL_OK=false
|
|
498
|
+
fi
|
|
499
|
+
|
|
500
|
+
if docker ps --format '{{.Names}}' | grep -q "claude-ntfy"; then
|
|
501
|
+
info "ntfy : OK (running)"
|
|
502
|
+
else
|
|
503
|
+
error "ntfy : DOWN"
|
|
504
|
+
ALL_OK=false
|
|
505
|
+
fi
|
|
506
|
+
|
|
507
|
+
if docker ps --format '{{.Names}}' | grep -q "claude-tunnel"; then
|
|
508
|
+
info "tunnel : OK (running)"
|
|
509
|
+
else
|
|
510
|
+
error "tunnel : DOWN"
|
|
511
|
+
ALL_OK=false
|
|
512
|
+
fi
|
|
513
|
+
|
|
514
|
+
# Test ntfy connectivity from code-server
|
|
515
|
+
if [ "$ALL_OK" = true ]; then
|
|
516
|
+
echo ""
|
|
517
|
+
local NTFY_TEST
|
|
518
|
+
NTFY_TEST=$(docker exec claude-remote sh -c "curl -s -o /dev/null -w '%{http_code}' http://ntfy:80/v1/health" 2>/dev/null || echo "000")
|
|
519
|
+
if [ "$NTFY_TEST" = "200" ]; then
|
|
520
|
+
info "ntfy health : OK"
|
|
521
|
+
else
|
|
522
|
+
error "ntfy health : FAIL (HTTP ${NTFY_TEST})"
|
|
523
|
+
ALL_OK=false
|
|
524
|
+
fi
|
|
525
|
+
fi
|
|
526
|
+
|
|
527
|
+
# Test notification
|
|
528
|
+
if [ "$ALL_OK" = true ]; then
|
|
529
|
+
echo ""
|
|
530
|
+
read -rp "Envoyer une notification de test sur ton phone ? (oui/non): " SEND_TEST
|
|
531
|
+
if [ "$SEND_TEST" = "oui" ]; then
|
|
532
|
+
source "${CLAUDE_REMOTE_HOME}/.env"
|
|
533
|
+
docker exec claude-remote sh -c "
|
|
534
|
+
curl -s \
|
|
535
|
+
-H 'Authorization: Bearer ${NTFY_TOKEN}' \
|
|
536
|
+
-H 'Title: Claude Remote' \
|
|
537
|
+
-d 'Ping ! Claude Remote fonctionne.' \
|
|
538
|
+
http://ntfy:80/${NTFY_TOPIC}
|
|
539
|
+
"
|
|
540
|
+
info "Notification envoyee"
|
|
541
|
+
fi
|
|
542
|
+
fi
|
|
543
|
+
|
|
544
|
+
echo ""
|
|
545
|
+
if [ "$ALL_OK" = true ]; then
|
|
546
|
+
info "Tout est OK"
|
|
547
|
+
else
|
|
548
|
+
error "Il y a des problemes. Lance 'claude-remote logs' pour voir les erreurs."
|
|
549
|
+
fi
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
cmd_help() {
|
|
553
|
+
echo "claude-remote -- Accede a Claude Code depuis ton phone"
|
|
554
|
+
echo ""
|
|
555
|
+
echo "Usage: claude-remote <command> [args]"
|
|
556
|
+
echo ""
|
|
557
|
+
echo "Commands:"
|
|
558
|
+
echo " init Voir l'etat de l'installation"
|
|
559
|
+
echo " setup Installation premiere fois"
|
|
560
|
+
echo " up Demarre les containers"
|
|
561
|
+
echo " down Arrete les containers"
|
|
562
|
+
echo " status Etat des containers"
|
|
563
|
+
echo " logs Logs du container principal"
|
|
564
|
+
echo " ping Teste la connexion et les notifications"
|
|
565
|
+
echo " add <path> Ajoute un projet"
|
|
566
|
+
echo " remove <name> Retire un projet"
|
|
567
|
+
echo " help Affiche cette aide"
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
# Main
|
|
571
|
+
case "${1:-init}" in
|
|
572
|
+
init) cmd_init ;;
|
|
573
|
+
setup) cmd_setup ;;
|
|
574
|
+
up) cmd_up ;;
|
|
575
|
+
down) cmd_down ;;
|
|
576
|
+
status) cmd_status ;;
|
|
577
|
+
logs) cmd_logs ;;
|
|
578
|
+
ping) cmd_ping ;;
|
|
579
|
+
add) cmd_add "${2:?Usage: claude-remote add <path>}" ;;
|
|
580
|
+
remove) cmd_remove "${2:?Usage: claude-remote remove <name>}" ;;
|
|
581
|
+
help) cmd_help ;;
|
|
582
|
+
*) error "Unknown command: $1"; cmd_help; exit 1 ;;
|
|
583
|
+
esac
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Claude Code hook: sends push notifications to self-hosted ntfy
|
|
3
|
+
# Called by Claude Code hooks (Notification, Stop)
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# notify.sh notification "message"
|
|
7
|
+
# notify.sh stop <exit_code>
|
|
8
|
+
# notify.sh test
|
|
9
|
+
#
|
|
10
|
+
# Required env vars: NTFY_URL, NTFY_TOPIC, NTFY_TOKEN, CLAUDE_DOMAIN
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
EVENT="${1:-}"
|
|
15
|
+
MESSAGE="${2:-}"
|
|
16
|
+
|
|
17
|
+
# Read JSON input from stdin (Claude Code passes hook data as JSON)
|
|
18
|
+
STDIN_JSON=""
|
|
19
|
+
if [ ! -t 0 ]; then
|
|
20
|
+
STDIN_JSON="$(cat)"
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
if [ -z "$EVENT" ]; then
|
|
24
|
+
echo "Usage: notify.sh <event> [message]" >&2
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
send_notification() {
|
|
29
|
+
local body="$1"
|
|
30
|
+
local priority="${2:-default}"
|
|
31
|
+
|
|
32
|
+
curl -s \
|
|
33
|
+
-H "Authorization: Bearer ${NTFY_TOKEN}" \
|
|
34
|
+
-H "Title: Claude Remote" \
|
|
35
|
+
-H "Priority: ${priority}" \
|
|
36
|
+
-H "Click: https://${CLAUDE_DOMAIN}" \
|
|
37
|
+
-d "${body}" \
|
|
38
|
+
"${NTFY_URL}/${NTFY_TOPIC}" > /dev/null 2>&1 || true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
case "$EVENT" in
|
|
42
|
+
tool-use)
|
|
43
|
+
# Parse tool name from JSON stdin
|
|
44
|
+
TOOL_NAME="unknown"
|
|
45
|
+
if [ -n "$STDIN_JSON" ] && command -v jq &> /dev/null; then
|
|
46
|
+
TOOL_NAME=$(echo "$STDIN_JSON" | jq -r '.tool_name // "unknown"')
|
|
47
|
+
fi
|
|
48
|
+
send_notification "Claude veut utiliser: ${TOOL_NAME}" "high"
|
|
49
|
+
;;
|
|
50
|
+
notification)
|
|
51
|
+
send_notification "Claude: ${MESSAGE}" "high"
|
|
52
|
+
;;
|
|
53
|
+
stop)
|
|
54
|
+
EXIT_CODE="${MESSAGE:-0}"
|
|
55
|
+
if [ "$EXIT_CODE" = "0" ]; then
|
|
56
|
+
send_notification "Claude a termine sa tache" "default"
|
|
57
|
+
else
|
|
58
|
+
send_notification "Erreur sur Claude (code: ${EXIT_CODE})" "urgent"
|
|
59
|
+
fi
|
|
60
|
+
;;
|
|
61
|
+
test)
|
|
62
|
+
send_notification "Test Claude Remote -- les notifications marchent !" "default"
|
|
63
|
+
;;
|
|
64
|
+
*)
|
|
65
|
+
echo "Unknown event: $EVENT" >&2
|
|
66
|
+
exit 1
|
|
67
|
+
;;
|
|
68
|
+
esac
|