Skip to content
This repository was archived by the owner on Aug 14, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions .github/workflows/publish-containers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Publish Containers

on:
release:
types: [published]

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push-python:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-python
tags: |
type=semver,pattern={{version}}
type=raw,value=latest

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./python
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

build-and-push-powershell:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-powershell
tags: |
type=semver,pattern={{version}}
type=raw,value=latest

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./powershell
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
52 changes: 52 additions & 0 deletions docs/run-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Running Ask Sage OpenAI Proxy from CLI

This guide explains how to run both the Python and PowerShell versions directly from your command line.

## Prerequisites

- **Python Version**: Python 3.11+
- **PowerShell Version**: PowerShell Core 7.4+ (pwsh)
- **Ask Sage API Key**: You need an API key from Ask Sage.

## Python (FastAPI)

1. Navigate to the `python` directory:
```bash
cd python
```

2. Create a virtual environment:
```bash
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```

3. Install dependencies:
```bash
pip install -r requirements.txt
```

4. Run the server:
```bash
export ASKSAGE_API_KEY="your-key-here"
uvicorn app.main:app --host 0.0.0.0 --port 8000
```

## PowerShell

1. Navigate to the `powershell` directory:
```bash
cd powershell
```

2. Run the script:
```powershell
$env:ASKSAGE_API_KEY = "your-key-here"
pwsh ./AskSageProxy.ps1
```

Or on Linux/macOS:
```bash
export ASKSAGE_API_KEY="your-key-here"
pwsh ./AskSageProxy.ps1
```
75 changes: 75 additions & 0 deletions docs/run-containers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Running Ask Sage OpenAI Proxy with Containers

This guide explains how to build and run the proxy using Docker or Podman. Both containers are based on a minimal Debian Trixie image and run as a non-root user for security.

## Python Version

### Building

**Docker:**
```bash
cd python
docker build -t asksage-python-proxy .
```

**Podman:**
```bash
cd python
podman build -t asksage-python-proxy .
```

### Running

**Docker:**
```bash
docker run -d \
-p 8000:8000 \
-e ASKSAGE_API_KEY="your-key-here" \
--name asksage-proxy \
asksage-python-proxy
```

**Podman:**
```bash
podman run -d \
-p 8000:8000 \
-e ASKSAGE_API_KEY="your-key-here" \
--name asksage-proxy \
asksage-python-proxy
```

## PowerShell Version

### Building

**Docker:**
```bash
cd powershell
docker build -t asksage-powershell-proxy .
```

**Podman:**
```bash
cd powershell
podman build -t asksage-powershell-proxy .
```

### Running

**Docker:**
```bash
docker run -d \
-p 8000:8000 \
-e ASKSAGE_API_KEY="your-key-here" \
--name asksage-pwsh-proxy \
asksage-powershell-proxy
```

**Podman:**
```bash
podman run -d \
-p 8000:8000 \
-e ASKSAGE_API_KEY="your-key-here" \
--name asksage-pwsh-proxy \
asksage-powershell-proxy
```
41 changes: 41 additions & 0 deletions powershell/Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Debian Trixie (testing) based image.
FROM debian:trixie-slim

