JustAPI Bot fix: comprehensive UI, docs, security, and deployment improvements · 7/14/2026
381467d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import { getDatabase, schema } from "@/db";
import { canReadRepo, canWriteRepo } from "@/lib/permissions";
import { startSSHServer } from "@/lib/ssh";
import { and, eq } from "drizzle-orm";
import { join } from "path";
import { uploadRepoToStorage, getStorageRepoPath, isCloudStorage } from "@/lib/git-storage";
const DATA_DIR = join(process.cwd(), "data");
const REPOS_DIR = process.env.GIT_REPOS_PATH || join(DATA_DIR, "repositories");
const SSH_DIR = join(DATA_DIR, "ssh");
const HOST_KEY_PATH = process.env.GIT_SSH_HOST_KEY || join(SSH_DIR, "host_key");
const SSH_PORT = parseInt(process.env.GIT_SSH_PORT || "2222", 10);
async function main() {
console.log(`Starting OpenCodeHub SSH Server on port ${SSH_PORT}...`);
console.log(`Repository path: ${REPOS_DIR}`);
await startSSHServer({
port: SSH_PORT,
hostKeyPath: HOST_KEY_PATH,
reposPath: REPOS_DIR,
authenticateUser: async (_, key) => {
// We ignore the username (usually 'git') and authenticate by key
const db = getDatabase();
// Iterate over all keys to find a match
const allKeys = await db.query.sshKeys.findMany();
for (const dbKey of allKeys) {
try {
// Parse DB key
const parts = dbKey.publicKey.trim().split(" ");
if (parts.length < 2) continue;
const dbKeyData = Buffer.from(parts[1], 'base64');
// Compare buffers
if (dbKeyData.equals(key)) {
return {
valid: true,
userId: dbKey.userId,
canRead: true, // Specific repo auth is handled in authorizeRepo
canWrite: true
};
}
} catch (e) {
console.error("Error checking key:", e);
}
}
return { valid: false };
},
authorizeRepo: async (userId, repoPath, operation) => {
// repoPath is "owner/repo.git"
const [ownerName, repoNameWithGit] = repoPath.split("/");
const repoName = repoNameWithGit.replace(".git", "");
if (!ownerName || !repoName) return false;
const db = getDatabase();
// Find Repo
const owner = await db.query.users.findFirst({
where: eq(schema.users.username, ownerName)
});
if (!owner) return false;
const repo = await db.query.repositories.findFirst({
where: and(
eq(schema.repositories.ownerId, owner.id),
eq(schema.repositories.name, repoName)
)
});
if (!repo) return false;
// Check if user is admin
const user = await db.query.users.findFirst({
where: eq(schema.users.id, userId)
});
const isAdmin = user?.isAdmin === true;
if (operation === "read") {
return canReadRepo(userId, repo, { isAdmin });
} else {
return canWriteRepo(userId, repo, { isAdmin });
}
},
onPush: async (userId, repoPath, refs) => {
console.log(`Push received to ${repoPath} from ${userId}`);
// Resolve Repo ID
const [ownerName, repoNameWithGit] = repoPath.split("/");
const repoName = repoNameWithGit.replace(".git", "");
const db = getDatabase();
const owner = await db.query.users.findFirst({ where: eq(schema.users.username, ownerName) });
if (!owner) return;
const repo = await db.query.repositories.findFirst({
where: and(eq(schema.repositories.ownerId, owner.id), eq(schema.repositories.name, repoName))
});
if (repo) {
// 1. Run Analysis
import("@/lib/analysis").then(({ analyzeRepository }) => {
analyzeRepository(repo.id, userId).catch(console.error);
});
// 2. Trigger Workflows
import("@/lib/workflows").then(({ triggerRepoWorkflows }) => {
refs.forEach(refName => {
// refs contains ref names like "refs/heads/main"
if (refName.startsWith("refs/heads/")) {
// We need to get the latest commit SHA for this ref
// For now, trigger with a placeholder - the workflow engine will resolve it
triggerRepoWorkflows(repo.id, "HEAD", refName, userId).catch(console.error);
}
});
});
// 3. Sync to Cloud Storage (if enabled)
if (await isCloudStorage()) {
console.log(`[SSH] Syncing ${repoPath} to storage...`);
const storagePath = getStorageRepoPath(owner.username, repo.name);
const localRepoPath = join(REPOS_DIR, owner.username, `${repo.name}.git`);
await uploadRepoToStorage(localRepoPath, storagePath);
console.log(`[SSH] Sync complete.`);
}
}
}
});
}
main().catch(console.error);