fix: build

This commit is contained in:
2026-04-28 22:09:19 -04:00
parent fcfa43cca3
commit 4c1523d81b
9 changed files with 60 additions and 20 deletions

View File

@@ -14,7 +14,7 @@ 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
RUN find ./web/static -type d -empty -delete && go build -ldflags="-w -s" -o aethera ./cmd
# Stage 3: Minimal Runtime
FROM alpine:3.21

View File

@@ -1,14 +1,22 @@
.PHONY: all frontend backend clean dev docker docker-run tests
.PHONY: all frontend backend clean dev docker docker-run tests check-static
all: frontend backend
frontend:
rm -rf frontend/public/dist
cd frontend && bun run build
rm -rf backend/web/static
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
cp -R frontend/public/. backend/web/static/
find backend/web/static -type d -empty -delete
touch backend/web/static/.gitkeep
backend:
check-static:
@test -f backend/web/static/index.html || (echo "missing backend/web/static/index.html; run 'make frontend' first" && exit 1)
@test -f backend/web/static/dist/main.js || (echo "missing backend/web/static/dist/main.js; run 'make frontend' first" && exit 1)
@test -f backend/web/static/dist/styles.css || (echo "missing backend/web/static/dist/styles.css; run 'make frontend' first" && exit 1)
backend: check-static
cd backend && go build -o ./dist/aethera ./cmd
clean:
@@ -17,7 +25,11 @@ clean:
rm -rf backend/web/static
dev:
cd backend && go run ./cmd --listen 0.0.0.0 &
rm -rf frontend/public/dist
cd frontend && bun run build
cd backend && AETHERA_STATIC_DIR=../frontend/public go run ./cmd --listen 0.0.0.0 & \
backend_pid=$$!; \
trap 'kill $$backend_pid' INT TERM EXIT; \
cd frontend && bun run dev
docker:

View File

@@ -59,6 +59,7 @@ Open your browser and navigate to the URL to begin using Aethera.
You can customize the server behavior with these command-line flags:
- `--data-dir`: Directory for storing generated images (default: `data`)
- `--static-dir`: Directory to serve frontend files from instead of embedded assets (useful for development)
- `--listen`: Address to listen on (default: `localhost`)
- `--port`: Port to listen on (default: `8080`)

View File

@@ -13,6 +13,7 @@ type cliParams struct {
ListenAddr string
ListenPort int
DataDir string
StaticDir string
SettingsFile string
}
@@ -41,5 +42,16 @@ func (p *cliParams) Validate() error {
return fmt.Errorf("failed to create images directory: %w", err)
}
// Validate Static Directory
if p.StaticDir != "" {
info, err := os.Stat(p.StaticDir)
if err != nil {
return fmt.Errorf("failed to access static directory: %w", err)
}
if !info.IsDir() {
return fmt.Errorf("static directory is not a directory: %s", p.StaticDir)
}
}
return nil
}

View File

@@ -16,12 +16,14 @@ var (
ListenAddr: getEnvOrDefault("LISTEN", "localhost"),
ListenPort: getEnvIntOrDefault("PORT", 8080),
DataDir: getEnvOrDefault("DATA_DIR", "./data"),
StaticDir: getEnvOrDefault("STATIC_DIR", ""),
}
rootCmd = &cobra.Command{Use: "aethera"}
)
func init() {
rootCmd.PersistentFlags().StringVar(&params.DataDir, "data-dir", params.DataDir, "Directory to store generated images (env: AETHERA_DATA_DIR)")
rootCmd.PersistentFlags().StringVar(&params.StaticDir, "static-dir", params.StaticDir, "Directory to serve static frontend files from instead of embedded assets (env: AETHERA_STATIC_DIR)")
rootCmd.PersistentFlags().StringVar(&params.ListenAddr, "listen", params.ListenAddr, "Address to listen on (env: AETHERA_LISTEN)")
rootCmd.PersistentFlags().IntVar(&params.ListenPort, "port", params.ListenPort, "Port to listen on (env: AETHERA_PORT)")
}
@@ -40,7 +42,7 @@ func main() {
// Start Server
rootCmd.Run = func(cmd *cobra.Command, args []string) {
server.StartServer(fileStore, params.DataDir, params.ListenAddr, params.ListenPort)
server.StartServer(fileStore, params.DataDir, params.StaticDir, params.ListenAddr, params.ListenPort)
}
if err := rootCmd.Execute(); err != nil {

View File

@@ -13,19 +13,24 @@ import (
"reichard.io/aethera/web"
)
func StartServer(settingsStore store.Store, dataDir, listenAddress string, listenPort int) {
func StartServer(settingsStore store.Store, dataDir, staticDir, listenAddress string, listenPort int) {
mux := http.NewServeMux()
// Create API Instance - use settingsStore as the unified store for both settings and chat
logger := logrus.New()
api := api.New(settingsStore, dataDir, logger)
// Serve embedded static assets
staticFS, err := fs.Sub(web.Assets, "static")
if err != nil {
logrus.Fatal("Failed to create static filesystem: ", err)
// Serve Static Assets
if staticDir != "" {
logrus.Infof("Serving static assets from directory: %s", staticDir)
mux.Handle("GET /", http.FileServer(http.Dir(staticDir)))
} else {
staticFS, err := fs.Sub(web.Assets, "static")
if err != nil {
logrus.Fatal("Failed to create static filesystem: ", err)
}
mux.Handle("GET /", http.FileServer(http.FS(staticFS)))
}
mux.Handle("GET /", http.FileServer(http.FS(staticFS)))
// Serve Generated Data
genFS := http.FileServer(http.Dir(path.Join(dataDir, "generated")))

View File

@@ -32,6 +32,7 @@
# Frontend
bun
typescript-language-server
watchman
];

View File

@@ -1,6 +1,7 @@
{
"name": "aethera",
"private": true,
"type": "module",
"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",
"build": "bun build src/main.ts --outdir public/dist --target browser && bunx tailwindcss -i styles.css -o public/dist/styles.css --minify",

View File

@@ -6,7 +6,7 @@ import {
getModels,
getSettings,
} from '../client';
import { ImageRecord } from '../types';
import { GenerateImageRequest, ImageRecord } from '../types';
import { applyFilter } from '../utils';
// Constants
@@ -26,6 +26,12 @@ interface StoredSettings {
}
// Utilities
const errorMessage = (err: unknown): string => {
if (err instanceof Error) return err.message;
if (typeof err === 'string') return err;
return 'An unexpected error occurred';
};
const fileToDataURL = (file: File): Promise<string> =>
new Promise((resolve, reject) => {
const reader = new FileReader();
@@ -252,8 +258,8 @@ Alpine.data('imageGenerator', () => {
});
},
async buildRequestData() {
const requestData: any = {
async buildRequestData(): Promise<GenerateImageRequest> {
const requestData: GenerateImageRequest = {
prompt: this.prompt,
n: parseInt(this.n.toString()),
seed: parseInt(this.seed.toString()),
@@ -281,8 +287,8 @@ Alpine.data('imageGenerator', () => {
const requestData = await this.buildRequestData();
const data = await generateImage(requestData);
this.generatedImages.unshift(...data);
} catch (err: any) {
this.error = err;
} catch (err) {
this.error = errorMessage(err);
} finally {
this.loading = false;
}
@@ -294,8 +300,8 @@ Alpine.data('imageGenerator', () => {
this.generatedImages = this.generatedImages.filter(
(img: ImageRecord) => img.name !== filename,
);
} catch (err: any) {
this.error = err;
} catch (err) {
this.error = errorMessage(err);
}
},