@atercates/claude-deck 0.2.6 → 0.2.8
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 +82 -46
- package/middleware.ts +60 -0
- package/package.json +2 -1
- package/scripts/install.sh +234 -18
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ClaudeDeck
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Self-hosted web UI for managing Claude Code sessions.
|
|
4
4
|
|
|
5
5
|
[](https://discord.gg/cSjutkCGAh)
|
|
6
6
|
|
|
@@ -10,79 +10,110 @@ https://github.com/user-attachments/assets/0e2e66f7-037e-4739-99ec-608d1840df0a
|
|
|
10
10
|
|
|
11
11
|
## Installation
|
|
12
12
|
|
|
13
|
-
###
|
|
14
|
-
|
|
15
|
-
If you already have Node.js 24+ installed:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
# Install globally
|
|
19
|
-
npm install -g @atercates/claude-deck
|
|
20
|
-
|
|
21
|
-
# Run setup (checks/installs tmux, ripgrep, builds app)
|
|
22
|
-
claude-deck install
|
|
23
|
-
|
|
24
|
-
# Start the server
|
|
25
|
-
claude-deck start
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### Via curl (Installs everything)
|
|
29
|
-
|
|
30
|
-
For fresh installs without Node.js:
|
|
13
|
+
### Quick Install
|
|
31
14
|
|
|
32
15
|
```bash
|
|
33
16
|
curl -fsSL https://raw.githubusercontent.com/ATERCATES/claude-deck/main/scripts/install.sh | bash
|
|
34
|
-
claude-deck start
|
|
35
17
|
```
|
|
36
18
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Download native desktop apps from [Releases](https://github.com/ATERCATES/claude-deck/releases):
|
|
19
|
+
The installer will:
|
|
40
20
|
|
|
41
|
-
-
|
|
42
|
-
-
|
|
21
|
+
- Install Node.js 24 if needed (via [n](https://github.com/tj/n))
|
|
22
|
+
- Install pnpm
|
|
23
|
+
- Ask for port, SSH host/port (for VS Code remote button)
|
|
24
|
+
- Clone, build, and start as a systemd service
|
|
25
|
+
- First visit prompts you to create an account
|
|
43
26
|
|
|
44
|
-
|
|
27
|
+
### Non-Interactive
|
|
45
28
|
|
|
46
|
-
|
|
29
|
+
```bash
|
|
30
|
+
bash install.sh --port 3011 --ssh-host myserver.com --ssh-port 22 -y
|
|
31
|
+
```
|
|
47
32
|
|
|
48
33
|
### Manual Install
|
|
49
34
|
|
|
50
35
|
```bash
|
|
51
36
|
git clone https://github.com/ATERCATES/claude-deck
|
|
52
37
|
cd claude-deck
|
|
53
|
-
|
|
54
|
-
|
|
38
|
+
pnpm install
|
|
39
|
+
pnpm build
|
|
40
|
+
pnpm start # http://localhost:3011
|
|
55
41
|
```
|
|
56
42
|
|
|
57
43
|
### Prerequisites
|
|
58
44
|
|
|
59
45
|
- Node.js 24+
|
|
60
|
-
- tmux
|
|
61
|
-
- [
|
|
62
|
-
|
|
46
|
+
- tmux (with `set -g mouse on` in `~/.tmux.conf`)
|
|
47
|
+
- [Claude Code](https://github.com/anthropics/claude-code)
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
Create a `.env` file in the project root:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
PORT=3011
|
|
55
|
+
|
|
56
|
+
# SSH config for "Open in VS Code" remote button (optional)
|
|
57
|
+
SSH_HOST=myserver.example.com
|
|
58
|
+
SSH_PORT=22
|
|
59
|
+
```
|
|
63
60
|
|
|
64
61
|
## Features
|
|
65
62
|
|
|
66
|
-
- **
|
|
67
|
-
- **
|
|
63
|
+
- **Session management** - Resume, create, and organize Claude Code sessions
|
|
64
|
+
- **Mobile-first** - Full functionality from your phone
|
|
68
65
|
- **Multi-pane layout** - Run up to 4 sessions side-by-side
|
|
66
|
+
- **Active session monitoring** - Real-time status (running/waiting/idle)
|
|
69
67
|
- **Code search** - Fast codebase search with syntax-highlighted results (Cmd+K)
|
|
70
|
-
- **File picker** - Browse and attach files to sessions, with direct upload from mobile
|
|
71
|
-
- **Clone from GitHub** - Clone repos directly from the UI when creating projects
|
|
72
68
|
- **Git integration** - Status, diffs, commits, PRs from the UI
|
|
73
69
|
- **Git worktrees** - Isolated branches with auto-setup
|
|
74
70
|
- **Dev servers** - Start/stop Node.js and Docker servers
|
|
75
71
|
- **Session orchestration** - Conductor/worker model via MCP
|
|
72
|
+
- **VS Code integration** - Open projects in VS Code with one click (supports SSH remote)
|
|
73
|
+
- **Auth** - Login with username/password, optional TOTP 2FA
|
|
76
74
|
|
|
77
|
-
##
|
|
75
|
+
## Service Management
|
|
78
76
|
|
|
79
77
|
```bash
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
sudo systemctl start claudedeck
|
|
79
|
+
sudo systemctl stop claudedeck
|
|
80
|
+
sudo systemctl restart claudedeck
|
|
81
|
+
sudo systemctl status claudedeck
|
|
82
|
+
sudo journalctl -u claudedeck -f # tail logs
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Reverse Proxy (nginx)
|
|
86
|
+
|
|
87
|
+
For HTTPS access behind nginx:
|
|
88
|
+
|
|
89
|
+
```nginx
|
|
90
|
+
map $http_upgrade $connection_upgrade {
|
|
91
|
+
default upgrade;
|
|
92
|
+
'' close;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
server {
|
|
96
|
+
listen 443 ssl;
|
|
97
|
+
server_name claudedeck.example.com;
|
|
98
|
+
|
|
99
|
+
ssl_certificate /path/to/cert.pem;
|
|
100
|
+
ssl_certificate_key /path/to/key.pem;
|
|
101
|
+
|
|
102
|
+
location / {
|
|
103
|
+
proxy_pass http://127.0.0.1:3011;
|
|
104
|
+
proxy_set_header Host $host;
|
|
105
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
106
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
107
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
108
|
+
|
|
109
|
+
proxy_http_version 1.1;
|
|
110
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
111
|
+
proxy_set_header Connection $connection_upgrade;
|
|
112
|
+
|
|
113
|
+
proxy_read_timeout 86400s;
|
|
114
|
+
proxy_send_timeout 86400s;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
86
117
|
```
|
|
87
118
|
|
|
88
119
|
## Mobile Access
|
|
@@ -95,12 +126,17 @@ Use [Tailscale](https://tailscale.com) for secure access from your phone:
|
|
|
95
126
|
|
|
96
127
|
## Documentation
|
|
97
128
|
|
|
98
|
-
|
|
129
|
+
Detailed technical docs are in `docs/`:
|
|
130
|
+
|
|
131
|
+
- [`docs/architecture.md`](docs/architecture.md) - System overview, data flow, directory structure
|
|
132
|
+
- [`docs/decisions.md`](docs/decisions.md) - Why SQLite, why tmux, why JSONL
|
|
133
|
+
- [`docs/troubleshooting.md`](docs/troubleshooting.md) - Common issues and fixes
|
|
134
|
+
- [`docs/setup.md`](docs/setup.md) - Requirements, Docker, PWA, production deployment
|
|
99
135
|
|
|
100
136
|
## Related Projects
|
|
101
137
|
|
|
102
|
-
- **[aTerm](https://github.com/ATERCATES/aTerm)** -
|
|
103
|
-
- **[LumifyHub](https://lumifyhub.io)** - Team collaboration platform with real-time chat and structured documentation
|
|
138
|
+
- **[aTerm](https://github.com/ATERCATES/aTerm)** - Native desktop terminal workspace for AI-assisted coding
|
|
139
|
+
- **[LumifyHub](https://lumifyhub.io)** - Team collaboration platform with real-time chat and structured documentation
|
|
104
140
|
|
|
105
141
|
## License
|
|
106
142
|
|
package/middleware.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
|
|
3
|
+
const PUBLIC_PATHS = [
|
|
4
|
+
"/login",
|
|
5
|
+
"/setup",
|
|
6
|
+
"/api/auth/",
|
|
7
|
+
"/_next/",
|
|
8
|
+
"/favicon.ico",
|
|
9
|
+
"/icon.svg",
|
|
10
|
+
"/icons/",
|
|
11
|
+
"/manifest.json",
|
|
12
|
+
"/sw.js",
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export async function middleware(request: NextRequest) {
|
|
16
|
+
const { pathname } = request.nextUrl;
|
|
17
|
+
|
|
18
|
+
if (PUBLIC_PATHS.some((p) => pathname.startsWith(p))) {
|
|
19
|
+
return NextResponse.next();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const sessionCookie = request.cookies.get("claude_deck_session");
|
|
23
|
+
|
|
24
|
+
const internalBase = `http://localhost:${process.env.PORT || 3011}`;
|
|
25
|
+
|
|
26
|
+
if (!sessionCookie?.value) {
|
|
27
|
+
const setupCheck = await fetch(`${internalBase}/api/auth/session`, {
|
|
28
|
+
headers: { cookie: "" },
|
|
29
|
+
});
|
|
30
|
+
const data = await setupCheck.json();
|
|
31
|
+
|
|
32
|
+
if (data.needsSetup) {
|
|
33
|
+
return NextResponse.redirect(new URL("/setup", request.url));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (pathname.startsWith("/api/")) {
|
|
37
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
38
|
+
}
|
|
39
|
+
return NextResponse.redirect(new URL("/login", request.url));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const sessionCheck = await fetch(`${internalBase}/api/auth/session`, {
|
|
43
|
+
headers: { cookie: `claude_deck_session=${sessionCookie.value}` },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!sessionCheck.ok) {
|
|
47
|
+
if (pathname.startsWith("/api/")) {
|
|
48
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
49
|
+
}
|
|
50
|
+
return NextResponse.redirect(new URL("/login", request.url));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return NextResponse.next();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const config = {
|
|
57
|
+
matcher: [
|
|
58
|
+
"/((?!_next/static|_next/image|favicon.ico|icon.svg|icons/|manifest.json|sw.js).*)",
|
|
59
|
+
],
|
|
60
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atercates/claude-deck",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Self-hosted web UI for managing Claude Code sessions",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claude-deck": "./scripts/claude-deck"
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"scripts/",
|
|
18
18
|
"stores/",
|
|
19
19
|
"styles/",
|
|
20
|
+
"middleware.ts",
|
|
20
21
|
"next.config.js",
|
|
21
22
|
"postcss.config.mjs",
|
|
22
23
|
"server.ts",
|
package/scripts/install.sh
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
# Usage:
|
|
6
6
|
# curl -fsSL https://raw.githubusercontent.com/ATERCATES/claude-deck/main/scripts/install.sh | bash
|
|
7
7
|
#
|
|
8
|
+
# Or with options:
|
|
9
|
+
# bash install.sh --port 3011 --ssh-host myserver.com --ssh-port 22
|
|
10
|
+
#
|
|
8
11
|
|
|
9
12
|
set -e
|
|
10
13
|
|
|
@@ -12,37 +15,250 @@ set -e
|
|
|
12
15
|
RED='\033[0;31m'
|
|
13
16
|
GREEN='\033[0;32m'
|
|
14
17
|
BLUE='\033[0;34m'
|
|
18
|
+
YELLOW='\033[1;33m'
|
|
15
19
|
BOLD='\033[1m'
|
|
20
|
+
DIM='\033[2m'
|
|
16
21
|
NC='\033[0m'
|
|
17
22
|
|
|
18
|
-
log_info()
|
|
23
|
+
log_info() { echo -e "${BLUE}==>${NC} $1"; }
|
|
19
24
|
log_success() { echo -e "${GREEN}==>${NC} $1"; }
|
|
20
|
-
|
|
25
|
+
log_warn() { echo -e "${YELLOW}==>${NC} $1"; }
|
|
26
|
+
log_error() { echo -e "${RED}==>${NC} $1"; }
|
|
21
27
|
|
|
28
|
+
INSTALL_DIR="$HOME/.claude-deck"
|
|
22
29
|
REPO_URL="https://github.com/ATERCATES/claude-deck.git"
|
|
23
|
-
|
|
30
|
+
NODE_MIN_VERSION=24
|
|
31
|
+
|
|
32
|
+
# ─── Parse CLI flags ──────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
FLAG_PORT=""
|
|
35
|
+
FLAG_SSH_HOST=""
|
|
36
|
+
FLAG_SSH_PORT=""
|
|
37
|
+
FLAG_NONINTERACTIVE=false
|
|
38
|
+
|
|
39
|
+
while [[ $# -gt 0 ]]; do
|
|
40
|
+
case "$1" in
|
|
41
|
+
--port) FLAG_PORT="$2"; shift 2 ;;
|
|
42
|
+
--ssh-host) FLAG_SSH_HOST="$2"; shift 2 ;;
|
|
43
|
+
--ssh-port) FLAG_SSH_PORT="$2"; shift 2 ;;
|
|
44
|
+
--yes|-y) FLAG_NONINTERACTIVE=true; shift ;;
|
|
45
|
+
*) shift ;;
|
|
46
|
+
esac
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
# ─── Interactive prompts ──────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
ask() {
|
|
52
|
+
local prompt="$1" default="$2" var="$3"
|
|
53
|
+
if [[ -t 0 ]] && [[ "$FLAG_NONINTERACTIVE" == false ]]; then
|
|
54
|
+
if [[ -n "$default" ]]; then
|
|
55
|
+
read -rp "$(echo -e "${BOLD}$prompt${NC} ${DIM}[$default]${NC}: ")" value
|
|
56
|
+
eval "$var=\"${value:-$default}\""
|
|
57
|
+
else
|
|
58
|
+
read -rp "$(echo -e "${BOLD}$prompt${NC}: ")" value
|
|
59
|
+
eval "$var=\"$value\""
|
|
60
|
+
fi
|
|
61
|
+
else
|
|
62
|
+
eval "$var=\"$default\""
|
|
63
|
+
fi
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# ─── Header ───────────────────────────────────────────────────────────────────
|
|
24
67
|
|
|
25
68
|
echo ""
|
|
26
|
-
echo -e "${BOLD}ClaudeDeck Installer${NC}"
|
|
69
|
+
echo -e "${BOLD} ClaudeDeck Installer${NC}"
|
|
70
|
+
echo -e "${DIM} Self-hosted web UI for Claude Code sessions${NC}"
|
|
27
71
|
echo ""
|
|
28
72
|
|
|
29
|
-
# Check
|
|
73
|
+
# ─── Check prerequisites ─────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
log_info "Checking prerequisites..."
|
|
76
|
+
|
|
30
77
|
if ! command -v git &> /dev/null; then
|
|
31
|
-
|
|
32
|
-
|
|
78
|
+
log_error "git is required. Install it with: sudo apt install git"
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
if ! command -v tmux &> /dev/null; then
|
|
83
|
+
log_error "tmux is required. Install it with: sudo apt install tmux"
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# ─── Node.js ──────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
install_node() {
|
|
90
|
+
log_info "Installing Node.js $NODE_MIN_VERSION..."
|
|
91
|
+
local N_PREFIX="$HOME/.n"
|
|
92
|
+
mkdir -p "$N_PREFIX"
|
|
93
|
+
curl -fsSL https://raw.githubusercontent.com/tj/n/master/bin/n -o /tmp/n
|
|
94
|
+
chmod +x /tmp/n
|
|
95
|
+
N_PREFIX="$N_PREFIX" /tmp/n "$NODE_MIN_VERSION"
|
|
96
|
+
rm -f /tmp/n
|
|
97
|
+
export PATH="$N_PREFIX/bin:$PATH"
|
|
98
|
+
log_success "Node.js $(node --version) installed"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Check for existing node
|
|
102
|
+
NODE_OK=false
|
|
103
|
+
if command -v node &> /dev/null; then
|
|
104
|
+
NODE_VERSION=$(node --version | sed 's/v//' | cut -d. -f1)
|
|
105
|
+
if [[ "$NODE_VERSION" -ge "$NODE_MIN_VERSION" ]]; then
|
|
106
|
+
NODE_OK=true
|
|
107
|
+
log_success "Node.js $(node --version) found"
|
|
108
|
+
fi
|
|
33
109
|
fi
|
|
34
110
|
|
|
35
|
-
#
|
|
36
|
-
if [[ -
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
111
|
+
# Check ~/.n/bin as well
|
|
112
|
+
if [[ "$NODE_OK" == false ]] && [[ -x "$HOME/.n/bin/node" ]]; then
|
|
113
|
+
export PATH="$HOME/.n/bin:$PATH"
|
|
114
|
+
NODE_VERSION=$(node --version | sed 's/v//' | cut -d. -f1)
|
|
115
|
+
if [[ "$NODE_VERSION" -ge "$NODE_MIN_VERSION" ]]; then
|
|
116
|
+
NODE_OK=true
|
|
117
|
+
log_success "Node.js $(node --version) found in ~/.n"
|
|
118
|
+
fi
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
if [[ "$NODE_OK" == false ]]; then
|
|
122
|
+
install_node
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# pnpm
|
|
126
|
+
if ! command -v pnpm &> /dev/null; then
|
|
127
|
+
log_info "Installing pnpm..."
|
|
128
|
+
npm install -g pnpm > /dev/null 2>&1
|
|
129
|
+
log_success "pnpm $(pnpm --version) installed"
|
|
40
130
|
else
|
|
41
|
-
|
|
42
|
-
mkdir -p "$(dirname "$INSTALL_DIR")"
|
|
43
|
-
git clone "$REPO_URL" "$INSTALL_DIR"
|
|
44
|
-
cd "$INSTALL_DIR"
|
|
131
|
+
log_success "pnpm $(pnpm --version) found"
|
|
45
132
|
fi
|
|
46
133
|
|
|
47
|
-
#
|
|
48
|
-
|
|
134
|
+
# ─── Configuration ────────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
echo ""
|
|
137
|
+
log_info "Configuration"
|
|
138
|
+
echo ""
|
|
139
|
+
|
|
140
|
+
PORT="${FLAG_PORT}"
|
|
141
|
+
SSH_HOST="${FLAG_SSH_HOST}"
|
|
142
|
+
SSH_PORT="${FLAG_SSH_PORT}"
|
|
143
|
+
|
|
144
|
+
ask "Port" "3011" PORT
|
|
145
|
+
ask "SSH host for VS Code remote button (leave empty to skip)" "" SSH_HOST
|
|
146
|
+
|
|
147
|
+
if [[ -n "$SSH_HOST" ]]; then
|
|
148
|
+
ask "SSH port" "22" SSH_PORT
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
echo ""
|
|
152
|
+
|
|
153
|
+
# ─── Install ──────────────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
if [[ -d "$INSTALL_DIR/repo" ]]; then
|
|
156
|
+
log_info "Updating existing installation..."
|
|
157
|
+
cd "$INSTALL_DIR/repo"
|
|
158
|
+
git pull --ff-only
|
|
159
|
+
else
|
|
160
|
+
log_info "Cloning ClaudeDeck..."
|
|
161
|
+
mkdir -p "$INSTALL_DIR"
|
|
162
|
+
git clone "$REPO_URL" "$INSTALL_DIR/repo"
|
|
163
|
+
cd "$INSTALL_DIR/repo"
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
# Dependencies
|
|
167
|
+
log_info "Installing dependencies..."
|
|
168
|
+
pnpm install > /dev/null 2>&1
|
|
169
|
+
|
|
170
|
+
# Approve native builds if needed
|
|
171
|
+
if grep -q "onlyBuiltDependencies" package.json 2>/dev/null; then
|
|
172
|
+
: # already configured
|
|
173
|
+
else
|
|
174
|
+
node -e "
|
|
175
|
+
const pkg = require('./package.json');
|
|
176
|
+
pkg.pnpm = pkg.pnpm || {};
|
|
177
|
+
pkg.pnpm.onlyBuiltDependencies = ['better-sqlite3', 'esbuild', 'node-pty', 'sharp'];
|
|
178
|
+
require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
|
179
|
+
"
|
|
180
|
+
pnpm install > /dev/null 2>&1
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
# Write .env
|
|
184
|
+
log_info "Writing .env..."
|
|
185
|
+
cat > "$INSTALL_DIR/repo/.env" << EOF
|
|
186
|
+
PORT=$PORT
|
|
187
|
+
EOF
|
|
188
|
+
|
|
189
|
+
if [[ -n "$SSH_HOST" ]]; then
|
|
190
|
+
echo "SSH_HOST=$SSH_HOST" >> "$INSTALL_DIR/repo/.env"
|
|
191
|
+
fi
|
|
192
|
+
if [[ -n "$SSH_PORT" ]] && [[ "$SSH_PORT" != "22" ]]; then
|
|
193
|
+
echo "SSH_PORT=$SSH_PORT" >> "$INSTALL_DIR/repo/.env"
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
# Build
|
|
197
|
+
log_info "Building for production (this may take a minute)..."
|
|
198
|
+
pnpm build
|
|
199
|
+
|
|
200
|
+
# tmux config
|
|
201
|
+
if [[ ! -f "$HOME/.tmux.conf" ]] || ! grep -q "mouse on" "$HOME/.tmux.conf" 2>/dev/null; then
|
|
202
|
+
log_info "Enabling tmux mouse support..."
|
|
203
|
+
echo "set -g mouse on" >> "$HOME/.tmux.conf"
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# ─── Systemd service ─────────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
NODE_BIN=$(which node)
|
|
209
|
+
TSX_BIN="$INSTALL_DIR/repo/node_modules/.bin/tsx"
|
|
210
|
+
|
|
211
|
+
SERVICE_FILE="[Unit]
|
|
212
|
+
Description=ClaudeDeck
|
|
213
|
+
After=network.target
|
|
214
|
+
|
|
215
|
+
[Service]
|
|
216
|
+
Type=simple
|
|
217
|
+
User=$USER
|
|
218
|
+
WorkingDirectory=$INSTALL_DIR/repo
|
|
219
|
+
Environment=NODE_ENV=production
|
|
220
|
+
Environment=PATH=$(dirname "$NODE_BIN"):$INSTALL_DIR/repo/node_modules/.bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
221
|
+
ExecStart=$TSX_BIN --env-file=.env server.ts
|
|
222
|
+
Restart=on-failure
|
|
223
|
+
RestartSec=5
|
|
224
|
+
|
|
225
|
+
[Install]
|
|
226
|
+
WantedBy=multi-user.target"
|
|
227
|
+
|
|
228
|
+
INSTALL_SERVICE=false
|
|
229
|
+
if [[ -t 0 ]] && [[ "$FLAG_NONINTERACTIVE" == false ]]; then
|
|
230
|
+
echo ""
|
|
231
|
+
ask "Install as systemd service? (y/n)" "y" INSTALL_SVC_ANSWER
|
|
232
|
+
[[ "$INSTALL_SVC_ANSWER" == "y" ]] && INSTALL_SERVICE=true
|
|
233
|
+
else
|
|
234
|
+
INSTALL_SERVICE=true
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
if [[ "$INSTALL_SERVICE" == true ]]; then
|
|
238
|
+
log_info "Installing systemd service..."
|
|
239
|
+
echo "$SERVICE_FILE" | sudo tee /etc/systemd/system/claudedeck.service > /dev/null
|
|
240
|
+
sudo systemctl daemon-reload
|
|
241
|
+
sudo systemctl enable claudedeck > /dev/null 2>&1
|
|
242
|
+
sudo systemctl restart claudedeck
|
|
243
|
+
sleep 2
|
|
244
|
+
|
|
245
|
+
if systemctl is-active --quiet claudedeck; then
|
|
246
|
+
log_success "Service running on port $PORT"
|
|
247
|
+
else
|
|
248
|
+
log_error "Service failed to start. Check: sudo journalctl -u claudedeck -f"
|
|
249
|
+
fi
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
# ─── Done ─────────────────────────────────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
echo ""
|
|
255
|
+
echo -e "${GREEN}${BOLD} ClaudeDeck installed!${NC}"
|
|
256
|
+
echo ""
|
|
257
|
+
echo -e " ${BOLD}Local:${NC} http://localhost:$PORT"
|
|
258
|
+
if [[ -n "$SSH_HOST" ]]; then
|
|
259
|
+
echo -e " ${BOLD}Remote:${NC} Configure your reverse proxy to point to port $PORT"
|
|
260
|
+
fi
|
|
261
|
+
echo ""
|
|
262
|
+
echo -e " ${DIM}First visit will prompt you to create an account.${NC}"
|
|
263
|
+
echo -e " ${DIM}Commands: sudo systemctl {start|stop|restart|status} claudedeck${NC}"
|
|
264
|
+
echo ""
|