fixes
Server Deploy / deploy (push) Successful in 2m24s

This commit is contained in:
2026-07-27 15:59:45 +01:00
parent fdfe8e8e46
commit 66140aaf58
8 changed files with 97 additions and 14 deletions
+1
View File
@@ -2,6 +2,7 @@ node_modules
dist
build
.env
.env.bck
docs/*
!docs/superpowers/
.superpowers
@@ -7,7 +7,16 @@ import { Field } from "@/components/Field";
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
export function LinkForm({ onLinked }: { onLinked: (instanceId: string) => void }) {
export function LinkForm({
onLinked,
claimId,
}: {
onLinked: (instanceId: string) => void;
// When set, this is a PAID placeholder awaiting its real install UUID: claim
// it in place rather than creating a fresh (Free) instance. The name was
// chosen at checkout, so it is not asked for again.
claimId?: string;
}) {
const [id, setId] = useState("");
const [name, setName] = useState("");
const [error, setError] = useState<string | undefined>();
@@ -28,7 +37,9 @@ export function LinkForm({ onLinked }: { onLinked: (instanceId: string) => void
setBusy(true);
setError(undefined);
try {
const inst = await api.link(value, name.trim());
const inst = claimId
? await api.claimLink(claimId, value)
: await api.link(value, name.trim());
onLinked(inst.instance_id);
} catch (err) {
setError(
@@ -58,12 +69,14 @@ export function LinkForm({ onLinked }: { onLinked: (instanceId: string) => void
</>
}
/>
<Field
label="Name it (optional)"
value={name}
onChange={(e) => setName(e.target.value)}
hint="So you can tell it apart from your other installs."
/>
{!claimId && (
<Field
label="Name it (optional)"
value={name}
onChange={(e) => setName(e.target.value)}
hint="So you can tell it apart from your other installs."
/>
)}
<Button type="submit" disabled={busy} className="justify-self-start">
{busy ? "Linking…" : "Link and issue licence"}
</Button>
@@ -1,6 +1,6 @@
"use client";
import { useRouter } from "next/navigation";
import { useRouter, useSearchParams } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { LinkForm } from "./LinkForm";
import { PageHeader } from "@/components/PageHeader";
@@ -8,6 +8,9 @@ import { PageHeader } from "@/components/PageHeader";
export default function LinkPage() {
const router = useRouter();
const qc = useQueryClient();
// A paid placeholder passes its id here so its install is claimed in place
// rather than a second, Free instance being created alongside it.
const claimId = useSearchParams().get("claim") ?? undefined;
return (
<div className="grid max-w-2xl gap-6">
@@ -17,6 +20,7 @@ export default function LinkPage() {
subtitle="Every licence is tied to one install, so we need its ID before we can issue yours. Paste it below and your licence is ready on the next screen."
/>
<LinkForm
claimId={claimId}
onLinked={(instanceId) => {
qc.invalidateQueries({ queryKey: ["account"] });
// Straight to the download, not back to a list: the licence is
+11 -1
View File
@@ -221,7 +221,17 @@ export function InstanceRecord({
<div className="flex flex-wrap items-center gap-2.5">
{state === "none" ? (
<LinkButton href="/instances/link">Link an install</LinkButton>
// A paid placeholder (awaiting_link) must CLAIM its install, not
// create a second Free instance beside it — so pass its id.
<LinkButton
href={
instance.status === "awaiting_link"
? `/instances/link?claim=${instance.instance_id}`
: "/instances/link"
}
>
Link an install
</LinkButton>
) : cloud && instance.slug ? (
<>
<LinkButton
+24
View File
@@ -0,0 +1,24 @@
# Vantage self-hosted — copy to .env and fill in.
# Used by: docker compose up -d
# --- Required ---
# host:port agents dial for gRPC. No default; boot fails without it.
# Must be reachable from managed servers. Use the public host, port 9090.
GRPC_HOST=192.168.1.250:9090
# Gitea host used to build agent install scripts and download URLs.
GITEA_HOST=gitea.hostxtra.co.uk
# 64-char hex (32 bytes) for AES-256-GCM. Required for private keys,
# secrets, OIDC secrets, RDP/VNC credentials.
# Generate: openssl rand -hex 32
KEY_ENCRYPTION_KEY=
# --- Optional (defaults shown) ---
# MongoDB is bundled in this compose file. Override only to use an external DB.
MONGO_URI=mongodb://mongo:27017/vantage
# Where workflow run logs are written inside the server container.
# VANTAGE_WORKFLOW_LOG_DIR=/data/workflow-logs
+18 -1
View File
@@ -12,6 +12,21 @@ services:
interval: 10s
timeout: 5s
retries: 5
mongo:
image: mongo:7
restart: unless-stopped
volumes:
- mongo_data:/data/db
healthcheck:
test:
- CMD
- mongosh
- --quiet
- --eval
- "db.adminCommand('ping')"
interval: 10s
timeout: 5s
retries: 5
guacd:
image: docker.io/guacamole/guacd:1.6.0
restart: unless-stopped
@@ -24,7 +39,7 @@ services:
- 8080:8080
- 9090:9090
environment:
MONGO_URI: ${MONGO_URI:-}
MONGO_URI: ${MONGO_URI:-mongodb://mongo:27017/vantage}
REDIS_ADDR: redis:6379
GITEA_HOST: ${GITEA_HOST}
GRPC_HOST: ${GRPC_HOST}
@@ -37,6 +52,8 @@ services:
depends_on:
redis:
condition: service_healthy
mongo:
condition: service_healthy
volumes:
- ./data:/data
web:
+2 -2
View File
@@ -17,13 +17,13 @@ import (
func main() {
mongoURI := getEnv("MONGO_URI", "mongodb://localhost:27017")
dbName := getEnv("MONGO_DB", "vantage")
if os.Getenv("GRPC_HOST") == "" {
log.Fatal("GRPC_HOST is required (host:port agents dial for gRPC)")
}
if err := db.Connect(mongoURI, dbName); err != nil {
// DB name comes from the MONGO_URI path; "vantage" is the fallback.
if err := db.Connect(mongoURI, "vantage"); err != nil {
log.Fatalf("failed to connect to MongoDB: %v", err)
}
log.Println("connected to MongoDB")
+15 -1
View File
@@ -2,16 +2,30 @@ package db
import (
"context"
"fmt"
"time"
"go.mongodb.org/mongo-driver/v2/mongo"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/connstring"
)
var Client *mongo.Client
var Database *mongo.Database
func Connect(uri, dbName string) error {
// Connect resolves the database name from the URI path (e.g.
// mongodb://host:27017/vantage), falling back to fallbackDB when the URI names
// none, so a bare URI still works without a separate MONGO_DB env var.
func Connect(uri, fallbackDB string) error {
cs, err := connstring.ParseAndValidate(uri)
if err != nil {
return fmt.Errorf("parse MONGO_URI: %w", err)
}
dbName := cs.Database
if dbName == "" {
dbName = fallbackDB
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()