Compare commits
1 Commits
master
...
fffe58a9ac
| Author | SHA1 | Date | |
|---|---|---|---|
| fffe58a9ac |
68
Dockerfile
68
Dockerfile
@@ -1,30 +1,68 @@
|
|||||||
# Step 1: Build Frontend
|
# Multi-stage build for Aethera
|
||||||
|
# Stage 1: Build frontend assets
|
||||||
FROM oven/bun:1 AS frontend-builder
|
FROM oven/bun:1 AS frontend-builder
|
||||||
|
|
||||||
WORKDIR /app/frontend
|
WORKDIR /app/frontend
|
||||||
|
|
||||||
|
# Copy frontend package files
|
||||||
COPY frontend/package.json frontend/bun.lock ./
|
COPY frontend/package.json frontend/bun.lock ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
RUN bun install --frozen-lockfile
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
|
# Copy frontend source code
|
||||||
COPY frontend/ ./
|
COPY frontend/ ./
|
||||||
|
|
||||||
|
# Build frontend assets
|
||||||
RUN bun run build
|
RUN bun run build
|
||||||
|
|
||||||
# Stage 2: Build Backend
|
# Stage 2: Build Go binary
|
||||||
FROM golang:1.25-alpine AS backend-builder
|
FROM golang:1.25-alpine AS backend-builder
|
||||||
WORKDIR /app
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
COPY backend/go.mod backend/go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
COPY backend/ ./
|
|
||||||
COPY --from=frontend-builder /app/frontend/public/ ./web/static/
|
|
||||||
RUN go build -ldflags="-w -s" -o aethera ./cmd
|
|
||||||
|
|
||||||
# Stage 3: Minimal Runtime
|
|
||||||
FROM alpine:3.21
|
|
||||||
RUN apk add --no-cache ca-certificates
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install build dependencies
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
|
# Copy go mod files
|
||||||
|
COPY backend/go.mod backend/go.sum ./
|
||||||
|
|
||||||
|
# Download Go dependencies
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy backend source code
|
||||||
|
COPY backend/ ./
|
||||||
|
|
||||||
|
# Copy frontend assets from previous stage
|
||||||
|
COPY --from=frontend-builder /app/frontend/public/dist ./public/dist
|
||||||
|
COPY --from=frontend-builder /app/frontend/public/index.html ./public/
|
||||||
|
COPY --from=frontend-builder /app/frontend/public/pages ./public/pages
|
||||||
|
|
||||||
|
# Build the Go binary
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o aethera ./cmd
|
||||||
|
|
||||||
|
# Stage 3: Create minimal runtime image
|
||||||
|
FROM alpine:3.21
|
||||||
|
|
||||||
|
# Install ca-certificates for HTTPS calls
|
||||||
|
RUN apk add --no-cache ca-certificates
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the binary from the builder stage
|
||||||
COPY --from=backend-builder /app/aethera .
|
COPY --from=backend-builder /app/aethera .
|
||||||
|
|
||||||
|
# Copy static assets
|
||||||
|
COPY --from=backend-builder /app/public ./public
|
||||||
|
|
||||||
|
# Create data directory
|
||||||
RUN mkdir -p /app/data
|
RUN mkdir -p /app/data
|
||||||
|
|
||||||
|
# Expose the default port
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
ENV AETHERA_LISTEN=0.0.0.0
|
|
||||||
ENV AETHERA_PORT=8080
|
# Set the entrypoint
|
||||||
ENV AETHERA_DATA_DIR=/app/data
|
|
||||||
ENTRYPOINT ["./aethera"]
|
ENTRYPOINT ["./aethera"]
|
||||||
|
|
||||||
|
# Default command with recommended production settings
|
||||||
|
CMD ["--listen", "0.0.0.0", "--port", "8080", "--data-dir", "/app/data"]
|
||||||
|
|||||||
9
Makefile
9
Makefile
@@ -1,12 +1,9 @@
|
|||||||
.PHONY: all frontend backend clean dev docker docker-run tests
|
.PHONY: all frontend backend clean dev docker docker-run
|
||||||
|
|
||||||
all: frontend backend
|
all: frontend backend
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
cd frontend && bun run build
|
cd frontend && bun run build
|
||||||
mkdir -p backend/web/static
|
|
||||||
cp frontend/public/index.html backend/web/static/ 2>/dev/null || true
|
|
||||||
cp -r frontend/public/pages backend/web/static/ 2>/dev/null || true
|
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
cd backend && go build -o ./dist/aethera ./cmd
|
cd backend && go build -o ./dist/aethera ./cmd
|
||||||
@@ -14,7 +11,6 @@ backend:
|
|||||||
clean:
|
clean:
|
||||||
rm -rf frontend/public/dist
|
rm -rf frontend/public/dist
|
||||||
rm -rf backend/dist
|
rm -rf backend/dist
|
||||||
rm -rf backend/web/static
|
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
cd backend && go run ./cmd --listen 0.0.0.0 &
|
cd backend && go run ./cmd --listen 0.0.0.0 &
|
||||||
@@ -25,6 +21,3 @@ docker:
|
|||||||
|
|
||||||
docker-run:
|
docker-run:
|
||||||
docker run -p 8080:8080 -v aethera-data:/app/data aethera
|
docker run -p 8080:8080 -v aethera-data:/app/data aethera
|
||||||
|
|
||||||
tests:
|
|
||||||
cd backend && go test ./...
|
|
||||||
|
|||||||
2
backend/.gitignore
vendored
2
backend/.gitignore
vendored
@@ -1,3 +1 @@
|
|||||||
dist
|
dist
|
||||||
web/static/*
|
|
||||||
!web/static/.gitkeep
|
|
||||||
|
|||||||
@@ -4,11 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const envPrefix = "AETHERA_"
|
|
||||||
|
|
||||||
type cliParams struct {
|
type cliParams struct {
|
||||||
ListenAddr string
|
ListenAddr string
|
||||||
ListenPort int
|
ListenPort int
|
||||||
@@ -16,24 +13,6 @@ type cliParams struct {
|
|||||||
SettingsFile string
|
SettingsFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
// getEnvOrDefault returns the value of an environment variable or a default value
|
|
||||||
func getEnvOrDefault(key, defaultValue string) string {
|
|
||||||
if value := os.Getenv(envPrefix + key); value != "" {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// getEnvIntOrDefault returns the integer value of an environment variable or a default value
|
|
||||||
func getEnvIntOrDefault(key string, defaultValue int) int {
|
|
||||||
if value := os.Getenv(envPrefix + key); value != "" {
|
|
||||||
if intVal, err := strconv.Atoi(value); err == nil {
|
|
||||||
return intVal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *cliParams) Validate() error {
|
func (p *cliParams) Validate() error {
|
||||||
// Ensure Generated Directories
|
// Ensure Generated Directories
|
||||||
imgDir := path.Join(p.DataDir, "generated/images")
|
imgDir := path.Join(p.DataDir, "generated/images")
|
||||||
|
|||||||
@@ -13,17 +13,17 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
params = cliParams{
|
params = cliParams{
|
||||||
ListenAddr: getEnvOrDefault("LISTEN", "localhost"),
|
ListenAddr: "localhost",
|
||||||
ListenPort: getEnvIntOrDefault("PORT", 8080),
|
ListenPort: 8080,
|
||||||
DataDir: getEnvOrDefault("DATA_DIR", "./data"),
|
DataDir: "./data",
|
||||||
}
|
}
|
||||||
rootCmd = &cobra.Command{Use: "aethera"}
|
rootCmd = &cobra.Command{Use: "aethera"}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().StringVar(¶ms.DataDir, "data-dir", params.DataDir, "Directory to store generated images (env: AETHERA_DATA_DIR)")
|
rootCmd.PersistentFlags().StringVar(¶ms.DataDir, "data-dir", "data", "Directory to store generated images")
|
||||||
rootCmd.PersistentFlags().StringVar(¶ms.ListenAddr, "listen", params.ListenAddr, "Address to listen on (env: AETHERA_LISTEN)")
|
rootCmd.PersistentFlags().StringVar(¶ms.ListenAddr, "listen", "localhost", "Address to listen on")
|
||||||
rootCmd.PersistentFlags().IntVar(¶ms.ListenPort, "port", params.ListenPort, "Port to listen on (env: AETHERA_PORT)")
|
rootCmd.PersistentFlags().IntVar(¶ms.ListenPort, "port", 8080, "Port to listen on")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"time"
|
"time"
|
||||||
@@ -10,7 +9,6 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"reichard.io/aethera/internal/api"
|
"reichard.io/aethera/internal/api"
|
||||||
"reichard.io/aethera/internal/store"
|
"reichard.io/aethera/internal/store"
|
||||||
"reichard.io/aethera/web"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartServer(settingsStore store.Store, dataDir, listenAddress string, listenPort int) {
|
func StartServer(settingsStore store.Store, dataDir, listenAddress string, listenPort int) {
|
||||||
@@ -19,13 +17,12 @@ func StartServer(settingsStore store.Store, dataDir, listenAddress string, liste
|
|||||||
// Create API Instance - use settingsStore as the unified store for both settings and chat
|
// Create API Instance - use settingsStore as the unified store for both settings and chat
|
||||||
logger := logrus.New()
|
logger := logrus.New()
|
||||||
api := api.New(settingsStore, dataDir, logger)
|
api := api.New(settingsStore, dataDir, logger)
|
||||||
|
feFS := http.FileServer(http.Dir("../frontend/public/"))
|
||||||
|
mux.Handle("GET /", feFS)
|
||||||
|
|
||||||
// Serve embedded static assets
|
// Serve UI Pages
|
||||||
staticFS, err := fs.Sub(web.Assets, "static")
|
pagesFS := http.FileServer(http.Dir("../frontend/public/pages/"))
|
||||||
if err != nil {
|
mux.Handle("GET /pages/", http.StripPrefix("/pages/", pagesFS))
|
||||||
logrus.Fatal("Failed to create static filesystem: ", err)
|
|
||||||
}
|
|
||||||
mux.Handle("GET /", http.FileServer(http.FS(staticFS)))
|
|
||||||
|
|
||||||
// Serve Generated Data
|
// Serve Generated Data
|
||||||
genFS := http.FileServer(http.Dir(path.Join(dataDir, "generated")))
|
genFS := http.FileServer(http.Dir(path.Join(dataDir, "generated")))
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package web
|
|
||||||
|
|
||||||
import "embed"
|
|
||||||
|
|
||||||
//go:embed static/*
|
|
||||||
var Assets embed.FS
|
|
||||||
13
flake.nix
13
flake.nix
@@ -21,6 +21,11 @@
|
|||||||
config.allowUnfree = true;
|
config.allowUnfree = true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
oc = pkgs.writeShellScriptBin "oc" ''
|
||||||
|
PRJ_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
||||||
|
cd "$PRJ_ROOT" && OPENCODE_EXPERIMENTAL_LSP_TOOL=true opencode
|
||||||
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
@@ -33,11 +38,11 @@
|
|||||||
# Frontend
|
# Frontend
|
||||||
bun
|
bun
|
||||||
watchman
|
watchman
|
||||||
];
|
tailwindcss_4
|
||||||
|
|
||||||
shellHook = ''
|
# Custom Commands
|
||||||
export LD_LIBRARY_PATH=${pkgs.stdenv.cc.cc.lib}/lib:$LD_LIBRARY_PATH
|
oc
|
||||||
'';
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
"name": "aethera",
|
"name": "aethera",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "mkdir -p public/dist && bun build src/main.ts --outdir public/dist --target browser --watch & bunx tailwindcss -i styles.css -o public/dist/styles.css --watch",
|
"dev": "bun build src/main.ts --outdir public/dist --target browser --watch & bunx @tailwindcss/cli -i styles.css -o public/dist/styles.css --watch",
|
||||||
"build": "bun build src/main.ts --outdir public/dist --target browser && bunx tailwindcss -i styles.css -o public/dist/styles.css --minify",
|
"build": "bun build src/main.ts --outdir public/dist --target browser && bunx @tailwindcss/cli -i styles.css -o public/dist/styles.css --minify",
|
||||||
"lint": "eslint ./src/**"
|
"lint": "eslint ./src/**"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
Reference in New Issue
Block a user