diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..5a914fb --- /dev/null +++ b/build.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e +PACKAGE_DIR="./packages" +PACKAGE_NAME="./tixel-watch" + +if [ -d "${PACKAGE_DIR}" ]; then + rm -rf "${PACKAGE_DIR:?}/"* +else + mkdir -p "${PACKAGE_DIR}" +fi + +if [ -d "${PACKAGE_NAME}" ]; then + rm -rf "${PACKAGE_NAME:?}" +fi + +CGO_ENABLED=0 go build -ldflags="-s -w" -o "$PACKAGE_DIR"/tixel-watch . + +cp -r ./tixel-watch.service $PACKAGE_DIR/ +cp -r ./configs/ $PACKAGE_DIR/ +cp -r ./install.sh $PACKAGE_DIR/ +mv $PACKAGE_DIR tixel-watch + +tar -czvf tixel-watch.tar.gz ./tixel-watch diff --git a/config.go b/config.go index ac64554..1bebda9 100644 --- a/config.go +++ b/config.go @@ -69,7 +69,7 @@ type Config struct { func LoadConfig() (*Config, error) { viper.SetConfigName("config") viper.AddConfigPath(".") - viper.AddConfigPath("/etc/tixel-watch/") + viper.AddConfigPath("/opt/tixel/tixel-watch/") viper.SetConfigType("yaml") setConfigDefaults() diff --git a/config.yaml b/configs/config.yaml similarity index 100% rename from config.yaml rename to configs/config.yaml diff --git a/configs/elasticsearch.yml b/configs/elasticsearch.yml new file mode 100644 index 0000000..fdd1f9c --- /dev/null +++ b/configs/elasticsearch.yml @@ -0,0 +1,119 @@ +# ======================== Elasticsearch Configuration ========================= +# +# NOTE: Elasticsearch comes with reasonable defaults for most settings. +# Before you set out to tweak and tune the configuration, make sure you +# understand what are you trying to accomplish and the consequences. +# +# The primary way of configuring a node is via this file. This template lists +# the most important settings you may want to configure for a production cluster. +# +# Please consult the documentation for further information on configuration options: +# https://www.elastic.co/guide/en/elasticsearch/reference/index.html +# +# ---------------------------------- Cluster ----------------------------------- +# +# Use a descriptive name for your cluster: +# +cluster.name: tixel-elastic +# +# ------------------------------------ Node ------------------------------------ +# +# Use a descriptive name for the node: +# +#node.name: node-1 +# +# Add custom attributes to the node: +# +#node.attr.rack: r1 +# +# ----------------------------------- Paths ------------------------------------ +# +# Path to directory where to store the data (separate multiple locations by comma): +# +path.data: /var/lib/elasticsearch +# +# Path to log files: +# +path.logs: /var/log/elasticsearch +# +# ----------------------------------- Memory ----------------------------------- +# +# Lock the memory on startup: +# +#bootstrap.memory_lock: true +# +# Make sure that the heap size is set to about half the memory available +# on the system and that the owner of the process is allowed to use this +# limit. +# +# Elasticsearch performs poorly when the system is swapping the memory. +# +# ---------------------------------- Network ----------------------------------- +# +# By default Elasticsearch is only accessible on localhost. Set a different +# address here to expose this node on the network: +# +network.host: 0.0.0.0 +# +# By default Elasticsearch listens for HTTP traffic on the first free port it +# finds starting at 9200. Set a specific HTTP port here: +# +#http.port: 9200 +# +# For more information, consult the network module documentation. +# +# --------------------------------- Discovery ---------------------------------- +# +# Pass an initial list of hosts to perform discovery when this node is started: +# The default list of hosts is ["127.0.0.1", "[::1]"] +# +#discovery.seed_hosts: ["host1", "host2"] +# +# Bootstrap the cluster using an initial set of master-eligible nodes: +# +#cluster.initial_master_nodes: ["node-1", "node-2"] +# +# For more information, consult the discovery and cluster formation module documentation. +# +# ---------------------------------- Various ----------------------------------- +# +# Allow wildcard deletion of indices: +# +#action.destructive_requires_name: false + +#----------------------- BEGIN SECURITY AUTO CONFIGURATION ----------------------- +# +# The following settings, TLS certificates, and keys have been automatically +# generated to configure Elasticsearch security features on 26-08-2025 14:51:23 +# +# -------------------------------------------------------------------------------- + +# Enable security features +xpack.security.enabled: false + +xpack.security.enrollment.enabled: false + +# Enable encryption for HTTP API client connections, such as Kibana, Logstash, and Agents +xpack.security.http.ssl: + enabled: false + keystore.path: certs/http.p12 + +# Enable encryption and mutual authentication between cluster nodes +xpack.security.transport.ssl: + enabled: false + verification_mode: certificate + keystore.path: certs/transport.p12 + truststore.path: certs/transport.p12 +# Create a new cluster with the current node only +# Additional nodes can still join the cluster later +cluster.initial_master_nodes: ["frankfurt.tixeltec.de"] + +# Allow HTTP API connections from anywhere +# Connections are encrypted and require user authentication +http.host: 0.0.0.0 + +# Allow other nodes to join the cluster from anywhere +# Connections are encrypted and mutually authenticated +transport.host: 0.0.0.0 + +#----------------------- END SECURITY AUTO CONFIGURATION ------------------------- diff --git a/configs/jvm.options b/configs/jvm.options new file mode 100644 index 0000000..41c890e --- /dev/null +++ b/configs/jvm.options @@ -0,0 +1,86 @@ +################################################################ +## +## JVM configuration +## +################################################################ +## +## WARNING: DO NOT EDIT THIS FILE. If you want to override the +## JVM options in this file, or set any additional options, you +## should create one or more files in the jvm.options.d +## directory containing your adjustments. +## +## See https://www.elastic.co/guide/en/elasticsearch/reference/9.1/advanced-configuration.html#set-jvm-options +## for more information. +## +################################################################ + + + +################################################################ +## IMPORTANT: JVM heap size +################################################################ +## +## The heap size is automatically configured by Elasticsearch +## based on the available memory in your system and the roles +## each node is configured to fulfill. If specifying heap is +## required, it should be done through a file in jvm.options.d, +## which should be named with .options suffix, and the min and +## max should be set to the same value. For example, to set the +## heap to 4 GB, create a new file in the jvm.options.d +## directory containing these lines: +## +## -Xms4g +## -Xmx4g +## +## See https://www.elastic.co/guide/en/elasticsearch/reference/9.1/heap-size.html +## for more information +## +################################################################ + + +################################################################ +## Expert settings +################################################################ +## +## All settings below here are considered expert settings. Do +## not adjust them unless you understand what you are doing. Do +## not edit them in this file; instead, create a new file in the +## jvm.options.d directory containing your adjustments. +## +################################################################ + +-XX:+UseG1GC + +## JVM temporary directory +-Djava.io.tmpdir=${ES_TMPDIR} + +# Leverages accelerated vector hardware instructions; removing this may +# result in less optimal vector performance +20-:--add-modules=jdk.incubator.vector + +# Required to workaround performance issue in JDK 23, https://github.com/elastic/elasticsearch/issues/113030 +23:-XX:CompileCommand=dontinline,java/lang/invoke/MethodHandle.setAsTypeCache +23:-XX:CompileCommand=dontinline,java/lang/invoke/MethodHandle.asTypeUncached + +# Lucene 10: apply MADV_NORMAL advice to enable more aggressive readahead +-Dorg.apache.lucene.store.defaultReadAdvice=normal + +## heap dumps + +# generate a heap dump when an allocation from the Java heap fails; heap dumps +# are created in the working directory of the JVM unless an alternative path is +# specified +-XX:+HeapDumpOnOutOfMemoryError + +# exit right after heap dump on out of memory error +-XX:+ExitOnOutOfMemoryError + +# specify an alternative path for heap dumps; ensure the directory exists and +# has sufficient space +# -XX:HeapDumpPath=/heap/dump/path + +# specify an alternative path for JVM fatal error logs +-XX:ErrorFile=hs_err_pid%p.log + +## GC logging +-Xlog:gc*,gc+age=trace,safepoint:file=gc.log:utctime,level,pid,tags:filecount=32,filesize=64m diff --git a/configs/jvm.options.d/maxheap.options b/configs/jvm.options.d/maxheap.options new file mode 100644 index 0000000..e292d3f --- /dev/null +++ b/configs/jvm.options.d/maxheap.options @@ -0,0 +1,2 @@ +-Xms1g +-Xmx1g diff --git a/go.mod b/go.mod index ceeab46..c0e658a 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module tixel_elastic +module tixel_watch go 1.24.1 diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..c203051 --- /dev/null +++ b/install.sh @@ -0,0 +1,71 @@ +#!/bin/bash +set -e + +ES_VERSION="9.1.2" +ES_DEB_URL="https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ES_VERSION}-amd64.deb" +ES_RPM_URL="https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ES_VERSION}-x86_64.rpm" +ES_CONFIG_DIR="/etc/elasticsearch" +ES_JVM_OPTIONS="/etc/elasticsearch/jvm.options" +ES_JVM_OPTIONS_D="/etc/elasticsearch/jvm.options.d" +GO_SERVICE_NAME="tixel-watch" +GO_INSTALL_TARGET="/opt/tixel/tixel-watch" + +install_es_deb() { + echo "Installing Elasticsearch (Debian package)..." + wget "${ES_DEB_URL}" -O elasticsearch.deb + sudo dpkg -i elasticsearch.deb + sudo apt-get install -f -y +} + +install_es_rpm() { + echo "Installing Elasticsearch (RPM package)..." + wget "${ES_RPM_URL}" -O elasticsearch.rpm + sudo rpm --install elasticsearch.rpm +} + +setup_configuration() { + echo "Copying Elasticsearch configuration files..." + sudo cp ./configs/elasticsearch.yml "${ES_CONFIG_DIR}/elasticsearch.yml" + sudo cp ./configs/jvm.options "${ES_JVM_OPTIONS}" + sudo cp -r ./configs/jvm.options.d "${ES_JVM_OPTIONS_D}" + sudo chown root:elasticsearch "${ES_CONFIG_DIR}/elasticsearch.yml" "${ES_JVM_OPTIONS}" + sudo chmod 640 "${ES_CONFIG_DIR}/elasticsearch.yml" "${ES_JVM_OPTIONS}" +} + +setup_tixel_watch_service() { + echo "Setting up tixel-watch systemd service..." + if [ ! -d ${GO_INSTALL_TARGET} ]; then + mkdir -p ${GO_INSTALL_TARGET} + fi + sudo cp ./tixel-watch "$GO_INSTALL_TARGET"/ + sudo cp ./configs/config.yaml "$GO_INSTALL_TARGET"/ + sudo cp ./${GO_SERVICE_NAME}.service /etc/systemd/system/${GO_SERVICE_NAME}.service + sudo systemctl daemon-reload + sudo systemctl enable "${GO_SERVICE_NAME}" +} + +start_services() { + echo "Enabling and starting Elasticsearch service..." + sudo systemctl enable elasticsearch + sudo systemctl start elasticsearch + echo "Starting tixel-watch service..." + sudo systemctl start "${GO_SERVICE_NAME}" +} + +main() { + if command -v apt-get &>/dev/null; then + install_es_deb + elif command -v yum &>/dev/null || command -v dnf &>/dev/null; then + install_es_rpm + else + echo "Unsupported package manager. Aborting." + exit 1 + fi + + setup_configuration + setup_tixel_watch_service + start_services + echo "All done." +} + +main "$@" diff --git a/tixel-watch.service b/tixel-watch.service new file mode 100644 index 0000000..d954224 --- /dev/null +++ b/tixel-watch.service @@ -0,0 +1,19 @@ +[Unit] +Description=tixel-watch +After=network.target + +[Service] +Type=simple +User=tixstream +Group=tixstream +WorkingDirectory=/opt/tixel/tixel-watch +ExecStart=/opt/tixel/tixel-watch/tixel-watch +Restart=on-failure +RestartSec=5 +StandardOutput=syslog +StandardError=syslog +SyslogIdentifier=tixel-watch + +[Install] +WantedBy=multi-user.target + diff --git a/web_service.go b/web_service.go index 9d49ff5..091713b 100644 --- a/web_service.go +++ b/web_service.go @@ -6,6 +6,7 @@ import ( "fmt" "log/slog" "net/http" + "os/exec" "strconv" "strings" "time" @@ -121,11 +122,31 @@ func (ws *WebService) handleHealth(w http.ResponseWriter, r *http.Request) { return } + statusMap := make(map[string]any) + statusMap["elasticsearch"] = map[string]any{"status": "healthy", "timestamp": time.Now()} + + for _, service := range ws.config.Services { + statusCommand := []string{"sudo", "systemctl", "status", service.Name, "--no-pager"} + if service.Enabled { + serviceStatus, err := exec.Command(statusCommand[0], statusCommand[1:]...).Output() + if err != nil { + slog.Error("error executing status command", "error", err) + continue + } + lines := strings.SplitSeq(string(serviceStatus), "\n") + for line := range lines { + if strings.Contains(line, "Active:") { + serviceHealth, found := strings.CutPrefix(strings.TrimSpace(line), "Active:") + if found { + statusMap[service.Name] = map[string]any{"status": serviceHealth, "timestamp": time.Now()} + } + } + } + } + } + w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]any{ - "status": "healthy", - "timestamp": time.Now(), - }) + json.NewEncoder(w).Encode(statusMap) } func (ws *WebService) handleIndices(w http.ResponseWriter, r *http.Request) {