7 Commits

Author SHA1 Message Date
Your Name
183dcf5072 Improve heap configuration controls 2025-11-07 06:25:38 -06:00
Your Name
8fc7e89cdd Strip bootstrap.password from config cleanup 2025-11-07 05:24:08 -06:00
Your Name
825d522d5c Store bootstrap password only in keystore 2025-11-07 05:19:25 -06:00
Your Name
82825fd4c4 Fix path.plugins removal 2025-11-07 05:16:09 -06:00
Your Name
eeee0301c6 Preinstall analysis-icu and drop runtime plugin install 2025-11-07 05:05:49 -06:00
Your Name
ee566734a0 Bump to 1.0.12 with Java 21 and plugin automation 2025-11-07 04:53:08 -06:00
Your Name
a2f315f959 Ensure plugin installation works on Cloudron 2025-11-07 04:17:23 -06:00
5 changed files with 194 additions and 62 deletions

View File

@@ -4,7 +4,7 @@
"author": "Elastic and Cloudron Community",
"description": "Elasticsearch is a distributed, open source search and analytics engine for all types of data. This package is designed for internal use only.",
"tagline": "Distributed search and analytics engine",
"version": "1.0.0",
"version": "1.0.17",
"healthCheckPath": "/_cluster/health?pretty",
"httpPort": 9200,
"manifestVersion": 2,
@@ -17,10 +17,6 @@
"localDir": "/data"
}
},
"accessRestriction": {
"users": true,
"defaultValue": "internal"
},
"tags": [
"elasticsearch",
"search",

View File

@@ -16,11 +16,11 @@ RUN apt-get update && \
net-tools \
iputils-ping \
dnsutils \
openjdk-17-jdk
openjdk-21-jdk
# Set Elasticsearch version
ENV ELASTIC_VERSION=9.1.5
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
ENV JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
# Create elasticsearch user and group
RUN groupadd elasticsearch && \
@@ -37,8 +37,12 @@ RUN mkdir -p /usr/share/elasticsearch && \
RUN mkdir -p /app/data/{elasticsearch,logs,config,secrets,jdk/bin,run} && \
chown -R elasticsearch:elasticsearch /app/data
# Preinstall required plugins
RUN ES_JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch analysis-icu
# Copy configuration files
COPY elasticsearch.yml /app/elasticsearch.yml
COPY .env.template /app/.env.template
COPY start.sh /app/start.sh
COPY stop.sh /app/stop.sh

View File

@@ -32,6 +32,11 @@ After installation:
1. Check the app logs to ensure Elasticsearch has started correctly
2. Note the generated password from the logs or from `/app/data/credentials.txt`
3. Configure your other Cloudron apps to connect to Elasticsearch using the format: `http://elastic:<password>@localhost:9200`
4. (Recommended) Verify the bundled `analysis-icu` plugin (required by Nextclouds full-text search):
```bash
curl -X GET -u elastic:<password> "localhost:9200/_nodes/plugins?pretty"
```
The output should list `analysis-icu` before you kick off `occ fulltextsearch:index`. If you need additional plugins, rebuild this package with the desired plugin baked into the image (runtime installation is blocked by Elasticsearch 9s filesystem restrictions).
## Troubleshooting

View File

@@ -7,6 +7,7 @@ This package provides Elasticsearch for Cloudron, configured for internal use on
- Elasticsearch 9.1.5
- Single-node configuration optimized for Cloudron
- Security enabled with basic authentication
- Automatically installs the `analysis-icu` plugin (configurable)
- Internal access only by default (not publicly exposed)
- Automatic optimization based on container resources
@@ -69,6 +70,16 @@ curl -X PUT "http://elastic:PASSWORD@IPADDRESS:9200/nextcloud" -H 'Content-Type:
You can get the IP address from the Cloudron admin panel or by using the `cloudron status` command. The password is stored in `/app/data/credentials.txt`.
### Language Analysis Plugins
Many integrations (for example, Nextcloud Full-Text Search with German documents) require the `analysis-icu` plugin so Elasticsearch understands language-specific analyzers. This package ships with `analysis-icu` preinstalled. You can verify it from the Elasticsearch web terminal:
```bash
curl -X GET -u elastic:<password> "localhost:9200/_nodes/plugins?pretty"
```
Look for `analysis-icu` in the output before running `occ fulltextsearch:index`. If you need additional plugins, rebuild this package with the desired plugin installed in the Docker image (Elasticsearch 9 no longer allows runtime plugin installation into read-only filesystems).
## Security Notes
- The app is configured as internal-only by default, so it's not exposed to the public internet
@@ -82,10 +93,17 @@ You can get the IP address from the Cloudron admin panel or by using the `cloudr
The package automatically configures Elasticsearch based on the container's available resources:
- Java heap size is set to 50% of available memory
- Java heap size defaults to 50% of the app's memory allocation
- GC optimization for container environments
- Index settings tuned for single-node operation
You can override the automatic heap sizing when necessary:
- Define `ES_JAVA_HEAP` (e.g. `export ES_JAVA_HEAP=8g`) in `/app/data/.env`
- Provide a full `ES_JAVA_OPTS` string in `/app/data/.env` to take complete control
- Add explicit `-Xms`/`-Xmx` entries in `/app/data/config/jvm.options.d/heap.options`
- Set `ES_SKIP_AUTO_HEAP=1` in `/app/data/.env` to rely solely on the configuration files
## Limitations
- This package is for internal use only and is not exposed to the web by default

207
start.sh
View File

@@ -56,21 +56,34 @@ setup_password() {
# Set up Java environment
setup_java() {
if [ -L /app/data/jdk/bin/java ]; then
mkdir -p /app/data/jdk/bin
if [ -x /app/data/jdk/bin/java ]; then
echo "Java already configured: $(/app/data/jdk/bin/java -version 2>&1 | head -n 1)"
return 0
fi
echo "Setting up Java environment..."
# Try Java 17 first, then fall back to system Java
if [ -f /usr/lib/jvm/java-17-openjdk-amd64/bin/java ]; then
ln -sf /usr/lib/jvm/java-17-openjdk-amd64/bin/{java,javac,javadoc,jar} /app/data/jdk/bin/
else
JAVA_PATH=$(which java)
if [ -n "$JAVA_PATH" ]; then
ln -sf $JAVA_PATH /app/data/jdk/bin/java
JAVA_FOUND=0
for candidate in /usr/lib/jvm/java-21-openjdk-amd64/bin /usr/lib/jvm/java-17-openjdk-amd64/bin; do
if [ -x "$candidate/java" ]; then
ln -sf "$candidate/java" /app/data/jdk/bin/java
for tool in javac javadoc jar; do
ln -sf $(which $tool 2>/dev/null) /app/data/jdk/bin/$tool 2>/dev/null || true
[ -x "$candidate/$tool" ] && ln -sf "$candidate/$tool" /app/data/jdk/bin/$tool
done
JAVA_FOUND=1
break
fi
done
if [ $JAVA_FOUND -eq 0 ]; then
JAVA_PATH=$(command -v java || true)
if [ -n "$JAVA_PATH" ]; then
ln -sf "$JAVA_PATH" /app/data/jdk/bin/java
for tool in javac javadoc jar; do
TOOL_PATH=$(command -v $tool 2>/dev/null || true)
[ -n "$TOOL_PATH" ] && ln -sf "$TOOL_PATH" /app/data/jdk/bin/$tool
done
else
echo "ERROR: No Java found on system. Elasticsearch requires Java to run."
@@ -78,15 +91,13 @@ setup_java() {
fi
fi
# Verify Java is available
if [ -L /app/data/jdk/bin/java ]; then
echo "Java version: $(/app/data/jdk/bin/java -version 2>&1 | head -n 1)"
else
if [ ! -x /app/data/jdk/bin/java ]; then
echo "ERROR: Failed to link Java executable"
ls -l /app/data/jdk/bin || true
exit 1
fi
# Ensure Java symlinks have correct ownership
echo "Java version: $(/app/data/jdk/bin/java -version 2>&1 | head -n 1)"
chown -R elasticsearch:elasticsearch /app/data/jdk
}
@@ -100,24 +111,6 @@ configure_elasticsearch() {
chmod 755 $ES_PATH_CONF
chown -R elasticsearch:elasticsearch $ES_PATH_CONF
# Add bootstrap password to keystore
echo "Setting bootstrap password..."
# Verify keystore exists before trying to add password
if [ ! -f $ES_PATH_CONF/elasticsearch.keystore ]; then
echo "ERROR: Keystore not found, cannot add bootstrap password. Creating keystore first..."
setup_keystore || {
echo "CRITICAL ERROR: Failed to create keystore, cannot proceed."
exit 1
}
fi
# Now add the bootstrap password
if ! printf "%s" "$ELASTIC_PASSWORD" | su -c "ES_PATH_CONF=$ES_PATH_CONF ES_JAVA_HOME=/app/data/jdk $ES_HOME/bin/elasticsearch-keystore add -f -x 'bootstrap.password' --stdin" elasticsearch; then
echo "ERROR: Failed to add bootstrap password to keystore."
exit 1
fi
# Copy configuration files if needed
if [ ! -f $ES_PATH_CONF/elasticsearch.yml ]; then
echo "Setting up configuration files..."
@@ -126,6 +119,14 @@ configure_elasticsearch() {
chown -R elasticsearch:elasticsearch $ES_PATH_CONF
fi
ensure_directory_structure
clean_legacy_settings
ensure_setting "xpack.security.http.ssl.enabled" "false"
ensure_setting "network.host" "0.0.0.0"
ensure_setting "discovery.type" "single-node"
ensure_setting "path.data" "/app/data/elasticsearch"
ensure_setting "path.logs" "/app/data/logs"
# CRITICAL FIX: Remove any index-level settings from elasticsearch.yml to prevent startup failure
if [ -f $ES_PATH_CONF/elasticsearch.yml ]; then
echo "Checking elasticsearch.yml for index-level settings..."
@@ -204,13 +205,6 @@ configure_elasticsearch() {
chmod 600 $ES_PATH_CONF/{users,users_roles}
fi
# Ensure basic settings in elasticsearch.yml
for setting in "xpack.security.http.ssl.enabled: false" "network.host: 0.0.0.0" "discovery.type: single-node"; do
if ! grep -q "$setting" $ES_PATH_CONF/elasticsearch.yml; then
echo "$setting" >> $ES_PATH_CONF/elasticsearch.yml
fi
done
# Final permission check
echo "Final permission check on all data directories..."
chown -R elasticsearch:elasticsearch /app/data
@@ -360,20 +354,135 @@ setup_keystore() {
return 0
}
# Ensure writable dirs exist
ensure_directory_structure() {
mkdir -p /app/data/elasticsearch
mkdir -p /app/data/elasticsearch/plugins
mkdir -p /app/data/logs
chown -R elasticsearch:elasticsearch /app/data/elasticsearch /app/data/logs
}
clean_legacy_settings() {
sed -i '/^path\.home:/d' "$ES_PATH_CONF/elasticsearch.yml"
sed -i '/^path\.data:/d' "$ES_PATH_CONF/elasticsearch.yml"
sed -i '/^path\.logs:/d' "$ES_PATH_CONF/elasticsearch.yml"
sed -i '/^path\.plugins:/d' "$ES_PATH_CONF/elasticsearch.yml"
sed -i '/^bootstrap\.password:/d' "$ES_PATH_CONF/elasticsearch.yml"
}
ensure_setting() {
local key="$1"
local value="$2"
if grep -q "^$key:" "$ES_PATH_CONF/elasticsearch.yml"; then
sed -i "s|^$key:.*|$key: $value|" "$ES_PATH_CONF/elasticsearch.yml"
else
echo "$key: $value" >> "$ES_PATH_CONF/elasticsearch.yml"
fi
}
default_gc_opts() {
echo "-XX:+UseG1GC -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -Xlog:gc*,gc+age=trace,safepoint:file=/app/data/logs/gc/gc.log:utctime,level,pid,tags:filecount=32,filesize=64m"
}
normalize_heap_value() {
local raw="${1:-}"
raw=$(echo "$raw" | tr '[:upper:]' '[:lower:]')
if [[ "$raw" =~ ^[0-9]+[mg]$ ]]; then
echo "$raw"
return 0
fi
if [[ "$raw" =~ ^[0-9]+$ ]]; then
echo "${raw}m"
return 0
fi
return 1
}
detect_memory_limit_bytes() {
local limit=""
if [ -r /sys/fs/cgroup/memory.max ]; then
limit=$(cat /sys/fs/cgroup/memory.max)
if [ "$limit" != "max" ]; then
echo "$limit"
return 0
fi
fi
if [ -r /sys/fs/cgroup/memory/memory.limit_in_bytes ]; then
limit=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes)
echo "$limit"
return 0
fi
if [ -n "${CLOUDRON_MEMORY_LIMIT:-}" ]; then
# CLOUDRON_MEMORY_LIMIT is in MB
echo $((CLOUDRON_MEMORY_LIMIT * 1024 * 1024))
return 0
fi
echo ""
}
has_custom_heap_file() {
local heap_file="$ES_PATH_CONF/jvm.options.d/heap.options"
if [ -f "$heap_file" ] && grep -Eq '^-Xms|^-Xmx' "$heap_file"; then
return 0
fi
return 1
}
# Configure JVM heap size
configure_heap() {
# Calculate optimal heap size (50% of available memory)
CONTAINER_MEM=$(cat /sys/fs/cgroup/memory.max 2>/dev/null || echo "4294967296")
[ "$CONTAINER_MEM" = "max" ] && CONTAINER_MEM="4294967296" # Default to 4GB if unlimited
if [ "${ES_SKIP_AUTO_HEAP:-0}" = "1" ]; then
echo "Skipping automatic heap sizing because ES_SKIP_AUTO_HEAP=1"
return 0
fi
HEAP_SIZE=$(expr $CONTAINER_MEM / 2097152) # Convert to MB and take 50%
[ $HEAP_SIZE -gt 31744 ] && HEAP_SIZE=31744 # Max 31GB
[ $HEAP_SIZE -lt 512 ] && HEAP_SIZE=512 # Min 512MB
if [ -n "${ES_JAVA_OPTS:-}" ]; then
echo "Detected user-specified ES_JAVA_OPTS; not overriding heap settings."
return 0
fi
# Set JVM options
export ES_JAVA_OPTS="-Xms${HEAP_SIZE}m -Xmx${HEAP_SIZE}m -XX:+UseG1GC -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30"
export ES_JAVA_OPTS="$ES_JAVA_OPTS -Xlog:gc*,gc+age=trace,safepoint:file=/app/data/logs/gc/gc.log:utctime,level,pid,tags:filecount=32,filesize=64m"
if has_custom_heap_file; then
echo "Detected custom heap options in jvm.options.d; not overriding heap settings."
return 0
fi
local heap_value=""
if [ -n "${ES_JAVA_HEAP:-}" ]; then
if heap_value=$(normalize_heap_value "$ES_JAVA_HEAP"); then
echo "Using heap size from ES_JAVA_HEAP (${heap_value})."
else
echo "Warning: ES_JAVA_HEAP value '$ES_JAVA_HEAP' is invalid; falling back to automatic sizing."
heap_value=""
fi
fi
if [ -z "$heap_value" ]; then
local limit_bytes
limit_bytes=$(detect_memory_limit_bytes)
if [ -z "$limit_bytes" ] || [ "$limit_bytes" = "0" ]; then
limit_bytes=$((4 * 1024 * 1024 * 1024))
fi
# Treat huge values as "unlimited" and fall back to 4GB to avoid 31GB default
if [ "$limit_bytes" -gt $((256 * 1024 * 1024 * 1024)) ]; then
limit_bytes=$((4 * 1024 * 1024 * 1024))
fi
local heap_mb=$((limit_bytes / 1024 / 1024 / 2))
[ "$heap_mb" -gt 31744 ] && heap_mb=31744
[ "$heap_mb" -lt 512 ] && heap_mb=512
heap_value="${heap_mb}m"
echo "Auto-configured heap size to ${heap_value} based on container limit."
fi
export ES_JAVA_OPTS="-Xms${heap_value} -Xmx${heap_value} $(default_gc_opts)"
export PATH=$ES_HOME/bin:$PATH
}
@@ -385,7 +494,7 @@ start_elasticsearch() {
# Command to start Elasticsearch
ES_START_CMD="ES_PATH_CONF=$ES_PATH_CONF ES_JAVA_HOME=/app/data/jdk $ES_HOME/bin/elasticsearch"
ES_START_CMD="$ES_START_CMD -E xpack.security.enabled=true -E bootstrap.password=$ELASTIC_PASSWORD"
ES_START_CMD="$ES_START_CMD -E xpack.security.enabled=true"
# Add explicit settings for transport SSL
ES_START_CMD="$ES_START_CMD -E xpack.security.transport.ssl.enabled=true"
ES_START_CMD="$ES_START_CMD -E xpack.security.transport.ssl.verification_mode=certificate"