mirror of
https://github.com/BillyOutlast/ProxmoxVE.git
synced 2026-10-11 21:29:59 +00:00
Merge branch 'main' into upstream-sync-20260311-140455
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
"interface_port": 8080,
|
||||
"documentation": "https://github.com/ggml-org/llama.cpp/tree/master/tools/server",
|
||||
"website": "https://github.com/ggml-org/llama.cpp",
|
||||
"logo": "https://raw.githubusercontent.com/ggml-org/llama.cpp/master/media/llama_cpp_icon.png",
|
||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/llama-cpp.webp",
|
||||
"config_path": "/opt/llamacpp/models",
|
||||
"description": "llama.cpp is a high-performance LLM inference server with OpenAI-compatible API. This installation uses the Vulkan backend for GPU acceleration across AMD, Intel, and NVIDIA GPUs. Features automatic model downloading from HuggingFace, built-in web UI, and supports multiple concurrent requests.",
|
||||
"install_methods": [
|
||||
|
||||
@@ -6,8 +6,12 @@
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
apk update && apk add curl >/dev/null 2>&1
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
|
||||
# Set default URL if not already defined (for standalone execution)
|
||||
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main}"
|
||||
|
||||
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/core.func)
|
||||
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/error_handler.func)
|
||||
load_functions
|
||||
catch_errors
|
||||
|
||||
@@ -117,7 +121,7 @@ update_os() {
|
||||
msg_info "Updating Container OS"
|
||||
$STD apk -U upgrade
|
||||
local tools_content
|
||||
tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || {
|
||||
tools_content=$(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/tools.func) || {
|
||||
msg_error "Failed to download tools.func"
|
||||
exit 115
|
||||
}
|
||||
@@ -187,7 +191,7 @@ EOF
|
||||
msg_ok "Customized Container"
|
||||
fi
|
||||
|
||||
echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update
|
||||
echo "bash -c \"\$(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/ct/${app}.sh)\"" >/usr/bin/update
|
||||
chmod +x /usr/bin/update
|
||||
post_progress_to_api
|
||||
}
|
||||
|
||||
+14
-11
@@ -83,16 +83,19 @@ variables() {
|
||||
fi
|
||||
}
|
||||
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)
|
||||
# Set default URL if not already defined by the calling script
|
||||
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main}"
|
||||
|
||||
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/api.func)
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/core.func)
|
||||
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/error_handler.func)
|
||||
load_functions
|
||||
catch_errors
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
source <(wget -qO- https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(wget -qO- https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(wget -qO- ${COMMUNITY_SCRIPTS_URL}/misc/core.func)
|
||||
source <(wget -qO- ${COMMUNITY_SCRIPTS_URL}/misc/error_handler.func)
|
||||
load_functions
|
||||
catch_errors
|
||||
fi
|
||||
@@ -3416,7 +3419,7 @@ msg_menu() {
|
||||
# - Otherwise: shows update/setting menu and runs update_script with cleanup
|
||||
# ------------------------------------------------------------------------------
|
||||
start() {
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/tools.func)
|
||||
if command -v pveversion >/dev/null 2>&1; then
|
||||
install_script || return 0
|
||||
return 0
|
||||
@@ -3551,9 +3554,9 @@ build_container() {
|
||||
pushd "$TEMP_DIR" >/dev/null
|
||||
local _func_url
|
||||
if [ "$var_os" == "alpine" ]; then
|
||||
_func_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/alpine-install.func"
|
||||
_func_url="${COMMUNITY_SCRIPTS_URL}/misc/alpine-install.func"
|
||||
else
|
||||
_func_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/install.func"
|
||||
_func_url="${COMMUNITY_SCRIPTS_URL}/misc/install.func"
|
||||
fi
|
||||
export FUNCTIONS_FILE_PATH="$(curl -fsSL "$_func_url")"
|
||||
if [[ -z "$FUNCTIONS_FILE_PATH" || ${#FUNCTIONS_FILE_PATH} -lt 100 ]]; then
|
||||
@@ -4107,7 +4110,7 @@ EOF'
|
||||
# that sends "configuring" status AFTER the host already reported "failed"
|
||||
export CONTAINER_INSTALLING=true
|
||||
|
||||
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)"
|
||||
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/install/${var_install}.sh)"
|
||||
local lxc_exit=$?
|
||||
|
||||
unset CONTAINER_INSTALLING
|
||||
@@ -4413,7 +4416,7 @@ EOF'
|
||||
if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then
|
||||
echo -e "${TAB}${HOLD}${DGN}Setting up MOTD and SSH for debugging...${CL}"
|
||||
if pct exec "$CTID" -- bash -c "
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/install.func)
|
||||
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/install.func)
|
||||
declare -f motd_ssh >/dev/null 2>&1 && motd_ssh || true
|
||||
" >/dev/null 2>&1; then
|
||||
local ct_ip=$(pct exec "$CTID" ip a s dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1)
|
||||
@@ -4485,7 +4488,7 @@ EOF'
|
||||
# Re-run install script in existing container (don't destroy/recreate)
|
||||
set +Eeuo pipefail
|
||||
trap - ERR
|
||||
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)"
|
||||
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/install/${var_install}.sh)"
|
||||
local apt_retry_exit=$?
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
|
||||
+2
-2
@@ -529,7 +529,7 @@ silent() {
|
||||
if [[ $rc -ne 0 ]]; then
|
||||
# Source explain_exit_code if needed
|
||||
if ! declare -f explain_exit_code >/dev/null 2>&1; then
|
||||
if ! source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func); then
|
||||
if ! source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main}/misc/error_handler.func); then
|
||||
explain_exit_code() { echo "unknown (error_handler.func download failed)"; }
|
||||
fi
|
||||
fi
|
||||
@@ -850,7 +850,7 @@ get_header() {
|
||||
local app_type=${APP_TYPE:-ct} # Default to 'ct' if not set
|
||||
local header_dir="${app_type}"
|
||||
[[ "$app_type" == "addon" ]] && header_dir="tools"
|
||||
local header_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/${header_dir}/headers/${app_name}"
|
||||
local header_url="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main}/${header_dir}/headers/${app_name}"
|
||||
local local_header_path="/usr/local/community-scripts/headers/${app_type}/${app_name}"
|
||||
|
||||
mkdir -p "$(dirname "$local_header_path")"
|
||||
|
||||
+8
-4
@@ -32,8 +32,12 @@ if ! command -v curl >/dev/null 2>&1; then
|
||||
apt update >/dev/null 2>&1
|
||||
apt install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
|
||||
# Set default URL if not already defined (for standalone execution)
|
||||
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main}"
|
||||
|
||||
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/core.func)
|
||||
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/error_handler.func)
|
||||
load_functions
|
||||
catch_errors
|
||||
|
||||
@@ -240,7 +244,7 @@ EOF
|
||||
post_progress_to_api
|
||||
|
||||
local tools_content
|
||||
tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || {
|
||||
tools_content=$(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/tools.func) || {
|
||||
msg_error "Failed to download tools.func"
|
||||
exit 115
|
||||
}
|
||||
@@ -319,7 +323,7 @@ EOF
|
||||
systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//')
|
||||
msg_ok "Customized Container"
|
||||
fi
|
||||
echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update
|
||||
echo "bash -c \"\$(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/ct/${app}.sh)\"" >/usr/bin/update
|
||||
chmod +x /usr/bin/update
|
||||
|
||||
if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# Fix Plan: llama.cpp CPU Backend Missing
|
||||
|
||||
## Problem Summary
|
||||
|
||||
The llama.cpp service fails to start with the error:
|
||||
```
|
||||
llama_model_load: error loading model: make_cpu_buft_list: no CPU backend found
|
||||
```
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
### Initial Hypothesis (INCORRECT)
|
||||
Initially suspected that CPU backend files were missing from the installation.
|
||||
|
||||
### Actual Root Cause (CONFIRMED)
|
||||
The CPU backend files **ARE present** in `/opt/llamacpp/bin/`, but they fail to load due to a **missing dependency**:
|
||||
|
||||
```
|
||||
$ ldd /opt/llamacpp/bin/libggml-cpu-haswell.so
|
||||
libgomp.so.1 => not found <-- MISSING!
|
||||
```
|
||||
|
||||
**`libgomp.so.1`** (GNU OpenMP runtime library) is required by the CPU backend libraries but is not installed in the container.
|
||||
|
||||
### Why RPC and Vulkan Backends Load Successfully
|
||||
- `libggml-rpc.so` - No `libgomp` dependency ✅
|
||||
- `libggml-vulkan.so` - No `libgomp` dependency ✅
|
||||
- `libggml-cpu-*.so` - **Requires `libgomp.so.1`** ❌
|
||||
|
||||
## Solution
|
||||
|
||||
### Fix: Install libgomp1 Package
|
||||
|
||||
Add `libgomp1` to the dependencies in [`install/llamacpp-install.sh`](install/llamacpp-install.sh):
|
||||
|
||||
```bash
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt-get install -y \
|
||||
curl \
|
||||
wget \
|
||||
ca-certificates \
|
||||
vulkan-tools \
|
||||
libvulkan1 \
|
||||
mesa-vulkan-drivers \
|
||||
pciutils \
|
||||
libgomp1 # <-- Add this package
|
||||
msg_ok "Installed Dependencies"
|
||||
```
|
||||
|
||||
### Why This Works
|
||||
- `libgomp1` provides `libgomp.so.1` (GNU OpenMP runtime)
|
||||
- CPU backends use OpenMP for parallel processing
|
||||
- Without this library, the dynamic linker fails to load `libggml-cpu-*.so`
|
||||
|
||||
## Verification Steps
|
||||
|
||||
After installing `libgomp1`, verify the fix:
|
||||
|
||||
```bash
|
||||
# 1. Verify libgomp is installed
|
||||
ldd /opt/llamacpp/bin/libggml-cpu-haswell.so | grep gomp
|
||||
# Expected: libgomp.so.1 => /lib/x86_64-linux-gnu/libgomp.so.1 (0x...)
|
||||
|
||||
# 2. Verify CPU backend loads
|
||||
/opt/llamacpp/bin/llama-server --version
|
||||
# Expected output should include:
|
||||
# load_backend: loaded RPC backend from ...
|
||||
# load_backend: loaded Vulkan backend from ...
|
||||
# load_backend: loaded CPU backend from .../libggml-cpu-*.so <-- This should now appear
|
||||
|
||||
# 3. Test model loading
|
||||
systemctl start llamacpp
|
||||
journalctl -u llamacpp -f
|
||||
# Should no longer show "no CPU backend found" error
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### File to Modify
|
||||
[`install/llamacpp-install.sh`](install/llamacpp-install.sh) - Line 16-23
|
||||
|
||||
### Change Required
|
||||
```diff
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt-get install -y \
|
||||
curl \
|
||||
wget \
|
||||
ca-certificates \
|
||||
vulkan-tools \
|
||||
libvulkan1 \
|
||||
mesa-vulkan-drivers \
|
||||
pciutils \
|
||||
+ libgomp1
|
||||
msg_ok "Installed Dependencies"
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [GitHub Issue #17491: CPU backend loader does not respect LD_LIBRARY_PATH](https://github.com/ggml-org/llama.cpp/issues/17491)
|
||||
- [GitHub Issue #14691: make_cpu_buft_list: no CPU backend found](https://github.com/ggml-org/llama.cpp/issues/14691)
|
||||
- [GNU OpenMP (libgomp) Documentation](https://gcc.gnu.org/onlinedocs/libgomp/)
|
||||
@@ -0,0 +1,178 @@
|
||||
# llama.cpp Server Script Plan for ProxmoxVE
|
||||
|
||||
## Overview
|
||||
|
||||
This plan outlines the creation of a ProxmoxVE LXC container script for deploying **llama.cpp server** with GPU passthrough and Vulkan backend support. The script will install the latest Vulkan build and configure a default model for immediate use.
|
||||
|
||||
## Reference Links
|
||||
|
||||
- **GitHub Discussion**: https://github.com/community-scripts/ProxmoxVE/discussions/2403
|
||||
- **llama.cpp Repository**: https://github.com/ggml-org/llama.cpp
|
||||
- **llama.cpp Server Documentation**: https://github.com/ggml-org/llama.cpp/tree/master/tools/server
|
||||
- **Vulkan Build Downloads**: https://github.com/ggml-org/llama.cpp/releases
|
||||
|
||||
## Implementation Status
|
||||
|
||||
✅ **COMPLETED** - All files have been created.
|
||||
|
||||
## Files Created
|
||||
|
||||
### 1. Container Script: `ct/llamacpp.sh`
|
||||
|
||||
**Purpose**: Main script for creating the LXC container
|
||||
|
||||
**Key Features**:
|
||||
- Debian 13 base (recommended for latest Vulkan support)
|
||||
- GPU passthrough enabled via `var_gpu="yes"`
|
||||
- 4 CPU cores, 8GB RAM minimum (model requires ~10GB for Q8_0)
|
||||
- 20GB disk (model + binaries + cache)
|
||||
- Auto-detects GPU type (AMD/Intel/NVIDIA) during updates
|
||||
- Downloads appropriate build (Vulkan or CUDA)
|
||||
|
||||
### 2. Installation Script: `install/llamacpp-install.sh`
|
||||
|
||||
**Purpose**: Install llama.cpp with Vulkan support inside the container
|
||||
|
||||
**Key Features**:
|
||||
- Auto-detects GPU vendor (AMD/Intel/NVIDIA)
|
||||
- Downloads latest Vulkan build from GitHub releases
|
||||
- Falls back to Vulkan if CUDA download fails
|
||||
- Creates systemd service with default model
|
||||
- Configures GPU permissions automatically
|
||||
- Creates GPU passthrough documentation file
|
||||
|
||||
### 3. JSON Metadata: `frontend/public/json/llamacpp.json`
|
||||
|
||||
**Purpose**: Frontend metadata for the script catalog
|
||||
|
||||
**Key Features**:
|
||||
- Category: AI/ML (0)
|
||||
- Port: 8080
|
||||
- Updateable: true
|
||||
- Comprehensive notes for GPU passthrough
|
||||
|
||||
### 4. Header File: `ct/headers/llamacpp`
|
||||
|
||||
**Purpose**: ASCII art header for the script
|
||||
|
||||
## Default Configuration
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Model | `unsloth/Qwen3.5-9B-GGUF:Q8_0` |
|
||||
| Port | 8080 |
|
||||
| Context Size | 8192 tokens |
|
||||
| GPU Layers | -1 (all layers on GPU) |
|
||||
| Host | 0.0.0.0 (all interfaces) |
|
||||
|
||||
## GPU Passthrough Configuration
|
||||
|
||||
### AMD GPU Configuration
|
||||
|
||||
Add to `/etc/pve/lxc/<CTID>.conf`:
|
||||
```ini
|
||||
dev0: /dev/kfd,gid=104
|
||||
dev1: /dev/dri/renderD128,gid=104
|
||||
lxc.cgroup2.devices.allow: c 226:0 rwm
|
||||
lxc.cgroup2.devices.allow: c 226:128 rwm
|
||||
```
|
||||
|
||||
### Intel GPU Configuration
|
||||
|
||||
Add to `/etc/pve/lxc/<CTID>.conf`:
|
||||
```ini
|
||||
dev0: /dev/dri/renderD128,gid=104
|
||||
lxc.cgroup2.devices.allow: c 226:128 rwm
|
||||
```
|
||||
|
||||
### NVIDIA GPU Configuration
|
||||
|
||||
For NVIDIA, use the nvidia-container-toolkit approach:
|
||||
```ini
|
||||
lxc.cgroup2.devices.allow: c 195:* rwm
|
||||
lxc.cgroup2.devices.allow: c 509:* rwm
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
The server provides OpenAI-compatible endpoints:
|
||||
|
||||
| Endpoint | Description |
|
||||
|----------|-------------|
|
||||
| `POST /v1/chat/completions` | Chat completion |
|
||||
| `POST /v1/completions` | Text completion |
|
||||
| `POST /v1/embeddings` | Text embeddings |
|
||||
| `GET /v1/models` | List available models |
|
||||
| `GET /health` | Health check |
|
||||
| `GET /` | Built-in Web UI |
|
||||
|
||||
## Resource Requirements
|
||||
|
||||
| Resource | Minimum | Recommended |
|
||||
|----------|---------|-------------|
|
||||
| CPU | 2 cores | 4+ cores |
|
||||
| RAM | 8GB | 16GB+ |
|
||||
| Disk | 20GB | 50GB+ |
|
||||
| GPU | Vulkan 1.1+ | Vulkan 1.3+ |
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Container creation succeeds
|
||||
- [ ] GPU passthrough is detected (`vulkaninfo` works)
|
||||
- [ ] llama.cpp server starts successfully
|
||||
- [ ] Model downloads automatically from HuggingFace
|
||||
- [ ] API responds on port 8080
|
||||
- [ ] Chat completion endpoint works
|
||||
- [ ] Update script functions correctly
|
||||
- [ ] Service restarts after reboot
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Chat Completion (curl)
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/v1/chat/completions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"messages": [{"role": "user", "content": "Hello!"}],
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 100
|
||||
}'
|
||||
```
|
||||
|
||||
### Change Model
|
||||
|
||||
```bash
|
||||
# Edit the service file
|
||||
nano /etc/systemd/system/llamacpp.service
|
||||
|
||||
# Change the -hf parameter
|
||||
# Example: -hf TheBloke/Llama-2-7B-GGUF:Q4_K_M
|
||||
|
||||
# Reload and restart
|
||||
systemctl daemon-reload
|
||||
systemctl restart llamacpp
|
||||
```
|
||||
|
||||
### Check Logs
|
||||
|
||||
```bash
|
||||
journalctl -u llamacpp -f
|
||||
```
|
||||
|
||||
## Files Summary
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `ct/llamacpp.sh` | Container creation script |
|
||||
| `install/llamacpp-install.sh` | Installation script |
|
||||
| `frontend/public/json/llamacpp.json` | Frontend metadata |
|
||||
| `ct/headers/llamacpp` | ASCII art header |
|
||||
| `plans/llamacpp-server-plan.md` | This documentation |
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Test the script on a Proxmox VE 8.x host with GPU passthrough
|
||||
2. Verify GPU detection works for AMD, Intel, and NVIDIA
|
||||
3. Test model download and inference
|
||||
4. Verify update mechanism works correctly
|
||||
@@ -0,0 +1,167 @@
|
||||
# Wakapi ProxmoxVE Script Plan
|
||||
|
||||
## Overview
|
||||
|
||||
This plan outlines the creation of a ProxmoxVE community script for deploying **Wakapi** - a minimalist, self-hosted WakaTime-compatible backend for coding statistics.
|
||||
|
||||
- **Source**: https://github.com/muety/wakapi
|
||||
- **Website**: https://wakapi.dev
|
||||
- **License**: MIT
|
||||
- **Language**: Go
|
||||
- **Default Port**: 3000
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[User runs ct/wakapi.sh] --> B[build.func processes request]
|
||||
B --> C[Container created with Debian 13]
|
||||
C --> D[install/wakapi-install.sh executed]
|
||||
D --> E[Download Wakapi binary from GitHub]
|
||||
E --> F[Create config.yml with SQLite]
|
||||
F --> G[Create systemd service]
|
||||
G --> H[Wakapi running on port 3000]
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
### 1. ct/wakapi.sh
|
||||
|
||||
Main container creation script that:
|
||||
- Defines container resources: 2 CPU, 2GB RAM, 8GB disk
|
||||
- Uses Debian 13 as base OS
|
||||
- Implements update_script function for version updates
|
||||
- Sources build.func from the repository
|
||||
|
||||
**Key Configuration:**
|
||||
```bash
|
||||
APP="Wakapi"
|
||||
var_tags="productivity;analytics"
|
||||
var_cpu="2"
|
||||
var_ram="2048"
|
||||
var_disk="8"
|
||||
var_os="debian"
|
||||
var_version="13"
|
||||
var_unprivileged="1"
|
||||
```
|
||||
|
||||
### 2. install/wakapi-install.sh
|
||||
|
||||
Installation script that:
|
||||
- Downloads the latest Wakapi binary from GitHub releases
|
||||
- Creates necessary directories: /opt/wakapi
|
||||
- Generates configuration file with SQLite backend
|
||||
- Creates systemd service for automatic startup
|
||||
- Generates a secure password salt
|
||||
|
||||
**Installation Steps:**
|
||||
1. Install dependencies: ca-certificates
|
||||
2. Download Wakapi binary using fetch_and_deploy_gh_release
|
||||
3. Create /opt/wakapi directory
|
||||
4. Generate config.yml with:
|
||||
- SQLite database
|
||||
- Random password salt
|
||||
- Proper security settings
|
||||
5. Create systemd service
|
||||
6. Enable and start service
|
||||
|
||||
### 3. ct/headers/wakapi
|
||||
|
||||
ASCII art header for the script output.
|
||||
|
||||
### 4. frontend/public/json/wakapi.json
|
||||
|
||||
Metadata file containing:
|
||||
- Application name and slug
|
||||
- Categories: productivity, analytics
|
||||
- Installation methods
|
||||
- Default credentials info
|
||||
- Documentation links
|
||||
|
||||
## Configuration Details
|
||||
|
||||
### Wakapi Configuration (config.yml)
|
||||
|
||||
```yaml
|
||||
env: production
|
||||
server:
|
||||
port: 3000
|
||||
listen_ipv4: 0.0.0.0
|
||||
public_url: http://localhost:3000
|
||||
|
||||
security:
|
||||
password_salt: <generated>
|
||||
insecure_cookies: true
|
||||
allow_signup: true
|
||||
|
||||
db:
|
||||
dialect: sqlite3
|
||||
name: /opt/wakapi/data/wakapi.db
|
||||
```
|
||||
|
||||
### Systemd Service
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Wakapi - Coding Statistics
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/wakapi
|
||||
ExecStart=/opt/wakapi/wakapi -config /opt/wakapi/config.yml
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
## Update Strategy
|
||||
|
||||
The update_script function will:
|
||||
1. Check current version against GitHub releases
|
||||
2. Stop the Wakapi service
|
||||
3. Backup the config.yml and data directory
|
||||
4. Download new binary
|
||||
5. Restore configuration
|
||||
6. Restart service
|
||||
|
||||
## Resource Requirements
|
||||
|
||||
| Resource | Value | Notes |
|
||||
|----------|-------|-------|
|
||||
| CPU | 2 cores | Sufficient for personal use |
|
||||
| RAM | 2GB | Adequate for SQLite backend |
|
||||
| Disk | 8GB | Stores binary, config, and database |
|
||||
| OS | Debian 13 | Latest stable |
|
||||
|
||||
## Post-Installation
|
||||
|
||||
After installation, users need to:
|
||||
1. Access Wakapi at http://IP:3000
|
||||
2. Create an account (first user becomes admin)
|
||||
3. Configure WakaTime client with:
|
||||
- API URL: http://IP:3000/api
|
||||
- API Key: From user settings
|
||||
|
||||
## Notes for Implementation
|
||||
|
||||
1. **Binary Selection**: Wakapi provides pre-built binaries for Linux amd64/arm64. Use the appropriate binary based on architecture.
|
||||
|
||||
2. **Password Salt**: Generate a random 32-character salt for password hashing.
|
||||
|
||||
3. **Data Persistence**: The SQLite database is stored in /opt/wakapi/data/ which persists across updates.
|
||||
|
||||
4. **No Default Credentials**: Wakapi requires user registration on first access - no default username/password.
|
||||
|
||||
5. **Categories**: Use category IDs from the project - likely productivity and analytics related.
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Create ct/wakapi.sh with update_script function
|
||||
- [ ] Create install/wakapi-install.sh with binary download and config
|
||||
- [ ] Create ct/headers/wakapi ASCII art
|
||||
- [ ] Create frontend/public/json/wakapi.json metadata
|
||||
- [ ] Test the scripts in a Proxmox environment
|
||||
@@ -1,108 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 tteck
|
||||
# Author: tteck (tteckster)
|
||||
# Co-Author: MickLesk (Canbiz)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://netbird.io/ | Github: https://github.com/netbirdio/netbird
|
||||
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
_ __ __ ____ _ __
|
||||
/ | / /__ / /_/ __ )(_)________/ /
|
||||
/ |/ / _ \/ __/ __ / / ___/ __ /
|
||||
/ /| / __/ /_/ /_/ / / / / /_/ /
|
||||
/_/ |_/\___/\__/_____/_/_/ \__,_/
|
||||
|
||||
EOF
|
||||
}
|
||||
header_info
|
||||
set -e
|
||||
|
||||
# Telemetry
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "add-netbird-lxc" "addon"
|
||||
|
||||
while true; do
|
||||
read -p "This will add NetBird to an existing LXC Container ONLY. Proceed(y/n)?" yn
|
||||
case $yn in
|
||||
[Yy]*) break ;;
|
||||
[Nn]*) exit ;;
|
||||
*) echo "Please answer yes or no." ;;
|
||||
esac
|
||||
done
|
||||
header_info
|
||||
echo "Loading..."
|
||||
|
||||
function msg() {
|
||||
local TEXT="$1"
|
||||
echo -e "$TEXT"
|
||||
}
|
||||
|
||||
NODE=$(hostname)
|
||||
MSG_MAX_LENGTH=0
|
||||
while read -r line; do
|
||||
TAG=$(echo "$line" | awk '{print $1}')
|
||||
ITEM=$(echo "$line" | awk '{print substr($0,36)}')
|
||||
OFFSET=2
|
||||
if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then
|
||||
MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))
|
||||
fi
|
||||
CTID_MENU+=("$TAG" "$ITEM " "OFF")
|
||||
done < <(pct list | awk 'NR>1')
|
||||
|
||||
while [ -z "${CTID:+x}" ]; do
|
||||
CTID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Containers on $NODE" --radiolist \
|
||||
"\nSelect a container to add NetBird to:\n" \
|
||||
16 $(($MSG_MAX_LENGTH + 23)) 6 \
|
||||
"${CTID_MENU[@]}" 3>&1 1>&2 2>&3)
|
||||
done
|
||||
|
||||
LXC_STATUS=$(pct status "$CTID" | awk '{print $2}')
|
||||
if [[ "$LXC_STATUS" != "running" ]]; then
|
||||
msg "\e[1;33m The container $CTID is not running. Starting it now...\e[0m"
|
||||
pct start "$CTID"
|
||||
while [[ "$(pct status "$CTID" | awk '{print $2}')" != "running" ]]; do
|
||||
msg "\e[1;33m Waiting for the container to start...\e[0m"
|
||||
sleep 2
|
||||
done
|
||||
msg "\e[1;32m Container $CTID is now running.\e[0m"
|
||||
fi
|
||||
|
||||
DISTRO=$(pct exec "$CTID" -- cat /etc/os-release | grep -w "ID" | cut -d'=' -f2 | tr -d '"')
|
||||
if [[ "$DISTRO" != "debian" && "$DISTRO" != "ubuntu" ]]; then
|
||||
msg "\e[1;31m Error: This script only supports Debian or Ubuntu LXC containers. Detected: $DISTRO. Aborting...\e[0m"
|
||||
exit 238
|
||||
fi
|
||||
|
||||
CTID_CONFIG_PATH=/etc/pve/lxc/${CTID}.conf
|
||||
cat <<EOF >>$CTID_CONFIG_PATH
|
||||
lxc.cgroup2.devices.allow: c 10:200 rwm
|
||||
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file
|
||||
EOF
|
||||
header_info
|
||||
msg "Installing NetBird..."
|
||||
pct exec "$CTID" -- bash -c '
|
||||
if ! command -v curl &>/dev/null; then
|
||||
apt-get update -qq
|
||||
apt-get install -y curl >/dev/null
|
||||
fi
|
||||
apt install -y ca-certificates gpg &>/dev/null
|
||||
curl -fsSL "https://pkgs.netbird.io/debian/public.key" | gpg --dearmor >/usr/share/keyrings/netbird-archive-keyring.gpg
|
||||
echo "deb [signed-by=/usr/share/keyrings/netbird-archive-keyring.gpg] https://pkgs.netbird.io/debian stable main" >/etc/apt/sources.list.d/netbird.list
|
||||
apt-get update &>/dev/null
|
||||
apt-get install -y netbird-ui &>/dev/null
|
||||
if systemctl list-unit-files docker.service &>/dev/null; then
|
||||
mkdir -p /etc/systemd/system/netbird.service.d
|
||||
cat <<OVERRIDE >/etc/systemd/system/netbird.service.d/after-docker.conf
|
||||
[Unit]
|
||||
After=docker.service
|
||||
Wants=docker.service
|
||||
OVERRIDE
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
'
|
||||
msg "\e[1;32m ✔ Installed NetBird.\e[0m"
|
||||
sleep 2
|
||||
msg "\e[1;31m Reboot ${CTID} LXC to apply the changes, then run netbird up in the LXC console\e[0m"
|
||||
@@ -1,171 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 tteck
|
||||
# Author: tteck (tteckster)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://tailscale.com/ | Github: https://github.com/tailscale/tailscale
|
||||
|
||||
set -Eeuo pipefail
|
||||
trap 'echo -e "\n[ERROR] in line $LINENO: exit code $?"' ERR
|
||||
|
||||
function header_info() {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
______ _ __ __
|
||||
/_ __/___ _(_) /_____________ _/ /__
|
||||
/ / / __ `/ / / ___/ ___/ __ `/ / _ \
|
||||
/ / / /_/ / / (__ ) /__/ /_/ / / __/
|
||||
/_/ \__,_/_/_/____/\___/\__,_/_/\___/
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
function msg_info() { echo -e " \e[1;36m➤\e[0m $1"; }
|
||||
function msg_ok() { echo -e " \e[1;32m✔\e[0m $1"; }
|
||||
function msg_error() { echo -e " \e[1;31m✖\e[0m $1"; }
|
||||
|
||||
# Telemetry
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "add-tailscale-lxc" "addon"
|
||||
|
||||
header_info
|
||||
|
||||
if ! command -v pveversion &>/dev/null; then
|
||||
msg_error "This script must be run on the Proxmox VE host (not inside an LXC container)"
|
||||
exit 232
|
||||
fi
|
||||
|
||||
while true; do
|
||||
read -rp "This will add Tailscale to an existing LXC Container ONLY. Proceed (y/n)? " yn
|
||||
case "$yn" in
|
||||
[Yy]*) break ;;
|
||||
[Nn]*) exit 0 ;;
|
||||
*) echo "Please answer yes or no." ;;
|
||||
esac
|
||||
done
|
||||
|
||||
header_info
|
||||
msg_info "Loading container list..."
|
||||
|
||||
NODE=$(hostname)
|
||||
MSG_MAX_LENGTH=0
|
||||
CTID_MENU=()
|
||||
|
||||
while read -r line; do
|
||||
TAG=$(echo "$line" | awk '{print $1}')
|
||||
ITEM=$(echo "$line" | awk '{print substr($0,36)}')
|
||||
OFFSET=2
|
||||
((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=$((${#ITEM} + OFFSET))
|
||||
CTID_MENU+=("$TAG" "$ITEM" "OFF")
|
||||
done < <(pct list | awk 'NR>1')
|
||||
|
||||
CTID=""
|
||||
while [[ -z "${CTID}" ]]; do
|
||||
CTID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Containers on $NODE" --radiolist \
|
||||
"\nSelect a container to add Tailscale to:\n" \
|
||||
16 $((MSG_MAX_LENGTH + 23)) 6 \
|
||||
"${CTID_MENU[@]}" 3>&1 1>&2 2>&3) || exit 0
|
||||
done
|
||||
|
||||
CTID_CONFIG_PATH="/etc/pve/lxc/${CTID}.conf"
|
||||
|
||||
# Skip if already configured
|
||||
grep -q "lxc.cgroup2.devices.allow: c 10:200 rwm" "$CTID_CONFIG_PATH" || echo "lxc.cgroup2.devices.allow: c 10:200 rwm" >>"$CTID_CONFIG_PATH"
|
||||
grep -q "lxc.mount.entry: /dev/net/tun" "$CTID_CONFIG_PATH" || echo "lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file" >>"$CTID_CONFIG_PATH"
|
||||
|
||||
header_info
|
||||
msg_info "Installing Tailscale in CT $CTID"
|
||||
|
||||
pct exec "$CTID" -- sh -c '
|
||||
set -e
|
||||
|
||||
# Detect OS inside container
|
||||
if [ -f /etc/alpine-release ]; then
|
||||
# ── Alpine Linux ──
|
||||
echo "[INFO] Alpine Linux detected, installing Tailscale via apk..."
|
||||
|
||||
# Enable community repo if not already enabled
|
||||
if ! grep -q "^[^#].*community" /etc/apk/repositories 2>/dev/null; then
|
||||
ALPINE_VERSION=$(cat /etc/alpine-release | cut -d. -f1,2)
|
||||
echo "https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VERSION}/community" >> /etc/apk/repositories
|
||||
fi
|
||||
|
||||
apk update
|
||||
apk add --no-cache tailscale
|
||||
|
||||
# Enable and start Tailscale service
|
||||
rc-update add tailscale default 2>/dev/null || true
|
||||
rc-service tailscale start 2>/dev/null || true
|
||||
|
||||
else
|
||||
# ── Debian / Ubuntu ──
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Source os-release properly (handles quoted values)
|
||||
. /etc/os-release
|
||||
|
||||
# Fallback if DNS is poisoned or blocked
|
||||
ORIG_RESOLV="/etc/resolv.conf"
|
||||
BACKUP_RESOLV="/tmp/resolv.conf.backup"
|
||||
|
||||
# Check DNS resolution using multiple methods (dig may not be installed)
|
||||
dns_check_failed=true
|
||||
if command -v dig >/dev/null 2>&1; then
|
||||
if dig +short pkgs.tailscale.com 2>/dev/null | grep -qvE "^127\.|^0\.0\.0\.0$|^$"; then
|
||||
dns_check_failed=false
|
||||
fi
|
||||
elif command -v host >/dev/null 2>&1; then
|
||||
if host pkgs.tailscale.com 2>/dev/null | grep -q "has address"; then
|
||||
dns_check_failed=false
|
||||
fi
|
||||
elif command -v nslookup >/dev/null 2>&1; then
|
||||
if nslookup pkgs.tailscale.com 2>/dev/null | grep -q "Address:"; then
|
||||
dns_check_failed=false
|
||||
fi
|
||||
elif command -v getent >/dev/null 2>&1; then
|
||||
if getent hosts pkgs.tailscale.com >/dev/null 2>&1; then
|
||||
dns_check_failed=false
|
||||
fi
|
||||
else
|
||||
# No DNS tools available, try curl directly and assume DNS works
|
||||
dns_check_failed=false
|
||||
fi
|
||||
|
||||
if $dns_check_failed; then
|
||||
echo "[INFO] DNS resolution for pkgs.tailscale.com failed (blocked or redirected)."
|
||||
echo "[INFO] Temporarily overriding /etc/resolv.conf with Cloudflare DNS (1.1.1.1)"
|
||||
cp "$ORIG_RESOLV" "$BACKUP_RESOLV"
|
||||
echo "nameserver 1.1.1.1" >"$ORIG_RESOLV"
|
||||
fi
|
||||
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
echo "[INFO] curl not found, installing..."
|
||||
apt-get update -qq
|
||||
apt-get install -y curl >/dev/null
|
||||
fi
|
||||
|
||||
# Ensure keyrings directory exists
|
||||
mkdir -p /usr/share/keyrings
|
||||
|
||||
curl -fsSL "https://pkgs.tailscale.com/stable/${ID}/${VERSION_CODENAME}.noarmor.gpg" \
|
||||
| tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
|
||||
|
||||
echo "deb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/${ID} ${VERSION_CODENAME} main" \
|
||||
>/etc/apt/sources.list.d/tailscale.list
|
||||
|
||||
apt-get update -qq
|
||||
apt-get install -y tailscale >/dev/null
|
||||
|
||||
if [ -f /tmp/resolv.conf.backup ]; then
|
||||
echo "[INFO] Restoring original /etc/resolv.conf"
|
||||
mv /tmp/resolv.conf.backup /etc/resolv.conf
|
||||
fi
|
||||
fi
|
||||
'
|
||||
|
||||
TAGS=$(awk -F': ' '/^tags:/ {print $2}' "$CTID_CONFIG_PATH")
|
||||
TAGS="${TAGS:+$TAGS; }tailscale"
|
||||
pct set "$CTID" -tags "$TAGS"
|
||||
|
||||
msg_ok "Tailscale installed on CT $CTID"
|
||||
msg_info "Reboot the container, then run 'tailscale up' inside the container to activate."
|
||||
@@ -1,363 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/bakito/adguardhome-sync
|
||||
|
||||
if ! command -v curl &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||
if [[ -f "/etc/alpine-release" ]]; then
|
||||
apk -U add curl >/dev/null 2>&1
|
||||
else
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
APP="AdGuardHome-Sync"
|
||||
APP_TYPE="addon"
|
||||
INSTALL_PATH="/opt/adguardhome-sync"
|
||||
CONFIG_PATH="/opt/adguardhome-sync/adguardhome-sync.yaml"
|
||||
DEFAULT_PORT=8080
|
||||
|
||||
# Initialize all core functions (colors, formatting, icons, STD mode)
|
||||
load_functions
|
||||
init_tool_telemetry "" "addon"
|
||||
|
||||
# ==============================================================================
|
||||
# HEADER
|
||||
# ==============================================================================
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
___ __ ____ __ _____
|
||||
/ | ____/ /___ ___ ______ __________/ / / / /___ ____ ___ ___ / ___/__ ______ _____
|
||||
/ /| |/ __ / __ `/ / / / __ `/ ___/ __ / /_/ / __ \/ __ `__ \/ _ \ \__ \/ / / / __ \/ ___/
|
||||
/ ___ / /_/ / /_/ / /_/ / /_/ / / / /_/ / __ / /_/ / / / / / / __/ ___/ / /_/ / / / / /__
|
||||
/_/ |_\__,_/\__, /\__,_/\__,_/_/ \__,_/_/ /_/\____/_/ /_/ /_/\___/ /____/\__, /_/ /_/\___/
|
||||
/____/ /____/
|
||||
EOF
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ==============================================================================
|
||||
get_ip() {
|
||||
ifconfig | grep -v '127.0.0.1' | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -m1 -Eo '([0-9]*\.){3}[0-9]*' || echo "127.0.0.1"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# OS DETECTION
|
||||
# ==============================================================================
|
||||
if [[ -f "/etc/alpine-release" ]]; then
|
||||
OS="Alpine"
|
||||
SERVICE_PATH="/etc/init.d/adguardhome-sync"
|
||||
elif [[ -f "/etc/debian_version" ]]; then
|
||||
OS="Debian"
|
||||
SERVICE_PATH="/etc/systemd/system/adguardhome-sync.service"
|
||||
else
|
||||
msg_error "Unsupported OS detected. Exiting."
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# DEPENDENCY CHECK
|
||||
# ==============================================================================
|
||||
if ! command -v jq &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Installing jq \033[m' >&2
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
apk -U add jq >/dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling ${APP}"
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
rc-service adguardhome-sync stop &>/dev/null || true
|
||||
rc-update del adguardhome-sync &>/dev/null || true
|
||||
rm -f "$SERVICE_PATH"
|
||||
else
|
||||
systemctl disable --now adguardhome-sync.service &>/dev/null || true
|
||||
rm -f "$SERVICE_PATH"
|
||||
fi
|
||||
rm -rf "$INSTALL_PATH"
|
||||
rm -f "/usr/local/bin/update_adguardhome-sync"
|
||||
rm -f "$HOME/.adguardhome-sync"
|
||||
msg_ok "${APP} has been uninstalled"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
if check_for_gh_release "adguardhome-sync" "bakito/adguardhome-sync"; then
|
||||
msg_info "Stopping service"
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
rc-service adguardhome-sync stop &>/dev/null || true
|
||||
else
|
||||
systemctl stop adguardhome-sync.service &>/dev/null || true
|
||||
fi
|
||||
msg_ok "Stopped service"
|
||||
|
||||
msg_info "Backing up configuration"
|
||||
cp "$CONFIG_PATH" /tmp/adguardhome-sync.yaml.bak 2>/dev/null || true
|
||||
msg_ok "Backed up configuration"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "adguardhome-sync" "bakito/adguardhome-sync" "prebuild" "latest" "$INSTALL_PATH" "adguardhome-sync_*_linux_amd64.tar.gz"
|
||||
|
||||
msg_info "Restoring configuration"
|
||||
cp /tmp/adguardhome-sync.yaml.bak "$CONFIG_PATH" 2>/dev/null || true
|
||||
rm -f /tmp/adguardhome-sync.yaml.bak
|
||||
msg_ok "Restored configuration"
|
||||
|
||||
msg_info "Starting service"
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
rc-service adguardhome-sync start
|
||||
else
|
||||
systemctl start adguardhome-sync.service
|
||||
fi
|
||||
msg_ok "Started service"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
local ip
|
||||
ip=$(get_ip)
|
||||
|
||||
fetch_and_deploy_gh_release "adguardhome-sync" "bakito/adguardhome-sync" "prebuild" "latest" "$INSTALL_PATH" "adguardhome-sync_*_linux_amd64.tar.gz"
|
||||
|
||||
# Gather configuration from user
|
||||
echo ""
|
||||
echo -e "${TAB}Enter details for your AdGuard Home instances."
|
||||
echo -e "${TAB}The Origin is your primary instance, Replica will sync from it."
|
||||
echo ""
|
||||
|
||||
# Origin instance
|
||||
echo -e "${YW}── Origin (Primary) Instance ──${CL}"
|
||||
local origin_url origin_user origin_pass
|
||||
read -rp " Origin URL (e.g., http://192.168.1.1): " origin_url
|
||||
origin_url="${origin_url:-http://192.168.1.1}"
|
||||
# Add http:// if no protocol specified
|
||||
[[ ! "$origin_url" =~ ^https?:// ]] && origin_url="http://${origin_url}"
|
||||
read -rp " Origin Username [admin]: " origin_user
|
||||
origin_user="${origin_user:-admin}"
|
||||
read -rsp " Origin Password: " origin_pass
|
||||
echo ""
|
||||
origin_pass="${origin_pass:-changeme}"
|
||||
|
||||
# Replica instance
|
||||
echo ""
|
||||
echo -e "${YW}── Replica Instance ──${CL}"
|
||||
local replica_url replica_user replica_pass
|
||||
read -rp " Replica URL (e.g., http://192.168.1.2): " replica_url
|
||||
replica_url="${replica_url:-http://192.168.1.2}"
|
||||
# Add http:// if no protocol specified
|
||||
[[ ! "$replica_url" =~ ^https?:// ]] && replica_url="http://${replica_url}"
|
||||
read -rp " Replica Username [admin]: " replica_user
|
||||
replica_user="${replica_user:-admin}"
|
||||
read -rsp " Replica Password: " replica_pass
|
||||
echo ""
|
||||
replica_pass="${replica_pass:-changeme}"
|
||||
echo ""
|
||||
|
||||
msg_info "Creating configuration"
|
||||
cat <<EOF >"$CONFIG_PATH"
|
||||
# AdGuardHome-Sync Configuration
|
||||
# Documentation: https://github.com/bakito/adguardhome-sync
|
||||
|
||||
# Cron expression for sync interval (e.g., every 2 hours: "0 */2 * * *")
|
||||
cron: "0 */2 * * *"
|
||||
|
||||
# Run sync on startup
|
||||
runOnStart: true
|
||||
|
||||
# Continue sync on errors
|
||||
continueOnError: false
|
||||
|
||||
# Origin AdGuardHome instance (primary)
|
||||
origin:
|
||||
url: "${origin_url}"
|
||||
username: "${origin_user}"
|
||||
password: "${origin_pass}"
|
||||
insecureSkipVerify: false
|
||||
|
||||
# Replica instances (one or more)
|
||||
replicas:
|
||||
- url: "${replica_url}"
|
||||
username: "${replica_user}"
|
||||
password: "${replica_pass}"
|
||||
insecureSkipVerify: false
|
||||
# Add more replicas as needed:
|
||||
# - url: "http://192.168.1.3"
|
||||
# username: "admin"
|
||||
# password: "changeme"
|
||||
|
||||
# API settings (web UI)
|
||||
api:
|
||||
port: ${DEFAULT_PORT}
|
||||
darkMode: true
|
||||
metrics:
|
||||
enabled: false
|
||||
|
||||
# Sync features (all enabled by default)
|
||||
features:
|
||||
dns:
|
||||
accessLists: true
|
||||
serverConfig: true
|
||||
rewrites: true
|
||||
dhcp:
|
||||
serverConfig: true
|
||||
staticLeases: true
|
||||
generalSettings: true
|
||||
queryLogConfig: true
|
||||
statsConfig: true
|
||||
clientSettings: true
|
||||
services: true
|
||||
filters: true
|
||||
theme: true
|
||||
EOF
|
||||
chmod 600 "$CONFIG_PATH"
|
||||
msg_ok "Created configuration"
|
||||
|
||||
msg_info "Creating service"
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
cat <<EOF >"$SERVICE_PATH"
|
||||
#!/sbin/openrc-run
|
||||
|
||||
name="adguardhome-sync"
|
||||
description="AdGuardHome Sync"
|
||||
command="${INSTALL_PATH}/adguardhome-sync"
|
||||
command_args="run --config ${CONFIG_PATH}"
|
||||
command_background=true
|
||||
pidfile="/run/\${RC_SVCNAME}.pid"
|
||||
output_log="/var/log/adguardhome-sync.log"
|
||||
error_log="/var/log/adguardhome-sync.log"
|
||||
|
||||
depend() {
|
||||
need net
|
||||
after firewall
|
||||
}
|
||||
EOF
|
||||
chmod +x "$SERVICE_PATH"
|
||||
rc-update add adguardhome-sync default
|
||||
rc-service adguardhome-sync start
|
||||
else
|
||||
cat <<EOF >"$SERVICE_PATH"
|
||||
[Unit]
|
||||
Description=AdGuardHome Sync
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=${INSTALL_PATH}/adguardhome-sync run --config ${CONFIG_PATH}
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now adguardhome-sync &>/dev/null
|
||||
fi
|
||||
msg_ok "Created and started service"
|
||||
|
||||
# Create update script
|
||||
msg_info "Creating update script"
|
||||
cat <<'UPDATEEOF' >/usr/local/bin/update_adguardhome-sync
|
||||
#!/usr/bin/env bash
|
||||
# AdGuardHome-Sync Update Script
|
||||
type=update bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/adguardhome-sync.sh)"
|
||||
UPDATEEOF
|
||||
chmod +x /usr/local/bin/update_adguardhome-sync
|
||||
msg_ok "Created update script (/usr/local/bin/update_adguardhome-sync)"
|
||||
|
||||
echo ""
|
||||
msg_ok "${APP} installed successfully"
|
||||
msg_ok "Web UI: ${BL}http://${ip}:${DEFAULT_PORT}${CL}"
|
||||
msg_ok "Config: ${BL}${CONFIG_PATH}${CL}"
|
||||
echo ""
|
||||
msg_warn "Edit the config file to add your AdGuardHome instances!"
|
||||
msg_warn " Origin: Your primary AdGuardHome instance"
|
||||
msg_warn " Replicas: One or more replica instances to sync to"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
header_info
|
||||
if [[ -d "$INSTALL_PATH" && -f "$INSTALL_PATH/adguardhome-sync" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
header_info
|
||||
|
||||
IP=$(get_ip)
|
||||
|
||||
# Check if already installed
|
||||
if [[ -d "$INSTALL_PATH" && -f "$INSTALL_PATH/adguardhome-sync" ]]; then
|
||||
msg_warn "${APP} is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall ${APP}? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update ${APP}? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "${APP} is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - AdGuardHome-Sync (Go binary)"
|
||||
echo -e "${TAB} - Systemd/OpenRC service"
|
||||
echo -e "${TAB} - Web UI on port ${DEFAULT_PORT}"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install ${APP}? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,245 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) 2021-2026 tteck
|
||||
# Author: tteck (tteckster)
|
||||
# License: MIT
|
||||
# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
___ ____ ______ __ __
|
||||
/ _ | / / / /_ __/__ __ _ ___ / /__ _/ /____ ___
|
||||
/ __ |/ / / / / / -_) ' \/ _ \/ / _ `/ __/ -_|_-<
|
||||
/_/ |_/_/_/ /_/ \__/_/_/_/ .__/_/\_,_/\__/\__/___/
|
||||
/_/
|
||||
EOF
|
||||
}
|
||||
|
||||
set -eEuo pipefail
|
||||
shopt -s expand_aliases
|
||||
alias die='EXIT=$? LINE=$LINENO error_exit'
|
||||
trap die ERR
|
||||
function error_exit() {
|
||||
trap - ERR
|
||||
local DEFAULT='Unknown failure occured.'
|
||||
local REASON="\e[97m${1:-$DEFAULT}\e[39m"
|
||||
local FLAG="\e[91m[ERROR] \e[93m$EXIT@$LINE"
|
||||
msg "$FLAG $REASON" 1>&2
|
||||
[ ! -z ${CTID-} ] && cleanup_ctid
|
||||
exit $EXIT
|
||||
}
|
||||
function warn() {
|
||||
local REASON="\e[97m$1\e[39m"
|
||||
local FLAG="\e[93m[WARNING]\e[39m"
|
||||
msg "$FLAG $REASON"
|
||||
}
|
||||
function info() {
|
||||
local REASON="$1"
|
||||
local FLAG="\e[36m[INFO]\e[39m"
|
||||
msg "$FLAG $REASON"
|
||||
}
|
||||
function msg() {
|
||||
local TEXT="$1"
|
||||
echo -e "$TEXT"
|
||||
}
|
||||
|
||||
# Telemetry
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "all-templates" "addon"
|
||||
|
||||
function validate_container_id() {
|
||||
local ctid="$1"
|
||||
# Check if ID is numeric
|
||||
if ! [[ "$ctid" =~ ^[0-9]+$ ]]; then
|
||||
return 1
|
||||
fi
|
||||
# Check if config file exists for VM or LXC
|
||||
if [[ -f "/etc/pve/qemu-server/${ctid}.conf" ]] || [[ -f "/etc/pve/lxc/${ctid}.conf" ]]; then
|
||||
return 1
|
||||
fi
|
||||
# Check if ID is used in LVM logical volumes
|
||||
if lvs --noheadings -o lv_name 2>/dev/null | grep -qE "(^|[-_])${ctid}($|[-_])"; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
function get_valid_container_id() {
|
||||
local suggested_id="${1:-$(pvesh get /cluster/nextid)}"
|
||||
while ! validate_container_id "$suggested_id"; do
|
||||
suggested_id=$((suggested_id + 1))
|
||||
done
|
||||
echo "$suggested_id"
|
||||
}
|
||||
function cleanup_ctid() {
|
||||
if pct status $CTID &>/dev/null; then
|
||||
if [ "$(pct status $CTID | awk '{print $2}')" == "running" ]; then
|
||||
pct stop $CTID
|
||||
fi
|
||||
pct destroy $CTID
|
||||
fi
|
||||
}
|
||||
|
||||
# Stop Proxmox VE Monitor-All if running
|
||||
if systemctl is-active -q ping-instances.service; then
|
||||
systemctl stop ping-instances.service
|
||||
fi
|
||||
header_info
|
||||
echo "Loading..."
|
||||
pveam update >/dev/null 2>&1
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "All Templates" --yesno "This will allow for the creation of one of the many Template LXC Containers. Proceed?" 10 68
|
||||
TEMPLATE_MENU=()
|
||||
MSG_MAX_LENGTH=0
|
||||
while read -r TAG ITEM; do
|
||||
OFFSET=2
|
||||
((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=${#ITEM}+OFFSET
|
||||
TEMPLATE_MENU+=("$ITEM" "$TAG " "OFF")
|
||||
done < <(pveam available)
|
||||
TEMPLATE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "All Template LXCs" --radiolist "\nSelect a Template LXC to create:\n" 16 $((MSG_MAX_LENGTH + 58)) 10 "${TEMPLATE_MENU[@]}" 3>&1 1>&2 2>&3 | tr -d '"')
|
||||
[ -z "$TEMPLATE" ] && {
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "No Template LXC Selected" --msgbox "It appears that no Template LXC container was selected" 10 68
|
||||
msg "Done"
|
||||
exit
|
||||
}
|
||||
|
||||
# Setup script environment
|
||||
NAME=$(echo "$TEMPLATE" | grep -oE '^[^-]+-[^-]+')
|
||||
PASS="$(openssl rand -base64 8)"
|
||||
|
||||
# Get valid Container ID
|
||||
CTID=$(pvesh get /cluster/nextid)
|
||||
if ! validate_container_id "$CTID"; then
|
||||
warn "Container ID $CTID is already in use."
|
||||
CTID=$(get_valid_container_id "$CTID")
|
||||
info "Using next available ID: $CTID"
|
||||
fi
|
||||
|
||||
PCT_OPTIONS="
|
||||
-features keyctl=1,nesting=1
|
||||
-hostname $NAME
|
||||
-tags proxmox-helper-scripts
|
||||
-onboot 0
|
||||
-cores 2
|
||||
-memory 2048
|
||||
-password $PASS
|
||||
-net0 name=eth0,bridge=vmbr0,ip=dhcp
|
||||
-unprivileged 1
|
||||
"
|
||||
DEFAULT_PCT_OPTIONS=(
|
||||
-arch $(dpkg --print-architecture)
|
||||
)
|
||||
|
||||
# Set the CONTENT and CONTENT_LABEL variables
|
||||
function select_storage() {
|
||||
local CLASS=$1
|
||||
local CONTENT
|
||||
local CONTENT_LABEL
|
||||
case $CLASS in
|
||||
container)
|
||||
CONTENT='rootdir'
|
||||
CONTENT_LABEL='Container'
|
||||
;;
|
||||
template)
|
||||
CONTENT='vztmpl'
|
||||
CONTENT_LABEL='Container template'
|
||||
;;
|
||||
*) false || die "Invalid storage class." ;;
|
||||
esac
|
||||
|
||||
# Query all storage locations
|
||||
local -a MENU
|
||||
while read -r line; do
|
||||
local TAG=$(echo $line | awk '{print $1}')
|
||||
local TYPE=$(echo $line | awk '{printf "%-10s", $2}')
|
||||
local FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( "%9sB", $6)}')
|
||||
local ITEM=" Type: $TYPE Free: $FREE "
|
||||
local OFFSET=2
|
||||
if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then
|
||||
local MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))
|
||||
fi
|
||||
MENU+=("$TAG" "$ITEM" "OFF")
|
||||
done < <(pvesm status -content $CONTENT | awk 'NR>1')
|
||||
|
||||
# Select storage location
|
||||
if [ $((${#MENU[@]} / 3)) -eq 0 ]; then
|
||||
warn "'$CONTENT_LABEL' needs to be selected for at least one storage location."
|
||||
die "Unable to detect valid storage location."
|
||||
elif [ $((${#MENU[@]} / 3)) -eq 1 ]; then
|
||||
printf ${MENU[0]}
|
||||
else
|
||||
local STORAGE
|
||||
while [ -z "${STORAGE:+x}" ]; do
|
||||
STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \
|
||||
"Which storage pool would you like to use for the ${CONTENT_LABEL,,}?\n\n" \
|
||||
16 $(($MSG_MAX_LENGTH + 23)) 6 \
|
||||
"${MENU[@]}" 3>&1 1>&2 2>&3) || die "Menu aborted."
|
||||
done
|
||||
printf $STORAGE
|
||||
fi
|
||||
}
|
||||
header_info
|
||||
# Get template storage
|
||||
TEMPLATE_STORAGE=$(select_storage template)
|
||||
info "Using '$TEMPLATE_STORAGE' for template storage."
|
||||
|
||||
# Get container storage
|
||||
CONTAINER_STORAGE=$(select_storage container)
|
||||
info "Using '$CONTAINER_STORAGE' for container storage."
|
||||
|
||||
# Download template
|
||||
msg "Downloading LXC template (Patience)..."
|
||||
pveam download $TEMPLATE_STORAGE $TEMPLATE >/dev/null || die "A problem occured while downloading the LXC template."
|
||||
|
||||
# Create variable for 'pct' options
|
||||
PCT_OPTIONS=(${PCT_OPTIONS[@]:-${DEFAULT_PCT_OPTIONS[@]}})
|
||||
[[ " ${PCT_OPTIONS[@]} " =~ " -rootfs " ]] || PCT_OPTIONS+=(-rootfs $CONTAINER_STORAGE:${PCT_DISK_SIZE:-8})
|
||||
|
||||
# Create LXC
|
||||
msg "Creating LXC container..."
|
||||
pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} ${PCT_OPTIONS[@]} >/dev/null ||
|
||||
die "A problem occured while trying to create container."
|
||||
|
||||
# Save password
|
||||
echo "$NAME password: ${PASS}" >>~/$NAME.creds # file is located in the Proxmox root directory
|
||||
|
||||
# Start container
|
||||
msg "Starting LXC Container..."
|
||||
pct start "$CTID"
|
||||
sleep 5
|
||||
|
||||
# Get container IP
|
||||
set +eEuo pipefail
|
||||
max_attempts=5
|
||||
attempt=1
|
||||
IP=""
|
||||
while [[ $attempt -le $max_attempts ]]; do
|
||||
IP=$(pct exec $CTID ip a show dev eth0 | grep -oP 'inet \K[^/]+')
|
||||
if [[ -n $IP ]]; then
|
||||
break
|
||||
else
|
||||
warn "Attempt $attempt: IP address not found. Pausing for 5 seconds..."
|
||||
sleep 5
|
||||
((attempt++))
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z $IP ]]; then
|
||||
warn "Maximum number of attempts reached. IP address not found."
|
||||
IP="NOT FOUND"
|
||||
fi
|
||||
|
||||
set -eEuo pipefail
|
||||
# Start Proxmox VE Monitor-All if available
|
||||
if [[ -f /etc/systemd/system/ping-instances.service ]]; then
|
||||
systemctl start ping-instances.service
|
||||
fi
|
||||
|
||||
# Success message
|
||||
header_info
|
||||
echo
|
||||
info "LXC container '$CTID' was successfully created, and its IP address is ${IP}."
|
||||
echo
|
||||
info "Proceed to the LXC console to complete the setup."
|
||||
echo
|
||||
info "login: root"
|
||||
info "password: $PASS"
|
||||
echo
|
||||
@@ -1,219 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: summoningpixels
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/getarcaneapp/arcane
|
||||
if ! command -v curl &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
APP="Arcane"
|
||||
APP_TYPE="addon"
|
||||
INSTALL_PATH="/opt/arcane"
|
||||
COMPOSE_FILE="${INSTALL_PATH}/compose.yaml"
|
||||
ENV_FILE="${INSTALL_PATH}/.env"
|
||||
DEFAULT_PORT=3552
|
||||
|
||||
# Initialize all core functions (colors, formatting, icons, STD mode)
|
||||
load_functions
|
||||
|
||||
# ==============================================================================
|
||||
# HEADER
|
||||
# ==============================================================================
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
___ ____ _________ _ ________
|
||||
/ | / __ \/ ____/ | / | / / ____/
|
||||
/ /| | / /_/ / / / /| | / |/ / __/
|
||||
/ ___ |/ _, _/ /___/ ___ |/ /| / /___
|
||||
/_/ |_/_/ |_|\____/_/ |_/_/ |_/_____/
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling ${APP}"
|
||||
|
||||
if [[ -f "$COMPOSE_FILE" ]]; then
|
||||
msg_info "Stopping and removing Docker containers"
|
||||
cd "$INSTALL_PATH"
|
||||
$STD docker compose down --volumes --remove-orphans
|
||||
msg_ok "Stopped and removed Docker containers"
|
||||
fi
|
||||
|
||||
rm -rf "$INSTALL_PATH"
|
||||
rm -f "/usr/local/bin/update_arcane"
|
||||
msg_ok "${APP} has been uninstalled"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
msg_info "Pulling latest ${APP} image"
|
||||
cd "$INSTALL_PATH"
|
||||
$STD docker compose pull
|
||||
msg_ok "Pulled latest image"
|
||||
|
||||
msg_info "Restarting ${APP}"
|
||||
$STD docker compose up -d --remove-orphans
|
||||
msg_ok "Restarted ${APP}"
|
||||
|
||||
msg_ok "Updated successfully"
|
||||
exit
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# CHECK DOCKER
|
||||
# ==============================================================================
|
||||
function check_docker() {
|
||||
if ! command -v docker &>/dev/null; then
|
||||
msg_error "Docker is not installed. This script requires an existing Docker LXC. Exiting."
|
||||
exit 10
|
||||
fi
|
||||
if ! docker compose version &>/dev/null; then
|
||||
msg_error "Docker Compose plugin is not available. Please install it before running this script. Exiting."
|
||||
exit 10
|
||||
fi
|
||||
msg_ok "Docker $(docker --version | cut -d' ' -f3 | tr -d ',') and Docker Compose are available"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
check_docker
|
||||
|
||||
msg_info "Creating install directory"
|
||||
mkdir -p "$INSTALL_PATH"
|
||||
msg_ok "Created ${INSTALL_PATH}"
|
||||
|
||||
# Generate secrets and config values
|
||||
local ENCRYPTION_KEY JWT_SECRET PROJ_DIR
|
||||
ENCRYPTION_KEY=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c32)
|
||||
JWT_SECRET=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c32)
|
||||
PROJ_DIR="/etc/arcane/projects"
|
||||
|
||||
msg_info "Creating stacks directory"
|
||||
mkdir -p "$PROJ_DIR"
|
||||
msg_ok "Created ${PROJ_DIR}"
|
||||
|
||||
msg_info "Downloading Docker Compose file"
|
||||
curl -fsSL "https://raw.githubusercontent.com/getarcaneapp/arcane/refs/heads/main/docker/examples/compose.basic.yaml" -o "$COMPOSE_FILE"
|
||||
msg_ok "Downloaded Docker Compose file"
|
||||
|
||||
msg_info "Downloading .env file"
|
||||
curl -fsSL "https://raw.githubusercontent.com/getarcaneapp/arcane/refs/heads/main/.env.example" -o "$ENV_FILE"
|
||||
chmod 600 "$ENV_FILE"
|
||||
msg_ok "Downloaded .env file"
|
||||
|
||||
msg_info "Configuring compose and env files"
|
||||
sed -i '/^[[:space:]]*#/!s|/host/path/to/projects|'"$PROJ_DIR"'|g' "$COMPOSE_FILE"
|
||||
sed -i '/^[[:space:]]*#/!s|ENCRYPTION_KEY=.*|ENCRYPTION_KEY='"$ENCRYPTION_KEY"'|g' "$COMPOSE_FILE"
|
||||
sed -i '/^[[:space:]]*#/!s|JWT_SECRET=.*|JWT_SECRET='"$JWT_SECRET"'|g' "$COMPOSE_FILE"
|
||||
sed -i '/^[[:space:]]*#/!s|APP_URL=.*|APP_URL=http://localhost:'"$DEFAULT_PORT"'|g' "$ENV_FILE"
|
||||
sed -i '/^[[:space:]]*#/!s|ENCRYPTION_KEY=.*|#&|g' "$ENV_FILE"
|
||||
sed -i '/^[[:space:]]*#/!s|JWT_SECRET=.*|#&|g' "$ENV_FILE"
|
||||
msg_ok "Configured compose and env files"
|
||||
|
||||
msg_info "Starting ${APP}"
|
||||
cd "$INSTALL_PATH"
|
||||
$STD docker compose up -d
|
||||
msg_ok "Started ${APP}"
|
||||
|
||||
# Create update script
|
||||
msg_info "Creating update script"
|
||||
cat <<'UPDATEEOF' >/usr/local/bin/update_arcane
|
||||
#!/usr/bin/env bash
|
||||
# Arcane Update Script
|
||||
type=update bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/arcane.sh)"
|
||||
UPDATEEOF
|
||||
chmod +x /usr/local/bin/update_arcane
|
||||
msg_ok "Created update script (/usr/local/bin/update_arcane)"
|
||||
|
||||
echo ""
|
||||
msg_ok "${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}"
|
||||
echo ""
|
||||
echo -e "Arcane Credentials"
|
||||
echo -e "=================="
|
||||
echo -e "User: arcane"
|
||||
echo -e "Password: arcane-admin"
|
||||
echo ""
|
||||
msg_warn "On first access, you'll be prompted to change your password."
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
header_info
|
||||
if [[ -f "$COMPOSE_FILE" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
header_info
|
||||
get_lxc_ip
|
||||
|
||||
# Check if already installed
|
||||
if [[ -f "$COMPOSE_FILE" ]]; then
|
||||
msg_warn "${APP} is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall ${APP}? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update ${APP}? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "${APP} is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - Arcane (via Docker Compose)"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install ${APP}? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,215 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://coolify.io/ | Github: https://github.com/coollabsio/coolify
|
||||
if ! command -v curl &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||
if [[ -f /etc/alpine-release ]]; then
|
||||
apk update >/dev/null 2>&1
|
||||
apk add --no-cache curl >/dev/null 2>&1
|
||||
else
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
APP="Coolify"
|
||||
APP_TYPE="addon"
|
||||
INSTALL_PATH="/data/coolify"
|
||||
DEFAULT_PORT=8000
|
||||
|
||||
# Initialize all core functions (colors, formatting, icons, STD mode)
|
||||
load_functions
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling ${APP}"
|
||||
|
||||
if command -v docker &>/dev/null; then
|
||||
msg_info "Stopping and removing Docker containers"
|
||||
cd /data/coolify/source 2>/dev/null && docker compose down --remove-orphans 2>/dev/null || true
|
||||
$STD docker stop $(docker ps -aq) 2>/dev/null || true
|
||||
$STD docker rm $(docker ps -aq) 2>/dev/null || true
|
||||
$STD docker network prune -f 2>/dev/null || true
|
||||
msg_ok "Stopped and removed Docker containers"
|
||||
fi
|
||||
|
||||
rm -rf "$INSTALL_PATH"
|
||||
msg_ok "${APP} has been uninstalled"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
msg_info "Updating ${APP}"
|
||||
$STD bash <(curl -fsSL https://cdn.coollabs.io/coolify/install.sh)
|
||||
msg_ok "Updated ${APP}"
|
||||
|
||||
msg_ok "Updated successfully"
|
||||
exit
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# PROXMOX HOST CHECK
|
||||
# ==============================================================================
|
||||
function check_proxmox_host() {
|
||||
if command -v pveversion &>/dev/null; then
|
||||
msg_error "Running on the Proxmox host is NOT recommended!"
|
||||
msg_error "This should be executed inside an LXC container."
|
||||
echo ""
|
||||
echo -n "${TAB}Continue anyway? (y/N): "
|
||||
read -r confirm
|
||||
if [[ ! "${confirm,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_warn "Aborted. Please run this inside an LXC container."
|
||||
exit 0
|
||||
fi
|
||||
msg_warn "Proceeding on Proxmox host at your own risk!"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# CHECK / INSTALL DOCKER
|
||||
# ==============================================================================
|
||||
function check_or_install_docker() {
|
||||
if command -v docker &>/dev/null; then
|
||||
msg_ok "Docker $(docker --version | cut -d' ' -f3 | tr -d ',') is available"
|
||||
if docker compose version &>/dev/null; then
|
||||
msg_ok "Docker Compose is available"
|
||||
else
|
||||
msg_error "Docker Compose plugin is not available. Please install it."
|
||||
exit 10
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
msg_warn "Docker is not installed."
|
||||
echo -n "${TAB}Install Docker now? (y/N): "
|
||||
read -r install_docker_prompt
|
||||
if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_error "Docker is required for ${APP}. Exiting."
|
||||
exit 10
|
||||
fi
|
||||
|
||||
msg_info "Installing Docker"
|
||||
if [[ -f /etc/alpine-release ]]; then
|
||||
$STD apk add docker docker-cli-compose
|
||||
$STD rc-service docker start
|
||||
$STD rc-update add docker default
|
||||
else
|
||||
DOCKER_CONFIG_PATH='/etc/docker/daemon.json'
|
||||
mkdir -p "$(dirname "$DOCKER_CONFIG_PATH")"
|
||||
echo -e '{\n "log-driver": "journald"\n}' >"$DOCKER_CONFIG_PATH"
|
||||
$STD sh <(curl -fsSL https://get.docker.com)
|
||||
fi
|
||||
msg_ok "Installed Docker"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
check_or_install_docker
|
||||
|
||||
msg_info "Installing dependencies"
|
||||
if [[ -f /etc/alpine-release ]]; then
|
||||
$STD apk add --no-cache git openssl
|
||||
else
|
||||
$STD apt-get update
|
||||
$STD apt-get install -y git openssl
|
||||
fi
|
||||
msg_ok "Installed dependencies"
|
||||
|
||||
msg_warn "WARNING: This will run an external installer from https://coolify.io/"
|
||||
msg_warn "The following code is NOT maintained or audited by our repository."
|
||||
msg_warn "Review: https://cdn.coollabs.io/coolify/install.sh"
|
||||
echo ""
|
||||
echo -n "${TAB}Do you want to continue? (y/N): "
|
||||
read -r confirm
|
||||
if [[ ! "${confirm,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_info "Installing ${APP} (this installs Docker and pulls containers)"
|
||||
$STD bash <(curl -fsSL https://cdn.coollabs.io/coolify/install.sh)
|
||||
msg_ok "Installed ${APP}"
|
||||
|
||||
echo ""
|
||||
msg_ok "${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
header_info
|
||||
if [[ -d "$INSTALL_PATH" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
header_info
|
||||
check_proxmox_host
|
||||
get_lxc_ip
|
||||
|
||||
# Check if already installed
|
||||
if [[ -d "$INSTALL_PATH" ]]; then
|
||||
msg_warn "${APP} is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall ${APP}? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update ${APP}? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "${APP} is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - Coolify (via external installer)"
|
||||
echo -e "${TAB} - Docker (if not already installed)"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install ${APP}? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,365 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/9001/copyparty
|
||||
|
||||
if ! command -v curl &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
load_functions
|
||||
init_tool_telemetry "" "addon"
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
VERBOSE=${var_verbose:-no}
|
||||
APP="CopyParty"
|
||||
APP_TYPE="addon"
|
||||
BIN_PATH="/usr/local/bin/copyparty-sfx.py"
|
||||
CONF_PATH="/etc/copyparty.conf"
|
||||
LOG_PATH="/var/log/copyparty"
|
||||
DATA_PATH="/var/lib/copyparty"
|
||||
SVC_USER="copyparty"
|
||||
SVC_GROUP="copyparty"
|
||||
SRC_URL="https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py"
|
||||
DEFAULT_PORT=3923
|
||||
|
||||
# ==============================================================================
|
||||
# OS DETECTION
|
||||
# ==============================================================================
|
||||
if [[ -f "/etc/alpine-release" ]]; then
|
||||
OS="Alpine"
|
||||
PKG_MANAGER="apk add --no-cache"
|
||||
SERVICE_PATH="/etc/init.d/copyparty"
|
||||
elif grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then
|
||||
OS="Debian"
|
||||
PKG_MANAGER="apt-get install -y"
|
||||
SERVICE_PATH="/etc/systemd/system/copyparty.service"
|
||||
else
|
||||
msg_error "Unsupported OS detected. Exiting."
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# HEADER
|
||||
# ==============================================================================
|
||||
function header_info() {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
______ ____ __
|
||||
/ ____/___ ____ __ __/ __ \____ ______/ /___ __
|
||||
/ / / __ \/ __ \/ / / / /_/ / __ `/ ___/ __/ / / /
|
||||
/ /___/ /_/ / /_/ / /_/ / ____/ /_/ / / / /_/ /_/ /
|
||||
\____/\____/ .___/\__, /_/ \__,_/_/ \__/\__, /
|
||||
/_/ /____/ /____/
|
||||
EOF
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ==============================================================================
|
||||
|
||||
# ==============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ==============================================================================
|
||||
function setup_user_and_dirs() {
|
||||
msg_info "Creating $SVC_USER user and directories"
|
||||
if ! id "$SVC_USER" &>/dev/null; then
|
||||
if [[ "$OS" == "Debian" ]]; then
|
||||
useradd -r -s /sbin/nologin -d "$DATA_PATH" "$SVC_USER"
|
||||
else
|
||||
addgroup -S "$SVC_GROUP" 2>/dev/null || true
|
||||
adduser -S -D -H -G "$SVC_GROUP" -h "$DATA_PATH" -s /sbin/nologin "$SVC_USER" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
mkdir -p "$DATA_PATH" "$LOG_PATH"
|
||||
chown -R "$SVC_USER:$SVC_GROUP" "$DATA_PATH" "$LOG_PATH"
|
||||
chmod 755 "$DATA_PATH" "$LOG_PATH"
|
||||
msg_ok "User/Group/Dirs ready"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling ${APP}"
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
rc-service copyparty stop &>/dev/null || true
|
||||
rc-update del copyparty &>/dev/null || true
|
||||
rm -f "$SERVICE_PATH"
|
||||
else
|
||||
systemctl disable --now copyparty.service &>/dev/null || true
|
||||
rm -f "$SERVICE_PATH"
|
||||
fi
|
||||
rm -f "$BIN_PATH" "$CONF_PATH"
|
||||
rm -rf "$DATA_PATH" "$LOG_PATH"
|
||||
userdel "$SVC_USER" 2>/dev/null || true
|
||||
groupdel "$SVC_GROUP" 2>/dev/null || true
|
||||
rm -f "/usr/local/bin/update_copyparty"
|
||||
rm -f "$HOME/.copyparty"
|
||||
msg_ok "${APP} has been uninstalled"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
if check_for_gh_release "copyparty-sfx.py" "9001/copyparty"; then
|
||||
msg_info "Stopping service"
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
rc-service copyparty stop &>/dev/null || true
|
||||
else
|
||||
systemctl stop copyparty.service &>/dev/null || true
|
||||
fi
|
||||
msg_ok "Stopped service"
|
||||
|
||||
msg_info "Updating ${APP}"
|
||||
curl -fsSL "$SRC_URL" -o "$BIN_PATH"
|
||||
chmod +x "$BIN_PATH"
|
||||
chown "$SVC_USER:$SVC_GROUP" "$BIN_PATH"
|
||||
msg_ok "Updated ${APP}"
|
||||
|
||||
msg_info "Starting service"
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
rc-service copyparty start
|
||||
else
|
||||
systemctl start copyparty.service
|
||||
fi
|
||||
msg_ok "Started service"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
local port data_path enable_auth admin_user admin_pass
|
||||
|
||||
echo ""
|
||||
read -rp "${TAB}Enter port for ${APP} [${DEFAULT_PORT}]: " port
|
||||
port=${port:-$DEFAULT_PORT}
|
||||
|
||||
read -rp "${TAB}Set data directory [${DATA_PATH}]: " data_path
|
||||
data_path=${data_path:-$DATA_PATH}
|
||||
|
||||
echo -n "${TAB}Enable authentication? (Y/n): "
|
||||
read -r enable_auth
|
||||
if [[ "${enable_auth,,}" =~ ^(n|no)$ ]]; then
|
||||
admin_user=""
|
||||
admin_pass=""
|
||||
msg_ok "Configured without authentication"
|
||||
else
|
||||
read -rp "${TAB}Set admin username [admin]: " admin_user
|
||||
admin_user=${admin_user:-admin}
|
||||
read -rsp "${TAB}Set admin password [helper-scripts.com]: " admin_pass
|
||||
echo ""
|
||||
admin_pass=${admin_pass:-helper-scripts.com}
|
||||
msg_ok "Configured with admin user: ${admin_user}"
|
||||
fi
|
||||
|
||||
msg_info "Installing dependencies"
|
||||
if [[ "$OS" == "Debian" ]]; then
|
||||
$STD $PKG_MANAGER python3 python3-pil ffmpeg curl
|
||||
else
|
||||
$STD $PKG_MANAGER python3 py3-pillow ffmpeg curl
|
||||
fi
|
||||
msg_ok "Dependencies installed (with thumbnail support)"
|
||||
|
||||
setup_user_and_dirs
|
||||
|
||||
# Use data_path if provided
|
||||
if [[ "$data_path" != "$DATA_PATH" ]]; then
|
||||
DATA_PATH="$data_path"
|
||||
mkdir -p "$DATA_PATH"
|
||||
chown "$SVC_USER:$SVC_GROUP" "$DATA_PATH"
|
||||
fi
|
||||
|
||||
msg_info "Downloading ${APP}"
|
||||
curl -fsSL "$SRC_URL" -o "$BIN_PATH"
|
||||
chmod +x "$BIN_PATH"
|
||||
chown "$SVC_USER:$SVC_GROUP" "$BIN_PATH"
|
||||
msg_ok "Downloaded to ${BIN_PATH}"
|
||||
|
||||
msg_info "Creating configuration"
|
||||
cat <<EOF >"$CONF_PATH"
|
||||
[global]
|
||||
p: ${port}
|
||||
ansi
|
||||
e2dsa
|
||||
e2ts
|
||||
theme: 2
|
||||
grid
|
||||
no-robots
|
||||
force-js
|
||||
lo: ${LOG_PATH}/cpp-%Y-%m%d.txt.xz
|
||||
|
||||
EOF
|
||||
|
||||
if [[ -n "$admin_user" && -n "$admin_pass" ]]; then
|
||||
cat <<EOF >>"$CONF_PATH"
|
||||
[accounts]
|
||||
${admin_user}: ${admin_pass}
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat <<EOF >>"$CONF_PATH"
|
||||
[/]
|
||||
${DATA_PATH}
|
||||
accs:
|
||||
EOF
|
||||
|
||||
if [[ -n "$admin_user" ]]; then
|
||||
cat <<EOF >>"$CONF_PATH"
|
||||
rw: *
|
||||
rwmda: ${admin_user}
|
||||
EOF
|
||||
else
|
||||
cat <<EOF >>"$CONF_PATH"
|
||||
rw: *
|
||||
EOF
|
||||
fi
|
||||
|
||||
chmod 640 "$CONF_PATH"
|
||||
chown "$SVC_USER:$SVC_GROUP" "$CONF_PATH"
|
||||
msg_ok "Created configuration"
|
||||
|
||||
msg_info "Creating service"
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
cat <<'SERVICEEOF' >"$SERVICE_PATH"
|
||||
#!/sbin/openrc-run
|
||||
|
||||
name="copyparty"
|
||||
description="CopyParty file server"
|
||||
command="$(command -v python3)"
|
||||
command_args="/usr/local/bin/copyparty-sfx.py -c /etc/copyparty.conf"
|
||||
command_background=true
|
||||
directory="/var/lib/copyparty"
|
||||
pidfile="/run/copyparty.pid"
|
||||
output_log="/var/log/copyparty/copyparty.log"
|
||||
error_log="/var/log/copyparty/copyparty.err"
|
||||
|
||||
depend() {
|
||||
need net
|
||||
}
|
||||
SERVICEEOF
|
||||
chmod +x "$SERVICE_PATH"
|
||||
$STD rc-update add copyparty default
|
||||
$STD rc-service copyparty start
|
||||
else
|
||||
cat <<SERVICEEOF >"$SERVICE_PATH"
|
||||
[Unit]
|
||||
Description=CopyParty file server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=${SVC_USER}
|
||||
Group=${SVC_GROUP}
|
||||
WorkingDirectory=${DATA_PATH}
|
||||
ExecStart=/usr/bin/python3 ${BIN_PATH} -c ${CONF_PATH}
|
||||
Restart=always
|
||||
StandardOutput=append:${LOG_PATH}/copyparty.log
|
||||
StandardError=append:${LOG_PATH}/copyparty.err
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICEEOF
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now copyparty.service &>/dev/null
|
||||
fi
|
||||
msg_ok "Created and started service"
|
||||
|
||||
# Create update script
|
||||
msg_info "Creating update script"
|
||||
ensure_usr_local_bin_persist
|
||||
cat <<'UPDATEEOF' >/usr/local/bin/update_copyparty
|
||||
#!/usr/bin/env bash
|
||||
# CopyParty Update Script
|
||||
type=update bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/copyparty.sh)"
|
||||
UPDATEEOF
|
||||
chmod +x /usr/local/bin/update_copyparty
|
||||
msg_ok "Created update script (/usr/local/bin/update_copyparty)"
|
||||
|
||||
echo ""
|
||||
msg_ok "${APP} installed successfully"
|
||||
msg_ok "Web UI: ${BL}http://${LOCAL_IP}:${port}${CL}"
|
||||
msg_ok "Storage: ${BL}${DATA_PATH}${CL}"
|
||||
msg_ok "Config: ${BL}${CONF_PATH}${CL}"
|
||||
if [[ -n "$admin_user" ]]; then
|
||||
echo ""
|
||||
msg_ok "Login: ${GN}${admin_user}${CL} / ${GN}${admin_pass}${CL}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
header_info
|
||||
ensure_usr_local_bin_persist
|
||||
get_lxc_ip
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
if [[ -f "$BIN_PATH" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if already installed
|
||||
if [[ -f "$BIN_PATH" ]]; then
|
||||
msg_warn "${APP} is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall ${APP}? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update ${APP}? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "${APP} is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - CopyParty (Python file server)"
|
||||
echo -e "${TAB} - Thumbnail support (Pillow, FFmpeg)"
|
||||
echo -e "${TAB} - Systemd/OpenRC service"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install ${APP}? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,229 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/fccview/cronmaster
|
||||
|
||||
if ! command -v curl &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
load_functions
|
||||
init_tool_telemetry "" "addon"
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
APP="CronMaster"
|
||||
APP_TYPE="addon"
|
||||
INSTALL_PATH="/opt/cronmaster"
|
||||
CONFIG_PATH="/opt/cronmaster/.env"
|
||||
SERVICE_PATH="/etc/systemd/system/cronmaster.service"
|
||||
DEFAULT_PORT=3000
|
||||
|
||||
# ==============================================================================
|
||||
# HEADER
|
||||
# ==============================================================================
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
______ __ ___ __
|
||||
/ ____/________ ____ / |/ /___ ______/ /____ _____
|
||||
/ / / ___/ __ \/ __ \/ /|_/ / __ `/ ___/ __/ _ \/ ___/
|
||||
/ /___/ / / /_/ / / / / / / / /_/ (__ ) /_/ __/ /
|
||||
\____/_/ \____/_/ /_/_/ /_/\__,_/____/\__/\___/_/
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# OS DETECTION
|
||||
# ==============================================================================
|
||||
if ! grep -qE 'ID=debian|ID=ubuntu' /etc/os-release 2>/dev/null; then
|
||||
echo -e "${CROSS} Unsupported OS detected. This script only supports Debian and Ubuntu."
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling ${APP}"
|
||||
systemctl disable --now cronmaster.service &>/dev/null || true
|
||||
rm -f "$SERVICE_PATH"
|
||||
rm -rf "$INSTALL_PATH"
|
||||
rm -f "/usr/local/bin/update_cronmaster"
|
||||
rm -f "$HOME/.cronmaster"
|
||||
rm -f "/root/cronmaster.creds"
|
||||
msg_ok "${APP} has been uninstalled"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
if check_for_gh_release "cronmaster" "fccview/cronmaster"; then
|
||||
msg_info "Stopping service"
|
||||
systemctl stop cronmaster.service &>/dev/null || true
|
||||
msg_ok "Stopped service"
|
||||
|
||||
msg_info "Backing up configuration"
|
||||
cp "$CONFIG_PATH" /tmp/cronmaster.env.bak 2>/dev/null || true
|
||||
msg_ok "Backed up configuration"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "cronmaster" "fccview/cronmaster" "prebuild" "latest" "$INSTALL_PATH" "cronmaster_*_prebuild.tar.gz"
|
||||
|
||||
msg_info "Restoring configuration"
|
||||
cp /tmp/cronmaster.env.bak "$CONFIG_PATH" 2>/dev/null || true
|
||||
rm -f /tmp/cronmaster.env.bak
|
||||
msg_ok "Restored configuration"
|
||||
|
||||
msg_info "Starting service"
|
||||
systemctl start cronmaster
|
||||
msg_ok "Started service"
|
||||
msg_ok "Updated successfully"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
# Setup Node.js (only installs if not present or different version)
|
||||
if command -v node &>/dev/null; then
|
||||
msg_ok "Node.js already installed ($(node -v))"
|
||||
else
|
||||
NODE_VERSION="22" setup_nodejs
|
||||
fi
|
||||
|
||||
fetch_and_deploy_gh_release "cronmaster" "fccview/cronmaster" "prebuild" "latest" "$INSTALL_PATH" "cronmaster_*_prebuild.tar.gz"
|
||||
|
||||
local AUTH_PASS
|
||||
AUTH_PASS="$(openssl rand -base64 18 | cut -c1-13)"
|
||||
|
||||
msg_info "Creating configuration"
|
||||
cat <<EOF >"$CONFIG_PATH"
|
||||
NODE_ENV=production
|
||||
AUTH_PASSWORD=${AUTH_PASS}
|
||||
PORT=${DEFAULT_PORT}
|
||||
HOSTNAME=0.0.0.0
|
||||
NEXT_TELEMETRY_DISABLED=1
|
||||
EOF
|
||||
chmod 600 "$CONFIG_PATH"
|
||||
msg_ok "Created configuration"
|
||||
|
||||
msg_info "Creating service"
|
||||
cat <<EOF >"$SERVICE_PATH"
|
||||
[Unit]
|
||||
Description=CronMaster Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=${INSTALL_PATH}
|
||||
EnvironmentFile=${CONFIG_PATH}
|
||||
ExecStart=/usr/bin/node ${INSTALL_PATH}/server.js
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now cronmaster
|
||||
msg_ok "Created and started service"
|
||||
|
||||
# Create update script
|
||||
msg_info "Creating update script"
|
||||
ensure_usr_local_bin_persist
|
||||
cat <<EOF >/usr/local/bin/update_cronmaster
|
||||
#!/usr/bin/env bash
|
||||
# CronMaster Update Script
|
||||
type=update bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/cronmaster.sh)"
|
||||
EOF
|
||||
chmod +x /usr/local/bin/update_cronmaster
|
||||
msg_ok "Created update script (/usr/local/bin/update_cronmaster)"
|
||||
|
||||
# Save credentials
|
||||
local CREDS_FILE="/root/cronmaster.creds"
|
||||
cat <<EOF >"$CREDS_FILE"
|
||||
CronMaster Credentials
|
||||
======================
|
||||
Password: ${AUTH_PASS}
|
||||
|
||||
Web UI: http://${LOCAL_IP}:${DEFAULT_PORT}
|
||||
EOF
|
||||
echo ""
|
||||
msg_ok "${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}"
|
||||
msg_ok "Credentials saved to: ${BL}${CREDS_FILE}${CL}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
header_info
|
||||
ensure_usr_local_bin_persist
|
||||
get_lxc_ip
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
if [[ -d "$INSTALL_PATH" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if already installed
|
||||
if [[ -d "$INSTALL_PATH" && -n "$(ls -A "$INSTALL_PATH" 2>/dev/null)" ]]; then
|
||||
msg_warn "${APP} is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall ${APP}? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update ${APP}? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "${APP} is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - Node.js 22"
|
||||
echo -e "${TAB} - CronMaster (prebuild)"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install ${APP}? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,93 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 tteck
|
||||
# Author: tteck (tteckster)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://www.crowdsec.net/ | Github: https://github.com/crowdsecurity/crowdsec
|
||||
|
||||
YW=$(echo "\033[33m")
|
||||
BL=$(echo "\033[36m")
|
||||
RD=$(echo "\033[01;31m")
|
||||
BGN=$(echo "\033[4;92m")
|
||||
GN=$(echo "\033[1;92m")
|
||||
DGN=$(echo "\033[32m")
|
||||
CL=$(echo "\033[m")
|
||||
BFR="\\r\\033[K"
|
||||
HOLD="-"
|
||||
CM="${GN}✓${CL}"
|
||||
APP="CrowdSec"
|
||||
hostname="$(hostname)"
|
||||
|
||||
# Telemetry
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "crowdsec" "addon"
|
||||
|
||||
set -o errexit
|
||||
set -o errtrace
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
shopt -s expand_aliases
|
||||
alias die='EXIT=$? LINE=$LINENO error_exit'
|
||||
trap die ERR
|
||||
|
||||
function error_exit() {
|
||||
trap - ERR
|
||||
local reason="Unknown failure occured."
|
||||
local msg="${1:-$reason}"
|
||||
local flag="${RD}‼ ERROR ${CL}$EXIT@$LINE"
|
||||
echo -e "$flag $msg" 1>&2
|
||||
exit "$EXIT"
|
||||
}
|
||||
if command -v pveversion >/dev/null 2>&1; then
|
||||
echo -e "⚠️ Can't Install on Proxmox "
|
||||
exit
|
||||
fi
|
||||
while true; do
|
||||
read -p "This will Install ${APP} on $hostname. Proceed(y/n)?" yn
|
||||
case $yn in
|
||||
[Yy]*) break ;;
|
||||
[Nn]*) exit ;;
|
||||
*) echo "Please answer yes or no." ;;
|
||||
esac
|
||||
done
|
||||
clear
|
||||
function header_info() {
|
||||
echo -e "${BL}
|
||||
_____ _ _____
|
||||
/ ____| | |/ ____|
|
||||
| | _ __ _____ ____| | (___ ___ ___
|
||||
| | | __/ _ \ \ /\ / / _ |\___ \ / _ \/ __|
|
||||
| |____| | | (_) \ V V / (_| |____) | __/ (__
|
||||
\_____|_| \___/ \_/\_/ \__ _|_____/ \___|\___|
|
||||
${CL}"
|
||||
}
|
||||
|
||||
header_info
|
||||
|
||||
function msg_info() {
|
||||
local msg="$1"
|
||||
echo -ne " ${HOLD} ${YW}${msg}..."
|
||||
}
|
||||
|
||||
function msg_ok() {
|
||||
local msg="$1"
|
||||
echo -e "${BFR} ${CM} ${GN}${msg}${CL}"
|
||||
}
|
||||
|
||||
msg_info "Setting up ${APP} Repository"
|
||||
apt-get update &>/dev/null
|
||||
apt-get install -y curl &>/dev/null
|
||||
apt-get install -y gnupg &>/dev/null
|
||||
curl -fsSL "https://install.crowdsec.net" | bash &>/dev/null
|
||||
msg_ok "Setup ${APP} Repository"
|
||||
|
||||
msg_info "Installing ${APP}"
|
||||
apt-get update &>/dev/null
|
||||
apt-get install -y crowdsec &>/dev/null
|
||||
msg_ok "Installed ${APP} on $hostname"
|
||||
|
||||
msg_info "Installing ${APP} Common Bouncer"
|
||||
apt-get install -y crowdsec-firewall-bouncer-iptables &>/dev/null
|
||||
msg_ok "Installed ${APP} Common Bouncer"
|
||||
|
||||
msg_ok "Completed successfully!\n"
|
||||
Binary file not shown.
@@ -1,209 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: tteck (tteckster) | Addon: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://dockge.kuma.pet/ | Github: https://github.com/louislam/dockge
|
||||
if ! command -v curl &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||
if [[ -f /etc/alpine-release ]]; then
|
||||
apk update >/dev/null 2>&1
|
||||
apk add --no-cache curl >/dev/null 2>&1
|
||||
else
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
APP="Dockge"
|
||||
APP_TYPE="addon"
|
||||
INSTALL_PATH="/opt/dockge"
|
||||
STACKS_PATH="/opt/stacks"
|
||||
COMPOSE_FILE="${INSTALL_PATH}/compose.yaml"
|
||||
DEFAULT_PORT=5001
|
||||
|
||||
# Initialize all core functions (colors, formatting, icons, STD mode)
|
||||
load_functions
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling ${APP}"
|
||||
|
||||
if [[ -f "$COMPOSE_FILE" ]]; then
|
||||
msg_info "Stopping and removing Docker containers"
|
||||
cd "$INSTALL_PATH"
|
||||
$STD docker compose down --remove-orphans
|
||||
msg_ok "Stopped and removed Docker containers"
|
||||
fi
|
||||
|
||||
rm -rf "$INSTALL_PATH"
|
||||
msg_ok "${APP} has been uninstalled"
|
||||
msg_warn "Stacks directory ${STACKS_PATH} was NOT removed. Delete manually if no longer needed."
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
msg_info "Pulling latest ${APP} image"
|
||||
cd "$INSTALL_PATH"
|
||||
$STD docker compose pull
|
||||
msg_ok "Pulled latest image"
|
||||
|
||||
msg_info "Restarting ${APP}"
|
||||
$STD docker compose up -d --remove-orphans
|
||||
msg_ok "Restarted ${APP}"
|
||||
|
||||
msg_ok "Updated successfully"
|
||||
exit
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# PROXMOX HOST CHECK
|
||||
# ==============================================================================
|
||||
function check_proxmox_host() {
|
||||
if command -v pveversion &>/dev/null; then
|
||||
msg_error "Running on the Proxmox host is NOT recommended!"
|
||||
msg_error "This should be executed inside an LXC container."
|
||||
echo ""
|
||||
echo -n "${TAB}Continue anyway? (y/N): "
|
||||
read -r confirm
|
||||
if [[ ! "${confirm,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_warn "Aborted. Please run this inside an LXC container."
|
||||
exit 0
|
||||
fi
|
||||
msg_warn "Proceeding on Proxmox host at your own risk!"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# CHECK / INSTALL DOCKER
|
||||
# ==============================================================================
|
||||
function check_or_install_docker() {
|
||||
if command -v docker &>/dev/null; then
|
||||
msg_ok "Docker $(docker --version | cut -d' ' -f3 | tr -d ',') is available"
|
||||
if docker compose version &>/dev/null; then
|
||||
msg_ok "Docker Compose is available"
|
||||
else
|
||||
msg_error "Docker Compose plugin is not available. Please install it."
|
||||
exit 10
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
msg_warn "Docker is not installed."
|
||||
echo -n "${TAB}Install Docker now? (y/N): "
|
||||
read -r install_docker_prompt
|
||||
if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_error "Docker is required for ${APP}. Exiting."
|
||||
exit 10
|
||||
fi
|
||||
|
||||
msg_info "Installing Docker"
|
||||
if [[ -f /etc/alpine-release ]]; then
|
||||
$STD apk add docker docker-cli-compose
|
||||
$STD rc-service docker start
|
||||
$STD rc-update add docker default
|
||||
else
|
||||
DOCKER_CONFIG_PATH='/etc/docker/daemon.json'
|
||||
mkdir -p "$(dirname "$DOCKER_CONFIG_PATH")"
|
||||
echo -e '{\n "log-driver": "journald"\n}' >"$DOCKER_CONFIG_PATH"
|
||||
$STD sh <(curl -fsSL https://get.docker.com)
|
||||
fi
|
||||
msg_ok "Installed Docker"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
check_or_install_docker
|
||||
|
||||
msg_info "Creating install directories"
|
||||
mkdir -p "$INSTALL_PATH" "$STACKS_PATH"
|
||||
msg_ok "Created ${INSTALL_PATH} and ${STACKS_PATH}"
|
||||
|
||||
msg_info "Downloading Docker Compose file"
|
||||
curl -fsSL "https://raw.githubusercontent.com/louislam/dockge/master/compose.yaml" -o "$COMPOSE_FILE"
|
||||
msg_ok "Downloaded Docker Compose file"
|
||||
|
||||
msg_info "Starting ${APP}"
|
||||
cd "$INSTALL_PATH"
|
||||
$STD docker compose up -d
|
||||
msg_ok "Started ${APP}"
|
||||
|
||||
echo ""
|
||||
msg_ok "${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
header_info
|
||||
if [[ -f "$COMPOSE_FILE" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
header_info
|
||||
check_proxmox_host
|
||||
get_lxc_ip
|
||||
|
||||
# Check if already installed
|
||||
if [[ -f "$COMPOSE_FILE" ]]; then
|
||||
msg_warn "${APP} is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall ${APP}? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update ${APP}? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "${APP} is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - Dockge (via Docker Compose)"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install ${APP}? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,214 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://dokploy.com/ | Github: https://github.com/Dokploy/dokploy
|
||||
if ! command -v curl &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||
if [[ -f /etc/alpine-release ]]; then
|
||||
apk update >/dev/null 2>&1
|
||||
apk add --no-cache curl >/dev/null 2>&1
|
||||
else
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
APP="Dokploy"
|
||||
APP_TYPE="addon"
|
||||
INSTALL_PATH="/etc/dokploy"
|
||||
DEFAULT_PORT=3000
|
||||
|
||||
# Initialize all core functions (colors, formatting, icons, STD mode)
|
||||
load_functions
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling ${APP}"
|
||||
|
||||
if command -v docker &>/dev/null; then
|
||||
msg_info "Stopping and removing Docker containers"
|
||||
$STD docker stop $(docker ps -aq) 2>/dev/null || true
|
||||
$STD docker rm $(docker ps -aq) 2>/dev/null || true
|
||||
$STD docker network prune -f 2>/dev/null || true
|
||||
msg_ok "Stopped and removed Docker containers"
|
||||
fi
|
||||
|
||||
rm -rf "$INSTALL_PATH"
|
||||
msg_ok "${APP} has been uninstalled"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
msg_info "Updating ${APP}"
|
||||
$STD curl -sSL https://dokploy.com/install.sh | bash -s update
|
||||
msg_ok "Updated ${APP}"
|
||||
|
||||
msg_ok "Updated successfully"
|
||||
exit
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# PROXMOX HOST CHECK
|
||||
# ==============================================================================
|
||||
function check_proxmox_host() {
|
||||
if command -v pveversion &>/dev/null; then
|
||||
msg_error "Running on the Proxmox host is NOT recommended!"
|
||||
msg_error "This should be executed inside an LXC container."
|
||||
echo ""
|
||||
echo -n "${TAB}Continue anyway? (y/N): "
|
||||
read -r confirm
|
||||
if [[ ! "${confirm,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_warn "Aborted. Please run this inside an LXC container."
|
||||
exit 0
|
||||
fi
|
||||
msg_warn "Proceeding on Proxmox host at your own risk!"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# CHECK / INSTALL DOCKER
|
||||
# ==============================================================================
|
||||
function check_or_install_docker() {
|
||||
if command -v docker &>/dev/null; then
|
||||
msg_ok "Docker $(docker --version | cut -d' ' -f3 | tr -d ',') is available"
|
||||
if docker compose version &>/dev/null; then
|
||||
msg_ok "Docker Compose is available"
|
||||
else
|
||||
msg_error "Docker Compose plugin is not available. Please install it."
|
||||
exit 10
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
msg_warn "Docker is not installed."
|
||||
echo -n "${TAB}Install Docker now? (y/N): "
|
||||
read -r install_docker_prompt
|
||||
if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_error "Docker is required for ${APP}. Exiting."
|
||||
exit 10
|
||||
fi
|
||||
|
||||
msg_info "Installing Docker"
|
||||
if [[ -f /etc/alpine-release ]]; then
|
||||
$STD apk add docker docker-cli-compose
|
||||
$STD rc-service docker start
|
||||
$STD rc-update add docker default
|
||||
else
|
||||
DOCKER_CONFIG_PATH='/etc/docker/daemon.json'
|
||||
mkdir -p "$(dirname "$DOCKER_CONFIG_PATH")"
|
||||
echo -e '{\n "log-driver": "journald"\n}' >"$DOCKER_CONFIG_PATH"
|
||||
$STD sh <(curl -fsSL https://get.docker.com)
|
||||
fi
|
||||
msg_ok "Installed Docker"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
check_or_install_docker
|
||||
|
||||
msg_info "Installing dependencies"
|
||||
if [[ -f /etc/alpine-release ]]; then
|
||||
$STD apk add --no-cache git openssl
|
||||
else
|
||||
$STD apt-get update
|
||||
$STD apt-get install -y git openssl redis
|
||||
fi
|
||||
msg_ok "Installed dependencies"
|
||||
|
||||
msg_warn "WARNING: This will run an external installer from https://dokploy.com/"
|
||||
msg_warn "The following code is NOT maintained or audited by our repository."
|
||||
echo ""
|
||||
echo -n "${TAB}Do you want to continue? (y/N): "
|
||||
read -r confirm
|
||||
if [[ ! "${confirm,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_info "Installing ${APP} (this installs Docker and pulls containers)"
|
||||
$STD bash <(curl -sSL https://dokploy.com/install.sh)
|
||||
msg_ok "Installed ${APP}"
|
||||
|
||||
echo ""
|
||||
msg_ok "${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
header_info
|
||||
if [[ -d "$INSTALL_PATH" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
header_info
|
||||
check_proxmox_host
|
||||
get_lxc_ip
|
||||
|
||||
# Check if already installed
|
||||
if [[ -d "$INSTALL_PATH" ]]; then
|
||||
msg_warn "${APP} is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall ${APP}? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update ${APP}? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "${APP} is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - Dokploy (via external installer)"
|
||||
echo -e "${TAB} - Docker (if not already installed)"
|
||||
echo -e "${TAB} - Redis"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install ${APP}? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,246 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/filebrowserspace/quantum
|
||||
|
||||
function header_info() {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
_______ __ ____ ____ __
|
||||
/ ____(_) /__ / __ )_________ _ __________ _____ / __ \__ ______ _____ / /___ ______ ___
|
||||
/ /_ / / / _ \/ __ / ___/ __ \ | /| / / ___/ _ \/ ___/ / / / / / / / __ `/ __ \/ __/ / / / __ `__ \
|
||||
/ __/ / / / __/ /_/ / / / /_/ / |/ |/ (__ ) __/ / / /_/ / /_/ / /_/ / / / / /_/ /_/ / / / / / /
|
||||
/_/ /_/_/\___/_____/_/ \____/|__/|__/____/\___/_/ \___\_\__,_/\__,_/_/ /_/\__/\__,_/_/ /_/ /_/
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
YW=$(echo "\033[33m")
|
||||
GN=$(echo "\033[1;92m")
|
||||
RD=$(echo "\033[01;31m")
|
||||
BL=$(echo "\033[36m")
|
||||
CL=$(echo "\033[m")
|
||||
CM="${GN}✔️${CL}"
|
||||
CROSS="${RD}✖️${CL}"
|
||||
INFO="${BL}ℹ️${CL}"
|
||||
|
||||
APP="FileBrowser Quantum"
|
||||
INSTALL_PATH="/usr/local/bin/filebrowser"
|
||||
CONFIG_PATH="/usr/local/community-scripts/fq-config.yaml"
|
||||
DEFAULT_PORT=8080
|
||||
SRC_DIR="/"
|
||||
TMP_BIN="/tmp/filebrowser.$$"
|
||||
|
||||
# Telemetry
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "filebrowser-quantum" "addon"
|
||||
|
||||
# Get primary IP
|
||||
IFACE=$(ip -4 route | awk '/default/ {print $5; exit}')
|
||||
IP=$(ip -4 addr show "$IFACE" | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1)
|
||||
[[ -z "$IP" ]] && IP=$(hostname -I | awk '{print $1}')
|
||||
[[ -z "$IP" ]] && IP="127.0.0.1"
|
||||
|
||||
# OS Detection
|
||||
if [[ -f "/etc/alpine-release" ]]; then
|
||||
OS="Alpine"
|
||||
SERVICE_PATH="/etc/init.d/filebrowser"
|
||||
PKG_MANAGER="apk add --no-cache"
|
||||
elif [[ -f "/etc/debian_version" ]]; then
|
||||
OS="Debian"
|
||||
SERVICE_PATH="/etc/systemd/system/filebrowser.service"
|
||||
PKG_MANAGER="apt-get install -y"
|
||||
else
|
||||
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||
exit 238
|
||||
fi
|
||||
|
||||
header_info
|
||||
|
||||
function msg_info() { echo -e "${INFO} ${YW}$1...${CL}"; }
|
||||
function msg_ok() { echo -e "${CM} ${GN}$1${CL}"; }
|
||||
function msg_error() { echo -e "${CROSS} ${RD}$1${CL}"; }
|
||||
|
||||
# Detect legacy FileBrowser installation
|
||||
LEGACY_DB="/usr/local/community-scripts/filebrowser.db"
|
||||
LEGACY_BIN="/usr/local/bin/filebrowser"
|
||||
LEGACY_SERVICE_DEB="/etc/systemd/system/filebrowser.service"
|
||||
LEGACY_SERVICE_ALP="/etc/init.d/filebrowser"
|
||||
|
||||
if [[ -f "$LEGACY_DB" || -f "$LEGACY_BIN" && ! -f "$CONFIG_PATH" ]]; then
|
||||
echo -e "${YW}⚠️ Detected legacy FileBrowser installation.${CL}"
|
||||
echo -n "Uninstall legacy FileBrowser and continue with Quantum install? (y/n): "
|
||||
read -r remove_legacy
|
||||
if [[ "${remove_legacy,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_info "Uninstalling legacy FileBrowser"
|
||||
if [[ -f "$LEGACY_SERVICE_DEB" ]]; then
|
||||
systemctl disable --now filebrowser.service &>/dev/null
|
||||
rm -f "$LEGACY_SERVICE_DEB"
|
||||
elif [[ -f "$LEGACY_SERVICE_ALP" ]]; then
|
||||
rc-service filebrowser stop &>/dev/null
|
||||
rc-update del filebrowser &>/dev/null
|
||||
rm -f "$LEGACY_SERVICE_ALP"
|
||||
fi
|
||||
rm -f "$LEGACY_BIN" "$LEGACY_DB"
|
||||
msg_ok "Legacy FileBrowser removed"
|
||||
else
|
||||
echo -e "${YW}❌ Installation aborted by user.${CL}"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Existing installation
|
||||
if [[ -f "$INSTALL_PATH" ]]; then
|
||||
echo -e "${YW}⚠️ ${APP} is already installed.${CL}"
|
||||
echo -n "Uninstall ${APP}? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_info "Uninstalling ${APP}"
|
||||
if [[ "$OS" == "Debian" ]]; then
|
||||
systemctl disable --now filebrowser.service &>/dev/null
|
||||
rm -f "$SERVICE_PATH"
|
||||
else
|
||||
rc-service filebrowser stop &>/dev/null
|
||||
rc-update del filebrowser &>/dev/null
|
||||
rm -f "$SERVICE_PATH"
|
||||
fi
|
||||
rm -f "$INSTALL_PATH" "$CONFIG_PATH"
|
||||
msg_ok "${APP} has been uninstalled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "Update ${APP}? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_info "Updating ${APP}"
|
||||
if ! command -v curl &>/dev/null; then $PKG_MANAGER curl &>/dev/null; fi
|
||||
curl -fsSL https://github.com/gtsteffaniak/filebrowser/releases/latest/download/linux-amd64-filebrowser -o "$TMP_BIN"
|
||||
chmod +x "$TMP_BIN"
|
||||
mv -f "$TMP_BIN" /usr/local/bin/filebrowser
|
||||
msg_ok "Updated ${APP}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${YW}⚠️ Update skipped. Exiting.${CL}"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "${YW}⚠️ ${APP} is not installed.${CL}"
|
||||
echo -n "Enter port number (Default: ${DEFAULT_PORT}): "
|
||||
read -r PORT
|
||||
PORT=${PORT:-$DEFAULT_PORT}
|
||||
|
||||
echo -n "Install ${APP}? (y/n): "
|
||||
read -r install_prompt
|
||||
if ! [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
echo -e "${YW}⚠️ Installation skipped. Exiting.${CL}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_info "Installing ${APP} on ${OS}"
|
||||
$PKG_MANAGER curl ffmpeg &>/dev/null
|
||||
curl -fsSL https://github.com/gtsteffaniak/filebrowser/releases/latest/download/linux-amd64-filebrowser -o "$TMP_BIN"
|
||||
chmod +x "$TMP_BIN"
|
||||
mv -f "$TMP_BIN" /usr/local/bin/filebrowser
|
||||
msg_ok "Installed ${APP}"
|
||||
|
||||
msg_info "Preparing configuration directory"
|
||||
mkdir -p /usr/local/community-scripts
|
||||
chown root:root /usr/local/community-scripts
|
||||
chmod 755 /usr/local/community-scripts
|
||||
msg_ok "Directory prepared"
|
||||
|
||||
echo -n "Use No Authentication? (y/N): "
|
||||
read -r noauth_prompt
|
||||
|
||||
# === YAML CONFIG GENERATION ===
|
||||
if [[ "${noauth_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
cat <<EOF >"$CONFIG_PATH"
|
||||
server:
|
||||
port: $PORT
|
||||
sources:
|
||||
- path: "$SRC_DIR"
|
||||
name: "RootFS"
|
||||
config:
|
||||
denyByDefault: false
|
||||
disableIndexing: false
|
||||
indexingIntervalMinutes: 240
|
||||
conditionals:
|
||||
rules:
|
||||
- neverWatchPath: "/proc"
|
||||
- neverWatchPath: "/sys"
|
||||
- neverWatchPath: "/dev"
|
||||
- neverWatchPath: "/run"
|
||||
- neverWatchPath: "/tmp"
|
||||
- neverWatchPath: "/lost+found"
|
||||
auth:
|
||||
methods:
|
||||
noauth: true
|
||||
EOF
|
||||
msg_ok "Configured with no authentication"
|
||||
else
|
||||
cat <<EOF >"$CONFIG_PATH"
|
||||
server:
|
||||
port: $PORT
|
||||
sources:
|
||||
- path: "$SRC_DIR"
|
||||
name: "RootFS"
|
||||
config:
|
||||
denyByDefault: false
|
||||
disableIndexing: false
|
||||
indexingIntervalMinutes: 240
|
||||
conditionals:
|
||||
rules:
|
||||
- neverWatchPath: "/proc"
|
||||
- neverWatchPath: "/sys"
|
||||
- neverWatchPath: "/dev"
|
||||
- neverWatchPath: "/run"
|
||||
- neverWatchPath: "/tmp"
|
||||
- neverWatchPath: "/lost+found"
|
||||
auth:
|
||||
adminUsername: admin
|
||||
adminPassword: helper-scripts.com
|
||||
EOF
|
||||
msg_ok "Configured with default admin (admin / helper-scripts.com)"
|
||||
fi
|
||||
|
||||
msg_info "Creating service"
|
||||
if [[ "$OS" == "Debian" ]]; then
|
||||
cat <<EOF >"$SERVICE_PATH"
|
||||
[Unit]
|
||||
Description=FileBrowser Quantum
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
WorkingDirectory=/usr/local/community-scripts
|
||||
ExecStart=/usr/local/bin/filebrowser -c $CONFIG_PATH
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable --now filebrowser &>/dev/null
|
||||
else
|
||||
cat <<EOF >"$SERVICE_PATH"
|
||||
#!/sbin/openrc-run
|
||||
|
||||
command="/usr/local/bin/filebrowser"
|
||||
command_args="-c $CONFIG_PATH"
|
||||
command_background=true
|
||||
directory="/usr/local/community-scripts"
|
||||
pidfile="/usr/local/community-scripts/pidfile"
|
||||
|
||||
depend() {
|
||||
need net
|
||||
}
|
||||
EOF
|
||||
chmod +x "$SERVICE_PATH"
|
||||
rc-update add filebrowser default &>/dev/null
|
||||
rc-service filebrowser start &>/dev/null
|
||||
fi
|
||||
|
||||
msg_ok "Service created successfully"
|
||||
echo -e "${CM} ${GN}${APP} is reachable at: ${BL}http://$IP:$PORT${CL}"
|
||||
@@ -1,190 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: tteck (tteckster) | Co-Author: MickLesk
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://filebrowser.org/ | Github: https://github.com/filebrowser/filebrowser
|
||||
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
_______ __ ____
|
||||
/ ____(_) /__ / __ )_________ _ __________ _____
|
||||
/ /_ / / / _ \/ __ / ___/ __ \ | /| / / ___/ _ \/ ___/
|
||||
/ __/ / / / __/ /_/ / / / /_/ / |/ |/ (__ ) __/ /
|
||||
/_/ /_/_/\___/_____/_/ \____/|__/|__/____/\___/_/
|
||||
EOF
|
||||
}
|
||||
|
||||
YW=$(echo "\033[33m")
|
||||
GN=$(echo "\033[1;92m")
|
||||
RD=$(echo "\033[01;31m")
|
||||
BL=$(echo "\033[36m")
|
||||
CL=$(echo "\033[m")
|
||||
CM="${GN}✔️${CL}"
|
||||
CROSS="${RD}✖️${CL}"
|
||||
INFO="${BL}ℹ️${CL}"
|
||||
|
||||
APP="FileBrowser"
|
||||
INSTALL_PATH="/usr/local/bin/filebrowser"
|
||||
DB_PATH="/usr/local/community-scripts/filebrowser.db"
|
||||
DEFAULT_PORT=8080
|
||||
|
||||
# Telemetry
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "filebrowser" "addon"
|
||||
|
||||
# Get first non-loopback IP & Detect primary network interface dynamically
|
||||
IFACE=$(ip -4 route | awk '/default/ {print $5; exit}')
|
||||
IP=$(ip -4 addr show "$IFACE" | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1)
|
||||
|
||||
[[ -z "$IP" ]] && IP=$(hostname -I | awk '{print $1}')
|
||||
[[ -z "$IP" ]] && IP="127.0.0.1"
|
||||
|
||||
# Detect OS
|
||||
if [[ -f "/etc/alpine-release" ]]; then
|
||||
OS="Alpine"
|
||||
SERVICE_PATH="/etc/init.d/filebrowser"
|
||||
PKG_MANAGER="apk add --no-cache"
|
||||
elif [[ -f "/etc/debian_version" ]]; then
|
||||
OS="Debian"
|
||||
SERVICE_PATH="/etc/systemd/system/filebrowser.service"
|
||||
PKG_MANAGER="apt-get install -y"
|
||||
else
|
||||
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||
exit 238
|
||||
fi
|
||||
|
||||
header_info
|
||||
|
||||
function msg_info() {
|
||||
local msg="$1"
|
||||
echo -e "${INFO} ${YW}${msg}...${CL}"
|
||||
}
|
||||
|
||||
function msg_ok() {
|
||||
local msg="$1"
|
||||
echo -e "${CM} ${GN}${msg}${CL}"
|
||||
}
|
||||
|
||||
function msg_error() {
|
||||
local msg="$1"
|
||||
echo -e "${CROSS} ${RD}${msg}${CL}"
|
||||
}
|
||||
|
||||
if [ -f "$INSTALL_PATH" ]; then
|
||||
echo -e "${YW}⚠️ ${APP} is already installed.${CL}"
|
||||
read -r -p "Would you like to uninstall ${APP}? (y/N): " uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_info "Uninstalling ${APP}"
|
||||
if [[ "$OS" == "Debian" ]]; then
|
||||
systemctl disable --now filebrowser.service &>/dev/null
|
||||
rm -f "$SERVICE_PATH"
|
||||
else
|
||||
rc-service filebrowser stop &>/dev/null
|
||||
rc-update del filebrowser &>/dev/null
|
||||
rm -f "$SERVICE_PATH"
|
||||
fi
|
||||
rm -f "$INSTALL_PATH" "$DB_PATH"
|
||||
msg_ok "${APP} has been uninstalled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
read -r -p "Would you like to update ${APP}? (y/N): " update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_info "Updating ${APP}"
|
||||
if ! command -v curl &>/dev/null; then $PKG_MANAGER curl &>/dev/null; fi
|
||||
curl -fsSL "https://github.com/filebrowser/filebrowser/releases/latest/download/linux-amd64-filebrowser.tar.gz" | tar -xzv -C /usr/local/bin &>/dev/null
|
||||
chmod +x "$INSTALL_PATH"
|
||||
msg_ok "Updated ${APP}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${YW}⚠️ Update skipped. Exiting.${CL}"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "${YW}⚠️ ${APP} is not installed.${CL}"
|
||||
read -r -p "Enter port number (Default: ${DEFAULT_PORT}): " PORT
|
||||
PORT=${PORT:-$DEFAULT_PORT}
|
||||
|
||||
read -r -p "Would you like to install ${APP}? (y/n): " install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_info "Installing ${APP} on ${OS}"
|
||||
$PKG_MANAGER wget tar curl &>/dev/null
|
||||
curl -fsSL "https://github.com/filebrowser/filebrowser/releases/latest/download/linux-amd64-filebrowser.tar.gz" | tar -xzv -C /usr/local/bin &>/dev/null
|
||||
chmod +x "$INSTALL_PATH"
|
||||
msg_ok "Installed ${APP}"
|
||||
|
||||
msg_info "Creating FileBrowser directory"
|
||||
mkdir -p /usr/local/community-scripts
|
||||
chown root:root /usr/local/community-scripts
|
||||
chmod 755 /usr/local/community-scripts
|
||||
touch "$DB_PATH"
|
||||
chown root:root "$DB_PATH"
|
||||
chmod 644 "$DB_PATH"
|
||||
msg_ok "Directory created successfully"
|
||||
|
||||
read -r -p "Would you like to use No Authentication? (y/N): " auth_prompt
|
||||
if [[ "${auth_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_info "Configuring No Authentication"
|
||||
cd /usr/local/community-scripts
|
||||
filebrowser config init -a '0.0.0.0' -p "$PORT" -d "$DB_PATH" &>/dev/null
|
||||
filebrowser config set -a '0.0.0.0' -p "$PORT" -d "$DB_PATH" &>/dev/null
|
||||
filebrowser config init --auth.method=noauth &>/dev/null
|
||||
filebrowser config set --auth.method=noauth &>/dev/null
|
||||
filebrowser users add ID 1 --perm.admin &>/dev/null
|
||||
msg_ok "No Authentication configured"
|
||||
else
|
||||
msg_info "Setting up default authentication"
|
||||
cd /usr/local/community-scripts
|
||||
filebrowser config init -a '0.0.0.0' -p "$PORT" -d "$DB_PATH" &>/dev/null
|
||||
filebrowser config set -a '0.0.0.0' -p "$PORT" -d "$DB_PATH" &>/dev/null
|
||||
filebrowser users add admin helper-scripts.com --perm.admin --database "$DB_PATH" &>/dev/null
|
||||
msg_ok "Default authentication configured (admin:helper-scripts.com)"
|
||||
fi
|
||||
|
||||
msg_info "Creating service"
|
||||
if [[ "$OS" == "Debian" ]]; then
|
||||
cat <<EOF >"$SERVICE_PATH"
|
||||
[Unit]
|
||||
Description=Filebrowser
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
WorkingDirectory=/usr/local/community-scripts
|
||||
ExecStartPre=/bin/touch /usr/local/community-scripts/filebrowser.db
|
||||
ExecStartPre=/usr/local/bin/filebrowser config set -a "0.0.0.0" -p ${PORT} -d /usr/local/community-scripts/filebrowser.db
|
||||
ExecStart=/usr/local/bin/filebrowser -r / -d /usr/local/community-scripts/filebrowser.db -p ${PORT}
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now filebrowser
|
||||
else
|
||||
cat <<EOF >"$SERVICE_PATH"
|
||||
#!/sbin/openrc-run
|
||||
|
||||
command="/usr/local/bin/filebrowser"
|
||||
command_args="-r / -d $DB_PATH -p $PORT"
|
||||
command_background=true
|
||||
pidfile="/var/run/filebrowser.pid"
|
||||
directory="/usr/local/community-scripts"
|
||||
|
||||
depend() {
|
||||
need net
|
||||
}
|
||||
EOF
|
||||
chmod +x "$SERVICE_PATH"
|
||||
rc-update add filebrowser default &>/dev/null
|
||||
rc-service filebrowser start &>/dev/null
|
||||
fi
|
||||
msg_ok "Service created successfully"
|
||||
|
||||
echo -e "${CM} ${GN}${APP} is reachable at: ${BL}http://$IP:$PORT${CL}"
|
||||
else
|
||||
echo -e "${YW}⚠️ Installation skipped. Exiting.${CL}"
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,207 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 tteck
|
||||
# Author: tteck (tteckster) | MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://nicolargo.github.io/glances/ | Github: https://github.com/nicolargo/glances
|
||||
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
________
|
||||
/ ____/ /___ _____ ________ _____
|
||||
/ / __/ / __ `/ __ \/ ___/ _ \/ ___/
|
||||
/ /_/ / / /_/ / / / / /__/ __(__ )
|
||||
\____/_/\__,_/_/ /_/\___/\___/____/
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
APP="Glances"
|
||||
YW=$(echo "\033[33m")
|
||||
GN=$(echo "\033[1;92m")
|
||||
RD=$(echo "\033[01;31m")
|
||||
BL=$(echo "\033[36m")
|
||||
CL=$(echo "\033[m")
|
||||
CM="${GN}✔️${CL}"
|
||||
CROSS="${RD}✖️${CL}"
|
||||
INFO="${BL}ℹ️${CL}"
|
||||
|
||||
function msg_info() { echo -e "${INFO} ${YW}$1...${CL}"; }
|
||||
function msg_ok() { echo -e "${CM} ${GN}$1${CL}"; }
|
||||
function msg_error() { echo -e "${CROSS} ${RD}$1${CL}"; }
|
||||
|
||||
# Telemetry
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "glances" "addon"
|
||||
|
||||
get_lxc_ip() {
|
||||
if command -v hostname >/dev/null 2>&1 && hostname -I 2>/dev/null; then
|
||||
hostname -I | awk '{print $1}'
|
||||
elif command -v ip >/dev/null 2>&1; then
|
||||
ip -4 addr show scope global | awk '/inet / {print $2}' | cut -d/ -f1 | head -n1
|
||||
else
|
||||
echo "127.0.0.1"
|
||||
fi
|
||||
}
|
||||
IP=$(get_lxc_ip)
|
||||
|
||||
install_glances_debian() {
|
||||
msg_info "Installing dependencies"
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y gcc lm-sensors wireless-tools curl >/dev/null 2>&1
|
||||
msg_ok "Installed dependencies"
|
||||
|
||||
msg_info "Setting up Python + uv"
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
setup_uv PYTHON_VERSION="3.12"
|
||||
msg_ok "Setup Python + uv"
|
||||
|
||||
msg_info "Installing $APP (with web UI)"
|
||||
cd /opt
|
||||
mkdir -p glances
|
||||
cd glances
|
||||
uv venv --clear
|
||||
source .venv/bin/activate >/dev/null 2>&1
|
||||
uv pip install --upgrade pip wheel setuptools >/dev/null 2>&1
|
||||
uv pip install "glances[web]" >/dev/null 2>&1
|
||||
deactivate
|
||||
msg_ok "Installed $APP"
|
||||
|
||||
msg_info "Creating systemd service"
|
||||
cat <<EOF >/etc/systemd/system/glances.service
|
||||
[Unit]
|
||||
Description=Glances - An eye on your system
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/opt/glances/.venv/bin/glances -w
|
||||
Restart=on-failure
|
||||
WorkingDirectory=/opt/glances
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now glances
|
||||
msg_ok "Created systemd service"
|
||||
|
||||
echo -e "\n$APP is now running at: http://$IP:61208\n"
|
||||
}
|
||||
|
||||
# update on Debian/Ubuntu
|
||||
update_glances_debian() {
|
||||
if [[ ! -d /opt/glances/.venv ]]; then
|
||||
msg_error "$APP is not installed"
|
||||
exit 233
|
||||
fi
|
||||
msg_info "Updating $APP"
|
||||
cd /opt/glances
|
||||
source .venv/bin/activate
|
||||
uv pip install --upgrade "glances[web]" >/dev/null 2>&1
|
||||
deactivate
|
||||
systemctl restart glances
|
||||
msg_ok "Updated successfully!"
|
||||
}
|
||||
|
||||
# uninstall on Debian/Ubuntu
|
||||
uninstall_glances_debian() {
|
||||
msg_info "Uninstalling $APP"
|
||||
systemctl disable -q --now glances || true
|
||||
rm -f /etc/systemd/system/glances.service
|
||||
rm -rf /opt/glances
|
||||
msg_ok "Removed $APP"
|
||||
}
|
||||
|
||||
# install on Alpine
|
||||
install_glances_alpine() {
|
||||
msg_info "Installing dependencies"
|
||||
apk update >/dev/null 2>&1
|
||||
$STD apk add --no-cache \
|
||||
gcc musl-dev linux-headers python3-dev \
|
||||
python3 py3-pip py3-virtualenv lm-sensors wireless-tools curl >/dev/null 2>&1
|
||||
msg_ok "Installed dependencies"
|
||||
|
||||
msg_info "Setting up Python + uv"
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
setup_uv PYTHON_VERSION="3.12"
|
||||
msg_ok "Setup Python + uv"
|
||||
|
||||
msg_info "Installing $APP (with web UI)"
|
||||
cd /opt
|
||||
mkdir -p glances
|
||||
cd glances
|
||||
uv venv --clear
|
||||
source .venv/bin/activate
|
||||
uv pip install --upgrade pip wheel setuptools >/dev/null 2>&1
|
||||
uv pip install "glances[web]" >/dev/null 2>&1
|
||||
deactivate
|
||||
msg_ok "Installed $APP"
|
||||
|
||||
msg_info "Creating OpenRC service"
|
||||
cat <<'EOF' >/etc/init.d/glances
|
||||
#!/sbin/openrc-run
|
||||
command="/opt/glances/.venv/bin/glances"
|
||||
command_args="-w"
|
||||
command_background="yes"
|
||||
pidfile="/run/glances.pid"
|
||||
name="glances"
|
||||
description="Glances monitoring tool"
|
||||
EOF
|
||||
chmod +x /etc/init.d/glances
|
||||
rc-update add glances default
|
||||
rc-service glances start
|
||||
msg_ok "Created OpenRC service"
|
||||
|
||||
echo -e "\n$APP is now running at: http://$IP:61208\n"
|
||||
}
|
||||
|
||||
# update on Alpine
|
||||
update_glances_alpine() {
|
||||
if [[ ! -d /opt/glances/.venv ]]; then
|
||||
msg_error "$APP is not installed"
|
||||
exit 233
|
||||
fi
|
||||
msg_info "Updating $APP"
|
||||
cd /opt/glances
|
||||
source .venv/bin/activate
|
||||
uv pip install --upgrade "glances[web]" >/dev/null 2>&1
|
||||
deactivate
|
||||
rc-service glances restart
|
||||
msg_ok "Updated successfully!"
|
||||
}
|
||||
|
||||
# uninstall on Alpine
|
||||
uninstall_glances_alpine() {
|
||||
msg_info "Uninstalling $APP"
|
||||
rc-service glances stop || true
|
||||
rc-update del glances || true
|
||||
rm -f /etc/init.d/glances
|
||||
rm -rf /opt/glances
|
||||
msg_ok "Removed $APP"
|
||||
}
|
||||
|
||||
# options menu
|
||||
OPTIONS=(Install "Install $APP"
|
||||
Update "Update $APP"
|
||||
Uninstall "Uninstall $APP")
|
||||
|
||||
CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "$APP" --menu "Select an option:" 12 58 3 \
|
||||
"${OPTIONS[@]}" 3>&1 1>&2 2>&3 || true)
|
||||
|
||||
# OS detection
|
||||
if grep -qi "alpine" /etc/os-release; then
|
||||
case "$CHOICE" in
|
||||
Install) install_glances_alpine ;;
|
||||
Update) update_glances_alpine ;;
|
||||
Uninstall) uninstall_glances_alpine ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
else
|
||||
case "$CHOICE" in
|
||||
Install) install_glances_debian ;;
|
||||
Update) update_glances_debian ;;
|
||||
Uninstall) uninstall_glances_debian ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
fi
|
||||
@@ -1,280 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: vhsdream
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/alangrainger/immich-public-proxy
|
||||
|
||||
if ! command -v curl &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
APP="Immich Public Proxy"
|
||||
APP_TYPE="addon"
|
||||
INSTALL_PATH="/opt/immich-proxy"
|
||||
CONFIG_PATH="/opt/immich-proxy/app"
|
||||
DEFAULT_PORT=3000
|
||||
|
||||
# Initialize all core functions (colors, formatting, icons, $STD mode)
|
||||
load_functions
|
||||
init_tool_telemetry "" "addon"
|
||||
|
||||
# ==============================================================================
|
||||
# HEADER
|
||||
# ==============================================================================
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
____ _ __ ____
|
||||
/ _/___ ___ ____ ___ (_)____/ /_ / __ \_________ _ ____ __
|
||||
/ // __ `__ \/ __ `__ \/ / ___/ __ \______/ /_/ / ___/ __ \| |/_/ / / /
|
||||
_/ // / / / / / / / / / / / /__/ / / /_____/ ____/ / / /_/ /> </ /_/ /
|
||||
/___/_/ /_/ /_/_/ /_/ /_/_/\___/_/ /_/ /_/ /_/ \____/_/|_|\__, /
|
||||
/____/
|
||||
EOF
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# OS DETECTION
|
||||
# ==============================================================================
|
||||
if [[ -f "/etc/alpine-release" ]]; then
|
||||
msg_error "Alpine is not supported for ${APP}. Use Debian."
|
||||
exit 238
|
||||
elif [[ -f "/etc/debian_version" ]]; then
|
||||
OS="Debian"
|
||||
SERVICE_PATH="/etc/systemd/system/immich-proxy.service"
|
||||
else
|
||||
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling ${APP}"
|
||||
systemctl disable --now immich-proxy.service &>/dev/null || true
|
||||
rm -f "$SERVICE_PATH"
|
||||
rm -rf "$INSTALL_PATH"
|
||||
rm -f "/usr/local/bin/update_immich-public-proxy"
|
||||
rm -f "$HOME/.immichpublicproxy"
|
||||
msg_ok "${APP} has been uninstalled"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
if check_for_gh_release "Immich Public Proxy" "alangrainger/immich-public-proxy"; then
|
||||
msg_info "Stopping service"
|
||||
systemctl stop immich-proxy.service &>/dev/null || true
|
||||
msg_ok "Stopped service"
|
||||
|
||||
msg_info "Backing up configuration"
|
||||
cp "$CONFIG_PATH"/.env /tmp/ipp.env.bak 2>/dev/null || true
|
||||
cp "$CONFIG_PATH"/config.json /tmp/ipp.config.json.bak 2>/dev/null || true
|
||||
msg_ok "Backed up configuration"
|
||||
|
||||
NODE_VERSION="24" setup_nodejs
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "Immich Public Proxy" "alangrainger/immich-public-proxy" "tarball" "latest" "$INSTALL_PATH"
|
||||
|
||||
msg_info "Restoring configuration"
|
||||
cp /tmp/ipp.env.bak "$CONFIG_PATH"/.env 2>/dev/null || true
|
||||
cp /tmp/ipp.config.json.bak "$CONFIG_PATH"/config.json 2>/dev/null || true
|
||||
rm -f /tmp/ipp.*.bak
|
||||
msg_ok "Restored configuration"
|
||||
|
||||
msg_info "Installing dependencies"
|
||||
cd "$CONFIG_PATH"
|
||||
$STD npm install
|
||||
msg_ok "Installed dependencies"
|
||||
|
||||
msg_info "Building ${APP}"
|
||||
$STD npm run build
|
||||
msg_ok "Built ${APP}"
|
||||
|
||||
msg_info "Updating service"
|
||||
create_service
|
||||
msg_ok "Updated service"
|
||||
|
||||
msg_info "Starting service"
|
||||
systemctl start immich-proxy
|
||||
msg_ok "Started service"
|
||||
msg_ok "Updated successfully"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
function create_service() {
|
||||
cat <<EOF >"$SERVICE_PATH"
|
||||
[Unit]
|
||||
Description=Immich Public Proxy
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=${INSTALL_PATH}/app
|
||||
EnvironmentFile=${CONFIG_PATH}/.env
|
||||
ExecStart=/usr/bin/node ${INSTALL_PATH}/app/dist/index.js
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
NODE_VERSION="24" setup_nodejs
|
||||
|
||||
# Force fresh download by removing version cache
|
||||
rm -f "$HOME/.immichpublicproxy"
|
||||
fetch_and_deploy_gh_release "Immich Public Proxy" "alangrainger/immich-public-proxy" "tarball" "latest" "$INSTALL_PATH"
|
||||
|
||||
msg_info "Installing dependencies"
|
||||
cd "$CONFIG_PATH"
|
||||
$STD npm install
|
||||
msg_ok "Installed dependencies"
|
||||
|
||||
msg_info "Building ${APP}"
|
||||
$STD npm run build
|
||||
msg_ok "Built ${APP}"
|
||||
|
||||
MAX_ATTEMPTS=3
|
||||
attempt=0
|
||||
while true; do
|
||||
attempt=$((attempt + 1))
|
||||
read -rp "${TAB3}Enter your LOCAL Immich IP or domain (ex. 192.168.1.100 or immich.local.lan): " DOMAIN
|
||||
if [[ -z "$DOMAIN" ]]; then
|
||||
if ((attempt >= MAX_ATTEMPTS)); then
|
||||
DOMAIN="${LOCAL_IP:-localhost}"
|
||||
msg_warn "Using fallback: $DOMAIN"
|
||||
break
|
||||
fi
|
||||
msg_warn "Domain cannot be empty! (Attempt $attempt/$MAX_ATTEMPTS)"
|
||||
elif [[ "$DOMAIN" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||||
valid_ip=true
|
||||
IFS='.' read -ra octets <<<"$DOMAIN"
|
||||
for octet in "${octets[@]}"; do
|
||||
if ((octet > 255)); then
|
||||
valid_ip=false
|
||||
break
|
||||
fi
|
||||
done
|
||||
if $valid_ip; then
|
||||
break
|
||||
else
|
||||
msg_warn "Invalid IP address!"
|
||||
fi
|
||||
elif [[ "$DOMAIN" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$ || "$DOMAIN" == "localhost" ]]; then
|
||||
break
|
||||
else
|
||||
msg_warn "Invalid domain format!"
|
||||
fi
|
||||
done
|
||||
|
||||
msg_info "Creating configuration"
|
||||
cat <<EOF >"$CONFIG_PATH"/.env
|
||||
NODE_ENV=production
|
||||
IMMICH_URL=http://${DOMAIN}:2283
|
||||
EOF
|
||||
chmod 600 "$CONFIG_PATH"/.env
|
||||
msg_ok "Created configuration"
|
||||
|
||||
msg_info "Creating service"
|
||||
create_service
|
||||
systemctl enable -q --now immich-proxy
|
||||
msg_ok "Created and started service"
|
||||
|
||||
# Create update script (simple wrapper that calls this addon with type=update)
|
||||
msg_info "Creating update script"
|
||||
cat <<'UPDATEEOF' >/usr/local/bin/update_immich-public-proxy
|
||||
#!/usr/bin/env bash
|
||||
# Immich Public Proxy Update Script
|
||||
type=update bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/immich-public-proxy.sh)"
|
||||
UPDATEEOF
|
||||
chmod +x /usr/local/bin/update_immich-public-proxy
|
||||
msg_ok "Created update script (/usr/local/bin/update_immich-public-proxy)"
|
||||
|
||||
echo ""
|
||||
msg_ok "${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}"
|
||||
echo ""
|
||||
msg_warn "Additional configuration is available at '/opt/immich-proxy/app/config.json'"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
header_info
|
||||
if [[ -d "$INSTALL_PATH" && -f "$SERVICE_PATH" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
header_info
|
||||
get_lxc_ip
|
||||
|
||||
# Check if already installed
|
||||
if [[ -d "$INSTALL_PATH" && -f "$SERVICE_PATH" ]]; then
|
||||
msg_warn "${APP} is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall ${APP}? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update ${APP}? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "${APP} is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - Node.js 24"
|
||||
echo -e "${TAB} - Immich Public Proxy"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install ${APP}? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,376 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/CyferShepard/Jellystat
|
||||
|
||||
if ! command -v curl &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
APP="Jellystat"
|
||||
APP_TYPE="addon"
|
||||
INSTALL_PATH="/opt/jellystat"
|
||||
CONFIG_PATH="/opt/jellystat/.env"
|
||||
DEFAULT_PORT=3000
|
||||
|
||||
# Initialize all core functions (colors, formatting, icons, STD mode)
|
||||
load_functions
|
||||
init_tool_telemetry "" "addon"
|
||||
|
||||
# ==============================================================================
|
||||
# HEADER
|
||||
# ==============================================================================
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
__ ____ __ __
|
||||
/ /__ / / /_ _______/ /_____ _/ /_
|
||||
__ / / _ \/ / / / / / ___/ __/ __ `/ __/
|
||||
/ /_/ / __/ / / /_/ (__ ) /_/ /_/ / /_
|
||||
\____/\___/_/_/\__, /____/\__/\__,_/\__/
|
||||
/____/
|
||||
EOF
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# OS DETECTION
|
||||
# ==============================================================================
|
||||
if [[ -f "/etc/alpine-release" ]]; then
|
||||
msg_error "Alpine is not supported for ${APP}. Use Debian/Ubuntu."
|
||||
exit 238
|
||||
elif [[ -f "/etc/debian_version" ]]; then
|
||||
OS="Debian"
|
||||
SERVICE_PATH="/etc/systemd/system/jellystat.service"
|
||||
else
|
||||
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling ${APP}"
|
||||
systemctl disable --now jellystat.service &>/dev/null || true
|
||||
rm -f "$SERVICE_PATH"
|
||||
rm -rf "$INSTALL_PATH"
|
||||
rm -f "/usr/local/bin/update_jellystat"
|
||||
rm -f "$HOME/.jellystat"
|
||||
msg_ok "${APP} has been uninstalled"
|
||||
|
||||
# Ask about PostgreSQL database removal
|
||||
echo ""
|
||||
echo -n "${TAB}Also remove PostgreSQL database 'jellystat'? (y/N): "
|
||||
read -r db_prompt
|
||||
if [[ "${db_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
if command -v psql &>/dev/null; then
|
||||
msg_info "Removing PostgreSQL database and user"
|
||||
$STD sudo -u postgres psql -c "DROP DATABASE IF EXISTS jellystat;" &>/dev/null || true
|
||||
$STD sudo -u postgres psql -c "DROP USER IF EXISTS jellystat;" &>/dev/null || true
|
||||
msg_ok "Removed PostgreSQL database 'jellystat' and user 'jellystat'"
|
||||
else
|
||||
msg_warn "PostgreSQL not found - database may have been removed already"
|
||||
fi
|
||||
else
|
||||
msg_warn "PostgreSQL database was NOT removed. Remove manually if needed:"
|
||||
echo -e "${TAB} sudo -u postgres psql -c \"DROP DATABASE jellystat;\""
|
||||
echo -e "${TAB} sudo -u postgres psql -c \"DROP USER jellystat;\""
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
if check_for_gh_release "jellystat" "CyferShepard/Jellystat"; then
|
||||
msg_info "Stopping service"
|
||||
systemctl stop jellystat.service &>/dev/null || true
|
||||
msg_ok "Stopped service"
|
||||
|
||||
msg_info "Backing up configuration"
|
||||
cp "$CONFIG_PATH" /tmp/jellystat.env.bak 2>/dev/null || true
|
||||
msg_ok "Backed up configuration"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "jellystat" "CyferShepard/Jellystat" "tarball" "latest" "$INSTALL_PATH"
|
||||
|
||||
msg_info "Restoring configuration"
|
||||
cp /tmp/jellystat.env.bak "$CONFIG_PATH" 2>/dev/null || true
|
||||
rm -f /tmp/jellystat.env.bak
|
||||
msg_ok "Restored configuration"
|
||||
|
||||
msg_info "Installing dependencies"
|
||||
cd "$INSTALL_PATH"
|
||||
$STD npm install
|
||||
msg_ok "Installed dependencies"
|
||||
|
||||
msg_info "Building ${APP}"
|
||||
$STD npm run build
|
||||
msg_ok "Built ${APP}"
|
||||
|
||||
msg_info "Starting service"
|
||||
systemctl start jellystat
|
||||
msg_ok "Started service"
|
||||
msg_ok "Updated successfully"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
# Setup Node.js (only installs if not present or different version)
|
||||
if command -v node &>/dev/null; then
|
||||
msg_ok "Node.js already installed ($(node -v))"
|
||||
else
|
||||
NODE_VERSION="22" setup_nodejs
|
||||
fi
|
||||
|
||||
# Setup PostgreSQL (only installs if not present)
|
||||
if command -v psql &>/dev/null; then
|
||||
msg_ok "PostgreSQL already installed"
|
||||
else
|
||||
PG_VERSION="17" setup_postgresql
|
||||
fi
|
||||
|
||||
# Create database and user (skip if already exists)
|
||||
local DB_NAME="jellystat"
|
||||
local DB_USER="jellystat"
|
||||
local DB_PASS
|
||||
|
||||
msg_info "Setting up PostgreSQL database"
|
||||
|
||||
# Check if database already exists
|
||||
if sudo -u postgres psql -lqt 2>/dev/null | cut -d \| -f 1 | grep -qw "$DB_NAME"; then
|
||||
msg_warn "Database '${DB_NAME}' already exists - skipping creation"
|
||||
echo -n "${TAB}Enter existing database password for '${DB_USER}': "
|
||||
read -rs DB_PASS
|
||||
echo ""
|
||||
else
|
||||
# Generate new password
|
||||
DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c16)
|
||||
|
||||
# Check if user exists, create if not
|
||||
if sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${DB_USER}'" 2>/dev/null | grep -q 1; then
|
||||
msg_info "User '${DB_USER}' exists, updating password"
|
||||
$STD sudo -u postgres psql -c "ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASS}';" || {
|
||||
msg_error "Failed to update PostgreSQL user"
|
||||
return 1
|
||||
}
|
||||
else
|
||||
$STD sudo -u postgres psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASS}';" || {
|
||||
msg_error "Failed to create PostgreSQL user"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
|
||||
# Create database (use template0 for UTF8 encoding compatibility)
|
||||
$STD sudo -u postgres psql -c "CREATE DATABASE ${DB_NAME} WITH OWNER ${DB_USER} ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE template0;" || {
|
||||
msg_error "Failed to create PostgreSQL database"
|
||||
return 1
|
||||
}
|
||||
$STD sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};" || {
|
||||
msg_error "Failed to grant privileges"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Grant schema permissions (required for PostgreSQL 15+)
|
||||
$STD sudo -u postgres psql -d "${DB_NAME}" -c "GRANT ALL ON SCHEMA public TO ${DB_USER};" || true
|
||||
|
||||
# Configure pg_hba.conf for password authentication on localhost
|
||||
local PG_HBA
|
||||
PG_HBA=$(sudo -u postgres psql -tAc "SHOW hba_file;" 2>/dev/null | tr -d ' ')
|
||||
if [[ -n "$PG_HBA" && -f "$PG_HBA" ]]; then
|
||||
# Check if md5/scram-sha-256 auth is already configured for local connections
|
||||
if ! grep -qE "^host\s+${DB_NAME}\s+${DB_USER}\s+127.0.0.1" "$PG_HBA"; then
|
||||
msg_info "Configuring PostgreSQL authentication"
|
||||
# Add password auth for jellystat user on localhost (before the default rules)
|
||||
sed -i "/^# IPv4 local connections:/a host ${DB_NAME} ${DB_USER} 127.0.0.1/32 scram-sha-256" "$PG_HBA"
|
||||
sed -i "/^# IPv4 local connections:/a host ${DB_NAME} ${DB_USER} ::1/128 scram-sha-256" "$PG_HBA"
|
||||
# Reload PostgreSQL to apply changes
|
||||
systemctl reload postgresql
|
||||
msg_ok "Configured PostgreSQL authentication"
|
||||
fi
|
||||
fi
|
||||
|
||||
msg_ok "Created PostgreSQL database '${DB_NAME}'"
|
||||
fi
|
||||
|
||||
# Generate JWT Secret
|
||||
local JWT_SECRET
|
||||
JWT_SECRET=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c32)
|
||||
|
||||
# Force fresh download by removing version cache
|
||||
rm -f "$HOME/.jellystat"
|
||||
fetch_and_deploy_gh_release "jellystat" "CyferShepard/Jellystat" "tarball" "latest" "$INSTALL_PATH"
|
||||
|
||||
msg_info "Installing dependencies"
|
||||
cd "$INSTALL_PATH"
|
||||
$STD npm install
|
||||
msg_ok "Installed dependencies"
|
||||
|
||||
msg_info "Building ${APP}"
|
||||
$STD npm run build
|
||||
msg_ok "Built ${APP}"
|
||||
|
||||
msg_info "Creating configuration"
|
||||
cat <<EOF >"$CONFIG_PATH"
|
||||
# Jellystat Configuration
|
||||
# Database
|
||||
POSTGRES_USER=${DB_USER}
|
||||
POSTGRES_PASSWORD=${DB_PASS}
|
||||
POSTGRES_IP=localhost
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_DB=${DB_NAME}
|
||||
|
||||
# Security
|
||||
JWT_SECRET=${JWT_SECRET}
|
||||
|
||||
# Server
|
||||
JS_LISTEN_IP=0.0.0.0
|
||||
JS_BASE_URL=/
|
||||
TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
|
||||
|
||||
# Optional: GeoLite for IP Geolocation
|
||||
# JS_GEOLITE_ACCOUNT_ID=
|
||||
# JS_GEOLITE_LICENSE_KEY=
|
||||
|
||||
# Optional: Master Override (if you forget your password)
|
||||
# JS_USER=admin
|
||||
# JS_PASSWORD=admin
|
||||
|
||||
# Optional: Minimum playback duration to record (seconds)
|
||||
# MINIMUM_SECONDS_TO_INCLUDE_PLAYBACK=1
|
||||
|
||||
# Optional: Self-signed certificates
|
||||
REJECT_SELF_SIGNED_CERTIFICATES=true
|
||||
EOF
|
||||
chmod 600 "$CONFIG_PATH"
|
||||
msg_ok "Created configuration"
|
||||
|
||||
msg_info "Creating service"
|
||||
cat <<EOF >"$SERVICE_PATH"
|
||||
[Unit]
|
||||
Description=Jellystat - Statistics for Jellyfin
|
||||
After=network.target postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=${INSTALL_PATH}/backend
|
||||
EnvironmentFile=${CONFIG_PATH}
|
||||
ExecStart=/usr/bin/node ${INSTALL_PATH}/backend/server.js
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable --now jellystat &>/dev/null
|
||||
msg_ok "Created and started service"
|
||||
|
||||
# Create update script (simple wrapper that calls this addon with type=update)
|
||||
msg_info "Creating update script"
|
||||
cat <<'UPDATEEOF' >/usr/local/bin/update_jellystat
|
||||
#!/usr/bin/env bash
|
||||
# Jellystat Update Script
|
||||
type=update bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/jellystat.sh)"
|
||||
UPDATEEOF
|
||||
chmod +x /usr/local/bin/update_jellystat
|
||||
msg_ok "Created update script (/usr/local/bin/update_jellystat)"
|
||||
|
||||
# Save credentials
|
||||
local CREDS_FILE="/root/jellystat.creds"
|
||||
cat <<EOF >"$CREDS_FILE"
|
||||
Jellystat Credentials
|
||||
=====================
|
||||
Database User: ${DB_USER}
|
||||
Database Password: ${DB_PASS}
|
||||
Database Name: ${DB_NAME}
|
||||
JWT Secret: ${JWT_SECRET}
|
||||
|
||||
Web UI: http://${LOCAL_IP}:${DEFAULT_PORT}
|
||||
EOF
|
||||
chmod 600 "$CREDS_FILE"
|
||||
|
||||
echo ""
|
||||
msg_ok "${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}"
|
||||
msg_ok "Credentials saved to: ${BL}${CREDS_FILE}${CL}"
|
||||
echo ""
|
||||
msg_warn "On first access, you'll need to configure your Jellyfin server connection."
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
header_info
|
||||
if [[ -d "$INSTALL_PATH" && -f "$INSTALL_PATH/package.json" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
header_info
|
||||
get_lxc_ip
|
||||
|
||||
# Check if already installed
|
||||
if [[ -d "$INSTALL_PATH" && -f "$INSTALL_PATH/package.json" ]]; then
|
||||
msg_warn "${APP} is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall ${APP}? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update ${APP}? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "${APP} is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - Node.js 22"
|
||||
echo -e "${TAB} - PostgreSQL 17"
|
||||
echo -e "${TAB} - Jellystat"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install ${APP}? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,293 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://komo.do/ | Github: https://github.com/mbecker20/komodo
|
||||
if ! command -v curl &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||
apt-get update >/dev/null 2>&1 || apk update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1 || apk add --no-cache curl >/dev/null 2>&1
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
APP="Komodo"
|
||||
APP_TYPE="addon"
|
||||
INSTALL_PATH="/opt/komodo"
|
||||
COMPOSE_ENV="${INSTALL_PATH}/compose.env"
|
||||
DEFAULT_PORT=9120
|
||||
|
||||
# Initialize all core functions (colors, formatting, icons, STD mode)
|
||||
load_functions
|
||||
|
||||
# ==============================================================================
|
||||
# HELPERS
|
||||
# ==============================================================================
|
||||
function find_compose_file() {
|
||||
COMPOSE_FILE=$(find "$INSTALL_PATH" -maxdepth 1 -type f -name '*.compose.yaml' ! -name 'compose.env' | head -n1)
|
||||
if [[ -z "${COMPOSE_FILE:-}" ]]; then
|
||||
msg_error "No valid compose file found in ${INSTALL_PATH}!"
|
||||
exit 233
|
||||
fi
|
||||
COMPOSE_BASENAME=$(basename "$COMPOSE_FILE")
|
||||
}
|
||||
|
||||
function check_legacy_db() {
|
||||
if [[ "$COMPOSE_BASENAME" == "sqlite.compose.yaml" || "$COMPOSE_BASENAME" == "postgres.compose.yaml" ]]; then
|
||||
msg_error "Detected outdated Komodo setup using SQLite or PostgreSQL (FerretDB v1)."
|
||||
echo -e "${YW}This configuration is no longer supported since Komodo v1.18.0.${CL}"
|
||||
echo -e "${YW}Please follow the migration guide:${CL}"
|
||||
echo -e "${BGN}https://github.com/community-scripts/ProxmoxVE/discussions/5689${CL}\n"
|
||||
exit 238
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling ${APP}"
|
||||
|
||||
find_compose_file
|
||||
msg_info "Stopping and removing Docker containers"
|
||||
cd "$INSTALL_PATH"
|
||||
$STD docker compose -p komodo -f "$COMPOSE_FILE" --env-file "$COMPOSE_ENV" down --volumes --remove-orphans
|
||||
msg_ok "Stopped and removed Docker containers"
|
||||
|
||||
rm -rf "$INSTALL_PATH"
|
||||
msg_ok "${APP} has been uninstalled"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
find_compose_file
|
||||
check_legacy_db
|
||||
|
||||
msg_info "Updating ${APP}"
|
||||
BACKUP_FILE="${INSTALL_PATH}/${COMPOSE_BASENAME}.bak_$(date +%Y%m%d_%H%M%S)"
|
||||
cp "$COMPOSE_FILE" "$BACKUP_FILE" || {
|
||||
msg_error "Failed to create backup of ${COMPOSE_BASENAME}!"
|
||||
exit 235
|
||||
}
|
||||
|
||||
GITHUB_URL="https://raw.githubusercontent.com/moghtech/komodo/main/compose/${COMPOSE_BASENAME}"
|
||||
if ! curl -fsSL "$GITHUB_URL" -o "$COMPOSE_FILE"; then
|
||||
msg_error "Failed to download ${COMPOSE_BASENAME} from GitHub!"
|
||||
mv "$BACKUP_FILE" "$COMPOSE_FILE"
|
||||
exit 115
|
||||
fi
|
||||
|
||||
if ! grep -qxF 'COMPOSE_KOMODO_BACKUPS_PATH=/etc/komodo/backups' "$COMPOSE_ENV"; then
|
||||
sed -i '/^COMPOSE_KOMODO_IMAGE_TAG=latest$/a COMPOSE_KOMODO_BACKUPS_PATH=/etc/komodo/backups' "$COMPOSE_ENV"
|
||||
fi
|
||||
|
||||
$STD docker compose -p komodo -f "$COMPOSE_FILE" --env-file "$COMPOSE_ENV" pull
|
||||
$STD docker compose -p komodo -f "$COMPOSE_FILE" --env-file "$COMPOSE_ENV" up -d
|
||||
msg_ok "Updated ${APP}"
|
||||
|
||||
msg_ok "Updated successfully"
|
||||
exit
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# PROXMOX HOST CHECK
|
||||
# ==============================================================================
|
||||
function check_proxmox_host() {
|
||||
if command -v pveversion &>/dev/null; then
|
||||
msg_error "Running on the Proxmox host is NOT recommended!"
|
||||
msg_error "This should be executed inside an LXC container."
|
||||
echo ""
|
||||
echo -n "${TAB}Continue anyway? (y/N): "
|
||||
read -r confirm
|
||||
if [[ ! "${confirm,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_warn "Aborted. Please run this inside an LXC container."
|
||||
exit 0
|
||||
fi
|
||||
msg_warn "Proceeding on Proxmox host at your own risk!"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# CHECK / INSTALL DOCKER
|
||||
# ==============================================================================
|
||||
function check_or_install_docker() {
|
||||
if command -v docker &>/dev/null; then
|
||||
msg_ok "Docker $(docker --version | cut -d' ' -f3 | tr -d ',') is available"
|
||||
if docker compose version &>/dev/null; then
|
||||
msg_ok "Docker Compose is available"
|
||||
else
|
||||
msg_error "Docker Compose plugin is not available. Please install it."
|
||||
exit 10
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
msg_warn "Docker is not installed."
|
||||
echo -n "${TAB}Install Docker now? (y/N): "
|
||||
read -r install_docker_prompt
|
||||
if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_error "Docker is required for ${APP}. Exiting."
|
||||
exit 10
|
||||
fi
|
||||
|
||||
msg_info "Installing Docker"
|
||||
if [[ -f /etc/alpine-release ]]; then
|
||||
$STD apk add docker docker-cli-compose
|
||||
$STD rc-service docker start
|
||||
$STD rc-update add docker default
|
||||
else
|
||||
DOCKER_CONFIG_PATH='/etc/docker/daemon.json'
|
||||
mkdir -p "$(dirname "$DOCKER_CONFIG_PATH")"
|
||||
echo -e '{\n "log-driver": "journald"\n}' >"$DOCKER_CONFIG_PATH"
|
||||
$STD sh <(curl -fsSL https://get.docker.com)
|
||||
fi
|
||||
msg_ok "Installed Docker"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
check_or_install_docker
|
||||
|
||||
echo -e "${TAB}Choose the database for Komodo:"
|
||||
echo -e "${TAB} 1) MongoDB (recommended)"
|
||||
echo -e "${TAB} 2) FerretDB"
|
||||
echo -n "${TAB}Enter your choice (default: 1): "
|
||||
read -r DB_CHOICE
|
||||
DB_CHOICE=${DB_CHOICE:-1}
|
||||
|
||||
case $DB_CHOICE in
|
||||
1) DB_COMPOSE_FILE="mongo.compose.yaml" ;;
|
||||
2) DB_COMPOSE_FILE="ferretdb.compose.yaml" ;;
|
||||
*)
|
||||
msg_warn "Invalid choice. Defaulting to MongoDB."
|
||||
DB_COMPOSE_FILE="mongo.compose.yaml"
|
||||
;;
|
||||
esac
|
||||
|
||||
msg_info "Creating install directory"
|
||||
mkdir -p "$INSTALL_PATH"
|
||||
msg_ok "Created ${INSTALL_PATH}"
|
||||
|
||||
msg_info "Downloading Docker Compose file"
|
||||
curl -fsSL "https://raw.githubusercontent.com/moghtech/komodo/main/compose/$DB_COMPOSE_FILE" -o "${INSTALL_PATH}/${DB_COMPOSE_FILE}"
|
||||
msg_ok "Downloaded ${DB_COMPOSE_FILE}"
|
||||
|
||||
msg_info "Configuring environment"
|
||||
curl -fsSL "https://raw.githubusercontent.com/moghtech/komodo/main/compose/compose.env" -o "$COMPOSE_ENV"
|
||||
|
||||
DB_PASSWORD=$(openssl rand -base64 16 | tr -d '/+=')
|
||||
ADMIN_PASSWORD=$(openssl rand -base64 8 | tr -d '/+=')
|
||||
PASSKEY=$(openssl rand -base64 24 | tr -d '/+=')
|
||||
WEBHOOK_SECRET=$(openssl rand -base64 24 | tr -d '/+=')
|
||||
JWT_SECRET=$(openssl rand -base64 24 | tr -d '/+=')
|
||||
|
||||
sed -i "s/^KOMODO_DB_USERNAME=.*/KOMODO_DB_USERNAME=komodo_admin/" "$COMPOSE_ENV"
|
||||
sed -i "s/^KOMODO_DB_PASSWORD=.*/KOMODO_DB_PASSWORD=${DB_PASSWORD}/" "$COMPOSE_ENV"
|
||||
sed -i "s/^KOMODO_INIT_ADMIN_PASSWORD=changeme/KOMODO_INIT_ADMIN_PASSWORD=${ADMIN_PASSWORD}/" "$COMPOSE_ENV"
|
||||
sed -i "s/^KOMODO_PASSKEY=.*/KOMODO_PASSKEY=${PASSKEY}/" "$COMPOSE_ENV"
|
||||
sed -i "s/^KOMODO_WEBHOOK_SECRET=.*/KOMODO_WEBHOOK_SECRET=${WEBHOOK_SECRET}/" "$COMPOSE_ENV"
|
||||
sed -i "s/^KOMODO_JWT_SECRET=.*/KOMODO_JWT_SECRET=${JWT_SECRET}/" "$COMPOSE_ENV"
|
||||
msg_ok "Configured environment"
|
||||
|
||||
msg_info "Starting ${APP}"
|
||||
cd "$INSTALL_PATH"
|
||||
$STD docker compose -p komodo -f "${INSTALL_PATH}/${DB_COMPOSE_FILE}" --env-file "$COMPOSE_ENV" up -d
|
||||
msg_ok "Started ${APP}"
|
||||
|
||||
{
|
||||
echo "Komodo Credentials"
|
||||
echo ""
|
||||
echo "Admin User : admin"
|
||||
echo "Admin Password: $ADMIN_PASSWORD"
|
||||
} >>~/komodo.creds
|
||||
|
||||
echo ""
|
||||
msg_ok "${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}"
|
||||
echo ""
|
||||
echo -e " Komodo Credentials"
|
||||
echo -e " =================="
|
||||
echo -e " User : admin"
|
||||
echo -e " Password: ${ADMIN_PASSWORD}"
|
||||
echo ""
|
||||
msg_info "Credentials saved to ~/komodo.creds"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
header_info
|
||||
COMPOSE_FILE=""
|
||||
COMPOSE_BASENAME=""
|
||||
if [[ -d "$INSTALL_PATH" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
header_info
|
||||
check_proxmox_host
|
||||
get_lxc_ip
|
||||
|
||||
# Declare variables used by find_compose_file
|
||||
COMPOSE_FILE=""
|
||||
COMPOSE_BASENAME=""
|
||||
|
||||
# Check if already installed
|
||||
if [[ -d "$INSTALL_PATH" ]]; then
|
||||
msg_warn "${APP} is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall ${APP}? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update ${APP}? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "${APP} is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - Komodo (via Docker Compose)"
|
||||
echo -e "${TAB} - MongoDB or FerretDB (your choice)"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install ${APP}? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,175 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 tteck
|
||||
# Author: tteck (tteckster)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://www.netdata.cloud/ | Github: https://github.com/netdata/netdata
|
||||
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
_ __ __ ____ __
|
||||
/ | / /__ / /_/ __ \____ _/ /_____ _
|
||||
/ |/ / _ \/ __/ / / / __ `/ __/ __ `/
|
||||
/ /| / __/ /_/ /_/ / /_/ / /_/ /_/ /
|
||||
/_/ |_/\___/\__/_____/\__,_/\__/\__,_/
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
YW=$(echo "\033[33m")
|
||||
BL=$(echo "\033[36m")
|
||||
RD=$(echo "\033[01;31m")
|
||||
GN=$(echo "\033[1;92m")
|
||||
CL=$(echo "\033[m")
|
||||
BFR="\\r\\033[K"
|
||||
HOLD="-"
|
||||
CM="${GN}✓${CL}"
|
||||
silent() { "$@" >/dev/null 2>&1; }
|
||||
|
||||
# Telemetry
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "netdata" "addon"
|
||||
|
||||
set -e
|
||||
header_info
|
||||
echo "Loading..."
|
||||
function msg_info() {
|
||||
local msg="$1"
|
||||
echo -ne " ${HOLD} ${YW}${msg}..."
|
||||
}
|
||||
|
||||
function msg_ok() {
|
||||
local msg="$1"
|
||||
echo -e "${BFR} ${CM} ${GN}${msg}${CL}"
|
||||
}
|
||||
|
||||
function msg_error() { echo -e "${RD}✗ $1${CL}"; }
|
||||
|
||||
# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.
|
||||
# Supported: Proxmox VE 8.0.x – 8.9.x and 9.0–9.1.x
|
||||
pve_check() {
|
||||
local PVE_VER
|
||||
PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')"
|
||||
|
||||
# Check for Proxmox VE 8.x: allow 8.0–8.9
|
||||
if [[ "$PVE_VER" =~ ^8\.([0-9]+) ]]; then
|
||||
local MINOR="${BASH_REMATCH[1]}"
|
||||
if ((MINOR < 0 || MINOR > 9)); then
|
||||
msg_error "This version of Proxmox VE is not supported."
|
||||
msg_error "Supported: Proxmox VE version 8.0 – 8.9"
|
||||
exit 105
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check for Proxmox VE 9.x: allow 9.0–9.1.x
|
||||
if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then
|
||||
local MINOR="${BASH_REMATCH[1]}"
|
||||
if ((MINOR < 0 || MINOR > 1)); then
|
||||
msg_error "This version of Proxmox VE is not yet supported."
|
||||
msg_error "Supported: Proxmox VE version 9.0–9.1.x"
|
||||
exit 105
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
# All other unsupported versions
|
||||
msg_error "This version of Proxmox VE is not supported."
|
||||
msg_error "Supported versions: Proxmox VE 8.0 – 8.9 or 9.0–9.1.x"
|
||||
exit 105
|
||||
}
|
||||
|
||||
detect_codename() {
|
||||
source /etc/os-release
|
||||
if [[ "$ID" != "debian" ]]; then
|
||||
msg_error "Unsupported base OS: $ID (only Proxmox VE / Debian supported)."
|
||||
exit 238
|
||||
fi
|
||||
CODENAME="${VERSION_CODENAME:-}"
|
||||
if [[ -z "$CODENAME" ]]; then
|
||||
msg_error "Could not detect Debian codename."
|
||||
exit 71
|
||||
fi
|
||||
echo "$CODENAME"
|
||||
}
|
||||
|
||||
get_latest_repo_pkg() {
|
||||
local REPO_URL=$1
|
||||
curl -fsSL "$REPO_URL" |
|
||||
grep -oP 'netdata-repo_[^"]+all\.deb' |
|
||||
sort -V |
|
||||
tail -n1
|
||||
}
|
||||
|
||||
install() {
|
||||
header_info
|
||||
while true; do
|
||||
read -p "Are you sure you want to install NetData on Proxmox VE host. Proceed(y/n)? " yn
|
||||
case $yn in
|
||||
[Yy]*) break ;;
|
||||
[Nn]*) exit ;;
|
||||
*) echo "Please answer yes or no." ;;
|
||||
esac
|
||||
done
|
||||
|
||||
read -r -p "Verbose mode? <y/N> " prompt
|
||||
[[ ${prompt,,} =~ ^(y|yes)$ ]] && STD="" || STD="silent"
|
||||
|
||||
CODENAME=$(detect_codename)
|
||||
REPO_URL="https://repo.netdata.cloud/repos/repoconfig/debian/${CODENAME}/"
|
||||
|
||||
msg_info "Setting up repository"
|
||||
$STD apt-get install -y debian-keyring
|
||||
PKG=$(get_latest_repo_pkg "$REPO_URL")
|
||||
if [[ -z "$PKG" ]]; then
|
||||
msg_error "Could not find netdata-repo package for Debian $CODENAME"
|
||||
exit 237
|
||||
fi
|
||||
curl -fsSL "${REPO_URL}${PKG}" -o "$PKG"
|
||||
$STD dpkg -i "$PKG"
|
||||
rm -f "$PKG"
|
||||
msg_ok "Set up repository"
|
||||
|
||||
msg_info "Installing Netdata"
|
||||
$STD apt-get update
|
||||
$STD apt-get install -y netdata
|
||||
msg_ok "Installed Netdata"
|
||||
msg_ok "Completed successfully!\n"
|
||||
echo -e "\n Netdata should be reachable at${BL} http://$(hostname -I | awk '{print $1}'):19999 ${CL}\n"
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
header_info
|
||||
read -r -p "Verbose mode? <y/N> " prompt
|
||||
[[ ${prompt,,} =~ ^(y|yes)$ ]] && STD="" || STD="silent"
|
||||
|
||||
msg_info "Uninstalling Netdata"
|
||||
systemctl stop netdata || true
|
||||
rm -rf /var/log/netdata /var/lib/netdata /var/cache/netdata /etc/netdata/go.d
|
||||
rm -rf /etc/apt/trusted.gpg.d/netdata-archive-keyring.gpg /etc/apt/sources.list.d/netdata.list
|
||||
$STD apt-get remove --purge -y netdata netdata-repo
|
||||
systemctl daemon-reload
|
||||
$STD apt autoremove -y
|
||||
$STD userdel netdata || true
|
||||
msg_ok "Uninstalled Netdata"
|
||||
msg_ok "Completed successfully!\n"
|
||||
}
|
||||
|
||||
header_info
|
||||
pve_check
|
||||
|
||||
OPTIONS=(Install "Install NetData on Proxmox VE"
|
||||
Uninstall "Uninstall NetData from Proxmox VE")
|
||||
|
||||
CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "NetData" \
|
||||
--menu "Select an option:" 10 58 2 "${OPTIONS[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
case $CHOICE in
|
||||
"Install") install ;;
|
||||
"Uninstall") uninstall ;;
|
||||
*)
|
||||
echo "Exiting..."
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
@@ -1,216 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: CrazyWolf13
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/xperimental/nextcloud-exporter
|
||||
|
||||
if ! command -v curl &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
load_functions
|
||||
init_tool_telemetry "" "addon"
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
VERBOSE=${var_verbose:-no}
|
||||
APP="nextcloud-exporter"
|
||||
APP_TYPE="tools"
|
||||
BINARY_PATH="/usr/bin/nextcloud-exporter"
|
||||
CONFIG_PATH="/etc/nextcloud-exporter.env"
|
||||
SERVICE_PATH="/etc/systemd/system/nextcloud-exporter.service"
|
||||
|
||||
# ==============================================================================
|
||||
# OS DETECTION
|
||||
# ==============================================================================
|
||||
if ! grep -qE 'ID=debian|ID=ubuntu' /etc/os-release 2>/dev/null; then
|
||||
echo -e "${CROSS} Unsupported OS detected. This script only supports Debian and Ubuntu."
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling Nextcloud-Exporter"
|
||||
systemctl disable -q --now nextcloud-exporter
|
||||
rm -f "$SERVICE_PATH"
|
||||
|
||||
if dpkg -l | grep -q nextcloud-exporter; then
|
||||
$STD apt-get remove -y nextcloud-exporter || $STD dpkg -r nextcloud-exporter
|
||||
fi
|
||||
|
||||
rm -f "$CONFIG_PATH"
|
||||
rm -f "/usr/local/bin/update_nextcloud-exporter"
|
||||
rm -f "$HOME/.nextcloud-exporter"
|
||||
msg_ok "Nextcloud-Exporter has been uninstalled"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
if check_for_gh_release "nextcloud-exporter" "xperimental/nextcloud-exporter"; then
|
||||
msg_info "Stopping service"
|
||||
systemctl stop nextcloud-exporter
|
||||
msg_ok "Stopped service"
|
||||
|
||||
fetch_and_deploy_gh_release "nextcloud-exporter" "xperimental/nextcloud-exporter" "binary" "latest"
|
||||
|
||||
msg_info "Starting service"
|
||||
systemctl start nextcloud-exporter
|
||||
msg_ok "Started service"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
read -erp "Enter URL of Nextcloud, example: (http://127.0.0.1:8080): " NEXTCLOUD_SERVER
|
||||
read -rsp "Enter Nextcloud auth token (press Enter to use username/password instead): " NEXTCLOUD_AUTH_TOKEN
|
||||
printf "\n"
|
||||
|
||||
if [[ -z "$NEXTCLOUD_AUTH_TOKEN" ]]; then
|
||||
read -erp "Enter Nextcloud username: " NEXTCLOUD_USERNAME
|
||||
read -rsp "Enter Nextcloud password: " NEXTCLOUD_PASSWORD
|
||||
printf "\n"
|
||||
fi
|
||||
|
||||
read -erp "Query additional info for apps? [Y/n]: " QUERY_APPS
|
||||
if [[ "${QUERY_APPS,,}" =~ ^(n|no)$ ]]; then
|
||||
NEXTCLOUD_INFO_APPS="false"
|
||||
fi
|
||||
|
||||
read -erp "Query update information? [Y/n]: " QUERY_UPDATES
|
||||
if [[ "${QUERY_UPDATES,,}" =~ ^(n|no)$ ]]; then
|
||||
NEXTCLOUD_INFO_UPDATE="false"
|
||||
fi
|
||||
|
||||
read -erp "Do you want to skip TLS-Verification (if using a self-signed Certificate on Nextcloud) [y/N]: " SKIP_TLS
|
||||
if [[ "${SKIP_TLS,,}" =~ ^(y|yes)$ ]]; then
|
||||
NEXTCLOUD_TLS_SKIP_VERIFY="true"
|
||||
fi
|
||||
|
||||
fetch_and_deploy_gh_release "nextcloud-exporter" "xperimental/nextcloud-exporter" "binary" "latest"
|
||||
|
||||
msg_info "Creating configuration"
|
||||
cat <<EOF >"$CONFIG_PATH"
|
||||
# https://github.com/xperimental/nextcloud-exporter
|
||||
NEXTCLOUD_SERVER="${NEXTCLOUD_SERVER}"
|
||||
NEXTCLOUD_AUTH_TOKEN="${NEXTCLOUD_AUTH_TOKEN:-}"
|
||||
NEXTCLOUD_USERNAME="${NEXTCLOUD_USERNAME:-}"
|
||||
NEXTCLOUD_PASSWORD="${NEXTCLOUD_PASSWORD:-}"
|
||||
NEXTCLOUD_INFO_UPDATE=${NEXTCLOUD_INFO_UPDATE:-"true"}
|
||||
NEXTCLOUD_INFO_APPS=${NEXTCLOUD_INFO_APPS:-"true"}
|
||||
NEXTCLOUD_TLS_SKIP_VERIFY=${NEXTCLOUD_TLS_SKIP_VERIFY:-"false"}
|
||||
NEXTCLOUD_LISTEN_ADDRESS=":9205"
|
||||
EOF
|
||||
msg_ok "Created configuration"
|
||||
|
||||
msg_info "Creating service"
|
||||
cat <<EOF >"$SERVICE_PATH"
|
||||
[Unit]
|
||||
Description=nextcloud-exporter
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
EnvironmentFile=$CONFIG_PATH
|
||||
ExecStart=$BINARY_PATH
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
systemctl enable -q --now nextcloud-exporter
|
||||
msg_ok "Created and started service"
|
||||
|
||||
# Create update script
|
||||
msg_info "Creating update script"
|
||||
ensure_usr_local_bin_persist
|
||||
cat <<'UPDATEEOF' >/usr/local/bin/update_nextcloud-exporter
|
||||
#!/usr/bin/env bash
|
||||
# nextcloud-exporter Update Script
|
||||
type=update bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/nextcloud-exporter.sh)"
|
||||
UPDATEEOF
|
||||
chmod +x /usr/local/bin/update_nextcloud-exporter
|
||||
msg_ok "Created update script (/usr/local/bin/update_nextcloud-exporter)"
|
||||
|
||||
echo ""
|
||||
msg_ok "Nextcloud-Exporter installed successfully"
|
||||
msg_ok "Metrics: ${BL}http://${LOCAL_IP}:9205/metrics${CL}"
|
||||
msg_ok "Config: ${BL}${CONFIG_PATH}${CL}"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
header_info
|
||||
ensure_usr_local_bin_persist
|
||||
get_lxc_ip
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
if [[ -f "$BINARY_PATH" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "Nextcloud-Exporter is not installed. Nothing to update."
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if already installed
|
||||
if [[ -f "$BINARY_PATH" ]]; then
|
||||
msg_warn "Nextcloud-Exporter is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall Nextcloud-Exporter? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update Nextcloud-Exporter? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "Nextcloud-Exporter is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - Nextcloud Exporter (binary)"
|
||||
echo -e "${TAB} - Systemd service"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install Nextcloud-Exporter? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 tteck
|
||||
# Author: tteck (tteckster)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://docs.olivetin.app/ | Github: https://github.com/OliveTin/OliveTin
|
||||
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
____ ___ _______
|
||||
/ __ \/ (_) _____/_ __(_)___
|
||||
/ / / / / / | / / _ \/ / / / __ \
|
||||
/ /_/ / / /| |/ / __/ / / / / / /
|
||||
\____/_/_/ |___/\___/_/ /_/_/ /_/
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
IP=$(hostname -I | awk '{print $1}')
|
||||
YW=$(echo "\033[33m")
|
||||
BL=$(echo "\033[36m")
|
||||
GN=$(echo "\033[1;92m")
|
||||
CL=$(echo "\033[m")
|
||||
BFR="\\r\\033[K"
|
||||
HOLD="-"
|
||||
CM="${GN}✓${CL}"
|
||||
APP="OliveTin"
|
||||
hostname="$(hostname)"
|
||||
|
||||
# Telemetry
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "olivetin" "addon"
|
||||
|
||||
set-e
|
||||
header_info
|
||||
|
||||
while true; do
|
||||
read -p "This will Install ${APP} on $hostname. Proceed(y/n)?" yn
|
||||
case $yn in
|
||||
[Yy]*) break ;;
|
||||
[Nn]*) exit ;;
|
||||
*) echo "Please answer yes or no." ;;
|
||||
esac
|
||||
done
|
||||
header_info
|
||||
|
||||
function msg_info() {
|
||||
local msg="$1"
|
||||
echo -ne " ${HOLD} ${YW}${msg}..."
|
||||
}
|
||||
|
||||
function msg_ok() {
|
||||
local msg="$1"
|
||||
echo -e "${BFR} ${CM} ${GN}${msg}${CL}"
|
||||
}
|
||||
|
||||
msg_info "Installing ${APP}"
|
||||
if ! command -v curl &>/dev/null; then
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
curl -fsSL "https://github.com/OliveTin/OliveTin/releases/latest/download/OliveTin_linux_amd64.deb" -o $(basename "https://github.com/OliveTin/OliveTin/releases/latest/download/OliveTin_linux_amd64.deb")
|
||||
dpkg -i OliveTin_linux_amd64.deb &>/dev/null
|
||||
systemctl enable --now OliveTin &>/dev/null
|
||||
rm OliveTin_linux_amd64.deb
|
||||
msg_ok "Installed ${APP} on $hostname"
|
||||
|
||||
msg_ok "Completed successfully!\n"
|
||||
echo -e "${APP} should be reachable by going to the following URL.
|
||||
${BL}http://$IP:1337${CL} \n"
|
||||
@@ -1,303 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://www.phpmyadmin.net/ | Github: https://github.com/phpmyadmin/phpmyadmin
|
||||
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
____ __ __ ___ ___ __ _
|
||||
/ __ \/ /_ ____ / |/ /_ __/ | ____/ /___ ___ (_)___
|
||||
/ /_/ / __ \/ __ \/ /|_/ / / / / /| |/ __ / __ `__ \/ / __ \
|
||||
/ ____/ / / / /_/ / / / / /_/ / ___ / /_/ / / / / / / / / / /
|
||||
/_/ /_/ /_/ .___/_/ /_/\__, /_/ |_\__,_/_/ /_/ /_/_/_/ /_/
|
||||
/_/ /____/
|
||||
EOF
|
||||
}
|
||||
|
||||
YW=$(echo "\033[33m")
|
||||
GN=$(echo "\033[1;92m")
|
||||
RD=$(echo "\033[01;31m")
|
||||
BL=$(echo "\033[36m")
|
||||
CL=$(echo "\033[m")
|
||||
CM="${GN}✔️${CL}"
|
||||
CROSS="${RD}✖️${CL}"
|
||||
INFO="${BL}ℹ️${CL}"
|
||||
|
||||
APP="phpMyAdmin"
|
||||
INSTALL_DIR_DEBIAN="/var/www/html/phpMyAdmin"
|
||||
INSTALL_DIR_ALPINE="/usr/share/phpmyadmin"
|
||||
|
||||
# Telemetry
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "phpmyadmin" "addon"
|
||||
|
||||
IFACE=$(ip -4 route | awk '/default/ {print $5; exit}')
|
||||
IP=$(ip -4 addr show "$IFACE" | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1)
|
||||
[[ -z "$IP" ]] && IP=$(hostname -I | awk '{print $1}')
|
||||
[[ -z "$IP" ]] && IP="127.0.0.1"
|
||||
|
||||
# Detect OS
|
||||
if [[ -f "/etc/alpine-release" ]]; then
|
||||
OS="Alpine"
|
||||
PKG_MANAGER_INSTALL="apk add --no-cache"
|
||||
PKG_QUERY="apk info -e"
|
||||
INSTALL_DIR="$INSTALL_DIR_ALPINE"
|
||||
elif [[ -f "/etc/debian_version" ]]; then
|
||||
OS="Debian"
|
||||
PKG_MANAGER_INSTALL="apt-get install -y"
|
||||
PKG_QUERY="dpkg -l"
|
||||
INSTALL_DIR="$INSTALL_DIR_DEBIAN"
|
||||
else
|
||||
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||
exit 238
|
||||
fi
|
||||
|
||||
header_info
|
||||
|
||||
function msg_info() { echo -e "${INFO} ${YW}${1}...${CL}"; }
|
||||
function msg_ok() { echo -e "${CM} ${GN}${1}${CL}"; }
|
||||
function msg_error() { echo -e "${CROSS} ${RD}${1}${CL}"; }
|
||||
|
||||
function check_internet() {
|
||||
if ! command -v curl &>/dev/null; then
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
msg_info "Checking Internet connectivity to GitHub"
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://github.com)
|
||||
if [[ "$HTTP_CODE" -ge 200 && "$HTTP_CODE" -lt 400 ]]; then
|
||||
msg_ok "Internet connectivity OK"
|
||||
else
|
||||
msg_error "Internet connectivity or GitHub unreachable (Status $HTTP_CODE). Exiting."
|
||||
exit 115
|
||||
fi
|
||||
}
|
||||
|
||||
function is_phpmyadmin_installed() {
|
||||
if [[ "$OS" == "Debian" ]]; then
|
||||
[[ -f "$INSTALL_DIR/config.inc.php" ]]
|
||||
else
|
||||
[[ -d "$INSTALL_DIR_ALPINE" ]] && rc-service lighttpd status &>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
function install_php_and_modules() {
|
||||
msg_info "Checking existing PHP installation"
|
||||
if command -v php >/dev/null 2>&1; then
|
||||
PHP_VERSION=$(php -r 'echo PHP_VERSION;')
|
||||
msg_ok "Found PHP version $PHP_VERSION"
|
||||
else
|
||||
msg_info "PHP not found, will install PHP core"
|
||||
fi
|
||||
|
||||
if [[ "$OS" == "Debian" ]]; then
|
||||
PHP_MODULES=("php" "php-mysqli" "php-mbstring" "php-zip" "php-gd" "php-json" "php-curl")
|
||||
MISSING_PACKAGES=()
|
||||
for pkg in "${PHP_MODULES[@]}"; do
|
||||
if ! dpkg -l | grep -qw "$pkg"; then
|
||||
MISSING_PACKAGES+=("$pkg")
|
||||
fi
|
||||
done
|
||||
if [[ ${#MISSING_PACKAGES[@]} -gt 0 ]]; then
|
||||
msg_info "Installing missing PHP packages: ${MISSING_PACKAGES[*]}"
|
||||
if ! apt-get update &>/dev/null || ! apt-get install -y "${MISSING_PACKAGES[@]}" &>/dev/null; then
|
||||
msg_error "Failed to install required PHP modules. Exiting."
|
||||
exit 237
|
||||
fi
|
||||
msg_ok "Installed missing PHP packages"
|
||||
else
|
||||
msg_ok "All required PHP modules are already installed"
|
||||
fi
|
||||
else
|
||||
msg_info "Installing Lighttpd and PHP for Alpine"
|
||||
$PKG_MANAGER_INSTALL lighttpd php php-fpm php-session php-json php-mysqli curl tar openssl &>/dev/null
|
||||
msg_ok "Installed Lighttpd and PHP"
|
||||
fi
|
||||
}
|
||||
|
||||
function install_phpmyadmin() {
|
||||
msg_info "Fetching latest phpMyAdmin release from GitHub"
|
||||
LATEST_VERSION_RAW=$(curl -s https://api.github.com/repos/phpmyadmin/phpmyadmin/releases/latest | grep tag_name | cut -d '"' -f4)
|
||||
LATEST_VERSION=$(echo "$LATEST_VERSION_RAW" | sed -e 's/^RELEASE_//' -e 's/_/./g')
|
||||
if [[ -z "$LATEST_VERSION" ]]; then
|
||||
msg_error "Could not determine latest phpMyAdmin version from GitHub – falling back to 5.2.2"
|
||||
LATEST_VERSION="RELEASE_5_2_2"
|
||||
fi
|
||||
msg_ok "Latest version: $LATEST_VERSION"
|
||||
|
||||
TARBALL_URL="https://files.phpmyadmin.net/phpMyAdmin/${LATEST_VERSION}/phpMyAdmin-${LATEST_VERSION}-all-languages.tar.gz"
|
||||
msg_info "Downloading ${TARBALL_URL}"
|
||||
if ! curl -fsSL "$TARBALL_URL" -o /tmp/phpmyadmin.tar.gz; then
|
||||
msg_error "Download failed: $TARBALL_URL"
|
||||
exit 115
|
||||
fi
|
||||
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
tar xf /tmp/phpmyadmin.tar.gz --strip-components=1 -C "$INSTALL_DIR"
|
||||
}
|
||||
|
||||
function configure_phpmyadmin() {
|
||||
if [[ "$OS" == "Debian" ]]; then
|
||||
cp "$INSTALL_DIR/config.sample.inc.php" "$INSTALL_DIR/config.inc.php"
|
||||
SECRET=$(openssl rand -base64 24)
|
||||
sed -i "s#\$cfg\['blowfish_secret'\] = '';#\$cfg['blowfish_secret'] = '${SECRET}';#" "$INSTALL_DIR/config.inc.php"
|
||||
chmod 660 "$INSTALL_DIR/config.inc.php"
|
||||
chown -R www-data:www-data "$INSTALL_DIR"
|
||||
systemctl restart apache2
|
||||
msg_ok "Configured phpMyAdmin with Apache"
|
||||
else
|
||||
msg_info "Configuring Lighttpd for phpMyAdmin (Alpine detected)"
|
||||
|
||||
mkdir -p /etc/lighttpd
|
||||
cat <<EOF >/etc/lighttpd/lighttpd.conf
|
||||
server.modules = (
|
||||
"mod_access",
|
||||
"mod_alias",
|
||||
"mod_accesslog",
|
||||
"mod_fastcgi"
|
||||
)
|
||||
|
||||
server.document-root = "${INSTALL_DIR}"
|
||||
server.port = 80
|
||||
|
||||
index-file.names = ( "index.php", "index.html" )
|
||||
|
||||
fastcgi.server = ( ".php" =>
|
||||
((
|
||||
"host" => "127.0.0.1",
|
||||
"port" => 9000,
|
||||
"check-local" => "disable"
|
||||
))
|
||||
)
|
||||
|
||||
alias.url = ( "/phpMyAdmin/" => "${INSTALL_DIR}/" )
|
||||
|
||||
accesslog.filename = "/var/log/lighttpd/access.log"
|
||||
server.errorlog = "/var/log/lighttpd/error.log"
|
||||
EOF
|
||||
|
||||
msg_info "Starting PHP-FPM and Lighttpd"
|
||||
|
||||
PHP_VERSION=$(php -r 'echo PHP_MAJOR_VERSION . PHP_MINOR_VERSION;')
|
||||
PHP_FPM_SERVICE="php-fpm${PHP_VERSION}"
|
||||
|
||||
if $STD rc-service "$PHP_FPM_SERVICE" start && $STD rc-update add "$PHP_FPM_SERVICE" default; then
|
||||
msg_ok "Started PHP-FPM service: $PHP_FPM_SERVICE"
|
||||
else
|
||||
msg_error "Failed to start PHP-FPM service: $PHP_FPM_SERVICE"
|
||||
exit 150
|
||||
fi
|
||||
|
||||
$STD rc-service lighttpd start
|
||||
$STD rc-update add lighttpd default
|
||||
msg_ok "Configured and started Lighttpd successfully"
|
||||
|
||||
fi
|
||||
}
|
||||
|
||||
function uninstall_phpmyadmin() {
|
||||
msg_info "Stopping Webserver"
|
||||
if [[ "$OS" == "Debian" ]]; then
|
||||
systemctl stop apache2
|
||||
else
|
||||
$STD rc-service lighttpd stop
|
||||
$STD rc-service php-fpm stop
|
||||
fi
|
||||
|
||||
msg_info "Removing phpMyAdmin directory"
|
||||
rm -rf "$INSTALL_DIR"
|
||||
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
msg_info "Removing Lighttpd config"
|
||||
rm -f /etc/lighttpd/lighttpd.conf
|
||||
$STD rc-service php-fpm restart
|
||||
$STD rc-service lighttpd restart
|
||||
else
|
||||
$STD systemctl restart apache2
|
||||
fi
|
||||
msg_ok "Uninstalled phpMyAdmin"
|
||||
}
|
||||
|
||||
function update_phpmyadmin() {
|
||||
msg_info "Fetching latest phpMyAdmin release from GitHub"
|
||||
LATEST_VERSION_RAW=$(curl -s https://api.github.com/repos/phpmyadmin/phpmyadmin/releases/latest | grep tag_name | cut -d '"' -f4)
|
||||
LATEST_VERSION=$(echo "$LATEST_VERSION_RAW" | sed -e 's/^RELEASE_//' -e 's/_/./g')
|
||||
|
||||
if [[ -z "$LATEST_VERSION" ]]; then
|
||||
msg_error "Could not determine latest phpMyAdmin version from GitHub – falling back to 5.2.2"
|
||||
LATEST_VERSION="5.2.2"
|
||||
fi
|
||||
msg_ok "Latest version: $LATEST_VERSION"
|
||||
|
||||
TARBALL_URL="https://files.phpmyadmin.net/phpMyAdmin/${LATEST_VERSION}/phpMyAdmin-${LATEST_VERSION}-all-languages.tar.gz"
|
||||
msg_info "Downloading ${TARBALL_URL}"
|
||||
|
||||
if ! curl -fsSL "$TARBALL_URL" -o /tmp/phpmyadmin.tar.gz; then
|
||||
msg_error "Download failed: $TARBALL_URL"
|
||||
exit 115
|
||||
fi
|
||||
|
||||
BACKUP_DIR="/tmp/phpmyadmin-backup-$(date +%Y%m%d-%H%M%S)"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
BACKUP_ITEMS=("config.inc.php" "upload" "save" "tmp" "themes")
|
||||
|
||||
msg_info "Backing up existing phpMyAdmin data"
|
||||
for item in "${BACKUP_ITEMS[@]}"; do
|
||||
[[ -e "$INSTALL_DIR/$item" ]] && cp -a "$INSTALL_DIR/$item" "$BACKUP_DIR/" && echo " ↪︎ $item"
|
||||
done
|
||||
msg_ok "Backup completed: $BACKUP_DIR"
|
||||
|
||||
tar xf /tmp/phpmyadmin.tar.gz --strip-components=1 -C "$INSTALL_DIR"
|
||||
msg_ok "Extracted phpMyAdmin $LATEST_VERSION"
|
||||
|
||||
msg_info "Restoring preserved files"
|
||||
for item in "${BACKUP_ITEMS[@]}"; do
|
||||
[[ -e "$BACKUP_DIR/$item" ]] && cp -a "$BACKUP_DIR/$item" "$INSTALL_DIR/" && echo " ↪︎ $item restored"
|
||||
done
|
||||
msg_ok "Restoration completed"
|
||||
|
||||
configure_phpmyadmin
|
||||
}
|
||||
|
||||
if is_phpmyadmin_installed; then
|
||||
echo -e "${YW}⚠️ ${APP} is already installed at ${INSTALL_DIR}.${CL}"
|
||||
read -r -p "Would you like to Update (1), Uninstall (2) or Cancel (3)? [1/2/3]: " action
|
||||
action="${action//[[:space:]]/}"
|
||||
case "$action" in
|
||||
1)
|
||||
check_internet
|
||||
update_phpmyadmin
|
||||
;;
|
||||
2)
|
||||
uninstall_phpmyadmin
|
||||
;;
|
||||
3)
|
||||
echo -e "${YW}⚠️ Action cancelled. Exiting.${CL}"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo -e "${YW}⚠️ Invalid input. Exiting.${CL}"
|
||||
exit 112
|
||||
;;
|
||||
esac
|
||||
else
|
||||
read -r -p "Would you like to install ${APP}? (y/n): " install_prompt
|
||||
install_prompt="${install_prompt//[[:space:]]/}"
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
check_internet
|
||||
install_php_and_modules
|
||||
install_phpmyadmin
|
||||
configure_phpmyadmin
|
||||
if [[ "$OS" == "Debian" ]]; then
|
||||
echo -e "${CM} ${GN}${APP} is reachable at: ${BL}http://${IP}/phpMyAdmin${CL}"
|
||||
else
|
||||
echo -e "${CM} ${GN}${APP} is reachable at: ${BL}http://${IP}/${CL}"
|
||||
fi
|
||||
else
|
||||
echo -e "${YW}⚠️ Installation skipped. Exiting.${CL}"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
@@ -1,253 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: CrazyWolf13
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/eko/pihole-exporter/
|
||||
|
||||
if ! command -v curl &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
load_functions
|
||||
init_tool_telemetry "" "addon"
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
VERBOSE=${var_verbose:-no}
|
||||
APP="pihole-exporter"
|
||||
APP_TYPE="tools"
|
||||
INSTALL_PATH="/opt/pihole-exporter"
|
||||
CONFIG_PATH="/opt/pihole-exporter.env"
|
||||
|
||||
# ==============================================================================
|
||||
# OS DETECTION
|
||||
# ==============================================================================
|
||||
if [[ -f "/etc/alpine-release" ]]; then
|
||||
OS="Alpine"
|
||||
SERVICE_PATH="/etc/init.d/pihole-exporter"
|
||||
elif grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then
|
||||
OS="Debian"
|
||||
SERVICE_PATH="/etc/systemd/system/pihole-exporter.service"
|
||||
else
|
||||
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling Pihole-Exporter"
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
rc-service pihole-exporter stop &>/dev/null
|
||||
rc-update del pihole-exporter &>/dev/null
|
||||
rm -f "$SERVICE_PATH"
|
||||
else
|
||||
systemctl disable -q --now pihole-exporter
|
||||
rm -f "$SERVICE_PATH"
|
||||
fi
|
||||
rm -rf "$INSTALL_PATH" "$CONFIG_PATH"
|
||||
rm -f "/usr/local/bin/update_pihole-exporter"
|
||||
rm -f "$HOME/.pihole-exporter"
|
||||
msg_ok "Pihole-Exporter has been uninstalled"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
if check_for_gh_release "pihole-exporter" "eko/pihole-exporter"; then
|
||||
msg_info "Stopping service"
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
rc-service pihole-exporter stop &>/dev/null
|
||||
else
|
||||
systemctl stop pihole-exporter
|
||||
fi
|
||||
msg_ok "Stopped service"
|
||||
|
||||
fetch_and_deploy_gh_release "pihole-exporter" "eko/pihole-exporter" "tarball" "latest"
|
||||
setup_go
|
||||
|
||||
msg_info "Building Pihole-Exporter"
|
||||
cd /opt/pihole-exporter/
|
||||
$STD /usr/local/bin/go build -o ./pihole-exporter
|
||||
msg_ok "Built Pihole-Exporter"
|
||||
|
||||
msg_info "Starting service"
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
rc-service pihole-exporter start &>/dev/null
|
||||
else
|
||||
systemctl start pihole-exporter
|
||||
fi
|
||||
msg_ok "Started service"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
read -erp "Enter the protocol to use (http/https), default https: " pihole_PROTOCOL
|
||||
read -erp "Enter the hostname of Pihole, example: (127.0.0.1): " pihole_HOSTNAME
|
||||
read -erp "Enter the port of Pihole, default 443: " pihole_PORT
|
||||
read -rsp "Enter Pihole password: " pihole_PASSWORD
|
||||
printf "\n"
|
||||
read -erp "Do you want to skip TLS-Verification (if using a self-signed Certificate on Pi-Hole) [y/N]: " SKIP_TLS
|
||||
if [[ "${SKIP_TLS,,}" =~ ^(y|yes)$ ]]; then
|
||||
pihole_SKIP_TLS="true"
|
||||
fi
|
||||
|
||||
fetch_and_deploy_gh_release "pihole-exporter" "eko/pihole-exporter" "tarball" "latest"
|
||||
setup_go
|
||||
msg_info "Building Pihole-Exporter on ${OS}"
|
||||
cd /opt/pihole-exporter/
|
||||
$STD /usr/local/bin/go build -o ./pihole-exporter
|
||||
msg_ok "Built Pihole-Exporter"
|
||||
|
||||
msg_info "Creating configuration"
|
||||
cat <<EOF >"$CONFIG_PATH"
|
||||
# https://github.com/eko/pihole-exporter/?tab=readme-ov-file#available-cli-options
|
||||
PIHOLE_PASSWORD="${pihole_PASSWORD}"
|
||||
PIHOLE_HOSTNAME="${pihole_HOSTNAME:-127.0.0.1}"
|
||||
PIHOLE_PORT="${pihole_PORT:-443}"
|
||||
SKIP_TLS_VERIFICATION="${pihole_SKIP_TLS:-false}"
|
||||
PIHOLE_PROTOCOL="${pihole_PROTOCOL:-https}"
|
||||
EOF
|
||||
msg_ok "Created configuration"
|
||||
|
||||
msg_info "Creating service"
|
||||
if [[ "$OS" == "Debian" ]]; then
|
||||
cat <<EOF >"$SERVICE_PATH"
|
||||
[Unit]
|
||||
Description=pihole-exporter
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
WorkingDirectory=/opt/pihole-exporter
|
||||
EnvironmentFile=$CONFIG_PATH
|
||||
ExecStart=/opt/pihole-exporter/pihole-exporter
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
systemctl enable -q --now pihole-exporter
|
||||
else
|
||||
cat <<EOF >"$SERVICE_PATH"
|
||||
#!/sbin/openrc-run
|
||||
|
||||
name="pihole-exporter"
|
||||
description="Pi-hole Exporter for Prometheus"
|
||||
command="${INSTALL_PATH}/pihole-exporter"
|
||||
command_background=true
|
||||
directory="/opt/pihole-exporter"
|
||||
pidfile="/run/\${RC_SVCNAME}.pid"
|
||||
output_log="/var/log/pihole-exporter.log"
|
||||
error_log="/var/log/pihole-exporter.log"
|
||||
|
||||
depend() {
|
||||
need net
|
||||
after firewall
|
||||
}
|
||||
|
||||
start_pre() {
|
||||
if [ -f "$CONFIG_PATH" ]; then
|
||||
export \$(grep -v '^#' $CONFIG_PATH | xargs)
|
||||
fi
|
||||
}
|
||||
EOF
|
||||
chmod +x "$SERVICE_PATH"
|
||||
$STD rc-update add pihole-exporter default
|
||||
$STD rc-service pihole-exporter start
|
||||
fi
|
||||
msg_ok "Created and started service"
|
||||
|
||||
# Create update script
|
||||
msg_info "Creating update script"
|
||||
ensure_usr_local_bin_persist
|
||||
cat <<'UPDATEEOF' >/usr/local/bin/update_pihole-exporter
|
||||
#!/usr/bin/env bash
|
||||
# pihole-exporter Update Script
|
||||
type=update bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/pihole-exporter.sh)"
|
||||
UPDATEEOF
|
||||
chmod +x /usr/local/bin/update_pihole-exporter
|
||||
msg_ok "Created update script (/usr/local/bin/update_pihole-exporter)"
|
||||
|
||||
echo ""
|
||||
msg_ok "Pihole-Exporter installed successfully"
|
||||
msg_ok "Metrics: ${BL}http://${LOCAL_IP}:9617/metrics${CL}"
|
||||
msg_ok "Config: ${BL}${CONFIG_PATH}${CL}"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
header_info
|
||||
ensure_usr_local_bin_persist
|
||||
get_lxc_ip
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
if [[ -d "$INSTALL_PATH" && -f "$INSTALL_PATH/pihole-exporter" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "Pihole-Exporter is not installed. Nothing to update."
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if already installed
|
||||
if [[ -d "$INSTALL_PATH" && -f "$INSTALL_PATH/pihole-exporter" ]]; then
|
||||
msg_warn "Pihole-Exporter is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall Pihole-Exporter? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update Pihole-Exporter? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "Pihole-Exporter is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - Pi-hole Exporter (Go binary)"
|
||||
echo -e "${TAB} - Systemd/OpenRC service"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install Pihole-Exporter? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,190 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: Andy Grunwald (andygrunwald)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/hansmi/prometheus-paperless-exporter
|
||||
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
load_functions
|
||||
init_tool_telemetry "" "addon"
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
VERBOSE=${var_verbose:-no}
|
||||
APP="prometheus-paperless-ngx-exporter"
|
||||
APP_TYPE="tools"
|
||||
BINARY_PATH="/usr/bin/prometheus-paperless-exporter"
|
||||
CONFIG_PATH="/etc/prometheus-paperless-ngx-exporter/config.env"
|
||||
SERVICE_PATH="/etc/systemd/system/prometheus-paperless-ngx-exporter.service"
|
||||
AUTH_TOKEN_FILE="/etc/prometheus-paperless-ngx-exporter/paperless_auth_token_file"
|
||||
|
||||
# ==============================================================================
|
||||
# OS DETECTION
|
||||
# ==============================================================================
|
||||
if ! grep -qE 'ID=debian|ID=ubuntu' /etc/os-release 2>/dev/null; then
|
||||
echo -e "${CROSS} Unsupported OS detected. This script only supports Debian and Ubuntu."
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling Prometheus-Paperless-NGX-Exporter"
|
||||
systemctl disable -q --now prometheus-paperless-ngx-exporter
|
||||
|
||||
if dpkg -l | grep -q prometheus-paperless-exporter; then
|
||||
$STD apt-get remove -y prometheus-paperless-exporter || $STD dpkg -r prometheus-paperless-exporter
|
||||
fi
|
||||
|
||||
rm -f "$SERVICE_PATH"
|
||||
rm -rf /etc/prometheus-paperless-ngx-exporter
|
||||
rm -f "/usr/local/bin/update_prometheus-paperless-ngx-exporter"
|
||||
rm -f "$HOME/.prometheus-paperless-ngx-exporter"
|
||||
msg_ok "Prometheus-Paperless-NGX-Exporter has been uninstalled"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
if check_for_gh_release "prom-paperless-exp" "hansmi/prometheus-paperless-exporter"; then
|
||||
msg_info "Stopping service"
|
||||
systemctl stop prometheus-paperless-ngx-exporter
|
||||
msg_ok "Stopped service"
|
||||
|
||||
fetch_and_deploy_gh_release "prom-paperless-exp" "hansmi/prometheus-paperless-exporter" "binary" "latest"
|
||||
|
||||
msg_info "Starting service"
|
||||
systemctl start prometheus-paperless-ngx-exporter
|
||||
msg_ok "Started service"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
read -erp "Enter URL of Paperless-NGX, example: (http://127.0.0.1:8000): " PAPERLESS_URL
|
||||
read -rsp "Enter Paperless-NGX authentication token: " PAPERLESS_AUTH_TOKEN
|
||||
printf "\n"
|
||||
|
||||
fetch_and_deploy_gh_release "prom-paperless-exp" "hansmi/prometheus-paperless-exporter" "binary" "latest"
|
||||
|
||||
msg_info "Creating configuration"
|
||||
mkdir -p /etc/prometheus-paperless-ngx-exporter
|
||||
cat <<EOF >"$CONFIG_PATH"
|
||||
# https://github.com/hansmi/prometheus-paperless-exporter
|
||||
PAPERLESS_URL="${PAPERLESS_URL}"
|
||||
EOF
|
||||
echo "${PAPERLESS_AUTH_TOKEN}" >"$AUTH_TOKEN_FILE"
|
||||
chmod 600 "$AUTH_TOKEN_FILE"
|
||||
msg_ok "Created configuration"
|
||||
|
||||
msg_info "Creating service"
|
||||
cat <<EOF >"$SERVICE_PATH"
|
||||
[Unit]
|
||||
Description=Prometheus Paperless NGX Exporter
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
EnvironmentFile=$CONFIG_PATH
|
||||
ExecStart=$BINARY_PATH \\
|
||||
--paperless_url=\${PAPERLESS_URL} \\
|
||||
--paperless_auth_token_file=$AUTH_TOKEN_FILE
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
systemctl enable -q --now prometheus-paperless-ngx-exporter
|
||||
msg_ok "Created and started service"
|
||||
|
||||
# Create update script
|
||||
msg_info "Creating update script"
|
||||
ensure_usr_local_bin_persist
|
||||
cat <<'UPDATEEOF' >/usr/local/bin/update_prometheus-paperless-ngx-exporter
|
||||
#!/usr/bin/env bash
|
||||
# prometheus-paperless-ngx-exporter Update Script
|
||||
type=update bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/prometheus-paperless-ngx-exporter.sh)"
|
||||
UPDATEEOF
|
||||
chmod +x /usr/local/bin/update_prometheus-paperless-ngx-exporter
|
||||
msg_ok "Created update script (/usr/local/bin/update_prometheus-paperless-ngx-exporter)"
|
||||
|
||||
echo ""
|
||||
msg_ok "Prometheus-Paperless-NGX-Exporter installed successfully"
|
||||
msg_ok "Metrics: ${BL}http://${LOCAL_IP}:8081/metrics${CL}"
|
||||
msg_ok "Config: ${BL}${CONFIG_PATH}${CL}"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
header_info
|
||||
ensure_usr_local_bin_persist
|
||||
get_lxc_ip
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
if [[ -f "$BINARY_PATH" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "Prometheus-Paperless-NGX-Exporter is not installed. Nothing to update."
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if already installed
|
||||
if [[ -f "$BINARY_PATH" ]]; then
|
||||
msg_warn "Prometheus-Paperless-NGX-Exporter is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall Prometheus-Paperless-NGX-Exporter? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update Prometheus-Paperless-NGX-Exporter? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "Prometheus-Paperless-NGX-Exporter is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - Prometheus Paperless NGX Exporter (binary)"
|
||||
echo -e "${TAB} - Systemd service"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install Prometheus-Paperless-NGX-Exporter? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,167 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 tteck
|
||||
# Author: tteck (tteckster)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://pyenv.run/ | Github: https://github.com/pyenv/pyenv
|
||||
|
||||
set -e
|
||||
YW=$(echo "\033[33m")
|
||||
RD=$(echo "\033[01;31m")
|
||||
BL=$(echo "\033[36m")
|
||||
GN=$(echo "\033[1;92m")
|
||||
CL=$(echo "\033[m")
|
||||
CM="${GN}✓${CL}"
|
||||
CROSS="${RD}✗${CL}"
|
||||
BFR="\\r\\033[K"
|
||||
HOLD="-"
|
||||
function msg_info() {
|
||||
local msg="$1"
|
||||
echo -ne " ${HOLD} ${YW}${msg}..."
|
||||
}
|
||||
|
||||
function msg_ok() {
|
||||
local msg="$1"
|
||||
echo -e "${BFR} ${CM} ${GN}${msg}${CL}"
|
||||
}
|
||||
function msg_error() {
|
||||
local msg="$1"
|
||||
echo -e "${BFR} ${CROSS} ${RD}${msg}${CL}"
|
||||
}
|
||||
|
||||
# Telemetry
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "pyenv" "addon"
|
||||
|
||||
if command -v pveversion >/dev/null 2>&1; then
|
||||
msg_error "Can't Install on Proxmox "
|
||||
exit
|
||||
fi
|
||||
msg_info "Installing pyenv"
|
||||
apt-get install -y \
|
||||
make \
|
||||
build-essential \
|
||||
libjpeg-dev \
|
||||
libpcap-dev \
|
||||
libssl-dev \
|
||||
zlib1g-dev \
|
||||
libbz2-dev \
|
||||
libreadline-dev \
|
||||
libsqlite3-dev \
|
||||
autoconf \
|
||||
git \
|
||||
curl \
|
||||
sudo \
|
||||
llvm \
|
||||
libncursesw5-dev \
|
||||
xz-utils \
|
||||
tk-dev \
|
||||
libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
libffi-dev \
|
||||
libopenjp2-7 \
|
||||
libtiff5 \
|
||||
libturbojpeg0-dev \
|
||||
liblzma-dev &>/dev/null
|
||||
|
||||
git clone https://github.com/pyenv/pyenv.git ~/.pyenv &>/dev/null
|
||||
set +e
|
||||
echo 'export PYENV_ROOT="$HOME/.pyenv"' >>~/.bashrc
|
||||
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >>~/.bashrc
|
||||
echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init --path)"\nfi' >>~/.bashrc
|
||||
msg_ok "Installed pyenv"
|
||||
. ~/.bashrc
|
||||
set -e
|
||||
msg_info "Installing Python 3.11.1"
|
||||
pyenv install 3.11.1 &>/dev/null
|
||||
pyenv global 3.11.1
|
||||
msg_ok "Installed Python 3.11.1"
|
||||
read -r -p "Would you like to install Home Assistant Beta? <y/N> " prompt
|
||||
if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_info "Installing Home Assistant Beta"
|
||||
cat <<EOF >/etc/systemd/system/homeassistant.service
|
||||
[Unit]
|
||||
Description=Home Assistant
|
||||
After=network-online.target
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/root/.homeassistant
|
||||
ExecStart=/srv/homeassistant/bin/hass -c "/root/.homeassistant"
|
||||
RestartForceExitStatus=100
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
mkdir /srv/homeassistant
|
||||
cd /srv/homeassistant
|
||||
python3 -m venv .
|
||||
source bin/activate
|
||||
python3 -m pip install wheel &>/dev/null
|
||||
pip3 install --upgrade pip &>/dev/null
|
||||
pip3 install psycopg2-binary &>/dev/null
|
||||
pip3 install --pre homeassistant &>/dev/null
|
||||
systemctl enable homeassistant &>/dev/null
|
||||
msg_ok "Installed Home Assistant Beta"
|
||||
echo -e " Go to $(hostname -I | awk '{print $1}'):8123"
|
||||
hass
|
||||
fi
|
||||
|
||||
read -r -p "Would you like to install ESPHome Beta? <y/N> " prompt
|
||||
if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_info "Installing ESPHome Beta"
|
||||
mkdir /srv/esphome
|
||||
cd /srv/esphome
|
||||
python3 -m venv .
|
||||
source bin/activate
|
||||
python3 -m pip install wheel &>/dev/null
|
||||
pip3 install --upgrade pip &>/dev/null
|
||||
pip3 install --pre esphome &>/dev/null
|
||||
cat <<EOF >/srv/esphome/start.sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 tteck
|
||||
# Author: tteck (tteckster)
|
||||
# License: MIT
|
||||
# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
|
||||
source /srv/esphome/bin/activate
|
||||
esphome dashboard /srv/esphome/
|
||||
EOF
|
||||
chmod +x start.sh
|
||||
cat <<EOF >/etc/systemd/system/esphomedashboard.service
|
||||
[Unit]
|
||||
Description=ESPHome Dashboard Service
|
||||
After=network.target
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/srv/esphome
|
||||
ExecStart=/srv/esphome/start.sh
|
||||
RestartSec=30
|
||||
Restart=on-failure
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable --now esphomedashboard &>/dev/null
|
||||
msg_ok "Installed ESPHome Beta"
|
||||
echo -e " Go to $(hostname -I | awk '{print $1}'):6052"
|
||||
exec $SHELL
|
||||
fi
|
||||
|
||||
read -r -p "Would you like to install Matter-Server (Beta)? <y/N> " prompt
|
||||
if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_info "Installing Matter Server"
|
||||
apt-get install -y \
|
||||
libcairo2-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
libgirepository1.0-dev \
|
||||
libpango1.0-dev \
|
||||
libgif-dev \
|
||||
g++ &>/dev/null
|
||||
python3 -m pip install wheel
|
||||
pip3 install --upgrade pip
|
||||
pip install python-matter-server[server]
|
||||
msg_ok "Installed Matter Server"
|
||||
echo -e "Start server > python -m matter_server.server"
|
||||
fi
|
||||
msg_ok "\nFinished\n"
|
||||
exec $SHELL
|
||||
@@ -1,246 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: CrazyWolf13
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/martabal/qbittorrent-exporter
|
||||
|
||||
if ! command -v curl &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
load_functions
|
||||
init_tool_telemetry "" "addon"
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
VERBOSE=${var_verbose:-no}
|
||||
APP="qbittorrent-exporter"
|
||||
APP_TYPE="tools"
|
||||
INSTALL_PATH="/opt/qbittorrent-exporter"
|
||||
CONFIG_PATH="/opt/qbittorrent-exporter.env"
|
||||
|
||||
# ==============================================================================
|
||||
# OS DETECTION
|
||||
# ==============================================================================
|
||||
if [[ -f "/etc/alpine-release" ]]; then
|
||||
OS="Alpine"
|
||||
SERVICE_PATH="/etc/init.d/qbittorrent-exporter"
|
||||
elif grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then
|
||||
OS="Debian"
|
||||
SERVICE_PATH="/etc/systemd/system/qbittorrent-exporter.service"
|
||||
else
|
||||
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||
exit 238
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling qBittorrent-Exporter"
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
rc-service qbittorrent-exporter stop &>/dev/null
|
||||
rc-update del qbittorrent-exporter &>/dev/null
|
||||
rm -f "$SERVICE_PATH"
|
||||
else
|
||||
systemctl disable -q --now qbittorrent-exporter
|
||||
rm -f "$SERVICE_PATH"
|
||||
fi
|
||||
rm -rf "$INSTALL_PATH" "$CONFIG_PATH"
|
||||
rm -f "/usr/local/bin/update_qbittorrent-exporter"
|
||||
rm -f "$HOME/.qbittorrent-exporter"
|
||||
msg_ok "qBittorrent-Exporter has been uninstalled"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
if check_for_gh_release "qbittorrent-exporter" "martabal/qbittorrent-exporter"; then
|
||||
msg_info "Stopping service"
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
rc-service qbittorrent-exporter stop &>/dev/null
|
||||
else
|
||||
systemctl stop qbittorrent-exporter
|
||||
fi
|
||||
msg_ok "Stopped service"
|
||||
|
||||
fetch_and_deploy_gh_release "qbittorrent-exporter" "martabal/qbittorrent-exporter" "tarball" "latest"
|
||||
setup_go
|
||||
|
||||
msg_info "Building qBittorrent-Exporter"
|
||||
cd /opt/qbittorrent-exporter
|
||||
$STD /usr/local/bin/go build -o ./qbittorrent-exporter
|
||||
msg_ok "Built qBittorrent-Exporter"
|
||||
|
||||
msg_info "Starting service"
|
||||
if [[ "$OS" == "Alpine" ]]; then
|
||||
rc-service qbittorrent-exporter start &>/dev/null
|
||||
else
|
||||
systemctl start qbittorrent-exporter
|
||||
fi
|
||||
msg_ok "Started service"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
read -erp "Enter URL of qBittorrent, example: (http://127.0.0.1:8080): " QBITTORRENT_BASE_URL
|
||||
read -erp "Enter qBittorrent username: " QBITTORRENT_USERNAME
|
||||
read -rsp "Enter qBittorrent password: " QBITTORRENT_PASSWORD
|
||||
printf "\n"
|
||||
|
||||
fetch_and_deploy_gh_release "qbittorrent-exporter" "martabal/qbittorrent-exporter" "tarball" "latest"
|
||||
setup_go
|
||||
msg_info "Building qBittorrent-Exporter on ${OS}"
|
||||
cd /opt/qbittorrent-exporter
|
||||
$STD /usr/local/bin/go build -o ./qbittorrent-exporter
|
||||
msg_ok "Built qBittorrent-Exporter"
|
||||
|
||||
msg_info "Creating configuration"
|
||||
cat <<EOF >"$CONFIG_PATH"
|
||||
# https://github.com/martabal/qbittorrent-exporter?tab=readme-ov-file#parameters
|
||||
QBITTORRENT_BASE_URL="${QBITTORRENT_BASE_URL}"
|
||||
QBITTORRENT_USERNAME="${QBITTORRENT_USERNAME}"
|
||||
QBITTORRENT_PASSWORD="${QBITTORRENT_PASSWORD}"
|
||||
EOF
|
||||
msg_ok "Created configuration"
|
||||
|
||||
msg_info "Creating service"
|
||||
if [[ "$OS" == "Debian" ]]; then
|
||||
cat <<EOF >"$SERVICE_PATH"
|
||||
[Unit]
|
||||
Description=qbittorrent-exporter
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
WorkingDirectory=/opt/qbittorrent-exporter
|
||||
EnvironmentFile=$CONFIG_PATH
|
||||
ExecStart=/opt/qbittorrent-exporter/qbittorrent-exporter
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
systemctl enable -q --now qbittorrent-exporter
|
||||
else
|
||||
cat <<EOF >"$SERVICE_PATH"
|
||||
#!/sbin/openrc-run
|
||||
|
||||
name="qbittorrent-exporter"
|
||||
description="qBittorrent Exporter for Prometheus"
|
||||
command="${INSTALL_PATH}/qbittorrent-exporter"
|
||||
command_background=true
|
||||
directory="/opt/qbittorrent-exporter"
|
||||
pidfile="/run/\${RC_SVCNAME}.pid"
|
||||
output_log="/var/log/qbittorrent-exporter.log"
|
||||
error_log="/var/log/qbittorrent-exporter.log"
|
||||
|
||||
depend() {
|
||||
need net
|
||||
after firewall
|
||||
}
|
||||
|
||||
start_pre() {
|
||||
if [ -f "$CONFIG_PATH" ]; then
|
||||
export \$(grep -v '^#' $CONFIG_PATH | xargs)
|
||||
fi
|
||||
}
|
||||
EOF
|
||||
chmod +x "$SERVICE_PATH"
|
||||
$STD rc-update add qbittorrent-exporter default
|
||||
$STD rc-service qbittorrent-exporter start
|
||||
fi
|
||||
msg_ok "Created and started service"
|
||||
|
||||
# Create update script
|
||||
msg_info "Creating update script"
|
||||
ensure_usr_local_bin_persist
|
||||
cat <<'UPDATEEOF' >/usr/local/bin/update_qbittorrent-exporter
|
||||
#!/usr/bin/env bash
|
||||
# qbittorrent-exporter Update Script
|
||||
type=update bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/qbittorrent-exporter.sh)"
|
||||
UPDATEEOF
|
||||
chmod +x /usr/local/bin/update_qbittorrent-exporter
|
||||
msg_ok "Created update script (/usr/local/bin/update_qbittorrent-exporter)"
|
||||
|
||||
echo ""
|
||||
msg_ok "qBittorrent-Exporter installed successfully"
|
||||
msg_ok "Metrics: ${BL}http://${LOCAL_IP}:8090/metrics${CL}"
|
||||
msg_ok "Config: ${BL}${CONFIG_PATH}${CL}"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
header_info
|
||||
ensure_usr_local_bin_persist
|
||||
get_lxc_ip
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
if [[ -d "$INSTALL_PATH" && -f "$INSTALL_PATH/qbittorrent-exporter" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "qBittorrent-Exporter is not installed. Nothing to update."
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if already installed
|
||||
if [[ -d "$INSTALL_PATH" && -f "$INSTALL_PATH/qbittorrent-exporter" ]]; then
|
||||
msg_warn "qBittorrent-Exporter is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall qBittorrent-Exporter? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update qBittorrent-Exporter? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "qBittorrent-Exporter is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - qBittorrent Exporter (Go binary)"
|
||||
echo -e "${TAB} - Systemd/OpenRC service"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install qBittorrent-Exporter? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -0,0 +1,402 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: community-scripts
|
||||
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://rocm.docs.amd.com
|
||||
|
||||
# ==============================================================================
|
||||
# ROCm ADDON - AMD ROCm Installation for Debian/Ubuntu LXC Containers
|
||||
# Supports: Debian 12, Debian 13, Ubuntu 22.04, Ubuntu 24.04
|
||||
# ==============================================================================
|
||||
|
||||
ensure_dependencies curl
|
||||
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/install.func)
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
load_functions
|
||||
init_tool_telemetry "" "addon"
|
||||
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
____ ____ ________ ___
|
||||
/ __ \/ __ \/ ____/ |/ /
|
||||
/ /_/ / / / / / / /|_/ /
|
||||
/ _, _/ /_/ / /___/ / / /
|
||||
/_/ |_|\____/\____/_/ /_/
|
||||
|
||||
ROCM Installer for Proxmox LXC Containers
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# ROCm REPOSITORY MAPPING
|
||||
# ==============================================================================
|
||||
# Sets ROCm-specific variables based on OS detection from install.func
|
||||
# Variables set: OS, OS_CODENAME, ROCM_REPO_CODENAME, ROCM_VERSION
|
||||
# Requires: OS_TYPE and OS_VERSION from detect_os() in install.func
|
||||
# ==============================================================================
|
||||
function setup_rocm_repo_mapping() {
|
||||
ROCM_VERSION="7.2"
|
||||
|
||||
case "${OS_TYPE}" in
|
||||
debian)
|
||||
OS="Debian"
|
||||
case "${OS_VERSION}" in
|
||||
12)
|
||||
OS_CODENAME="bookworm"
|
||||
ROCM_REPO_CODENAME="jammy"
|
||||
;;
|
||||
13)
|
||||
OS_CODENAME="trixie"
|
||||
ROCM_REPO_CODENAME="noble"
|
||||
;;
|
||||
*)
|
||||
msg_error "Unsupported Debian version: ${OS_VERSION}"
|
||||
msg_info "Supported versions: Debian 12, Debian 13"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
ubuntu)
|
||||
OS="Ubuntu"
|
||||
case "${OS_VERSION}" in
|
||||
22.04)
|
||||
OS_CODENAME="jammy"
|
||||
ROCM_REPO_CODENAME="jammy"
|
||||
;;
|
||||
24.04)
|
||||
OS_CODENAME="noble"
|
||||
ROCM_REPO_CODENAME="noble"
|
||||
;;
|
||||
*)
|
||||
msg_error "Unsupported Ubuntu version: ${OS_VERSION}"
|
||||
msg_info "Supported versions: Ubuntu 22.04, Ubuntu 24.04"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
msg_error "Unsupported OS: ${OS_TYPE}"
|
||||
msg_info "Supported OS: Debian 12, Debian 13, Ubuntu 22.04, Ubuntu 24.04"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
msg_ok "Detected: ${OS} ${OS_VERSION} (${OS_CODENAME})"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ==============================================================================
|
||||
function check_lxc() {
|
||||
if [[ -f "/proc/1/cgroup" ]] && grep -q "lxc" /proc/1/cgroup 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
if grep -q "container=lxc" /proc/1/environ 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL FUNCTIONS
|
||||
# ==============================================================================
|
||||
function install_rocm_debian() {
|
||||
msg_info "Adding ROCm repository GPG key"
|
||||
if ! download_gpg_key "https://repo.radeon.com/rocm/rocm.gpg.key" "/etc/apt/keyrings/rocm.gpg" "dearmor"; then
|
||||
msg_error "Failed to download or import ROCm GPG key"
|
||||
exit 1
|
||||
fi
|
||||
msg_ok "Added ROCm GPG key"
|
||||
|
||||
msg_info "Adding ROCm repository (using ${ROCM_REPO_CODENAME} for ${OS} ${OS_VERSION})"
|
||||
# Use deb822 format (new standard, replaces deprecated .list format)
|
||||
cat <<EOF >/etc/apt/sources.list.d/rocm.sources
|
||||
Types: deb
|
||||
URIs: https://repo.radeon.com/rocm/apt/${ROCM_VERSION}
|
||||
Suites: ${ROCM_REPO_CODENAME}
|
||||
Components: main
|
||||
Architectures: amd64
|
||||
Signed-By: /etc/apt/keyrings/rocm.gpg
|
||||
|
||||
Types: deb
|
||||
URIs: https://repo.radeon.com/graphics/${ROCM_VERSION}/ubuntu
|
||||
Suites: ${ROCM_REPO_CODENAME}
|
||||
Components: main
|
||||
Architectures: amd64
|
||||
Signed-By: /etc/apt/keyrings/rocm.gpg
|
||||
EOF
|
||||
msg_ok "Added ROCm repository"
|
||||
|
||||
msg_info "Setting package pin preferences"
|
||||
cat <<EOF >/etc/apt/preferences.d/rocm-pin-600
|
||||
Package: *
|
||||
Pin: release o=repo.radeon.com
|
||||
Pin-Priority: 600
|
||||
EOF
|
||||
msg_ok "Set package pin preferences"
|
||||
|
||||
msg_info "Updating package lists"
|
||||
$STD apt update
|
||||
msg_ok "Updated package lists"
|
||||
|
||||
msg_info "Installing ROCm packages"
|
||||
$STD apt install -y rocm
|
||||
msg_ok "Installed ROCm packages"
|
||||
|
||||
msg_info "Adding user to render and video groups"
|
||||
usermod -aG render,video root
|
||||
for user_home in /home/*/; do
|
||||
[[ -d "$user_home" ]] || continue
|
||||
user=$(basename "$user_home")
|
||||
usermod -aG render,video "$user" 2>/dev/null || true
|
||||
done
|
||||
msg_ok "Added users to render and video groups"
|
||||
|
||||
msg_info "Configuring /dev/kfd permissions"
|
||||
if [[ -e /dev/kfd ]]; then
|
||||
chgrp render /dev/kfd 2>/dev/null || true
|
||||
chmod 660 /dev/kfd
|
||||
msg_ok "Configured /dev/kfd permissions"
|
||||
else
|
||||
msg_warn "/dev/kfd not found - GPU passthrough may not be configured"
|
||||
fi
|
||||
|
||||
msg_info "Configuring environment"
|
||||
cat <<EOF >/etc/profile.d/rocm.sh
|
||||
export PATH=\$PATH:/opt/rocm/bin
|
||||
export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:/opt/rocm/lib
|
||||
EOF
|
||||
chmod +x /etc/profile.d/rocm.sh
|
||||
msg_ok "Configured environment"
|
||||
}
|
||||
|
||||
function install_rocm_ubuntu() {
|
||||
msg_info "Adding ROCm repository GPG key"
|
||||
if ! download_gpg_key "https://repo.radeon.com/rocm/rocm.gpg.key" "/etc/apt/keyrings/rocm.gpg" "dearmor"; then
|
||||
msg_error "Failed to download or import ROCm GPG key"
|
||||
exit 1
|
||||
fi
|
||||
msg_ok "Added ROCm GPG key"
|
||||
|
||||
msg_info "Adding ROCm repository"
|
||||
# Use deb822 format (new standard, replaces deprecated .list format)
|
||||
cat <<EOF >/etc/apt/sources.list.d/rocm.sources
|
||||
Types: deb
|
||||
URIs: https://repo.radeon.com/rocm/apt/${ROCM_VERSION}
|
||||
Suites: ${ROCM_REPO_CODENAME}
|
||||
Components: main
|
||||
Architectures: amd64
|
||||
Signed-By: /etc/apt/keyrings/rocm.gpg
|
||||
|
||||
Types: deb
|
||||
URIs: https://repo.radeon.com/graphics/${ROCM_VERSION}/ubuntu
|
||||
Suites: ${ROCM_REPO_CODENAME}
|
||||
Components: main
|
||||
Architectures: amd64
|
||||
Signed-By: /etc/apt/keyrings/rocm.gpg
|
||||
EOF
|
||||
msg_ok "Added ROCm repository"
|
||||
|
||||
msg_info "Setting package pin preferences"
|
||||
cat <<EOF >/etc/apt/preferences.d/rocm-pin-600
|
||||
Package: *
|
||||
Pin: release o=repo.radeon.com
|
||||
Pin-Priority: 600
|
||||
EOF
|
||||
msg_ok "Set package pin preferences"
|
||||
|
||||
msg_info "Updating package lists"
|
||||
$STD apt update
|
||||
msg_ok "Updated package lists"
|
||||
|
||||
msg_info "Installing ROCm packages"
|
||||
$STD apt install -y rocm
|
||||
msg_ok "Installed ROCm packages"
|
||||
|
||||
msg_info "Adding user to render and video groups"
|
||||
usermod -aG render,video root
|
||||
for user_home in /home/*/; do
|
||||
[[ -d "$user_home" ]] || continue
|
||||
user=$(basename "$user_home")
|
||||
usermod -aG render,video "$user" 2>/dev/null || true
|
||||
done
|
||||
msg_ok "Added users to render and video groups"
|
||||
|
||||
msg_info "Configuring /dev/kfd permissions"
|
||||
if [[ -e /dev/kfd ]]; then
|
||||
chgrp render /dev/kfd 2>/dev/null || true
|
||||
chmod 660 /dev/kfd
|
||||
msg_ok "Configured /dev/kfd permissions"
|
||||
else
|
||||
msg_warn "/dev/kfd not found - GPU passthrough may not be configured"
|
||||
fi
|
||||
|
||||
msg_info "Configuring environment"
|
||||
cat <<EOF >/etc/profile.d/rocm.sh
|
||||
export PATH=\$PATH:/opt/rocm/bin
|
||||
export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:/opt/rocm/lib
|
||||
EOF
|
||||
chmod +x /etc/profile.d/rocm.sh
|
||||
msg_ok "Configured environment"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall_rocm() {
|
||||
msg_info "Uninstalling ROCm"
|
||||
|
||||
msg_info "Removing ROCm packages"
|
||||
$STD apt remove -y rocm
|
||||
$STD apt autoremove -y
|
||||
msg_ok "Removed ROCm packages"
|
||||
|
||||
msg_info "Removing ROCm repository and keyring"
|
||||
# Remove both old .list and new .sources formats
|
||||
rm -f /etc/apt/sources.list.d/rocm.list
|
||||
rm -f /etc/apt/sources.list.d/rocm.sources
|
||||
rm -f /etc/apt/preferences.d/rocm-pin-600
|
||||
cleanup_tool_keyrings "rocm"
|
||||
$STD apt update
|
||||
msg_ok "Removed ROCm repository"
|
||||
|
||||
msg_info "Removing environment configuration"
|
||||
rm -f /etc/profile.d/rocm.sh
|
||||
msg_ok "Removed environment configuration"
|
||||
|
||||
msg_ok "ROCm has been uninstalled"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update_rocm() {
|
||||
if [[ ! -f /etc/apt/keyrings/rocm.gpg ]]; then
|
||||
msg_error "ROCm is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_info "Checking for ROCm updates"
|
||||
$STD apt update
|
||||
|
||||
local updates
|
||||
updates=$(apt list --upgradable 2>/dev/null | grep -c "rocm" || true)
|
||||
|
||||
if [[ "$updates" -gt 0 ]]; then
|
||||
msg_ok "Found ${updates} ROCm package update(s)"
|
||||
msg_info "Upgrading ROCm packages"
|
||||
$STD apt install --only-upgrade rocm
|
||||
msg_ok "Updated ROCm packages"
|
||||
else
|
||||
msg_ok "ROCm is already up-to-date"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# VERIFY INSTALLATION
|
||||
# ==============================================================================
|
||||
function verify_installation() {
|
||||
msg_info "Verifying ROCm installation"
|
||||
|
||||
if [[ -x /opt/rocm/bin/rocminfo ]]; then
|
||||
msg_ok "ROCm installed successfully"
|
||||
echo ""
|
||||
echo -e "${TAB}${BL}ROCm Version:${CL} $(/opt/rocm/bin/rocminfo --version 2>/dev/null | head -1 || echo 'Installed')"
|
||||
echo -e "${TAB}${BL}Install Path:${CL} /opt/rocm"
|
||||
echo ""
|
||||
echo -e "${TAB}${YW}To use ROCm, either:${CL}"
|
||||
echo -e "${TAB} 1. Log out and back in, or"
|
||||
echo -e "${TAB} 2. Run: source /etc/profile.d/rocm.sh"
|
||||
echo ""
|
||||
echo -e "${TAB}${YW}Verify installation with:${CL}"
|
||||
echo -e "${TAB} rocminfo"
|
||||
echo -e "${TAB} rocm-smi"
|
||||
else
|
||||
msg_warn "ROCm installed but rocminfo not found. GPU may not be available."
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
header_info
|
||||
|
||||
# Use detect_os from install.func (sets OS_TYPE, OS_VERSION, OS_FAMILY, etc.)
|
||||
detect_os
|
||||
|
||||
# Set up ROCm-specific variables based on OS detection
|
||||
setup_rocm_repo_mapping
|
||||
|
||||
# Use get_lxc_ip from core.func (sets LOCAL_IP environment variable)
|
||||
get_lxc_ip
|
||||
|
||||
# Check if running in LXC container
|
||||
if ! check_lxc; then
|
||||
msg_warn "This script is designed for LXC containers."
|
||||
msg_warn "Running on bare metal may work but is not officially supported."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check for existing installation
|
||||
if [[ -f /etc/apt/keyrings/rocm.gpg ]]; then
|
||||
msg_warn "ROCm is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall ROCm? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall_rocm
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update ROCm? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update_rocm
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "ROCm is not installed."
|
||||
echo ""
|
||||
|
||||
echo -e "${TAB}${BL}This will install AMD ROCm on ${OS} ${OS_VERSION}${CL}"
|
||||
echo -e "${TAB}${BL}Supported GPUs: AMD Radeon Instinct, Radeon Pro, and some consumer GPUs${CL}"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install ROCm? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
case "${OS}" in
|
||||
Debian) install_rocm_debian ;;
|
||||
Ubuntu) install_rocm_ubuntu ;;
|
||||
*)
|
||||
msg_error "Unsupported OS: ${OS}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
verify_installation
|
||||
|
||||
echo ""
|
||||
msg_ok "ROCm installation completed!"
|
||||
echo -e "${TAB}${GN}Documentation: ${BL}https://rocm.docs.amd.com${CL}"
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,223 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://runtipi.io/ | Github: https://github.com/runtipi/runtipi
|
||||
if ! command -v curl &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||
if [[ -f /etc/alpine-release ]]; then
|
||||
apk update >/dev/null 2>&1
|
||||
apk add --no-cache curl >/dev/null 2>&1
|
||||
else
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
APP="Runtipi"
|
||||
APP_TYPE="addon"
|
||||
INSTALL_PATH="/opt/runtipi"
|
||||
DEFAULT_PORT=80
|
||||
|
||||
# Initialize all core functions (colors, formatting, icons, STD mode)
|
||||
load_functions
|
||||
|
||||
# ==============================================================================
|
||||
# PROXMOX HOST CHECK
|
||||
# ==============================================================================
|
||||
function check_proxmox_host() {
|
||||
if command -v pveversion &>/dev/null; then
|
||||
msg_error "Running on the Proxmox host is NOT recommended!"
|
||||
msg_error "This should be executed inside an LXC container."
|
||||
echo ""
|
||||
echo -n "${TAB}Continue anyway? (y/N): "
|
||||
read -r confirm
|
||||
if [[ ! "${confirm,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_warn "Aborted. Please run this inside an LXC container."
|
||||
exit 0
|
||||
fi
|
||||
msg_warn "Proceeding on Proxmox host at your own risk!"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# CHECK / INSTALL DOCKER
|
||||
# ==============================================================================
|
||||
function check_or_install_docker() {
|
||||
if command -v docker &>/dev/null; then
|
||||
msg_ok "Docker $(docker --version | cut -d' ' -f3 | tr -d ',') is available"
|
||||
if docker compose version &>/dev/null; then
|
||||
msg_ok "Docker Compose is available"
|
||||
else
|
||||
msg_error "Docker Compose plugin is not available. Please install it."
|
||||
exit 10
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
msg_warn "Docker is not installed."
|
||||
echo -n "${TAB}Install Docker now? (y/N): "
|
||||
read -r install_docker_prompt
|
||||
if [[ ! "${install_docker_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_error "Docker is required for ${APP}. Exiting."
|
||||
exit 10
|
||||
fi
|
||||
|
||||
msg_info "Installing Docker"
|
||||
DOCKER_CONFIG_PATH='/etc/docker/daemon.json'
|
||||
mkdir -p "$(dirname "$DOCKER_CONFIG_PATH")"
|
||||
echo -e '{\n "log-driver": "journald"\n}' >"$DOCKER_CONFIG_PATH"
|
||||
$STD sh <(curl -fsSL https://get.docker.com)
|
||||
msg_ok "Installed Docker"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling ${APP}"
|
||||
|
||||
if [[ -f "${INSTALL_PATH}/runtipi-cli" ]]; then
|
||||
msg_info "Stopping ${APP}"
|
||||
cd "$INSTALL_PATH"
|
||||
$STD ./runtipi-cli stop 2>/dev/null || true
|
||||
msg_ok "Stopped ${APP}"
|
||||
fi
|
||||
|
||||
if command -v docker &>/dev/null; then
|
||||
msg_info "Removing Docker containers"
|
||||
cd "$INSTALL_PATH" 2>/dev/null && $STD docker compose down --remove-orphans 2>/dev/null || true
|
||||
msg_ok "Removed Docker containers"
|
||||
fi
|
||||
|
||||
rm -rf "$INSTALL_PATH"
|
||||
msg_ok "${APP} has been uninstalled"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
msg_info "Updating ${APP}"
|
||||
cd "$INSTALL_PATH"
|
||||
$STD ./runtipi-cli update latest
|
||||
msg_ok "Updated ${APP}"
|
||||
|
||||
msg_ok "Updated successfully"
|
||||
exit
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
check_or_install_docker
|
||||
|
||||
msg_info "Installing dependencies"
|
||||
$STD apt-get update
|
||||
$STD apt-get install -y openssl
|
||||
msg_ok "Installed dependencies"
|
||||
|
||||
msg_warn "WARNING: This will run an external installer from https://runtipi.io/"
|
||||
msg_warn "The following code is NOT maintained or audited by our repository."
|
||||
msg_warn "Review: https://raw.githubusercontent.com/runtipi/runtipi/master/scripts/install.sh"
|
||||
echo ""
|
||||
echo -n "${TAB}Do you want to continue? (y/N): "
|
||||
read -r confirm
|
||||
if [[ ! "${confirm,,}" =~ ^(y|yes)$ ]]; then
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_info "Installing ${APP} (this pulls Docker containers)"
|
||||
DOCKER_CONFIG_PATH='/etc/docker/daemon.json'
|
||||
mkdir -p "$(dirname "$DOCKER_CONFIG_PATH")"
|
||||
[[ ! -f "$DOCKER_CONFIG_PATH" ]] && echo -e '{\n "log-driver": "journald"\n}' >"$DOCKER_CONFIG_PATH"
|
||||
cd /opt
|
||||
curl -fsSL "https://raw.githubusercontent.com/runtipi/runtipi/master/scripts/install.sh" -o "install.sh"
|
||||
chmod +x install.sh
|
||||
$STD ./install.sh
|
||||
chmod 666 /opt/runtipi/state/settings.json 2>/dev/null || true
|
||||
rm -f /opt/install.sh
|
||||
msg_ok "Installed ${APP}"
|
||||
|
||||
echo ""
|
||||
msg_ok "${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
header_info
|
||||
if [[ -d "$INSTALL_PATH" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 233
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -f /etc/alpine-release ]]; then
|
||||
msg_error "${APP} does not support Alpine Linux. Please use a Debian or Ubuntu based LXC."
|
||||
exit 238
|
||||
fi
|
||||
|
||||
header_info
|
||||
check_proxmox_host
|
||||
get_lxc_ip
|
||||
|
||||
# Check if already installed
|
||||
if [[ -d "$INSTALL_PATH" ]]; then
|
||||
msg_warn "${APP} is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall ${APP}? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update ${APP}? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "${APP} is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - Runtipi (via external installer)"
|
||||
echo -e "${TAB} - Docker (if not already installed)"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install ${APP}? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,65 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 tteck
|
||||
# Author: tteck (tteckster)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://www.webmin.com/ | Github: https://github.com/webmin/webmin
|
||||
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
_ __ __ _
|
||||
| | /| / /__ / / __ _ (_)__
|
||||
| |/ |/ / -_) _ \/ ' \/ / _ \
|
||||
|__/|__/\__/_.__/_/_/_/_/_//_/
|
||||
|
||||
EOF
|
||||
}
|
||||
set -eEuo pipefail
|
||||
YW=$(echo "\033[33m")
|
||||
BL=$(echo "\033[36m")
|
||||
BGN=$(echo "\033[4;92m")
|
||||
GN=$(echo "\033[1;92m")
|
||||
DGN=$(echo "\033[32m")
|
||||
CL=$(echo "\033[m")
|
||||
CM="${GN}✓${CL}"
|
||||
BFR="\\r\\033[K"
|
||||
HOLD="-"
|
||||
|
||||
msg_info() {
|
||||
local msg="$1"
|
||||
echo -ne " ${HOLD} ${YW}${msg}..."
|
||||
}
|
||||
|
||||
msg_ok() {
|
||||
local msg="$1"
|
||||
echo -e "${BFR} ${CM} ${GN}${msg}${CL}"
|
||||
}
|
||||
|
||||
# Telemetry
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "webmin" "addon"
|
||||
|
||||
header_info
|
||||
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "Webmin Installer" --yesno "This Will Install Webmin on this LXC Container. Proceed?" 10 58
|
||||
|
||||
msg_info "Installing Prerequisites"
|
||||
apt update &>/dev/null
|
||||
apt-get -y install libnet-ssleay-perl libauthen-pam-perl libio-pty-perl unzip shared-mime-info curl &>/dev/null
|
||||
msg_ok "Installed Prerequisites"
|
||||
|
||||
LATEST=$(curl -fsSL https://api.github.com/repos/webmin/webmin/releases/latest | grep '"tag_name":' | cut -d'"' -f4)
|
||||
|
||||
msg_info "Downloading Webmin"
|
||||
curl -fsSL "https://github.com/webmin/webmin/releases/download/$LATEST/webmin_${LATEST}_all.deb" -o $(basename "https://github.com/webmin/webmin/releases/download/$LATEST/webmin_${LATEST}_all.deb")
|
||||
msg_ok "Downloaded Webmin"
|
||||
|
||||
msg_info "Installing Webmin"
|
||||
dpkg -i webmin_${LATEST}_all.deb &>/dev/null
|
||||
/usr/share/webmin/changepass.pl /etc/webmin root root &>/dev/null
|
||||
rm -rf /root/webmin_${LATEST}_all.deb
|
||||
msg_ok "Installed Webmin"
|
||||
|
||||
IP=$(hostname -I | cut -f1 -d ' ')
|
||||
echo -e "Successfully Installed!! Webmin should be reachable by going to ${BL}https://${IP}:10000${CL}"
|
||||
@@ -1,6 +0,0 @@
|
||||
___ ________ ____ __ _____
|
||||
/ | ____/ / ____/_ ______ __________/ / / / /___ ____ ___ ___ / ___/__ ______ _____
|
||||
/ /| |/ __ / / __/ / / / __ `/ ___/ __ / /_/ / __ \/ __ `__ \/ _ \______\__ \/ / / / __ \/ ___/
|
||||
/ ___ / /_/ / /_/ / /_/ / /_/ / / / /_/ / __ / /_/ / / / / / / __/_____/__/ / /_/ / / / / /__
|
||||
/_/ |_\__,_/\____/\__,_/\__,_/_/ \__,_/_/ /_/\____/_/ /_/ /_/\___/ /____/\__, /_/ /_/\___/
|
||||
/____/
|
||||
@@ -1,6 +0,0 @@
|
||||
___
|
||||
/ | ______________ _____ ___
|
||||
/ /| | / ___/ ___/ __ `/ __ \/ _ \
|
||||
/ ___ |/ / / /__/ /_/ / / / / __/
|
||||
/_/ |_/_/ \___/\__,_/_/ /_/\___/
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
______ __ ______ __ _____
|
||||
/ ____/___ ____/ /__ _____ / ____/___ ____/ /__ / ___/___ ______ _____ _____
|
||||
/ / / __ \/ __ / _ \/ ___/ / / / __ \/ __ / _ \ \__ \/ _ \/ ___/ | / / _ \/ ___/
|
||||
/ /___/ /_/ / /_/ / __/ / / /___/ /_/ / /_/ / __/ ___/ / __/ / | |/ / __/ /
|
||||
\____/\____/\__,_/\___/_/ \____/\____/\__,_/\___/ /____/\___/_/ |___/\___/_/
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
______ ___ ____
|
||||
/ ____/___ ____ / (_) __/_ __
|
||||
/ / / __ \/ __ \/ / / /_/ / / /
|
||||
/ /___/ /_/ / /_/ / / / __/ /_/ /
|
||||
\____/\____/\____/_/_/_/ \__, /
|
||||
/____/
|
||||
@@ -1,6 +0,0 @@
|
||||
______ ____ __
|
||||
/ ____/___ ____ __ __/ __ \____ ______/ /___ __
|
||||
/ / / __ \/ __ \/ / / / /_/ / __ `/ ___/ __/ / / /
|
||||
/ /___/ /_/ / /_/ / /_/ / ____/ /_/ / / / /_/ /_/ /
|
||||
\____/\____/ .___/\__, /_/ \__,_/_/ \__/\__, /
|
||||
/_/ /____/ /____/
|
||||
@@ -1,6 +0,0 @@
|
||||
______ __ ___ __
|
||||
/ ____/________ ____ / |/ /___ ______/ /____ _____
|
||||
/ / / ___/ __ \/ __ \/ /|_/ / __ `/ ___/ __/ _ \/ ___/
|
||||
/ /___/ / / /_/ / / / / / / / /_/ (__ ) /_/ __/ /
|
||||
\____/_/ \____/_/ /_/_/ /_/\__,_/____/\__/\___/_/
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
______ _______
|
||||
/ ____/________ _ ______/ / ___/___ _____
|
||||
/ / / ___/ __ \ | /| / / __ /\__ \/ _ \/ ___/
|
||||
/ /___/ / / /_/ / |/ |/ / /_/ /___/ / __/ /__
|
||||
\____/_/ \____/|__/|__/\__,_//____/\___/\___/
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
____ __
|
||||
/ __ \____ _____/ /______ ____
|
||||
/ / / / __ \/ ___/ //_/ __ `/ _ \
|
||||
/ /_/ / /_/ / /__/ ,< / /_/ / __/
|
||||
/_____/\____/\___/_/|_|\__, /\___/
|
||||
/____/
|
||||
@@ -1,6 +0,0 @@
|
||||
____ __ __
|
||||
/ __ \____ / /______ / /___ __ __
|
||||
/ / / / __ \/ //_/ __ \/ / __ \/ / / /
|
||||
/ /_/ / /_/ / ,< / /_/ / / /_/ / /_/ /
|
||||
/_____/\____/_/|_/ .___/_/\____/\__, /
|
||||
/_/ /____/
|
||||
@@ -1,6 +0,0 @@
|
||||
_______ __ ____
|
||||
/ ____(_) /__ / __ )_________ _ __________ _____
|
||||
/ /_ / / / _ \/ __ / ___/ __ \ | /| / / ___/ _ \/ ___/
|
||||
/ __/ / / / __/ /_/ / / / /_/ / |/ |/ (__ ) __/ /
|
||||
/_/ /_/_/\___/_____/_/ \____/|__/|__/____/\___/_/
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
_______ __ ____ ____ __
|
||||
/ ____(_) /__ / __ )_________ _ __________ _____ / __ \__ ______ _____ / /___ ______ ___
|
||||
/ /_ / / / _ \/ __ / ___/ __ \ | /| / / ___/ _ \/ ___/ / / / / / / / __ `/ __ \/ __/ / / / __ `__ \
|
||||
/ __/ / / / __/ /_/ / / / /_/ / |/ |/ (__ ) __/ / / /_/ / /_/ / /_/ / / / / /_/ /_/ / / / / / /
|
||||
/_/ /_/_/\___/_____/_/ \____/|__/|__/____/\___/_/ \___\_\__,_/\__,_/_/ /_/\__/\__,_/_/ /_/ /_/
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
________
|
||||
/ ____/ /___ _____ ________ _____
|
||||
/ / __/ / __ `/ __ \/ ___/ _ \/ ___/
|
||||
/ /_/ / / /_/ / / / / /__/ __(__ )
|
||||
\____/_/\__,_/_/ /_/\___/\___/____/
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
____ _ __ ____ __ ___ ____
|
||||
/ _/___ ___ ____ ___ (_)____/ /_ / __ \__ __/ /_ / (_)____ / __ \_________ _ ____ __
|
||||
/ // __ `__ \/ __ `__ \/ / ___/ __ \ / /_/ / / / / __ \/ / / ___/ / /_/ / ___/ __ \| |/_/ / / /
|
||||
_/ // / / / / / / / / / / / /__/ / / / / ____/ /_/ / /_/ / / / /__ / ____/ / / /_/ /> </ /_/ /
|
||||
/___/_/ /_/ /_/_/ /_/ /_/_/\___/_/ /_/ /_/ \__,_/_.___/_/_/\___/ /_/ /_/ \____/_/|_|\__, /
|
||||
/____/
|
||||
@@ -1,6 +0,0 @@
|
||||
__ ____ __ __
|
||||
/ /__ / / /_ _______/ /_____ _/ /_
|
||||
__ / / _ \/ / / / / / ___/ __/ __ `/ __/
|
||||
/ /_/ / __/ / / /_/ (__ ) /_/ /_/ / /_
|
||||
\____/\___/_/_/\__, /____/\__/\__,_/\__/
|
||||
/____/
|
||||
@@ -1,6 +0,0 @@
|
||||
__ __ __
|
||||
/ //_/___ ____ ___ ____ ____/ /___
|
||||
/ ,< / __ \/ __ `__ \/ __ \/ __ / __ \
|
||||
/ /| / /_/ / / / / / / /_/ / /_/ / /_/ /
|
||||
/_/ |_\____/_/ /_/ /_/\____/\__,_/\____/
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
__ __ __ __
|
||||
____ ___ _ __/ /______/ /___ __ ______/ / ___ _ ______ ____ _____/ /____ _____
|
||||
/ __ \/ _ \| |/_/ __/ ___/ / __ \/ / / / __ /_____/ _ \| |/_/ __ \/ __ \/ ___/ __/ _ \/ ___/
|
||||
/ / / / __/> </ /_/ /__/ / /_/ / /_/ / /_/ /_____/ __/> </ /_/ / /_/ / / / /_/ __/ /
|
||||
/_/ /_/\___/_/|_|\__/\___/_/\____/\__,_/\__,_/ \___/_/|_/ .___/\____/_/ \__/\___/_/
|
||||
/_/
|
||||
@@ -1,6 +0,0 @@
|
||||
____ ___ _______
|
||||
/ __ \/ (_) _____/_ __(_)___
|
||||
/ / / / / / | / / _ \/ / / / __ \
|
||||
/ /_/ / / /| |/ / __/ / / / / / /
|
||||
\____/_/_/ |___/\___/_/ /_/_/ /_/
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
__ __ ___ ___ __ _
|
||||
____ / /_ ____ / |/ /_ __/ | ____/ /___ ___ (_)___
|
||||
/ __ \/ __ \/ __ \/ /|_/ / / / / /| |/ __ / __ `__ \/ / __ \
|
||||
/ /_/ / / / / /_/ / / / / /_/ / ___ / /_/ / / / / / / / / / /
|
||||
/ .___/_/ /_/ .___/_/ /_/\__, /_/ |_\__,_/_/ /_/ /_/_/_/ /_/
|
||||
/_/ /_/ /____/
|
||||
@@ -1,6 +0,0 @@
|
||||
_ __ __ __
|
||||
____ (_) /_ ____ / /__ ___ _ ______ ____ _____/ /____ _____
|
||||
/ __ \/ / __ \/ __ \/ / _ \______/ _ \| |/_/ __ \/ __ \/ ___/ __/ _ \/ ___/
|
||||
/ /_/ / / / / / /_/ / / __/_____/ __/> </ /_/ / /_/ / / / /_/ __/ /
|
||||
/ .___/_/_/ /_/\____/_/\___/ \___/_/|_/ .___/\____/_/ \__/\___/_/
|
||||
/_/ /_/
|
||||
@@ -1,6 +0,0 @@
|
||||
__ __ __ __
|
||||
____ _________ ____ ___ ___ / /_/ /_ ___ __ _______ ____ ____ _____ ___ _____/ /__ __________ ____ ____ __ __ ___ _ ______ ____ _____/ /____ _____
|
||||
/ __ \/ ___/ __ \/ __ `__ \/ _ \/ __/ __ \/ _ \/ / / / ___/_____/ __ \/ __ `/ __ \/ _ \/ ___/ / _ \/ ___/ ___/_____/ __ \/ __ `/ |/_/_____/ _ \| |/_/ __ \/ __ \/ ___/ __/ _ \/ ___/
|
||||
/ /_/ / / / /_/ / / / / / / __/ /_/ / / / __/ /_/ (__ )_____/ /_/ / /_/ / /_/ / __/ / / / __(__ |__ )_____/ / / / /_/ /> </_____/ __/> </ /_/ / /_/ / / / /_/ __/ /
|
||||
/ .___/_/ \____/_/ /_/ /_/\___/\__/_/ /_/\___/\__,_/____/ / .___/\__,_/ .___/\___/_/ /_/\___/____/____/ /_/ /_/\__, /_/|_| \___/_/|_/ .___/\____/_/ \__/\___/_/
|
||||
/_/ /_/ /_/ /____/ /_/
|
||||
@@ -1,6 +0,0 @@
|
||||
__ _ __ __ __ __
|
||||
____ _/ /_ (_) /_/ /_____ _____________ ____ / /_ ___ _ ______ ____ _____/ /____ _____
|
||||
/ __ `/ __ \/ / __/ __/ __ \/ ___/ ___/ _ \/ __ \/ __/_____/ _ \| |/_/ __ \/ __ \/ ___/ __/ _ \/ ___/
|
||||
/ /_/ / /_/ / / /_/ /_/ /_/ / / / / / __/ / / / /_/_____/ __/> </ /_/ / /_/ / / / /_/ __/ /
|
||||
\__, /_.___/_/\__/\__/\____/_/ /_/ \___/_/ /_/\__/ \___/_/|_/ .___/\____/_/ \__/\___/_/
|
||||
/_/ /_/
|
||||
@@ -1,6 +0,0 @@
|
||||
____ __ _ _
|
||||
/ __ \__ ______ / /_(_)___ (_)
|
||||
/ /_/ / / / / __ \/ __/ / __ \/ /
|
||||
/ _, _/ /_/ / / / / /_/ / /_/ / /
|
||||
/_/ |_|\__,_/_/ /_/\__/_/ .___/_/
|
||||
/_/
|
||||
Reference in New Issue
Block a user