mirror of
https://github.com/Noooste/garage-ui.git
synced 2026-07-26 15:58:13 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a59caa41da | |||
| 7d4c7f51e3 | |||
| 8063ae18a8 | |||
| b8056c6d79 | |||
| b1b6e02a19 | |||
| 65447b927a | |||
| bde726fedf | |||
| 27cca273d5 | |||
| c1a1d64928 | |||
| 948bd06a3e | |||
| c4f7b0ccbc | |||
| f5deb0316b |
Binary file not shown.
|
After Width: | Height: | Size: 248 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 364 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 289 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 344 KiB |
+132
@@ -0,0 +1,132 @@
|
||||
<!-- omit in toc -->
|
||||
# Contributing to Garage UI
|
||||
|
||||
First off, thanks for taking the time to contribute! ❤️
|
||||
|
||||
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
|
||||
|
||||
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
|
||||
> - Star the project
|
||||
> - Tweet about it
|
||||
> - Refer this project in your project's readme
|
||||
> - Mention the project at local meetups and tell your friends/colleagues
|
||||
|
||||
<!-- omit in toc -->
|
||||
## Table of Contents
|
||||
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
- [I Have a Question](#i-have-a-question)
|
||||
- [I Want To Contribute](#i-want-to-contribute)
|
||||
- [Reporting Bugs](#reporting-bugs)
|
||||
- [Suggesting Enhancements](#suggesting-enhancements)
|
||||
- [Your First Code Contribution](#your-first-code-contribution)
|
||||
- [Improving The Documentation](#improving-the-documentation)
|
||||
- [Styleguides](#styleguides)
|
||||
- [Commit Messages](#commit-messages)
|
||||
- [Join The Project Team](#join-the-project-team)
|
||||
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project and everyone participating in it is governed by the
|
||||
[Garage UI Code of Conduct](https://github.com/Noooste/garage-ui/blob//CODE_OF_CONDUCT.md).
|
||||
By participating, you are expected to uphold this code. Please report unacceptable behavior
|
||||
to .
|
||||
|
||||
|
||||
## I Have a Question
|
||||
|
||||
> If you want to ask a question, we assume that you have read the available [Documentation]().
|
||||
|
||||
Before you ask a question, it is best to search for existing [Issues](https://github.com/Noooste/garage-ui/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
|
||||
|
||||
If you then still feel the need to ask a question and need clarification, we recommend the following:
|
||||
|
||||
- Open an [Issue](https://github.com/Noooste/garage-ui/issues/new).
|
||||
- Provide as much context as you can about what you're running into.
|
||||
- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant.
|
||||
|
||||
We will then take care of the issue as soon as possible.
|
||||
|
||||
<!--
|
||||
You might want to create a separate issue tag for questions and include it in this description. People should then tag their issues accordingly.
|
||||
|
||||
Depending on how large the project is, you may want to outsource the questioning, e.g. to Stack Overflow or Gitter. You may add additional contact and information possibilities:
|
||||
- IRC
|
||||
- Slack
|
||||
- Gitter
|
||||
- Stack Overflow tag
|
||||
- Blog
|
||||
- FAQ
|
||||
- Roadmap
|
||||
- E-Mail List
|
||||
- Forum
|
||||
-->
|
||||
|
||||
## I Want To Contribute
|
||||
|
||||
> ### Legal Notice <!-- omit in toc -->
|
||||
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project licence.
|
||||
|
||||
### Reporting Bugs
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### Before Submitting a Bug Report
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
|
||||
|
||||
- Make sure that you are using the latest version.
|
||||
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](). If you are looking for support, you might want to check [this section](#i-have-a-question)).
|
||||
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/Noooste/garage-ui/issues?q=label%3Abug).
|
||||
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
|
||||
- Collect information about the bug:
|
||||
- Stack trace (Traceback)
|
||||
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
|
||||
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
|
||||
- Possibly your input and the output
|
||||
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### How Do I Submit a Good Bug Report?
|
||||
|
||||
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to .
|
||||
<!-- You may add a PGP key to allow the messages to be sent encrypted as well. -->
|
||||
|
||||
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
|
||||
|
||||
- Open an [Issue](https://github.com/Noooste/garage-ui/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
|
||||
- Explain the behavior you would expect and the actual behavior.
|
||||
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
|
||||
- Provide the information you collected in the previous section.
|
||||
|
||||
Once it's filed:
|
||||
|
||||
- The project team will label the issue accordingly.
|
||||
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
|
||||
- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution).
|
||||
|
||||
<!-- You might want to create an issue template for bugs and errors that can be used as a guide and that defines the structure of the information to be included. If you do so, reference it here in the description. -->
|
||||
|
||||
|
||||
### Suggesting Enhancements
|
||||
|
||||
This section guides you through submitting an enhancement suggestion for Garage UI, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### Before Submitting an Enhancement
|
||||
|
||||
- Make sure that you are using the latest version.
|
||||
- Read the [documentation]() carefully and find out if the functionality is already covered, maybe by an individual configuration.
|
||||
- Perform a [search](https://github.com/Noooste/garage-ui/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
|
||||
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### How Do I Submit a Good Enhancement Suggestion?
|
||||
|
||||
Enhancement suggestions are tracked as [GitHub issues](https://github.com/Noooste/garage-ui/issues).
|
||||
|
||||
- Use a **clear and descriptive title** for the issue to identify the suggestion.
|
||||
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
|
||||
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
|
||||
- You may want to **include screenshots or screen recordings** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [LICEcap](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and the built-in [screen recorder in GNOME](https://help.gnome.org/users/gnome-help/stable/screen-shot-record.html.en) or [SimpleScreenRecorder](https://github.com/MaartenBaert/ssr) on Linux. <!-- this should only be included if the project has a GUI -->
|
||||
- **Explain why this enhancement would be useful** to most Garage UI users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Garage UI
|
||||
|
||||
A modern, full-stack web interface for managing [Garage](https://garagehq.deuxfleurs.fr/) distributed object storage systems. Built with React, TypeScript, Go, and Fiber for production-ready S3-compatible storage management.
|
||||
A web interface for managing [Garage](https://garagehq.deuxfleurs.fr/) object storage clusters.
|
||||
|
||||
[](https://github.com/Noooste/garage-ui/actions/workflows/build.yml)
|
||||
[](https://github.com/Noooste/garage-ui/actions/workflows/release.yml)
|
||||
@@ -9,665 +9,188 @@ A modern, full-stack web interface for managing [Garage](https://garagehq.deuxfl
|
||||
[](https://nodejs.org/)
|
||||
[](https://artifacthub.io/packages/search?repo=garage-ui)
|
||||
|
||||
Garage UI provides a comprehensive dashboard for managing your Garage S3 storage cluster, featuring bucket management, object operations, user access control, and real-time cluster monitoring—all through an intuitive web interface.
|
||||
|
||||
---
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src=".github/assets/dashboard.png" alt="Dashboard" /></td>
|
||||
<td><img src=".github/assets/buckets.png" alt="Buckets" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src=".github/assets/cluster.png" alt="Cluster" /></td>
|
||||
<td><img src=".github/assets/access-control.png" alt="Access Control" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Features
|
||||
|
||||
- **Bucket Management** - Create, configure, and delete S3 buckets with visual controls
|
||||
- **Object Operations** - Upload, download, and manage objects with drag-and-drop support
|
||||
- **User & Access Control** - Manage access keys, permissions, and user credentials
|
||||
- **Cluster Monitoring** - Real-time cluster health metrics, node status, and performance statistics
|
||||
- **Multi-Auth Support** - Flexible authentication with None, Basic Auth, and OIDC/OAuth2 modes
|
||||
- **Modern UI/UX** - Responsive React interface with dark mode, built on Tailwind CSS and shadcn/ui
|
||||
- **Production Ready** - Docker/Kubernetes deployment, health checks, and Prometheus metrics
|
||||
- **API-First Design** - RESTful API with Swagger/OpenAPI documentation
|
||||
|
||||
---
|
||||
- Bucket and object management
|
||||
- User access control
|
||||
- Cluster monitoring
|
||||
- Multiple authentication options (none/basic/OIDC)
|
||||
- Drag-and-drop file uploads
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Garage S3** storage cluster running (v2.0.0+)
|
||||
- **Docker** & Docker Compose (recommended) OR
|
||||
- **Go** 1.25+ and **Node.js** 25+ (for development)
|
||||
- Docker & Docker Compose
|
||||
- Garage S3 cluster (v2.1.0+) or use the included setup
|
||||
|
||||
### Using Docker Compose (Recommended)
|
||||
|
||||
1. **Clone the repository:**
|
||||
### 1. Clone & Setup
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Noooste/garage-ui.git
|
||||
cd garage-ui
|
||||
```
|
||||
|
||||
2. **Configure the application:**
|
||||
### 2. Start Garage
|
||||
|
||||
If you don't have Garage running:
|
||||
|
||||
```bash
|
||||
docker-compose up -d garage
|
||||
sleep 10
|
||||
|
||||
# Initialize cluster
|
||||
docker-compose exec garage garage layout assign -z dc1 -c 1G $(docker-compose exec garage garage node id -q)
|
||||
docker-compose exec garage garage layout apply --version 1
|
||||
|
||||
# Create admin key
|
||||
docker-compose exec garage garage key create admin-key
|
||||
```
|
||||
|
||||
Save the access key and secret key from the output.
|
||||
|
||||
### 3. Configure
|
||||
|
||||
```bash
|
||||
cp config.yaml.example config.yaml
|
||||
# Edit config.yaml with your Garage endpoints and credentials
|
||||
```
|
||||
|
||||
3. **Start the services:**
|
||||
Edit `config.yaml` with your Garage endpoints and admin token (from `garage.toml`).
|
||||
|
||||
### 4. Start UI
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
docker-compose up -d garage-ui
|
||||
```
|
||||
|
||||
4. **Access the UI:**
|
||||
|
||||
Open your browser to [http://localhost:8080](http://localhost:8080)
|
||||
|
||||
> The Docker Compose setup includes both Garage and Garage UI services for quick local development.
|
||||
|
||||
### Using Docker
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
-p 8080:8080 \
|
||||
-v $(pwd)/config.yaml:/app/config.yaml \
|
||||
-e GARAGE_UI_GARAGE_ENDPOINT=http://your-garage:3900 \
|
||||
-e GARAGE_UI_GARAGE_ADMIN_ENDPOINT=http://your-garage:3903 \
|
||||
noooste/garage-ui:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Docker Deployment
|
||||
|
||||
**Pull the latest image:**
|
||||
|
||||
```bash
|
||||
docker pull noooste/garage-ui:latest
|
||||
```
|
||||
|
||||
The Docker image is multi-platform, supporting `linux/amd64` and `linux/arm64`.
|
||||
|
||||
### Kubernetes Deployment with Helm
|
||||
|
||||
```bash
|
||||
# Add the Helm repository (if available)
|
||||
helm repo add garage-ui https://helm.noste.dev/
|
||||
|
||||
# Install the chart
|
||||
helm install garage-ui garage-ui/garage-ui \
|
||||
--set garage.endpoint=http://garage:3900 \
|
||||
--set garage.adminEndpoint=http://garage:3903 \
|
||||
--set garage.adminToken=your-admin-token
|
||||
```
|
||||
|
||||
Or use the local Helm chart:
|
||||
|
||||
```bash
|
||||
helm install garage-ui ./helm/garage-ui -f custom-values.yaml
|
||||
```
|
||||
|
||||
### Building from Source
|
||||
|
||||
**Backend:**
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
go mod download
|
||||
swag init # Generate API docs
|
||||
go build -o garage-ui .
|
||||
```
|
||||
|
||||
**Frontend:**
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
**Run:**
|
||||
|
||||
```bash
|
||||
# From project root
|
||||
./backend/garage-ui --config config.yaml
|
||||
```
|
||||
|
||||
---
|
||||
Access at http://localhost:8080
|
||||
|
||||
## Configuration
|
||||
|
||||
Garage UI can be configured via YAML file or environment variables.
|
||||
|
||||
### Configuration File
|
||||
|
||||
Create a `config.yaml` based on the provided example:
|
||||
Minimum required config:
|
||||
|
||||
```yaml
|
||||
# Server configuration
|
||||
server:
|
||||
host: "0.0.0.0"
|
||||
port: 8080
|
||||
environment: "production"
|
||||
|
||||
# Garage S3 Configuration
|
||||
garage:
|
||||
endpoint: "garage:3900"
|
||||
region: "eu-west-1" # Ensure to match Garage region from garage.toml
|
||||
endpoint: "http://garage:3900"
|
||||
admin_endpoint: "http://garage:3903"
|
||||
admin_token: "your-admin-token-here"
|
||||
|
||||
# Authentication Configuration
|
||||
# You can enable one or both authentication methods
|
||||
auth:
|
||||
# Admin authentication (username/password)
|
||||
admin:
|
||||
enabled: false # Set to true to enable admin login
|
||||
username: "admin"
|
||||
password: "secure-password"
|
||||
|
||||
# OIDC authentication (Keycloak, Auth0, etc.)
|
||||
oidc:
|
||||
enabled: false # Set to true to enable OIDC login
|
||||
provider_name: "Keycloak"
|
||||
client_id: "garage-ui"
|
||||
client_secret: "your-client-secret"
|
||||
issuer_url: "https://auth.example.com/realms/master"
|
||||
auth_url: "https://auth.example.com/realms/master/protocol/openid-connect/auth"
|
||||
token_url: "https://auth.example.com/realms/master/protocol/openid-connect/token"
|
||||
admin_token: "your-admin-token"
|
||||
region: "garage"
|
||||
```
|
||||
|
||||
See [`config.yaml.example`](config.yaml.example) for all available options.
|
||||
Enable authentication (optional):
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
admin:
|
||||
enabled: true
|
||||
username: "admin"
|
||||
password: "your-password"
|
||||
```
|
||||
|
||||
See [config.yaml.example](config.yaml.example) for all options.
|
||||
|
||||
### Environment Variables
|
||||
|
||||
All configuration can be set via environment variables with the prefix `GARAGE_UI_`:
|
||||
Override any config value with `GARAGE_UI_` prefix:
|
||||
|
||||
```bash
|
||||
GARAGE_UI_SERVER_PORT=8080
|
||||
GARAGE_UI_GARAGE_ENDPOINT=http://garage:3900
|
||||
GARAGE_UI_GARAGE_ADMIN_ENDPOINT=http://garage:3903
|
||||
GARAGE_UI_GARAGE_ADMIN_TOKEN=your-token
|
||||
GARAGE_UI_AUTH_ADMIN_ENABLED=true
|
||||
GARAGE_UI_AUTH_ADMIN_USERNAME=admin
|
||||
GARAGE_UI_AUTH_ADMIN_PASSWORD=password
|
||||
GARAGE_UI_AUTH_OIDC_ENABLED=false
|
||||
GARAGE_UI_LOGGING_LEVEL=info
|
||||
```
|
||||
|
||||
**Configuration Priority:** Environment variables override YAML file settings.
|
||||
## Deployment
|
||||
|
||||
---
|
||||
### Docker
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
garage-ui/
|
||||
├── backend/ # Go backend (Fiber framework)
|
||||
│ ├── internal/
|
||||
│ │ ├── auth/ # Authentication services (JWT, OIDC)
|
||||
│ │ ├── config/ # Configuration loading
|
||||
│ │ ├── handlers/ # HTTP request handlers
|
||||
│ │ ├── middleware/ # CORS, auth middleware
|
||||
│ │ ├── models/ # Data models and types
|
||||
│ │ ├── routes/ # API route definitions
|
||||
│ │ └── services/ # Business logic (S3, Garage Admin)
|
||||
│ ├── pkg/ # Shared packages
|
||||
│ ├── main.go # Application entry point
|
||||
│ └── go.mod # Go dependencies
|
||||
│
|
||||
├── frontend/ # React + TypeScript frontend
|
||||
│ ├── src/
|
||||
│ │ ├── components/ # Reusable UI components
|
||||
│ │ │ ├── buckets/ # Bucket-specific components
|
||||
│ │ │ ├── charts/ # Data visualization
|
||||
│ │ │ ├── layout/ # App layout and navigation
|
||||
│ │ │ └── ui/ # shadcn/ui component library
|
||||
│ │ ├── hooks/ # Custom React hooks
|
||||
│ │ ├── lib/ # API client and utilities
|
||||
│ │ ├── pages/ # Page components (Dashboard, Buckets, etc.)
|
||||
│ │ ├── types/ # TypeScript type definitions
|
||||
│ │ └── App.tsx # Main application component
|
||||
│ ├── package.json # Node dependencies
|
||||
│ └── vite.config.ts # Vite build configuration
|
||||
│
|
||||
├── helm/ # Kubernetes Helm chart
|
||||
│ └── garage-ui/
|
||||
│ ├── Chart.yaml # Chart metadata
|
||||
│ ├── values.yaml # Default values
|
||||
│ └── templates/ # K8s resource templates
|
||||
│
|
||||
├── .github/workflows/ # CI/CD pipelines
|
||||
│ ├── build.yml # Docker build and push
|
||||
│ └── release.yml # Helm chart releases
|
||||
│
|
||||
├── Dockerfile # Multi-stage build
|
||||
├── docker-compose.yml # Local development stack
|
||||
└── config.yaml.example # Configuration template
|
||||
```bash
|
||||
docker run -d -p 8080:8080 \
|
||||
-v $(pwd)/config.yaml:/app/config.yaml \
|
||||
noooste/garage-ui:latest
|
||||
```
|
||||
|
||||
**Key Directories:**
|
||||
### Kubernetes
|
||||
|
||||
- **`backend/internal/handlers/`** - REST API endpoints for buckets, objects, users, cluster, and monitoring
|
||||
- **`frontend/src/pages/`** - Main application views (Dashboard, Buckets, Cluster, Access Control)
|
||||
- **`backend/internal/services/`** - Core business logic interfacing with Garage Admin API and S3
|
||||
- **`frontend/src/lib/api.ts`** - Axios-based API client with automatic error handling
|
||||
|
||||
---
|
||||
```bash
|
||||
helm repo add garage-ui https://helm.noste.dev/
|
||||
helm install garage-ui garage-ui/garage-ui \
|
||||
--set garage.endpoint=http://garage:3900 \
|
||||
--set garage.adminEndpoint=http://garage:3903 \
|
||||
--set garage.adminToken=your-token
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Local Development Setup
|
||||
|
||||
**1. Start Garage (using Docker Compose):**
|
||||
|
||||
```bash
|
||||
docker-compose up garage -d
|
||||
```
|
||||
|
||||
**2. Run the backend:**
|
||||
|
||||
Backend (Go 1.25+):
|
||||
```bash
|
||||
cd backend
|
||||
go run main.go --config ../config.yaml
|
||||
```
|
||||
|
||||
The backend will start on `http://localhost:8080` with hot-reload (use [air](https://github.com/cosmtrek/air) for auto-reload).
|
||||
|
||||
**3. Run the frontend:**
|
||||
|
||||
Frontend (Node.js 25+):
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The frontend dev server starts on `http://localhost:3000` with API proxy to backend.
|
||||
API docs: http://localhost:8080/api/v1/
|
||||
|
||||
### Available Scripts
|
||||
## Garage Configuration
|
||||
|
||||
**Backend:**
|
||||
Garage UI requires these settings in your `garage.toml`:
|
||||
|
||||
```bash
|
||||
go run main.go # Start server
|
||||
go test ./... # Run tests
|
||||
swag init # Regenerate API docs
|
||||
go build -o garage-ui . # Build binary
|
||||
```toml
|
||||
# Admin API (required for Garage UI)
|
||||
[admin]
|
||||
api_bind_addr = "0.0.0.0:3903" # Default: 127.0.0.1:3903
|
||||
admin_token = "your-admin-token" # Generate with: openssl rand -base64 32
|
||||
|
||||
# S3 API
|
||||
[s3_api]
|
||||
s3_region = "garage" # Default: "garage"
|
||||
api_bind_addr = "[::]:3900" # Default: 127.0.0.1:3900
|
||||
```
|
||||
|
||||
**Frontend:**
|
||||
**Important:** The `admin_token` and `s3_region` in `garage.toml` must match your Garage UI `config.yaml`.
|
||||
|
||||
```bash
|
||||
npm run dev # Development server (Vite)
|
||||
npm run build # Production build
|
||||
npm run lint # ESLint checks
|
||||
npm run preview # Preview production build
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Backend tests
|
||||
cd backend && go test ./internal/...
|
||||
|
||||
# Frontend tests (if available)
|
||||
cd frontend && npm test
|
||||
```
|
||||
|
||||
### Building Docker Image
|
||||
|
||||
```bash
|
||||
docker build -t garage-ui:dev .
|
||||
```
|
||||
|
||||
The Dockerfile uses a multi-stage build:
|
||||
1. **Frontend build** - Node.js Alpine to compile React app
|
||||
2. **Backend build** - Go Alpine to compile binary
|
||||
3. **Runtime** - Minimal Alpine with ca-certificates
|
||||
|
||||
---
|
||||
|
||||
## API Documentation
|
||||
|
||||
Once the backend is running, access the Swagger UI documentation at:
|
||||
|
||||
**http://localhost:8080/api/v1/**
|
||||
|
||||
### API Endpoints Overview
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/health` | GET | Health check endpoint |
|
||||
| `/api/v1/buckets` | GET | List all buckets |
|
||||
| `/api/v1/buckets` | POST | Create a new bucket |
|
||||
| `/api/v1/buckets/{name}` | GET | Get bucket details |
|
||||
| `/api/v1/buckets/{name}` | DELETE | Delete a bucket |
|
||||
| `/api/v1/buckets/{name}/objects` | GET | List objects in bucket |
|
||||
| `/api/v1/objects/upload` | POST | Upload object to bucket |
|
||||
| `/api/v1/objects/download` | GET | Download object |
|
||||
| `/api/v1/objects/delete` | DELETE | Delete object |
|
||||
| `/api/v1/users` | GET | List access keys |
|
||||
| `/api/v1/users` | POST | Create access key |
|
||||
| `/api/v1/users/{id}` | DELETE | Delete access key |
|
||||
| `/api/v1/cluster/status` | GET | Cluster node status |
|
||||
| `/api/v1/cluster/health` | GET | Cluster health metrics |
|
||||
| `/api/v1/cluster/statistics` | GET | Storage statistics |
|
||||
| `/api/v1/metrics` | GET | Prometheus metrics |
|
||||
|
||||
**Authentication:** Bearer token in `Authorization` header when using OIDC or session-based auth.
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
Garage UI supports flexible authentication with the ability to enable one or both methods simultaneously:
|
||||
|
||||
### No Authentication
|
||||
|
||||
If neither admin nor OIDC authentication is enabled, the application runs without authentication - suitable for private networks or development.
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
admin:
|
||||
enabled: false
|
||||
oidc:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
### Admin Authentication
|
||||
|
||||
Simple username/password authentication using JWT tokens.
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
admin:
|
||||
enabled: true
|
||||
username: "admin"
|
||||
password: "your-secure-password"
|
||||
oidc:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
### OIDC (OpenID Connect) Authentication
|
||||
|
||||
Enterprise-grade authentication with providers like Keycloak, Auth0, Okta, etc.
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
admin:
|
||||
enabled: false
|
||||
oidc:
|
||||
enabled: true
|
||||
provider_name: "Keycloak"
|
||||
client_id: "garage-ui"
|
||||
client_secret: "your-client-secret"
|
||||
issuer_url: "https://auth.example.com/realms/master"
|
||||
auth_url: "https://auth.example.com/realms/master/protocol/openid-connect/auth"
|
||||
token_url: "https://auth.example.com/realms/master/protocol/openid-connect/token"
|
||||
userinfo_url: "https://auth.example.com/realms/master/protocol/openid-connect/userinfo"
|
||||
session_max_age: 86400 # 24 hours
|
||||
cookie_secure: true # Enable in production with HTTPS
|
||||
```
|
||||
|
||||
### Both Authentication Methods
|
||||
|
||||
You can enable both admin and OIDC authentication simultaneously. Users will be presented with both options on the login page:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
admin:
|
||||
enabled: true
|
||||
username: "admin"
|
||||
password: "your-secure-password"
|
||||
oidc:
|
||||
enabled: true
|
||||
provider_name: "Keycloak"
|
||||
client_id: "garage-ui"
|
||||
client_secret: "your-client-secret"
|
||||
issuer_url: "https://auth.example.com/realms/master"
|
||||
auth_url: "https://auth.example.com/realms/master/protocol/openid-connect/auth"
|
||||
token_url: "https://auth.example.com/realms/master/protocol/openid-connect/token"
|
||||
userinfo_url: "https://auth.example.com/realms/master/protocol/openid-connect/userinfo"
|
||||
session_max_age: 86400
|
||||
cookie_secure: true
|
||||
```
|
||||
|
||||
**Role-Based Access (Optional):**
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
oidc:
|
||||
role_attribute_path: "resource_access.garage-ui.roles"
|
||||
admin_role: "admin"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment Best Practices
|
||||
|
||||
### Production Considerations
|
||||
|
||||
1. **Use HTTPS** - Always enable TLS in production:
|
||||
```yaml
|
||||
auth:
|
||||
oidc:
|
||||
cookie_secure: true
|
||||
```
|
||||
|
||||
2. **Set Strong Admin Token** - Generate a secure token for Garage Admin API:
|
||||
```bash
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
3. **Configure CORS** - Restrict allowed origins:
|
||||
```yaml
|
||||
cors:
|
||||
allowed_origins:
|
||||
- "https://garage-ui.yourdomain.com"
|
||||
```
|
||||
|
||||
4. **Enable Monitoring** - Expose metrics endpoint for Prometheus:
|
||||
```yaml
|
||||
# Metrics available at /api/v1/metrics
|
||||
```
|
||||
|
||||
5. **Resource Limits** - Set appropriate CPU/memory limits in Kubernetes/Docker
|
||||
|
||||
### Health Checks
|
||||
|
||||
The application provides a health check endpoint:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/health
|
||||
# Response: {"status":"ok","version":"0.1.0"}
|
||||
```
|
||||
|
||||
Docker health check is configured automatically (every 30s).
|
||||
|
||||
### Kubernetes Deployment
|
||||
|
||||
The Helm chart includes:
|
||||
|
||||
- **Deployment** with configurable replicas
|
||||
- **Service** (ClusterIP/LoadBalancer)
|
||||
- **Ingress** with TLS support
|
||||
- **ConfigMap** for configuration
|
||||
- **NetworkPolicy** for security
|
||||
- **ServiceMonitor** for Prometheus (optional)
|
||||
|
||||
Example custom values:
|
||||
|
||||
```yaml
|
||||
# custom-values.yaml
|
||||
replicaCount: 2
|
||||
|
||||
image:
|
||||
repository: noooste/garage-ui
|
||||
tag: latest
|
||||
|
||||
garage:
|
||||
endpoint: "http://garage.storage.svc.cluster.local:3900"
|
||||
adminEndpoint: "http://garage.storage.svc.cluster.local:3903"
|
||||
adminToken: "your-secure-token"
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
hosts:
|
||||
- host: garage-ui.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: garage-ui-tls
|
||||
hosts:
|
||||
- garage-ui.example.com
|
||||
|
||||
config:
|
||||
auth:
|
||||
admin:
|
||||
enabled: false
|
||||
oidc:
|
||||
enabled: true
|
||||
client_id: garage-ui
|
||||
client_secret: secret
|
||||
issuer_url: https://auth.example.com/realms/master
|
||||
```
|
||||
|
||||
Install with:
|
||||
|
||||
```bash
|
||||
helm install garage-ui ./helm/garage-ui -f custom-values.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Technology Stack
|
||||
|
||||
### Backend
|
||||
|
||||
- **[Go](https://go.dev/)** 1.25+ - High-performance backend runtime
|
||||
- **[Fiber v3](https://gofiber.io/)** - Express-inspired web framework
|
||||
- **[Viper](https://github.com/spf13/viper)** - Configuration management
|
||||
- **[Minio Go SDK](https://github.com/minio/minio-go)** - S3 client library
|
||||
- **[go-oidc](https://github.com/coreos/go-oidc)** - OpenID Connect support
|
||||
- **[Zerolog](https://github.com/rs/zerolog)** - Structured logging
|
||||
- **[Swagger/Swag](https://github.com/swaggo/swag)** - API documentation
|
||||
|
||||
### Frontend
|
||||
|
||||
- **[React](https://react.dev/)** 19 - UI framework
|
||||
- **[TypeScript](https://www.typescriptlang.org/)** 5.9+ - Type safety
|
||||
- **[Vite](https://vitejs.dev/)** 7 - Build tool and dev server
|
||||
- **[React Router](https://reactrouter.com/)** 7 - Client-side routing
|
||||
- **[TanStack Query](https://tanstack.com/query)** - Server state management
|
||||
- **[Zustand](https://github.com/pmndrs/zustand)** - Client state management
|
||||
- **[React Hook Form](https://react-hook-form.com/)** + [Zod](https://zod.dev/) - Form validation
|
||||
- **[Tailwind CSS](https://tailwindcss.com/)** 4 - Utility-first styling
|
||||
- **[shadcn/ui](https://ui.shadcn.com/)** - Component library
|
||||
- **[Lucide Icons](https://lucide.dev/)** - Icon library
|
||||
- **[Recharts](https://recharts.org/)** - Data visualization
|
||||
- **[Axios](https://axios-http.com/)** - HTTP client
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Whether it's bug reports, feature requests, or code contributions, your input helps improve Garage UI.
|
||||
|
||||
### How to Contribute
|
||||
|
||||
1. **Fork the repository**
|
||||
2. **Create a feature branch** (`git checkout -b feature/amazing-feature`)
|
||||
3. **Make your changes**
|
||||
4. **Commit your changes** (`git commit -m 'Add amazing feature'`)
|
||||
5. **Push to the branch** (`git push origin feature/amazing-feature`)
|
||||
6. **Open a Pull Request**
|
||||
|
||||
### Development Guidelines
|
||||
|
||||
- Follow existing code style (use `gofmt` for Go, ESLint for TypeScript)
|
||||
- Add tests for new features when possible
|
||||
- Update documentation for API changes
|
||||
- Keep commits atomic and descriptive
|
||||
- Ensure all tests pass before submitting PR
|
||||
|
||||
### Reporting Issues
|
||||
|
||||
Please open an issue on GitHub with:
|
||||
- Clear description of the problem
|
||||
- Steps to reproduce
|
||||
- Expected vs actual behavior
|
||||
- Environment details (OS, Docker version, etc.)
|
||||
|
||||
---
|
||||
For complete Garage configuration, see the [official documentation](https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Issue: "Failed to connect to Garage Admin API"**
|
||||
|
||||
- **Solution:** Verify `garage.admin_endpoint` and `garage.admin_token` in config.yaml
|
||||
- Check Garage Admin API is accessible: `curl http://garage:3903/status -H "Authorization: Bearer your-token"`
|
||||
|
||||
**Issue: "CORS errors in browser console"**
|
||||
|
||||
- **Solution:** Configure CORS in config.yaml to allow frontend origin:
|
||||
```yaml
|
||||
cors:
|
||||
allowed_origins:
|
||||
- "http://localhost:3000" # For dev
|
||||
```
|
||||
|
||||
**Issue: "401 Unauthorized with OIDC"**
|
||||
|
||||
- **Solution:** Verify OIDC configuration, especially `issuer_url`, `client_id`, and `client_secret`
|
||||
- Check provider configuration allows the redirect URL: `http://your-app/auth/callback`
|
||||
|
||||
**Issue: "Objects not appearing after upload"**
|
||||
|
||||
- **Solution:** Check S3 endpoint connectivity and bucket permissions
|
||||
- Verify Garage bucket exists: `garage bucket list`
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable debug logging for troubleshooting:
|
||||
**Connection failed:**
|
||||
```bash
|
||||
curl http://localhost:3903/status -H "Authorization: Bearer your-token"
|
||||
```
|
||||
|
||||
**Enable debug logs:**
|
||||
```yaml
|
||||
logging:
|
||||
level: "debug"
|
||||
format: "json"
|
||||
```
|
||||
|
||||
Or via environment variable:
|
||||
|
||||
```bash
|
||||
GARAGE_UI_LOGGING_LEVEL=debug
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.
|
||||
MIT - see [LICENSE](LICENSE)
|
||||
|
||||
---
|
||||
## Links
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- [Garage](https://garagehq.deuxfleurs.fr/) - The lightweight, geo-distributed S3 storage system
|
||||
- [shadcn/ui](https://ui.shadcn.com/) - Beautiful, accessible React components
|
||||
- [Fiber](https://gofiber.io/) - Fast and lightweight Go web framework
|
||||
- All open-source contributors who made this project possible
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
- **Documentation:** [GitHub Wiki](https://github.com/Noooste/garage-ui/wiki) (coming soon)
|
||||
- **Issues:** [GitHub Issues](https://github.com/Noooste/garage-ui/issues)
|
||||
- **Discussions:** [GitHub Discussions](https://github.com/Noooste/garage-ui/discussions)
|
||||
|
||||
---
|
||||
|
||||
**Made with ❤️ for the Garage community**
|
||||
- [Issues](https://github.com/Noooste/garage-ui/issues)
|
||||
- [Contributing](CONTRIBUTING.md)
|
||||
- [Garage Docs](https://garagehq.deuxfleurs.fr/documentation/)
|
||||
|
||||
@@ -3,6 +3,7 @@ package auth
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
@@ -76,12 +77,22 @@ func parseEd25519PrivateKeyFromPEM(privateKeyPEM string) (ed25519.PrivateKey, er
|
||||
return nil, fmt.Errorf("failed to decode PEM block")
|
||||
}
|
||||
|
||||
// Check if it's raw Ed25519 private key bytes (64 bytes)
|
||||
if len(block.Bytes) == ed25519.PrivateKeySize {
|
||||
return ed25519.PrivateKey(block.Bytes), nil
|
||||
// Try to parse as PKCS#8 format (standard format from openssl genpkey -algorithm ED25519)
|
||||
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err == nil {
|
||||
// Successfully parsed as PKCS#8, check if it's an Ed25519 key
|
||||
if ed25519Key, ok := key.(ed25519.PrivateKey); ok {
|
||||
return ed25519Key, nil
|
||||
}
|
||||
return nil, fmt.Errorf("PKCS#8 key is not an Ed25519 key")
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid Ed25519 private key format: expected %d bytes, got %d",
|
||||
// Fallback: Check if it's raw Ed25519 private key bytes (64 bytes)
|
||||
if len(block.Bytes) == ed25519.PrivateKeySize {
|
||||
return block.Bytes, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid Ed25519 private key format: not PKCS#8 and not raw %d bytes (got %d bytes)",
|
||||
ed25519.PrivateKeySize, len(block.Bytes))
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,6 @@ list: async (): Promise<Bucket[]> => {
|
||||
- Activate/deactivate keys
|
||||
- Copy access key IDs
|
||||
- Permission configuration
|
||||
- Bucket policies (coming soon)
|
||||
|
||||
## Theme Customization
|
||||
|
||||
|
||||
Generated
+64
-64
@@ -1530,9 +1530,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit/node_modules/immer": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-11.0.1.tgz",
|
||||
"integrity": "sha512-naDCyggtcBWANtIrjQEajhhBEuL9b0Zg4zmlWK2CzS6xCWSE39/vvf4LqnMjUAWHBhot4m9MHCM/Z+mfWhUkiA==",
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.0.tgz",
|
||||
"integrity": "sha512-dlzb07f5LDY+tzs+iLCSXV2yuhaYfezqyZQc+n6baLECWkOMEWxkECAOnXL0ba7lsA25fM9b2jtzpu/uxo1a7g==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -2358,17 +2358,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz",
|
||||
"integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz",
|
||||
"integrity": "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.50.0",
|
||||
"@typescript-eslint/type-utils": "8.50.0",
|
||||
"@typescript-eslint/utils": "8.50.0",
|
||||
"@typescript-eslint/visitor-keys": "8.50.0",
|
||||
"@typescript-eslint/scope-manager": "8.50.1",
|
||||
"@typescript-eslint/type-utils": "8.50.1",
|
||||
"@typescript-eslint/utils": "8.50.1",
|
||||
"@typescript-eslint/visitor-keys": "8.50.1",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
@@ -2381,7 +2381,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.50.0",
|
||||
"@typescript-eslint/parser": "^8.50.1",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
@@ -2397,17 +2397,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz",
|
||||
"integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.1.tgz",
|
||||
"integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.50.0",
|
||||
"@typescript-eslint/types": "8.50.0",
|
||||
"@typescript-eslint/typescript-estree": "8.50.0",
|
||||
"@typescript-eslint/visitor-keys": "8.50.0",
|
||||
"@typescript-eslint/scope-manager": "8.50.1",
|
||||
"@typescript-eslint/types": "8.50.1",
|
||||
"@typescript-eslint/typescript-estree": "8.50.1",
|
||||
"@typescript-eslint/visitor-keys": "8.50.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2423,14 +2423,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz",
|
||||
"integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz",
|
||||
"integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.50.0",
|
||||
"@typescript-eslint/types": "^8.50.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.50.1",
|
||||
"@typescript-eslint/types": "^8.50.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2445,14 +2445,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz",
|
||||
"integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz",
|
||||
"integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.50.0",
|
||||
"@typescript-eslint/visitor-keys": "8.50.0"
|
||||
"@typescript-eslint/types": "8.50.1",
|
||||
"@typescript-eslint/visitor-keys": "8.50.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2463,9 +2463,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz",
|
||||
"integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz",
|
||||
"integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2480,15 +2480,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz",
|
||||
"integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.1.tgz",
|
||||
"integrity": "sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.50.0",
|
||||
"@typescript-eslint/typescript-estree": "8.50.0",
|
||||
"@typescript-eslint/utils": "8.50.0",
|
||||
"@typescript-eslint/types": "8.50.1",
|
||||
"@typescript-eslint/typescript-estree": "8.50.1",
|
||||
"@typescript-eslint/utils": "8.50.1",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
@@ -2505,9 +2505,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz",
|
||||
"integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz",
|
||||
"integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2519,16 +2519,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz",
|
||||
"integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz",
|
||||
"integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.50.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.50.0",
|
||||
"@typescript-eslint/types": "8.50.0",
|
||||
"@typescript-eslint/visitor-keys": "8.50.0",
|
||||
"@typescript-eslint/project-service": "8.50.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.50.1",
|
||||
"@typescript-eslint/types": "8.50.1",
|
||||
"@typescript-eslint/visitor-keys": "8.50.1",
|
||||
"debug": "^4.3.4",
|
||||
"minimatch": "^9.0.4",
|
||||
"semver": "^7.6.0",
|
||||
@@ -2586,16 +2586,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz",
|
||||
"integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.1.tgz",
|
||||
"integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.50.0",
|
||||
"@typescript-eslint/types": "8.50.0",
|
||||
"@typescript-eslint/typescript-estree": "8.50.0"
|
||||
"@typescript-eslint/scope-manager": "8.50.1",
|
||||
"@typescript-eslint/types": "8.50.1",
|
||||
"@typescript-eslint/typescript-estree": "8.50.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2610,13 +2610,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz",
|
||||
"integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz",
|
||||
"integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.50.0",
|
||||
"@typescript-eslint/types": "8.50.1",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -5082,16 +5082,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.50.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.0.tgz",
|
||||
"integrity": "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.1.tgz",
|
||||
"integrity": "sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.50.0",
|
||||
"@typescript-eslint/parser": "8.50.0",
|
||||
"@typescript-eslint/typescript-estree": "8.50.0",
|
||||
"@typescript-eslint/utils": "8.50.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.50.1",
|
||||
"@typescript-eslint/parser": "8.50.1",
|
||||
"@typescript-eslint/typescript-estree": "8.50.1",
|
||||
"@typescript-eslint/utils": "8.50.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {Tabs, TabsContent, TabsList, TabsTrigger} from '@/components/ui/tabs';
|
||||
import {Card, CardContent, CardDescription, CardHeader, CardTitle} from '@/components/ui/card';
|
||||
import {Tabs, TabsContent} from '@/components/ui/tabs';
|
||||
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card';
|
||||
import {Checkbox} from '@/components/ui/checkbox';
|
||||
import {Select, SelectOption} from '@/components/ui/select';
|
||||
import {accessApi, bucketsApi} from '@/lib/api';
|
||||
@@ -369,11 +369,6 @@ export function AccessControl() {
|
||||
/>
|
||||
<div className="p-4 sm:p-6 space-y-4 sm:space-y-6">
|
||||
<Tabs defaultValue="keys">
|
||||
<TabsList className="w-full sm:w-auto">
|
||||
<TabsTrigger value="keys" className="flex-1 sm:flex-initial">API Keys</TabsTrigger>
|
||||
<TabsTrigger value="policies" className="flex-1 sm:flex-initial">Bucket Policies</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="keys" className="space-y-4 sm:space-y-6 mt-4 sm:mt-6">
|
||||
{/* Stats */}
|
||||
<div className="grid gap-3 sm:gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||
@@ -568,22 +563,6 @@ export function AccessControl() {
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="policies" className="space-y-6 mt-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Bucket Policies</CardTitle>
|
||||
<CardDescription>
|
||||
Manage resource-based policies for your buckets
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground text-center py-12">
|
||||
Bucket policy editor coming soon...
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ name: garage-ui
|
||||
description: A Helm chart for Garage UI - Web interface for Garage S3 object storage
|
||||
icon: https://helm.noste.dev/garage.png
|
||||
type: application
|
||||
version: 0.1.7
|
||||
appVersion: "v0.0.11"
|
||||
version: 0.1.12
|
||||
appVersion: "v0.1.1"
|
||||
keywords:
|
||||
- garage
|
||||
- s3
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
A Helm chart for deploying [Garage UI](https://github.com/Noooste/garage-ui), a modern web interface for managing [Garage](https://garagehq.deuxfleurs.fr/) distributed object storage systems.
|
||||
|
||||
[](Chart.yaml)
|
||||
[](Chart.yaml)
|
||||
[](Chart.yaml)
|
||||
[](Chart.yaml)
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -815,15 +815,110 @@ The chart will:
|
||||
|
||||
This keeps sensitive data out of ConfigMaps while maintaining easy Helm-based management.
|
||||
|
||||
#### JWT Private Key for Session Tokens
|
||||
|
||||
The chart automatically manages JWT private keys for signing session tokens. You have three options:
|
||||
|
||||
**Method 1: Auto-generation** (recommended for most cases)
|
||||
|
||||
By default, if you don't provide a JWT private key, the chart will automatically generate an Ed25519 private key on first install and persist it in a Kubernetes secret. This key will be reused across upgrades, ensuring session tokens remain valid.
|
||||
|
||||
```yaml
|
||||
config:
|
||||
auth:
|
||||
# Leave both empty - chart will auto-generate and persist a key
|
||||
jwt_private_key: ""
|
||||
jwt_private_key_secret:
|
||||
name: ""
|
||||
```
|
||||
|
||||
The chart will:
|
||||
1. Run a pre-install/pre-upgrade Job to generate an Ed25519 key
|
||||
2. Store it in a secret named `<release-name>-jwt-key`
|
||||
3. Preserve the secret across chart upgrades (using `helm.sh/resource-policy: keep`)
|
||||
|
||||
**Method 2: Provide your own key inline**
|
||||
|
||||
Generate a key manually and include it in values:
|
||||
|
||||
```bash
|
||||
# Generate Ed25519 private key
|
||||
openssl genpkey -algorithm ED25519 -out jwt-key.pem
|
||||
```
|
||||
|
||||
```yaml
|
||||
config:
|
||||
auth:
|
||||
jwt_private_key: |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEI...
|
||||
-----END PRIVATE KEY-----
|
||||
jwt_private_key_secret:
|
||||
name: ""
|
||||
```
|
||||
|
||||
**Method 3: Use an existing Kubernetes secret**
|
||||
|
||||
Create the secret manually:
|
||||
|
||||
```bash
|
||||
# Generate key
|
||||
openssl genpkey -algorithm ED25519 -out jwt-key.pem
|
||||
|
||||
# Create secret
|
||||
kubectl create secret generic my-jwt-key \
|
||||
--from-file=jwt-key.pem=jwt-key.pem
|
||||
```
|
||||
|
||||
Configure in values:
|
||||
```yaml
|
||||
config:
|
||||
auth:
|
||||
jwt_private_key: ""
|
||||
jwt_private_key_secret:
|
||||
name: "my-jwt-key"
|
||||
key: "jwt-key.pem"
|
||||
```
|
||||
|
||||
**Important Notes:**
|
||||
- Ed25519 keys are recommended over RSA for better performance and security
|
||||
- The auto-generated key persists across upgrades, so tokens remain valid
|
||||
- For multi-replica deployments, all pods share the same key from the secret
|
||||
- The secret is marked with `helm.sh/resource-policy: keep` to prevent deletion during uninstall
|
||||
|
||||
#### OIDC Client Secret
|
||||
|
||||
For OIDC credentials, you can similarly use secrets (requires manual template modification):
|
||||
For OIDC credentials, you can similarly use secrets:
|
||||
|
||||
**Method 1: Let the chart create the secret**
|
||||
|
||||
```yaml
|
||||
config:
|
||||
auth:
|
||||
oidc:
|
||||
enabled: true
|
||||
client_secret: "your-oidc-secret"
|
||||
# Chart will create a secret automatically
|
||||
```
|
||||
|
||||
**Method 2: Use an existing secret**
|
||||
|
||||
```bash
|
||||
kubectl create secret generic garage-oidc \
|
||||
--from-literal=client-secret='your-oidc-secret'
|
||||
```
|
||||
|
||||
```yaml
|
||||
config:
|
||||
auth:
|
||||
oidc:
|
||||
enabled: true
|
||||
client_secret: "" # Leave empty when using existingSecret
|
||||
existingSecret:
|
||||
name: "garage-oidc"
|
||||
key: "client-secret"
|
||||
```
|
||||
|
||||
### Network Policies
|
||||
|
||||
Enable network policies for enhanced security:
|
||||
|
||||
@@ -46,7 +46,6 @@ spec:
|
||||
name: {{ include "garage-ui.fullname" . }}-admin-token
|
||||
key: admin-token
|
||||
{{- end }}
|
||||
{{- if or .Values.config.auth.jwt_private_key .Values.config.auth.jwt_private_key_secret.name }}
|
||||
- name: GARAGE_UI_AUTH_JWT_PRIVATE_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
@@ -55,9 +54,8 @@ spec:
|
||||
key: {{ .Values.config.auth.jwt_private_key_secret.key }}
|
||||
{{- else }}
|
||||
name: {{ include "garage-ui.fullname" . }}-jwt-key
|
||||
key: jwt-key
|
||||
key: jwt-key.pem
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.config.auth.oidc.enabled }}
|
||||
- name: GARAGE_UI_AUTH_OIDC_CLIENT_SECRET
|
||||
valueFrom:
|
||||
|
||||
@@ -45,7 +45,28 @@ metadata:
|
||||
name: {{ include "garage-ui.fullname" . }}-jwt-key
|
||||
labels:
|
||||
{{- include "garage-ui.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/resource-policy": keep
|
||||
type: Opaque
|
||||
data:
|
||||
jwt-key: {{ .Values.config.auth.jwt_private_key | b64enc | quote }}
|
||||
jwt-key.pem: {{ .Values.config.auth.jwt_private_key | b64enc | quote }}
|
||||
{{- end }}
|
||||
---
|
||||
{{- if not .Values.config.auth.jwt_private_key_secret.name }}
|
||||
{{- $secretName := printf "%s-jwt-key" (include "garage-ui.fullname" .) }}
|
||||
{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace $secretName }}
|
||||
{{- if not $existingSecret }}
|
||||
{{- $privateKey := genPrivateKey "ed25519" }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ $secretName }}
|
||||
labels:
|
||||
{{- include "garage-ui.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/resource-policy": keep
|
||||
type: Opaque
|
||||
data:
|
||||
jwt-key.pem: {{ $privateKey | b64enc | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -43,9 +43,11 @@ config:
|
||||
auth:
|
||||
# Ed25519 private key for JWT signing (PEM format)
|
||||
# Generate with: openssl genpkey -algorithm ED25519
|
||||
# If not provided, auto-generated on each restart (not recommended for production)
|
||||
# If not provided and no existing secret is specified, a key will be auto-generated
|
||||
# and persisted in a Kubernetes secret (recommended for production)
|
||||
jwt_private_key: ""
|
||||
# Use existing secret for JWT private key (recommended)
|
||||
# Use existing secret for JWT private key (optional)
|
||||
# If not specified, the chart will auto-generate a secret on first install
|
||||
jwt_private_key_secret:
|
||||
name: ""
|
||||
key: "jwt-key.pem"
|
||||
|
||||
Reference in New Issue
Block a user