# Install system dependencies
# libicu-dev and libssl-dev pull in the necessary libraries for .NET runtime.
RUN apt-get update && apt-get install -y --no-install-recommends \
wget \
ca-certificates \
libicu-dev \
libssl-dev \
&& rm -rf /var/lib/apt/lists/*

# Download and install PowerShell Core
ARG PS_VERSION=7.4.6
RUN wget -q https://github.com/PowerShell/PowerShell/releases/download/v${PS_VERSION}/powershell-${PS_VERSION}-linux-x64.tar.gz -O /tmp/powershell.tar.gz && \
mkdir -p /opt/microsoft/powershell/7 && \
tar zxf /tmp/powershell.tar.gz -C /opt/microsoft/powershell/7 && \
chmod +x /opt/microsoft/powershell/7/pwsh && \
ln -s /opt/microsoft/powershell/7/pwsh /usr/bin/pwsh && \
rm /tmp/powershell.tar.gz

# Create a non-root user
RUN groupadd -g 1000 appuser && \
useradd -u 1000 -g appuser -m -s /bin/bash appuser

WORKDIR /app

# Copy application code
COPY AskSageProxy.ps1 /app/AskSageProxy.ps1

# Ensure ownership by appuser
RUN chown -R appuser:appuser /app

# Switch to non-root user
USER appuser

# Expose port
EXPOSE 8000

# Run the proxy
CMD ["pwsh", "-File", "/app/AskSageProxy.ps1"]
59 changes: 59 additions & 0 deletions powershell/test-results.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<test-results xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="nunit_schema_2.5.xsd" name="Pester" total="7" errors="0" failures="7" not-run="0" inconclusive="0" ignored="0" skipped="0" invalid="0" date="2026-02-17" time="19:27:00">
<environment user-domain="" platform="Linux" machine-name="devbox" cwd="/app" nunit-version="2.5.8.0" clr-version="8.0.10" os-version="6.8.0" user="jules" />
<culture-info current-culture="" current-uiculture="" />
<test-suite type="TestFixture" name="Pester" executed="True" result="Failure" success="False" time="2.6746" asserts="0" description="Pester">
<results>
<test-suite type="TestFixture" name="/app/powershell/tests/AskSageProxy.Tests.ps1" executed="True" result="Failure" success="False" time="2.6746" asserts="0" description="/app/powershell/tests/AskSageProxy.Tests.ps1">
<results>
<test-suite type="TestFixture" name="AskSageProxy Integration" executed="True" result="Failure" success="False" time="2.1941" asserts="0" description="AskSageProxy Integration">
<results>
<test-case description="GET /healthz returns ok" name="AskSageProxy Integration.GET /healthz returns ok" time="0" asserts="0" success="False" result="Failure" executed="True">
<failure>
<message>This test should run but it did not. Most likely a setup in some parent block failed.</message>
<stack-trace />
</failure>
</test-case>
<test-case description="GET /v1/models returns models list" name="AskSageProxy Integration.GET /v1/models returns models list" time="0" asserts="0" success="False" result="Failure" executed="True">
<failure>
<message>This test should run but it did not. Most likely a setup in some parent block failed.</message>
<stack-trace />
</failure>
</test-case>
<test-case description="GET /v1/models/{model} returns specific model" name="AskSageProxy Integration.GET /v1/models/{model} returns specific model" time="0" asserts="0" success="False" result="Failure" executed="True">
<failure>
<message>This test should run but it did not. Most likely a setup in some parent block failed.</message>
<stack-trace />
</failure>
</test-case>
<test-case description="POST /v1/chat/completions returns response" name="AskSageProxy Integration.POST /v1/chat/completions returns response" time="0" asserts="0" success="False" result="Failure" executed="True">
<failure>
<message>This test should run but it did not. Most likely a setup in some parent block failed.</message>
<stack-trace />
</failure>
</test-case>
<test-case description="POST /v1/audio/speech returns audio data" name="AskSageProxy Integration.POST /v1/audio/speech returns audio data" time="0" asserts="0" success="False" result="Failure" executed="True">
<failure>
<message>This test should run but it did not. Most likely a setup in some parent block failed.</message>
<stack-trace />
</failure>
</test-case>
<test-case description="POST /v1/audio/transcriptions (Multipart) returns text" name="AskSageProxy Integration.POST /v1/audio/transcriptions (Multipart) returns text" time="0" asserts="0" success="False" result="Failure" executed="True">
<failure>
<message>This test should run but it did not. Most likely a setup in some parent block failed.</message>
<stack-trace />
</failure>
</test-case>
<test-case description="Returns 404 for unknown path" name="AskSageProxy Integration.Returns 404 for unknown path" time="0" asserts="0" success="False" result="Failure" executed="True">
<failure>
<message>This test should run but it did not. Most likely a setup in some parent block failed.</message>
<stack-trace />
</failure>
</test-case>
</results>
</test-suite>
</results>
</test-suite>
</results>
</test-suite>
</test-results>
45 changes: 30 additions & 15 deletions python/Containerfile
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
# OpenShift / RHEL9 friendly container build.
# Uses Red Hat UBI python image so it runs well under Podman and OpenShift.
FROM registry.access.redhat.com/ubi9/python-311:latest
# Debian Trixie (testing) based image.
FROM debian:trixie-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1

WORKDIR /opt/app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 \
python3-pip \
python3-venv \
&& rm -rf /var/lib/apt/lists/*

# Install deps
COPY requirements.txt /opt/app/requirements.txt
RUN pip install --upgrade pip && pip install -r requirements.txt
# Create a non-root user
RUN groupadd -g 1000 appuser && \
useradd -u 1000 -g appuser -m -s /bin/bash appuser

# Copy app
COPY app/ /opt/app/app/
WORKDIR /app

# OpenShift runs containers with an arbitrary UID. Make sure the app dir is readable.
RUN chgrp -R 0 /opt/app && chmod -R g=u /opt/app
# Install dependencies
COPY requirements.txt .
# Create venv and install dependencies
RUN python3 -m venv /app/venv && \
/app/venv/bin/pip install --upgrade pip && \
/app/venv/bin/pip install -r requirements.txt

EXPOSE 8000
# Copy application code
COPY app/ /app/app/

# Ensure ownership by appuser
RUN chown -R appuser:appuser /app

# Non-root by default (OpenShift will override with a random UID anyway)
USER 1001
# Switch to non-root user
USER appuser

# Expose port
EXPOSE 8000

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
# Run using the virtual environment python
CMD ["/app/venv/bin/uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Loading