Compare commits
6 Commits
0b323ed537
...
ci/portain
| Author | SHA1 | Date | |
|---|---|---|---|
| e593dbf662 | |||
| 5207d8c0c9 | |||
| d1c9a747b9 | |||
| 3d669713d7 | |||
| 1a6cf113c8 | |||
| 48d734516a |
@@ -338,41 +338,43 @@ steps:
|
|||||||
- security-trivy-orchestrator
|
- security-trivy-orchestrator
|
||||||
- security-trivy-web
|
- security-trivy-web
|
||||||
|
|
||||||
# ─── Deploy to Docker Swarm (main only) ─────────────────────
|
# ─── Deploy to Docker Swarm via Portainer API (main only) ─────────────────────
|
||||||
|
|
||||||
# ─── Deploy to Docker Swarm via Portainer (main only) ─────────────────────
|
|
||||||
|
|
||||||
deploy-swarm:
|
deploy-swarm:
|
||||||
image: alpine:3
|
image: alpine:3
|
||||||
environment:
|
environment:
|
||||||
SSH_PRIVATE_KEY:
|
|
||||||
from_secret: ssh_private_key
|
|
||||||
SSH_KNOWN_HOSTS:
|
|
||||||
from_secret: ssh_known_hosts
|
|
||||||
PORTAINER_URL:
|
PORTAINER_URL:
|
||||||
from_secret: portainer_url
|
from_secret: portainer_url
|
||||||
PORTAINER_API_KEY:
|
PORTAINER_API_KEY:
|
||||||
from_secret: portainer_api_key
|
from_secret: portainer_api_key
|
||||||
|
PORTAINER_STACK_ID: "121"
|
||||||
commands:
|
commands:
|
||||||
- apk add --no-cache curl openssh-client
|
- apk add --no-cache curl
|
||||||
- |
|
- |
|
||||||
set -e
|
set -e
|
||||||
echo "🚀 Deploying to Docker Swarm..."
|
echo "🚀 Deploying to Docker Swarm via Portainer API..."
|
||||||
|
|
||||||
# Setup SSH for fallback
|
# Use Portainer API to update the stack (forces pull of new images)
|
||||||
mkdir -p ~/.ssh
|
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
|
||||||
echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
|
-H "X-API-Key: $PORTAINER_API_KEY" \
|
||||||
chmod 600 ~/.ssh/known_hosts
|
-H "Content-Type: application/json" \
|
||||||
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
|
"$PORTAINER_URL/api/stacks/$PORTAINER_STACK_ID/git/redeploy")
|
||||||
chmod 600 ~/.ssh/id_ed25519
|
|
||||||
|
|
||||||
# Force service updates (images are pulled from public registry)
|
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
||||||
ssh -o StrictHostKeyChecking=no localadmin@10.1.1.45 \
|
BODY=$(echo "$RESPONSE" | head -n -1)
|
||||||
"docker service update --with-registry-auth --force mosaic-stack-api && \
|
|
||||||
docker service update --with-registry-auth --force mosaic-stack-web && \
|
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "202" ]; then
|
||||||
docker service update --with-registry-auth --force mosaic-stack-orchestrator && \
|
echo "✅ Stack update triggered successfully"
|
||||||
docker service update --with-registry-auth --force mosaic-stack-coordinator && \
|
else
|
||||||
echo '✅ All services updated'"
|
echo "❌ Stack update failed (HTTP $HTTP_CODE)"
|
||||||
|
echo "$BODY"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Wait for services to converge
|
||||||
|
echo "⏳ Waiting for services to converge..."
|
||||||
|
sleep 30
|
||||||
|
echo "✅ Deploy complete"
|
||||||
when:
|
when:
|
||||||
- branch: [main]
|
- branch: [main]
|
||||||
event: [push, manual, tag]
|
event: [push, manual, tag]
|
||||||
|
|||||||
46
.woodpecker/ci.yml.new
Normal file
46
.woodpecker/ci.yml.new
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Add this at the end of the file, replacing the deploy-swarm section
|
||||||
|
|
||||||
|
deploy-swarm:
|
||||||
|
image: alpine:3
|
||||||
|
environment:
|
||||||
|
SSH_PRIVATE_KEY:
|
||||||
|
from_secret: ssh_private_key
|
||||||
|
SSH_KNOWN_HOSTS:
|
||||||
|
from_secret: ssh_known_hosts
|
||||||
|
PORTAINER_URL:
|
||||||
|
from_secret: portainer_url
|
||||||
|
PORTAINER_API_KEY:
|
||||||
|
from_secret: portainer_api_key
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache curl
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
echo "🚀 Deploying via Portainer API..."
|
||||||
|
|
||||||
|
# Redeploy mosaic-stack (ID 121)
|
||||||
|
curl -sk -X POST \
|
||||||
|
-H "X-API-Key: $PORTAINER_API_KEY" \
|
||||||
|
"$PORTAINER_URL/api/stacks/121/git/redeploy" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"prune": false}' || \
|
||||||
|
|
||||||
|
# Fallback: Force service updates via SSH
|
||||||
|
echo "Trying SSH fallback..."
|
||||||
|
apk add --no-cache openssh-client
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
|
||||||
|
chmod 600 ~/.ssh/known_hosts
|
||||||
|
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
|
||||||
|
ssh -o StrictHostKeyChecking=no localadmin@10.1.1.45 \
|
||||||
|
"docker service update --force mosaic_api && \
|
||||||
|
docker service update --force mosaic_web && \
|
||||||
|
docker service update --force mosaic_orchestrator && \
|
||||||
|
docker service update --force mosaic_coordinator && \
|
||||||
|
echo '✅ Services updated'"
|
||||||
|
when:
|
||||||
|
- branch: [main]
|
||||||
|
event: [push, manual, tag]
|
||||||
|
depends_on:
|
||||||
|
- link-packages
|
||||||
@@ -1,15 +1,7 @@
|
|||||||
import {
|
import { Body, Controller, HttpException, Logger, Post, Req, Res, UseGuards } from "@nestjs/common";
|
||||||
Body,
|
|
||||||
Controller,
|
|
||||||
HttpException,
|
|
||||||
Logger,
|
|
||||||
Post,
|
|
||||||
Req,
|
|
||||||
Res,
|
|
||||||
UseGuards,
|
|
||||||
} from "@nestjs/common";
|
|
||||||
import type { Response } from "express";
|
import type { Response } from "express";
|
||||||
import { AuthGuard } from "../auth/guards/auth.guard";
|
import { AuthGuard } from "../auth/guards/auth.guard";
|
||||||
|
import { SkipCsrf } from "../common/decorators/skip-csrf.decorator";
|
||||||
import type { MaybeAuthenticatedRequest } from "../auth/types/better-auth-request.interface";
|
import type { MaybeAuthenticatedRequest } from "../auth/types/better-auth-request.interface";
|
||||||
import { ChatStreamDto } from "./chat-proxy.dto";
|
import { ChatStreamDto } from "./chat-proxy.dto";
|
||||||
import { ChatProxyService } from "./chat-proxy.service";
|
import { ChatProxyService } from "./chat-proxy.service";
|
||||||
@@ -23,6 +15,7 @@ export class ChatProxyController {
|
|||||||
// POST /api/chat/guest
|
// POST /api/chat/guest
|
||||||
// Guest chat endpoint - no authentication required
|
// Guest chat endpoint - no authentication required
|
||||||
// Uses a shared LLM configuration for unauthenticated users
|
// Uses a shared LLM configuration for unauthenticated users
|
||||||
|
@SkipCsrf()
|
||||||
@Post("guest")
|
@Post("guest")
|
||||||
async guestChat(
|
async guestChat(
|
||||||
@Body() body: ChatStreamDto,
|
@Body() body: ChatStreamDto,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
|
import { ConfigModule } from "@nestjs/config";
|
||||||
import { AuthModule } from "../auth/auth.module";
|
import { AuthModule } from "../auth/auth.module";
|
||||||
import { AgentConfigModule } from "../agent-config/agent-config.module";
|
import { AgentConfigModule } from "../agent-config/agent-config.module";
|
||||||
import { ContainerLifecycleModule } from "../container-lifecycle/container-lifecycle.module";
|
import { ContainerLifecycleModule } from "../container-lifecycle/container-lifecycle.module";
|
||||||
@@ -7,7 +8,7 @@ import { ChatProxyController } from "./chat-proxy.controller";
|
|||||||
import { ChatProxyService } from "./chat-proxy.service";
|
import { ChatProxyService } from "./chat-proxy.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [AuthModule, PrismaModule, ContainerLifecycleModule, AgentConfigModule],
|
imports: [AuthModule, PrismaModule, ContainerLifecycleModule, AgentConfigModule, ConfigModule],
|
||||||
controllers: [ChatProxyController],
|
controllers: [ChatProxyController],
|
||||||
providers: [ChatProxyService],
|
providers: [ChatProxyService],
|
||||||
exports: [ChatProxyService],
|
exports: [ChatProxyService],
|
||||||
|
|||||||
@@ -90,10 +90,7 @@ export class ChatProxyService {
|
|||||||
* - GUEST_LLM_API_KEY: API key (optional, for cloud providers)
|
* - GUEST_LLM_API_KEY: API key (optional, for cloud providers)
|
||||||
* - GUEST_LLM_MODEL: Model name to use
|
* - GUEST_LLM_MODEL: Model name to use
|
||||||
*/
|
*/
|
||||||
async proxyGuestChat(
|
async proxyGuestChat(messages: ChatMessage[], signal?: AbortSignal): Promise<Response> {
|
||||||
messages: ChatMessage[],
|
|
||||||
signal?: AbortSignal
|
|
||||||
): Promise<Response> {
|
|
||||||
const llmUrl = this.config.get<string>("GUEST_LLM_URL") ?? DEFAULT_GUEST_LLM_URL;
|
const llmUrl = this.config.get<string>("GUEST_LLM_URL") ?? DEFAULT_GUEST_LLM_URL;
|
||||||
const llmApiKey = this.config.get<string>("GUEST_LLM_API_KEY");
|
const llmApiKey = this.config.get<string>("GUEST_LLM_API_KEY");
|
||||||
const llmModel = this.config.get<string>("GUEST_LLM_MODEL") ?? DEFAULT_GUEST_LLM_MODEL;
|
const llmModel = this.config.get<string>("GUEST_LLM_MODEL") ?? DEFAULT_GUEST_LLM_MODEL;
|
||||||
@@ -103,7 +100,7 @@ export class ChatProxyService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (llmApiKey) {
|
if (llmApiKey) {
|
||||||
headers["Authorization"] = `Bearer ${llmApiKey}`;
|
headers.Authorization = `Bearer ${llmApiKey}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestInit: RequestInit = {
|
const requestInit: RequestInit = {
|
||||||
|
|||||||
@@ -280,13 +280,16 @@ export function useChat(options: UseChatOptions = {}): UseChatReturn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Streaming failed - check if auth error, try guest mode
|
// Streaming failed - check if auth error, try guest mode
|
||||||
const isAuthError = err instanceof Error &&
|
const isAuthError =
|
||||||
(err.message.includes("403") || err.message.includes("401") ||
|
err instanceof Error &&
|
||||||
err.message.includes("auth") || err.message.includes("Forbidden"));
|
(err.message.includes("403") ||
|
||||||
|
err.message.includes("401") ||
|
||||||
|
err.message.includes("auth") ||
|
||||||
|
err.message.includes("Forbidden"));
|
||||||
|
|
||||||
if (isAuthError) {
|
if (isAuthError) {
|
||||||
console.warn("Auth failed, trying guest chat mode");
|
console.warn("Auth failed, trying guest chat mode");
|
||||||
|
|
||||||
// Try guest chat streaming
|
// Try guest chat streaming
|
||||||
try {
|
try {
|
||||||
await new Promise<void>((guestResolve, guestReject) => {
|
await new Promise<void>((guestResolve, guestReject) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user