Merge pull request #342 from outis1one/claude/ionos-script-integration-x32ofw
Add garage-webui: browse Garage's buckets/objects like Backblaze's co…
This commit is contained in:
@@ -186,7 +186,7 @@ a ready-to-copy Caddy config snippet to `~/docker/caddy-snippets/`.
|
||||
|-------|---------|
|
||||
| `base` | `net-tools`, `ncdu`, `git`, `curl`, `wget`, `htop`, `tree`, `zip`/`unzip`, `ca-certificates`, `gnupg`, `jq`, `rsync`; `glow` (terminal markdown reader, Charm apt repo); Docker CE + Compose plugin; `openssh-server` with GitHub/Launchpad SSH key import, optional password-auth lockdown, and SSH Host aliases; optional NetBird overlay network |
|
||||
| `homelab` | `caddy`, `crowdsec`, `authelia`, `homeassistant`, `asterisk` (own dedicated coturn for TURN/STUN — see `mattermost` below for the other coturn-owning service), `pstn-trunk`, `sms-inbound`, `security-dashboard`, `sunshine`, `vpn-data-mount` (mount existing SMB shares from a NetBird-connected home box — SSH trust bootstrap, then read-only discovery of shares already configured there; never writes to the home box's Samba config; repeatable, pick from any number of a home box's shares in one pass; optional per-share [gocryptfs decrypt layer](#client-side-encryption-for-vpn-data-mount) so the VPS only ever handles ciphertext) |
|
||||
| `utilities` | `actualbudget`, `ai-gpu`, `ai-stack`, `archivebox`, `beszel` (lightweight server + Docker monitoring — CPU/RAM/disk/network, auto-discovers running containers via the Docker socket; complements Gatus rather than replacing it — Gatus is a black-box HTTP check, Beszel is white-box host/process monitoring), `beszel-agent` (agent-only Beszel install for a remote/homelab box reporting to a hub elsewhere — connects outbound over HTTPS, no VPN/port-forwarding/FQDN needed on that box), `changedetection`, `ddclient`, `filebrowser`, `fmd`, `garage` (self-hosted S3-compatible object storage, single node — MinIO CE's actively-maintained replacement), `gatus`, `homebox`, `iopaint`, `joplin`, `koha`, `magicmirror`, `mail-archiver`, `mattermost`, `mealie`, `meshcentral`, `n8n`, `nextcloud`, `ntfy`, `onlyoffice`, `paintplus`, `pihole` (standalone DNS ad/tracker blocking — not wired into any VPN's DNS push), `portainer`, `rustdesk`, `stirling-pdf`, `syncthing`, `traccar`, `unifi`, `uptimekuma`, `vaultwarden`, `watchyourlan`, `watchtower`, `wg-easy`, `wordpress` (multi-site, dedicated MariaDB per site — blogs, business sites, e-commerce via WooCommerce) |
|
||||
| `utilities` | `actualbudget`, `ai-gpu`, `ai-stack`, `archivebox`, `beszel` (lightweight server + Docker monitoring — CPU/RAM/disk/network, auto-discovers running containers via the Docker socket; complements Gatus rather than replacing it — Gatus is a black-box HTTP check, Beszel is white-box host/process monitoring), `beszel-agent` (agent-only Beszel install for a remote/homelab box reporting to a hub elsewhere — connects outbound over HTTPS, no VPN/port-forwarding/FQDN needed on that box), `changedetection`, `ddclient`, `filebrowser`, `fmd`, `garage` (self-hosted S3-compatible object storage, single node — MinIO CE's actively-maintained replacement), `garage-webui` (browser-based bucket/object browser for an existing `garage` install — folders/files view, the same kind of thing Backblaze's own web console gives you), `gatus`, `homebox`, `iopaint`, `joplin`, `koha`, `magicmirror`, `mail-archiver`, `mattermost`, `mealie`, `meshcentral`, `n8n`, `nextcloud`, `ntfy`, `onlyoffice`, `paintplus`, `pihole` (standalone DNS ad/tracker blocking — not wired into any VPN's DNS push), `portainer`, `rustdesk`, `stirling-pdf`, `syncthing`, `traccar`, `unifi`, `uptimekuma`, `vaultwarden`, `watchyourlan`, `watchtower`, `wg-easy`, `wordpress` (multi-site, dedicated MariaDB per site — blogs, business sites, e-commerce via WooCommerce) |
|
||||
| `media` | `arm`, `audiobookshelf`, `calibre-web`, `emby`, `immich`, `jellyfin`, `lyrion` |
|
||||
| `cameras` | `frigate`, `frigate-audio`, `frigate-notify`, `sky-cam` |
|
||||
| `gaming` | `drum-rhythm-game`, `js99er`, `kyber-launcher`, `kyber-server`, `minecraft`, `wolf`, `wolf-pair` |
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
#!/bin/bash
|
||||
# services/garage-webui.sh — Web UI for browsing/managing an existing Garage
|
||||
# instance's buckets and objects (folders/files view, the same kind of thing
|
||||
# Backblaze's own web console gives you for a B2 bucket).
|
||||
# Part of the modular post-install system (sourced by setup.sh).
|
||||
#
|
||||
# Can also be run standalone on any machine:
|
||||
# sudo bash garage-webui.sh
|
||||
# (Docker must already be installed when run standalone; requires
|
||||
# services/garage.sh already installed on the SAME machine — this reads
|
||||
# that install's admin API port/token straight out of its .env)
|
||||
|
||||
# ── Standalone bootstrap ──────────────────────────────────────────────────────
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
[[ "$(id -u)" == "0" ]] || { echo "Run with sudo: sudo bash $0"; exit 1; }
|
||||
|
||||
_SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
_COMMON="$_SELF_DIR/../lib/common.sh"
|
||||
|
||||
if [[ -f "$_COMMON" ]]; then
|
||||
# shellcheck source=../lib/common.sh
|
||||
source "$_COMMON"
|
||||
else
|
||||
log_info() { echo -e "\033[0;34m[INFO]\033[0m $*"; }
|
||||
log_success() { echo -e "\033[0;32m[OK]\033[0m $*"; }
|
||||
log_warning() { echo -e "\033[1;33m[WARN]\033[0m $*"; }
|
||||
log_error() { echo -e "\033[0;31m[ERROR]\033[0m $*" >&2; }
|
||||
|
||||
require_docker() {
|
||||
command -v docker &>/dev/null || {
|
||||
log_error "Docker not found. Install it first:"
|
||||
log_error " curl -fsSL https://get.docker.com | sudo sh"
|
||||
return 1
|
||||
}
|
||||
docker compose version &>/dev/null || {
|
||||
log_error "Docker Compose plugin missing:"
|
||||
log_error " sudo apt-get install -y docker-compose-plugin"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
ensure_docker_dir_ownership() {
|
||||
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$@" 2>/dev/null || true
|
||||
}
|
||||
|
||||
port_in_use() {
|
||||
local _port="$1" _proto="${2:-tcp}"
|
||||
local _flag="-tlnH"
|
||||
[ "$_proto" = "udp" ] && _flag="-ulnH"
|
||||
ss "$_flag" "sport = :${_port}" 2>/dev/null | grep -q .
|
||||
}
|
||||
|
||||
find_free_port() {
|
||||
local _varname="$1" _port="$2" _proto="${3:-tcp}"
|
||||
while port_in_use "$_port" "$_proto"; do
|
||||
_port=$((_port + 1))
|
||||
done
|
||||
eval "$_varname='$_port'"
|
||||
}
|
||||
|
||||
generate_password() {
|
||||
local _len="${1:-32}"
|
||||
tr -dc 'A-Za-z0-9' < /dev/urandom | head -c "$_len"
|
||||
}
|
||||
|
||||
prompt_text() {
|
||||
local _q="$1" _def="$2" _var="$3" _r
|
||||
[[ "${UNATTENDED:-false}" == "true" ]] && { eval "$_var='$_def'"; return; }
|
||||
read -r -p " $_q " _r
|
||||
eval "$_var='${_r:-$_def}'"
|
||||
}
|
||||
|
||||
prompt_reinstall_mode() {
|
||||
local _var="$1" _r
|
||||
[[ "${UNATTENDED:-false}" == "true" ]] && { eval "$_var='cancel'"; return; }
|
||||
echo ""
|
||||
echo " 1) Update — refresh the image only, leave config/data as-is"
|
||||
echo " 2) Full reinstall — wipe and reconfigure from scratch"
|
||||
echo " 3) Cancel — leave the existing install untouched"
|
||||
read -r -p " Choice [3]: " _r
|
||||
case "$_r" in
|
||||
1) eval "$_var='update'" ;;
|
||||
2) eval "$_var='fresh'" ;;
|
||||
*) eval "$_var='cancel'" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
configure_caddy_for_service() { CADDY_SERVICE_CONFIGURED=false; }
|
||||
|
||||
write_readme() {
|
||||
local _dir="$1"; shift
|
||||
mkdir -p "$_dir"
|
||||
cat > "$_dir/README.md"
|
||||
}
|
||||
fi
|
||||
|
||||
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
|
||||
ACTUAL_HOME="$(getent passwd "$ACTUAL_USER" 2>/dev/null | cut -d: -f6 || echo "${HOME:-/root}")"
|
||||
DOCKER_DIR="${DOCKER_DIR:-$ACTUAL_HOME/docker}"
|
||||
DRY_RUN="${DRY_RUN:-false}"
|
||||
UNATTENDED="${UNATTENDED:-false}"
|
||||
SITE_TZ="${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}"
|
||||
|
||||
register_service() { :; }
|
||||
_RUN_STANDALONE=1
|
||||
fi
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
register_service garage-webui utilities "Web UI for browsing/managing an existing Garage instance's buckets and objects" 3909
|
||||
|
||||
install_garage-webui() {
|
||||
require_docker || return 1
|
||||
|
||||
local GARAGE_DIR="$DOCKER_DIR/garage"
|
||||
local DIR="$DOCKER_DIR/garage-webui"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
echo "[DRY-RUN] Would check for an existing services/garage.sh install at $GARAGE_DIR"
|
||||
echo "[DRY-RUN] Would create $DIR with docker-compose.yml + .env"
|
||||
echo "[DRY-RUN] Would auto-scan for a free web UI port"
|
||||
echo "[DRY-RUN] Would generate an admin username/password and bcrypt-hash it (requires Docker)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ! -f "$GARAGE_DIR/.env" ]]; then
|
||||
log_error "No Garage install found at $GARAGE_DIR — this is a browser for an"
|
||||
log_error "existing Garage instance, not a replacement for it. Install Garage"
|
||||
log_error "first: sudo ./setup.sh garage"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local _garage_env
|
||||
_garage_env="$(cat "$GARAGE_DIR/.env")"
|
||||
local GARAGE_ADMIN_PORT GARAGE_ADMIN_TOKEN GARAGE_S3_API_PORT
|
||||
GARAGE_ADMIN_PORT="$(echo "$_garage_env" | sed -nE "s/^GARAGE_ADMIN_PORT=([0-9]+)\$/\1/p")"
|
||||
GARAGE_ADMIN_TOKEN="$(echo "$_garage_env" | sed -nE "s/^GARAGE_ADMIN_TOKEN='?([^']*)'?\$/\1/p")"
|
||||
GARAGE_S3_API_PORT="$(echo "$_garage_env" | sed -nE "s/^GARAGE_S3_API_PORT=([0-9]+)\$/\1/p")"
|
||||
|
||||
if [ -z "$GARAGE_ADMIN_PORT" ] || [ -z "$GARAGE_ADMIN_TOKEN" ] || [ -z "$GARAGE_S3_API_PORT" ]; then
|
||||
log_error "Garage is installed at $GARAGE_DIR but its .env is missing the admin"
|
||||
log_error "API port/token this needs (added in a newer version of services/garage.sh)."
|
||||
log_error "Re-run Garage's own installer first to backfill it, then rerun this:"
|
||||
log_error " sudo ./setup.sh garage (choose \"Update\" when prompted)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -f "$DIR/docker-compose.yml" && -f "$DIR/.env" ]]; then
|
||||
local MODE=""
|
||||
prompt_reinstall_mode MODE
|
||||
case "$MODE" in
|
||||
update)
|
||||
log_info "Refreshing the Garage Web UI image only — existing login is left as-is."
|
||||
( cd "$DIR" && docker compose pull && docker compose up -d ) \
|
||||
&& log_success "Garage Web UI refreshed" \
|
||||
|| log_warning "Refresh failed — check: docker compose -f $DIR/docker-compose.yml logs"
|
||||
return 0
|
||||
;;
|
||||
cancel)
|
||||
log_info "Leaving the existing install as-is."
|
||||
return 0
|
||||
;;
|
||||
fresh) ;; # fall through to the full install flow below
|
||||
esac
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════"
|
||||
echo " GARAGE WEB UI — browse/manage Garage's buckets and objects"
|
||||
echo "═══════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo " Connecting to the Garage instance at $GARAGE_DIR."
|
||||
echo ""
|
||||
|
||||
local WEB_PORT="3909"
|
||||
find_free_port WEB_PORT "$WEB_PORT"
|
||||
|
||||
local WEBUI_USER=""
|
||||
prompt_text " Admin username:" "admin" WEBUI_USER
|
||||
WEBUI_USER="${WEBUI_USER:-admin}"
|
||||
|
||||
local WEBUI_PASSWORD
|
||||
WEBUI_PASSWORD="$(generate_password 24)"
|
||||
|
||||
log_info "Generating bcrypt password hash (requires Docker)..."
|
||||
local _auth_line
|
||||
_auth_line="$(docker run --rm httpd:alpine htpasswd -nbB "$WEBUI_USER" "$WEBUI_PASSWORD" 2>/dev/null)"
|
||||
if [ -z "$_auth_line" ]; then
|
||||
log_error "Couldn't generate a bcrypt hash (needs to pull httpd:alpine) — aborting."
|
||||
return 1
|
||||
fi
|
||||
# Compose re-parses its own YAML for $VAR/${VAR} interpolation, so a
|
||||
# literal $ from the bcrypt hash (embedded directly into
|
||||
# docker-compose.yml below) has to be doubled or Compose treats it as
|
||||
# the start of a variable reference. Same fix services/wg-easy.sh uses
|
||||
# for its own bcrypt PASSWORD_HASH.
|
||||
local _auth_escaped="${_auth_line//\$/\$\$}"
|
||||
|
||||
mkdir -p "$DIR"
|
||||
ensure_docker_dir_ownership "$DIR"
|
||||
cd "$DIR" || return 1
|
||||
|
||||
cat > docker-compose.yml << COMPOSE
|
||||
name: garage-webui
|
||||
|
||||
services:
|
||||
garage-webui:
|
||||
image: khairul169/garage-webui:1.1.0
|
||||
container_name: garage-webui
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
environment:
|
||||
API_BASE_URL: "http://host.docker.internal:${GARAGE_ADMIN_PORT}"
|
||||
S3_ENDPOINT_URL: "http://host.docker.internal:${GARAGE_S3_API_PORT}"
|
||||
API_ADMIN_KEY: "${GARAGE_ADMIN_TOKEN}"
|
||||
AUTH_USER_PASS: "${_auth_escaped}"
|
||||
ports:
|
||||
- "${WEB_PORT}:3909"
|
||||
COMPOSE
|
||||
|
||||
cat > .env << ENV
|
||||
TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}
|
||||
|
||||
# Plain-text password — only for your own reference (the container itself
|
||||
# is only ever given the bcrypt hash, baked into docker-compose.yml).
|
||||
GARAGE_WEBUI_USER='${WEBUI_USER}'
|
||||
GARAGE_WEBUI_PASSWORD='${WEBUI_PASSWORD}'
|
||||
ENV
|
||||
chmod 600 .env
|
||||
|
||||
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$DIR"
|
||||
|
||||
log_info "Starting Garage Web UI..."
|
||||
docker compose up -d || { log_error "Failed to start Garage Web UI — check: docker compose logs"; return 1; }
|
||||
|
||||
configure_caddy_for_service "Garage Web UI" "$WEB_PORT" "garage-admin"
|
||||
|
||||
write_readme "$DIR" << MD
|
||||
# Garage Web UI
|
||||
|
||||
Browser-based admin UI for an existing Garage instance ($GARAGE_DIR) — bucket
|
||||
and object browser (see folders/files the way Backblaze's own web console
|
||||
shows a B2 bucket), cluster health, and key management.
|
||||
|
||||
## Login
|
||||
- URL: http://localhost:${WEB_PORT}
|
||||
- Username: \`$WEBUI_USER\`
|
||||
- Password: see \`.env\` (\`GARAGE_WEBUI_PASSWORD\`)
|
||||
|
||||
## Manage
|
||||
\`\`\`bash
|
||||
docker compose up -d
|
||||
docker compose down
|
||||
docker compose logs -f
|
||||
docker compose pull && docker compose up -d
|
||||
\`\`\`
|
||||
|
||||
This talks to Garage's admin API (\`GARAGE_ADMIN_PORT\`/\`GARAGE_ADMIN_TOKEN\` in
|
||||
$GARAGE_DIR/.env) and S3 API over \`host.docker.internal\`, both on this same
|
||||
machine — nothing here is sent over the network to any other box.
|
||||
MD
|
||||
|
||||
echo ""
|
||||
log_success "Garage Web UI ready."
|
||||
echo " URL: http://localhost:${WEB_PORT}"
|
||||
echo " Username: $WEBUI_USER"
|
||||
echo " Password: $WEBUI_PASSWORD (saved in $DIR/.env)"
|
||||
echo ""
|
||||
}
|
||||
|
||||
[[ "${_RUN_STANDALONE:-0}" == 1 ]] && install_garage-webui
|
||||
+50
-1
@@ -134,7 +134,7 @@ install_garage() {
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
echo "[DRY-RUN] Would create $DIR with garage.toml + docker-compose.yml + .env"
|
||||
echo "[DRY-RUN] Would auto-scan for free S3 API / RPC / admin ports"
|
||||
echo "[DRY-RUN] Would generate an RPC secret and persist it (never regenerated on update)"
|
||||
echo "[DRY-RUN] Would generate an RPC secret and an admin API token, persisted (never regenerated on update)"
|
||||
echo "[DRY-RUN] Would run the one-time cluster init: layout assign/apply, bucket create, key create"
|
||||
echo "[DRY-RUN] Would print the endpoint/bucket/access-key/secret for Kopia's sync-to s3"
|
||||
return 0
|
||||
@@ -146,6 +146,7 @@ install_garage() {
|
||||
case "$MODE" in
|
||||
update)
|
||||
log_info "Refreshing the Garage image only — existing data, config, and keys are left as-is."
|
||||
local _GARAGE_NEEDS_RESTART_FOR_ADMIN_TOKEN=0
|
||||
# .env fields added to this script after someone's initial
|
||||
# install (GARAGE_S3_API_PORT, added so services/backup.sh
|
||||
# can read it remotely) never get backfilled by Update on
|
||||
@@ -164,9 +165,42 @@ install_garage() {
|
||||
log_info "Backfilled GARAGE_S3_API_PORT=${_existing_port} into .env (added in a newer version of this script; services/backup.sh needs it to read this instance remotely)."
|
||||
fi
|
||||
fi
|
||||
if [[ -f "$DIR/.env" ]] && ! grep -q '^GARAGE_ADMIN_PORT=' "$DIR/.env"; then
|
||||
local _existing_admin_port
|
||||
_existing_admin_port="$(grep -oE '"[0-9]+:[0-9]+"' "$DIR/docker-compose.yml" 2>/dev/null | sed -n 3p | tr -d '"' | cut -d: -f1)"
|
||||
if [ -n "$_existing_admin_port" ]; then
|
||||
echo "GARAGE_ADMIN_PORT=${_existing_admin_port}" >> "$DIR/.env"
|
||||
log_info "Backfilled GARAGE_ADMIN_PORT=${_existing_admin_port} into .env."
|
||||
fi
|
||||
fi
|
||||
# Older installs' garage.toml predates admin_token, meaning
|
||||
# this instance's admin API (bucket/key management, object
|
||||
# listing — published to the host, not just the internal
|
||||
# Docker network) has been running with no authentication
|
||||
# at all. Add one now rather than leaving it open — nothing
|
||||
# in this repo talked to that API before services/
|
||||
# garage-webui.sh, so there's no existing authenticated
|
||||
# caller this could break.
|
||||
if [[ -f "$DIR/garage.toml" ]] && ! grep -q '^admin_token' "$DIR/garage.toml"; then
|
||||
local _new_admin_token
|
||||
_new_admin_token="$(openssl rand -base64 32)"
|
||||
printf 'admin_token = "%s"\n' "$_new_admin_token" >> "$DIR/garage.toml"
|
||||
if grep -q '^GARAGE_ADMIN_TOKEN=' "$DIR/.env"; then
|
||||
sed -i "s#^GARAGE_ADMIN_TOKEN=.*#GARAGE_ADMIN_TOKEN='${_new_admin_token}'#" "$DIR/.env"
|
||||
else
|
||||
echo "GARAGE_ADMIN_TOKEN='${_new_admin_token}'" >> "$DIR/.env"
|
||||
fi
|
||||
log_info "Garage's admin API had no auth token — added one and will restart to apply it."
|
||||
_GARAGE_NEEDS_RESTART_FOR_ADMIN_TOKEN=1
|
||||
fi
|
||||
( cd "$DIR" && docker compose pull && docker compose up -d ) \
|
||||
&& log_success "Garage image refreshed" \
|
||||
|| log_warning "Refresh failed — check: docker compose -f $DIR/docker-compose.yml logs"
|
||||
if [ "${_GARAGE_NEEDS_RESTART_FOR_ADMIN_TOKEN:-0}" = 1 ]; then
|
||||
( cd "$DIR" && docker compose restart garage ) \
|
||||
&& log_success "Admin API now requires GARAGE_ADMIN_TOKEN from .env." \
|
||||
|| log_warning "Restart failed — apply the new admin_token manually: docker compose -f $DIR/docker-compose.yml restart garage"
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
cancel)
|
||||
@@ -213,6 +247,15 @@ install_garage() {
|
||||
local RPC_SECRET
|
||||
RPC_SECRET="$(openssl rand -hex 32)"
|
||||
|
||||
# Without admin_token, Garage's admin API (bucket/key management,
|
||||
# metrics, object listing) is open to anyone who can reach ADMIN_PORT
|
||||
# — and that port is published to the host, not just the internal
|
||||
# Docker network. Nothing in this repo talked to that API before, so
|
||||
# this went unnoticed; services/garage-webui.sh is the first consumer,
|
||||
# so it's the point this needed locking down.
|
||||
local ADMIN_TOKEN
|
||||
ADMIN_TOKEN="$(openssl rand -base64 32)"
|
||||
|
||||
cat > garage.toml << TOML
|
||||
metadata_dir = "/meta"
|
||||
data_dir = "/data"
|
||||
@@ -231,6 +274,7 @@ root_domain = ".s3.garage.localhost"
|
||||
|
||||
[admin]
|
||||
api_bind_addr = "[::]:${ADMIN_PORT}"
|
||||
admin_token = "${ADMIN_TOKEN}"
|
||||
TOML
|
||||
|
||||
cat > docker-compose.yml << COMPOSE
|
||||
@@ -260,6 +304,11 @@ TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}
|
||||
# this instance as a Kopia sync-to s3 mirror target — keep this key name
|
||||
# stable, other scripts depend on it.
|
||||
GARAGE_S3_API_PORT=${S3_API_PORT}
|
||||
|
||||
# Read by services/garage-webui.sh (same host only — never sent over SSH)
|
||||
# to reach this instance's admin API for its bucket/object browser.
|
||||
GARAGE_ADMIN_PORT=${ADMIN_PORT}
|
||||
GARAGE_ADMIN_TOKEN='${ADMIN_TOKEN}'
|
||||
ENV
|
||||
chmod 600 .env
|
||||
|
||||
|
||||
Reference in New Issue
Block a user