Skip to content

Commit af063c1

Browse files
committed
Fix docker
1 parent 9384a5b commit af063c1

9 files changed

Lines changed: 310 additions & 347 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ cargo install hadrian
2424
hadrian
2525

2626
# Or with Docker
27-
docker run -p 8080:8080 -e OPENROUTER_API_KEY=sk-... ghcr.io/ScriptSmith/hadrian
27+
docker run -p 8080:8080 ghcr.io/scriptsmith/hadrian
2828
```
2929

3030
The gateway starts at `http://localhost:8080` with the chat UI. No database required for basic use.

docs/app/(home)/page.tsx

Lines changed: 3 additions & 315 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
11
"use client";
22

3-
import { useState, useEffect, useRef } from "react";
3+
import { useEffect, useRef } from "react";
44
import Link from "next/link";
5-
import {
6-
Server,
7-
Shield,
8-
Users,
9-
Zap,
10-
Eye,
11-
Code,
12-
Brain,
13-
Copy,
14-
Check,
15-
Download,
16-
X,
17-
} from "lucide-react";
5+
import { Server, Shield, Users, Zap, Eye, Code, Brain } from "lucide-react";
186
import { StoryEmbed } from "@/components/story-embed";
7+
import { QuickStartSelector } from "@/components/quick-start-selector";
198

