@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
func RegisterRoutes(r *gin.Engine) {
|
func RegisterRoutes(r *gin.Engine) {
|
||||||
r.GET("/install", handleInstallScript)
|
r.GET("/install", handleInstallScript)
|
||||||
|
r.GET("/update", handleUpdateScript)
|
||||||
|
|
||||||
// Auth endpoints (no session required)
|
// Auth endpoints (no session required)
|
||||||
r.GET("/auth/login", auth.HandleLogin)
|
r.GET("/auth/login", auth.HandleLogin)
|
||||||
@@ -236,6 +237,62 @@ func revokeAssignment(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{"revoked": true})
|
c.JSON(http.StatusOK, gin.H{"revoked": true})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleUpdateScript(c *gin.Context) {
|
||||||
|
giteaHost := os.Getenv("GITEA_HOST")
|
||||||
|
if giteaHost == "" {
|
||||||
|
giteaHost = "gitea.example.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
script := fmt.Sprintf(`#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
GITEA_HOST="%s"
|
||||||
|
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
case "$ARCH" in
|
||||||
|
x86_64) ARCH="amd64" ;;
|
||||||
|
aarch64) ARCH="arm64" ;;
|
||||||
|
*) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Get latest agent release tag
|
||||||
|
LATEST=$(curl -fsSL "https://${GITEA_HOST}/api/v1/repos/mrhid6/keymanager/releases?limit=10" \
|
||||||
|
| grep -o '"tag_name":"agent/v[^"]*"' | head -1 | sed 's/"tag_name":"//;s/"//')
|
||||||
|
|
||||||
|
if [ -z "$LATEST" ]; then
|
||||||
|
echo "Could not determine latest agent version" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="${LATEST#agent/}"
|
||||||
|
LATEST_ENCODED="${LATEST/\//%%2F}"
|
||||||
|
BINARY_URL="https://${GITEA_HOST}/mrhid6/keymanager/releases/download/${LATEST_ENCODED}/keymanager-agent-linux-${ARCH}"
|
||||||
|
CHECKSUM_URL="https://${GITEA_HOST}/mrhid6/keymanager/releases/download/${LATEST_ENCODED}/checksums.txt"
|
||||||
|
|
||||||
|
echo "Updating keymanager-agent to ${VERSION} (${ARCH})..."
|
||||||
|
|
||||||
|
curl -fsSL -o /tmp/keymanager-agent "${BINARY_URL}"
|
||||||
|
curl -fsSL -o /tmp/checksums.txt "${CHECKSUM_URL}"
|
||||||
|
|
||||||
|
cd /tmp
|
||||||
|
EXPECTED=$(grep "keymanager-agent-linux-${ARCH}" checksums.txt | awk '{print $1}')
|
||||||
|
ACTUAL=$(sha256sum keymanager-agent | awk '{print $1}')
|
||||||
|
if [ "$EXPECTED" != "$ACTUAL" ]; then
|
||||||
|
echo "Checksum mismatch!" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
systemctl stop keymanager-agent || true
|
||||||
|
install -m 0755 /tmp/keymanager-agent /usr/local/bin/keymanager-agent
|
||||||
|
systemctl start keymanager-agent
|
||||||
|
|
||||||
|
echo "keymanager-agent updated to ${VERSION} and restarted."
|
||||||
|
`, giteaHost)
|
||||||
|
|
||||||
|
c.Header("Content-Type", "text/x-shellscript")
|
||||||
|
c.String(http.StatusOK, script)
|
||||||
|
}
|
||||||
|
|
||||||
func handleInstallScript(c *gin.Context) {
|
func handleInstallScript(c *gin.Context) {
|
||||||
serverID := c.Query("server_id")
|
serverID := c.Query("server_id")
|
||||||
token := c.Query("token")
|
token := c.Query("token")
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export default function ServerDetailPage() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const serverId = params.id as string;
|
const serverId = params.id as string;
|
||||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||||
|
const [copiedUpdate, setCopiedUpdate] = useState(false);
|
||||||
|
|
||||||
const { data: server, isLoading, error } = useQuery({
|
const { data: server, isLoading, error } = useQuery({
|
||||||
queryKey: ["servers", serverId],
|
queryKey: ["servers", serverId],
|
||||||
@@ -115,6 +116,33 @@ export default function ServerDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Update Agent</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<p className="mb-4 text-sm text-text-secondary">
|
||||||
|
Run this command on the server as <code className="rounded bg-surface-2 px-1 py-0.5 text-xs font-mono text-text-primary">root</code> to update the agent to the latest version:
|
||||||
|
</p>
|
||||||
|
<div className="relative rounded-lg border border-border bg-[#0a0c14] p-4 font-mono text-sm">
|
||||||
|
<pre className="overflow-x-auto whitespace-pre-wrap break-all text-text-secondary leading-relaxed">
|
||||||
|
<span className="text-accent">$</span>{" "}
|
||||||
|
<span className="text-text-primary">{api.getUpdateCommand()}</span>
|
||||||
|
</pre>
|
||||||
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
await navigator.clipboard.writeText(api.getUpdateCommand());
|
||||||
|
setCopiedUpdate(true);
|
||||||
|
setTimeout(() => setCopiedUpdate(false), 2000);
|
||||||
|
}}
|
||||||
|
className="absolute right-3 top-3 rounded-md border border-border bg-surface-2 px-2.5 py-1 text-xs font-medium text-text-secondary transition-colors hover:border-accent/50 hover:text-text-primary"
|
||||||
|
>
|
||||||
|
{copiedUpdate ? <span className="text-success">Copied!</span> : "Copy"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||||
<Card className="lg:col-span-1">
|
<Card className="lg:col-span-1">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|||||||
@@ -102,6 +102,10 @@ export const api = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getUpdateCommand(): string {
|
||||||
|
return `curl -fsSL "${window.location.origin}/update" | bash`;
|
||||||
|
},
|
||||||
|
|
||||||
// Keys
|
// Keys
|
||||||
listKeys(): Promise<Key[]> {
|
listKeys(): Promise<Key[]> {
|
||||||
return request<Key[]>("/keys");
|
return request<Key[]>("/keys");
|
||||||
|
|||||||
Reference in New Issue
Block a user