209
function GitHubIcon({ className }: { className?: string }) {
2110
return (
@@ -25,307 +14,6 @@ function GitHubIcon({ className }: { className?: string }) {
2514
);
2615
}
2716

28-
// --- Quick Start Selector ---
29-
30-
type Method = "binary" | "docker" | "cargo";
31-
type OS = "linux-x86_64" | "linux-arm64" | "macos-arm64" | "windows";
32-
type Profile = "full" | "standard" | "minimal" | "tiny";
33-
type Libc = "gnu" | "musl";
34-
35-
const osLabels: Record<OS, string> = {
36-
"linux-x86_64": "Linux x86_64",
37-
"linux-arm64": "Linux ARM64",
38-
"macos-arm64": "macOS ARM64",
39-
windows: "Windows",
40-
};
41-
42-
const libcLabels: Record<Libc, string> = {
43-
gnu: "glibc",
44-
musl: "musl",
45-
};
46-
47-
function getTarget(os: OS, libc: Libc): string {
48-
switch (os) {
49-
case "linux-x86_64":
50-
return libc === "musl" ? "x86_64-unknown-linux-musl" : "x86_64-unknown-linux-gnu";
51-
case "linux-arm64":
52-
return "aarch64-unknown-linux-gnu";
53-
case "macos-arm64":
54-
return "aarch64-apple-darwin";
55-
case "windows":
56-
return "x86_64-pc-windows-msvc";
57-
}
58-
}
59-
60-
const profileSummaries: Record<Profile, string> = {
61-
full: "Everything",
62-
standard: "Production deployment",
63-
minimal: "Development and embedded use",
64-
tiny: "Stateless proxy",
65-
};
66-
67-
const featureMatrix: { name: string; profiles: Profile[] }[] = [
68-
{ name: "OpenAI", profiles: ["tiny", "minimal", "standard", "full"] },
69-
{ name: "Anthropic", profiles: ["minimal", "standard", "full"] },
70-
{ name: "AWS Bedrock", profiles: ["minimal", "standard", "full"] },
71-
{ name: "Google Vertex AI", profiles: ["minimal", "standard", "full"] },
72-
{ name: "Azure OpenAI", profiles: ["minimal", "standard", "full"] },
73-
{ name: "SQLite", profiles: ["minimal", "standard", "full"] },
74-
{ name: "Embedded UI", profiles: ["minimal", "standard", "full"] },
75-
{ name: "Setup wizard", profiles: ["minimal", "standard", "full"] },
76-
{ name: "PostgreSQL", profiles: ["standard", "full"] },
77-
{ name: "Redis caching", profiles: ["standard", "full"] },
78-
{ name: "SSO (OIDC / OAuth)", profiles: ["standard", "full"] },
79-
{ name: "CEL RBAC", profiles: ["standard", "full"] },
80-
{ name: "S3 storage", profiles: ["standard", "full"] },
81-
{ name: "Secrets managers", profiles: ["standard", "full"] },
82-
{ name: "OTLP & Prometheus", profiles: ["standard", "full"] },
83-
{ name: "OpenAPI docs", profiles: ["standard", "full"] },
84-
{ name: "Doc extraction", profiles: ["standard", "full"] },
85-
{ name: "SAML SSO", profiles: ["full"] },
86-
{ name: "Kreuzberg OCR", profiles: ["full"] },
87-
{ name: "ClamAV scanning", profiles: ["full"] },
88-
];
89-
90-
function getInstallCommand(method: Method, os: OS, profile: Profile, libc: Libc): string {
91-
if (method === "docker") {
92-
return [
93-
"docker run \\",
94-
" -p 8080:8080 \\",
95-
" -e OPENROUTER_API_KEY=sk-... \\",
96-
" ghcr.io/scriptsmith/hadrian",
97-
].join("\n");
98-
}
99-
if (method === "cargo") {
100-
return "cargo install hadrian\nhadrian";
101-
}
102-
const ext = os === "windows" ? "zip" : "tar.gz";
103-
const target = getTarget(os, libc);
104-
const filename = `hadrian-${target}-${profile}.${ext}`;
105-
const url = `https://github.com/ScriptSmith/hadrian/releases/latest/download/${filename}`;
106-
if (os === "windows") {
107-
return [`curl -LO \\`, ` ${url}`, `tar -xf ${filename}`, `.\\hadrian.exe`].join("\n");
108-
}
109-
return [`curl -L \\`, ` ${url} \\`, ` | tar xz`, `./hadrian`].join("\n");
110-
}
111-
112-
function getDownloadUrl(os: OS, profile: Profile, libc: Libc): string {
113-
const ext = os === "windows" ? "zip" : "tar.gz";
114-
const target = getTarget(os, libc);
115-
return `https://github.com/ScriptSmith/hadrian/releases/latest/download/hadrian-${target}-${profile}.${ext}`;
116-
}
117-
118-
function ToggleGroup<T extends string>({
119-
options,
120-
value,
121-
onChange,
122-
labels,
123-
disabled,
124-
}: {
125-
options: T[];
126-
value: T;
127-
onChange: (v: T) => void;
128-
labels?: Record<T, string>;
129-
disabled?: Set<T>;
130-
}) {
131-
return (
132-
<div className="flex flex-wrap gap-1.5">
133-
{options.map((opt) => {
134-
const isDisabled = disabled?.has(opt);
135-
return (
136-
<button
137-
key={opt}
138-
onClick={() => onChange(opt)}
139-
disabled={isDisabled}
140-
className={`rounded-md px-3 py-1.5 text-sm font-medium transition-colors ${
141-
isDisabled
142-
? "cursor-not-allowed bg-fd-muted text-fd-muted-foreground/40"
143-
: value === opt
144-
? "bg-fd-primary text-fd-primary-foreground"
145-
: "bg-fd-muted text-fd-muted-foreground hover:bg-fd-muted/80 hover:text-fd-foreground"
146-
}`}
147-
>
148-
{labels ? labels[opt] : opt}
149-
</button>
150-
);
151-
})}
152-
</div>
153-
);
154-
}
155-
156-
function getDisabledProfiles(os: OS, libc: Libc): Set<Profile> | undefined {
157-
if (os === "windows") return new Set(["full", "standard"]);
158-
if (os === "linux-arm64") return new Set(["full"]);
159-
if (os.startsWith("linux-") && libc === "musl") return new Set(["full"]);
160-
return undefined;
161-
}
162-
163-
function QuickStartSelector() {
164-
const [method, setMethod] = useState<Method>("binary");
165-
const [os, setOs] = useState<OS>("linux-x86_64");
166-
const [profile, setProfile] = useState<Profile>("standard");
167-
const [libc, setLibc] = useState<Libc>("musl");
168-
const [copied, setCopied] = useState(false);
169-
170-
const isLinux = os === "linux-x86_64" || os === "linux-arm64";
171-
const disabledProfiles = getDisabledProfiles(os, libc);
172-
const disabledLibcs = os === "linux-arm64" ? new Set<Libc>(["musl"]) : undefined;
173-
174-
const handleOsChange = (newOs: OS) => {
175-
setOs(newOs);
176-
// Reset libc to gnu for non-Linux or ARM64 (no musl builds)
177-
let newLibc = libc;
178-
if (!newOs.startsWith("linux-") || newOs === "linux-arm64") {
179-
newLibc = "gnu";
180-
setLibc("gnu");
181-
}
182-
// Adjust profile if it becomes unavailable
183-
const disabled = getDisabledProfiles(newOs, newLibc);
184-
if (disabled?.has(profile)) {
185-
setProfile(disabled.has("standard") ? "minimal" : "standard");
186-
}
187-
};
188-
189-
const handleLibcChange = (newLibc: Libc) => {
190-
setLibc(newLibc);
191-
if (newLibc === "musl" && profile === "full") {
192-
setProfile("standard");
193-
}
194-
};
195-
196-
const command = getInstallCommand(method, os, profile, libc);
197-
const downloadUrl = method === "binary" ? getDownloadUrl(os, profile, libc) : null;
198-
199-
const handleCopy = async () => {
200-
if (navigator.clipboard) {
201-
await navigator.clipboard.writeText(command);
202-
} else {
203-
const textarea = document.createElement("textarea");
204-
textarea.value = command;
205-
textarea.style.position = "fixed";
206-
textarea.style.opacity = "0";
207-
document.body.appendChild(textarea);
208-
textarea.select();
209-
document.execCommand("copy");
210-
document.body.removeChild(textarea);
211-
}
212-
setCopied(true);
213-
setTimeout(() => setCopied(false), 2000);
214-
};
215-
216-
return (
217-
<div className="overflow-hidden rounded-lg border border-fd-border bg-fd-card">
218-
<div className="space-y-3 border-b border-fd-border bg-fd-muted/50 p-4">
219-
<div className="flex flex-wrap items-center gap-3">
220-
<span className="w-16 shrink-0 text-sm font-medium text-fd-muted-foreground">Method</span>
221-
<ToggleGroup
222-
options={["binary", "docker", "cargo"] as Method[]}
223-
value={method}
224-
onChange={setMethod}
225-
labels={{ binary: "Binary", docker: "Docker", cargo: "Cargo" }}
226-
/>
227-
</div>
228-
{method === "binary" && (
229-
<>
230-
<div className="flex flex-wrap items-center gap-3">
231-
<span className="w-16 shrink-0 text-sm font-medium text-fd-muted-foreground">OS</span>
232-
<ToggleGroup
233-
options={["linux-x86_64", "linux-arm64", "macos-arm64", "windows"] as OS[]}
234-
value={os}
235-
onChange={handleOsChange}
236-
labels={osLabels}
237-
/>
238-
</div>
239-
{isLinux && (
240-
<div className="flex flex-wrap items-center gap-3">
241-
<span className="w-16 shrink-0 text-sm font-medium text-fd-muted-foreground">
242-
Libc
243-
</span>
244-
<ToggleGroup
245-
options={["gnu", "musl"] as Libc[]}
246-
value={libc}
247-
onChange={handleLibcChange}
248-
labels={libcLabels}
249-
disabled={disabledLibcs}
250-
/>
251-
</div>
252-
)}
253-
<div className="flex flex-wrap items-center gap-3">
254-
<span className="w-16 shrink-0 text-sm font-medium text-fd-muted-foreground">
255-
Features
256-
</span>
257-
<ToggleGroup
258-
options={["full", "standard", "minimal", "tiny"] as Profile[]}
259-
value={profile}
260-
onChange={setProfile}
261-
disabled={disabledProfiles}
262-
/>
263-
</div>
264-
</>
265-
)}
266-
</div>
267-
268-
{/* Feature matrix — shown for binary installs */}
269-
{method === "binary" && (
270-
<div className="border-b border-fd-border bg-fd-muted/20 px-4 py-3">
271-
<p className="mb-2 text-sm font-medium text-fd-foreground">{profileSummaries[profile]}</p>
272-
<div className="grid grid-cols-2 gap-x-6 gap-y-1 sm:grid-cols-3">
273-
{featureMatrix.map((f) => {
274-
const included = f.profiles.includes(profile);
275-
return (
276-
<div key={f.name} className="flex items-center gap-2 text-sm">
277-
{included ? (
278-
<Check className="h-3.5 w-3.5 shrink-0 text-green-500" />
279-
) : (
280-
<X className="h-3.5 w-3.5 shrink-0 text-fd-muted-foreground/40" />
281-
)}
282-
<span
283-
className={
284-
included ? "text-fd-foreground" : "text-fd-muted-foreground/50 line-through"
285-
}
286-
>
287-
{f.name}
288-
</span>
289-
</div>
290-
);
291-
})}
292-
</div>
293-
</div>
294-
)}
295-
296-
<div className="relative">
297-
<pre className="overflow-x-auto whitespace-pre-wrap break-all p-4 pr-12 text-sm">
298-
<code className="text-fd-foreground">{command}</code>
299-
</pre>
300-
<button
301-
onClick={handleCopy}
302-
className="absolute right-3 top-3 rounded-md p-1.5 text-fd-muted-foreground transition-colors hover:bg-fd-muted hover:text-fd-foreground"
303-
aria-label="Copy command"
304-
>
305-
{copied ? <Check className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
306-
</button>
307-
</div>
308-
309-
{downloadUrl && (
310-
<div className="border-t border-fd-border bg-fd-muted/30 px-4 py-3">
311-
<a
312-
href={downloadUrl}
313-
className="inline-flex items-center gap-2 rounded-lg bg-fd-primary px-4 py-2 text-sm font-medium text-fd-primary-foreground transition-colors hover:bg-fd-primary/90"
314-
>
315-
<Download className="h-4 w-4" />
316-
Download binary
317-
</a>
318-
<p className="mt-2 break-all text-xs text-fd-muted-foreground">
319-
<a href={downloadUrl} className="underline">
320-
{downloadUrl}
321-
</a>
322-
</p>
323-
</div>
324-
)}
325-
</div>
326-
);
327-
}
328-
32917
// --- See it in Action (Gallery) ---
33018

33119
const demos = [

0 commit comments

Comments
 (0)