Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.4.3
0.4.4
1 change: 1 addition & 0 deletions lib/cmd_install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ cmd_install() {

# 5. Generate docker-compose.yml
generate_compose_file
warn_custom_ports_under_host_net

# 6. Build Docker image
docker_build || die "Docker build failed. Check the output above for details."
Expand Down
39 changes: 39 additions & 0 deletions lib/cmd_security.sh
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,32 @@ _check_permissions() {
fi
}

# Flag ufw-docker presence when the bridged monitoring stack is installed. The
# node itself uses host networking on Linux (so its QUIC port is unaffected by
# ufw-docker's DOCKER-USER UDP drops), but Grafana stays on the bridge — and
# ufw-docker will block LAN access to it until the operator allows the port
# explicitly. Warn-only; no auto-apply, since `ufw-docker allow` needs root
# and the exact command is operator-environment specific.
_check_ufw_docker() {
[[ "$LOGOS_OS" == "linux" ]] || return 0

# The ufw-docker shell helper edits /etc/ufw/after.rules; the binary itself
# may not stick around in PATH after install. Match either signal.
local has_rules=false has_bin=false
grep -qE '^-A DOCKER-USER' /etc/ufw/after.rules 2>/dev/null && has_rules=true
command -v ufw-docker &>/dev/null && has_bin=true
[[ "$has_rules" == "false" && "$has_bin" == "false" ]] && return 0

# Only warn when there's actually a bridged surface affected. Gate on the
# monitoring compose existing on disk, not on the stack running — the
# block applies regardless of whether Grafana is currently up.
local mon_compose="$LOGOS_NODE_DIR/docker-compose.monitoring.yml"
[[ -f "$mon_compose" ]] || return 0

_add_finding "warn" "ufw-docker" \
"detected — may block LAN access to Grafana (${LOGOS_GRAFANA_PORT}/tcp). Allow with: sudo ufw-docker allow logos-grafana 3000/tcp"
}

# ── Scan (report only) ──────────────────────────────────────────────

_security_scan() {
Expand Down Expand Up @@ -316,6 +342,7 @@ _security_scan() {
_check_auto_updates
_check_fail2ban
_check_permissions
_check_ufw_docker

# Print findings
for finding in "${FINDINGS[@]}"; do
Expand Down Expand Up @@ -363,6 +390,18 @@ _security_apply() {
_check_auto_updates
_check_fail2ban
_check_permissions
_check_ufw_docker

# ufw-docker finding is informational — surface it up-front so the operator
# sees it before the interactive prompts. No corresponding _apply step;
# the suggested command needs root and is environment-specific.
for finding in "${FINDINGS[@]}"; do
if [[ "$finding" == *"ufw-docker"* ]]; then
echo -e " $finding"
echo ""
break
fi
done

local changes_made=0

Expand Down
30 changes: 26 additions & 4 deletions lib/cmd_start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,34 @@ cmd_start() {
return 0
fi

# Regenerate compose if port settings changed
# Regenerate compose if the on-disk file disagrees with current settings.
# Two flavors of drift: network mode (host vs bridge) and host port mapping
# (bridge mode only — host mode has no port maps to drift).
if [[ -f "$compose_path" ]]; then
if ! grep -q "\"${LOGOS_API_PORT}:8080\"" "$compose_path" 2>/dev/null || \
! grep -q "\"${LOGOS_UDP_PORT}:3000/udp\"" "$compose_path" 2>/dev/null; then
log_info "Port settings changed — regenerating docker-compose.yml"
local needs_regen=false
local file_has_host_mode=false
grep -q '^[[:space:]]*network_mode:[[:space:]]*host' "$compose_path" 2>/dev/null && file_has_host_mode=true

if [[ "$LOGOS_DOCKER_NETWORK_MODE" == "host" ]] && [[ "$file_has_host_mode" == "false" ]]; then
log_info "Switching to host networking — regenerating docker-compose.yml"
needs_regen=true
elif [[ "$LOGOS_DOCKER_NETWORK_MODE" != "host" ]] && [[ "$file_has_host_mode" == "true" ]]; then
log_info "Switching to bridge networking — regenerating docker-compose.yml"
needs_regen=true
elif [[ "$LOGOS_DOCKER_NETWORK_MODE" != "host" ]]; then
if ! grep -q "\"${LOGOS_API_PORT}:8080\"" "$compose_path" 2>/dev/null || \
! grep -q "\"${LOGOS_UDP_PORT}:3000/udp\"" "$compose_path" 2>/dev/null; then
log_info "Port settings changed — regenerating docker-compose.yml"
needs_regen=true
fi
fi

if [[ "$needs_regen" == "true" ]]; then
generate_compose_file
# OTLP endpoint baked into user_config.yaml depends on the network
# mode (logos-otel DNS under bridge, 127.0.0.1 under host). Rewrite
# if the mode just flipped. Idempotent / no-op otherwise.
migrate_user_config_otlp_endpoint "$(get_user_config_path)"
fi
fi

Expand Down
5 changes: 5 additions & 0 deletions lib/cmd_update.sh
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ cmd_update() {
save_setting "LOGOS_NODE_VERSION" "$LOGOS_NODE_VERSION"
save_setting "LOGOS_CIRCUITS_VERSION" "$LOGOS_CIRCUITS_VERSION"
generate_compose_file
warn_custom_ports_under_host_net
source "$LOGOS_NODE_LIB/cmd_reset.sh"
_perform_migration "update" "true"
return 0
Expand All @@ -261,6 +262,8 @@ cmd_update() {
save_setting "LOGOS_CIRCUITS_VERSION" "$LOGOS_CIRCUITS_VERSION"

generate_compose_file
warn_custom_ports_under_host_net
migrate_user_config_otlp_endpoint "$(get_user_config_path)"
docker_build || die "Failed to build updated Docker image"

log_success "Node updated to ${LOGOS_NODE_VERSION}"
Expand Down Expand Up @@ -292,6 +295,8 @@ cmd_update() {
echo ""
log_info "Node compose schema changed — regenerating docker-compose.yml"
generate_compose_file
warn_custom_ports_under_host_net
migrate_user_config_otlp_endpoint "$(get_user_config_path)"
if docker_is_running; then
if confirm "Recreate the container now to apply the new compose?"; then
log_step "Recreating node container..."
Expand Down
18 changes: 17 additions & 1 deletion lib/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,26 @@ _set_defaults() {
: "${LOGOS_GRAFANA_AUTH:=false}"
: "${LOGOS_GRAFANA_PASSWORD:=logos}"

# Docker network mode for the node container. Default to host networking on
# Linux so ufw-docker's DOCKER-USER deny rules (which drop UDP dport ≤32767
# to container IPs) can't kill the node's QUIC handshake replies — see
# https://github.com/logosnode/logosup/issues/18. Bridge stays the default
# everywhere else (Docker Desktop on macOS doesn't honor host mode).
# Operators with custom ports or other host-net problems can opt out by
# setting LOGOS_DOCKER_NETWORK_MODE=bridge in settings.env — that value is
# sourced before this function runs, so the override wins.
if [[ -z "${LOGOS_DOCKER_NETWORK_MODE:-}" ]]; then
if [[ "$(uname -s)" == "Linux" ]]; then
LOGOS_DOCKER_NETWORK_MODE="host"
else
LOGOS_DOCKER_NETWORK_MODE="bridge"
fi
fi

export LOGOS_NETWORK LOGOS_NODE_VERSION LOGOS_CIRCUITS_VERSION LOGOS_API_PORT LOGOS_UDP_PORT
export LOGOS_FAUCET_URL LOGOS_DASHBOARD_URL LOGOS_DOCKER_IMAGE LOGOS_CONTAINER_NAME
export LOGOS_NODE_REPO LOGOS_CLI_REPO LOGOS_BOOTSTRAP_PEERS LOGOS_GRAFANA_PORT
export LOGOS_GRAFANA_AUTH LOGOS_GRAFANA_PASSWORD
export LOGOS_GRAFANA_AUTH LOGOS_GRAFANA_PASSWORD LOGOS_DOCKER_NETWORK_MODE
}

# ── Init & Load ───────────────────────────────────────────────────────
Expand Down
116 changes: 111 additions & 5 deletions lib/docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,14 @@ check_docker() {
export DOCKER_COMPOSE DOCKER_CMD
}

# Generate docker-compose.yml from settings
# Generate docker-compose.yml from settings.
#
# Two shapes depending on $LOGOS_DOCKER_NETWORK_MODE:
# host — node shares the host net namespace. No docker port mapping, no
# bridge. Linux default. Sidesteps ufw-docker's DOCKER-USER UDP
# drops that break QUIC on bridged containers (issue #18).
# bridge — original behavior. Mac/Docker Desktop default; Linux opt-out
# for operators who customize LOGOS_API_PORT / LOGOS_UDP_PORT.
generate_compose_file() {
local compose_path
compose_path="$(get_compose_path)"
Expand All @@ -101,7 +108,40 @@ generate_compose_file() {
local host_gid
host_gid="$(id -g)"

cat > "$compose_path" << YAML
if [[ "$LOGOS_DOCKER_NETWORK_MODE" == "host" ]]; then
cat > "$compose_path" << YAML
services:
logos-node:
build:
context: ${dockerfile_dir}
args:
NODE_VERSION: "${LOGOS_NODE_VERSION}"
CIRCUITS_VERSION: "${LOGOS_CIRCUITS_VERSION}"
image: ${LOGOS_DOCKER_IMAGE}:${LOGOS_NODE_VERSION}
container_name: ${LOGOS_CONTAINER_NAME}
restart: unless-stopped
user: "${host_uid}:${host_gid}"
network_mode: host
working_dir: /app/data
volumes:
- ${LOGOS_NODE_DIR}/user_config.yaml:/app/data/user_config.yaml:ro
- ${LOGOS_NODE_DIR}/data:/app/data
environment:
- LOGOS_BLOCKCHAIN_CIRCUITS=/app/circuits
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/cryptarchia/info"]
interval: 30s
timeout: 5s
retries: 3
start_period: 120s
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
YAML
else
cat > "$compose_path" << YAML
services:
logos-node:
build:
Expand Down Expand Up @@ -140,8 +180,23 @@ networks:
logosnode-net:
name: logosnode-net
YAML
fi

log_success "Generated docker-compose.yml"
log_dim "Networking mode: ${LOGOS_DOCKER_NETWORK_MODE}"
}

# Under host networking, the node binary listens on its own internal defaults
# (HTTP 8080, QUIC 3000) and Docker's host:container port mapping no longer
# applies — so LOGOS_API_PORT and LOGOS_UDP_PORT customizations silently lose
# effect. Surface this once after compose generation so the operator can
# either revert the custom port or opt back into bridge networking.
warn_custom_ports_under_host_net() {
[[ "$LOGOS_DOCKER_NETWORK_MODE" == "host" ]] || return 0
[[ "$LOGOS_API_PORT" != "8080" || "$LOGOS_UDP_PORT" != "3000" ]] || return 0

log_warn "Custom API/UDP ports (${LOGOS_API_PORT}/${LOGOS_UDP_PORT}) are ignored under host networking."
log_info "To keep your custom ports, set ${BOLD}LOGOS_DOCKER_NETWORK_MODE=bridge${RESET} in ${LOGOS_SETTINGS_FILE}"
}

# Build the Docker image
Expand Down Expand Up @@ -248,6 +303,19 @@ docker_init_config() {
fi
}

# Endpoint the node should push OTLP metrics to. Depends on the network mode:
# under bridge, the otel collector is reachable via compose's container DNS;
# under host networking, the node container shares the host net namespace and
# can't resolve the bridge-internal DNS name, so it pushes to the loopback port
# we publish on the otel service (see lib/monitoring.sh).
_otlp_endpoint_for_mode() {
if [[ "$LOGOS_DOCKER_NETWORK_MODE" == "host" ]]; then
echo "http://127.0.0.1:4317"
else
echo "http://logos-otel:4317"
fi
}

# Enable OTLP metrics push in user_config.yaml so logos-otel can collect
# native node metrics. Idempotent: only rewrites `metrics: None`. If metrics
# is already configured (e.g. operator customized it) we leave it alone.
Expand All @@ -258,19 +326,57 @@ patch_user_config_for_otlp() {
[[ -f "$config_path" ]] || return 0
grep -qE '^[[:space:]]+metrics: None$' "$config_path" || return 0

awk '
local otlp_endpoint
otlp_endpoint="$(_otlp_endpoint_for_mode)"

awk -v endpoint="$otlp_endpoint" '
/^[[:space:]]+metrics: None$/ {
match($0, /^[[:space:]]+/)
indent = substr($0, 1, RLENGTH)
print indent "metrics: !Otlp"
print indent " endpoint: \"http://logos-otel:4317\""
print indent " endpoint: \"" endpoint "\""
print indent " host_identifier: \"logos-node\""
next
}
{ print }
' "$config_path" > "${config_path}.tmp" && mv "${config_path}.tmp" "$config_path"
chmod 600 "$config_path"
log_dim "Enabled OTLP metrics push to logos-otel (for monitoring stack)"
log_dim "Enabled OTLP metrics push to ${otlp_endpoint} (for monitoring stack)"
}

# Migrate an existing user_config.yaml OTLP endpoint to match the current
# network mode. Used on `logosup update` so 0.4.3 installs (which baked in
# `http://logos-otel:4317`) get rewritten to `http://127.0.0.1:4317` after
# switching to host networking on Linux. Reversible: opting back into bridge
# mode rewrites the endpoint the other way.
#
# Idempotent no-op when:
# - $config_path does not exist
# - No `endpoint:` line is present (metrics block absent / operator opted out)
# - The endpoint already matches the desired mode
#
# Never touches custom endpoints — only the two values this CLI is known to
# write. An operator who pointed metrics at their own collector is unaffected.
migrate_user_config_otlp_endpoint() {
local config_path="$1"
[[ -f "$config_path" ]] || return 0

local desired old
desired="$(_otlp_endpoint_for_mode)"
if [[ "$desired" == "http://127.0.0.1:4317" ]]; then
old="http://logos-otel:4317"
else
old="http://127.0.0.1:4317"
fi

grep -qE '^[[:space:]]+endpoint:[[:space:]]*"' "$config_path" || return 0
grep -qE "endpoint:[[:space:]]*\"${desired}\"" "$config_path" && return 0
# Regex-escape dots in the source pattern so they don't act as wildcards.
local old_escaped="${old//./\\.}"
grep -qE "endpoint:[[:space:]]*\"${old_escaped}\"" "$config_path" || return 0

sed_inplace "s|endpoint: \"${old_escaped}\"|endpoint: \"${desired}\"|" "$config_path"
log_dim "Migrated OTLP endpoint → ${desired}"
}

# Disable the tracing module's disk-based file output. The default config
Expand Down
20 changes: 19 additions & 1 deletion lib/monitoring.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@ generate_monitoring_compose_file() {

log_step "Generating monitoring compose file..."

# Under host networking the node container leaves the bridge, so the
# exporter can no longer reach it via compose DNS (logos-node:8080) and
# the node can no longer reach the otel collector via DNS either. Re-point
# both via host.docker.internal / loopback. Bridge mode is unchanged.
local node_api_url exporter_host_block otel_host_ports
if [[ "$LOGOS_DOCKER_NETWORK_MODE" == "host" ]]; then
node_api_url="http://host.docker.internal:${LOGOS_API_PORT}"
exporter_host_block=$' extra_hosts:\n - "host.docker.internal:host-gateway"'
# Bind 4317 to loopback only — never publish OTLP to the LAN.
otel_host_ports=$' ports:\n - "127.0.0.1:4317:4317"'
else
node_api_url="http://${LOGOS_CONTAINER_NAME}:8080"
exporter_host_block=""
otel_host_ports=""
fi

cat > "$compose_path" << COMPOSE
services:
logos-exporter:
Expand All @@ -67,10 +83,11 @@ services:
ports:
- "9100:9100"
environment:
- NODE_API_URL=http://${LOGOS_CONTAINER_NAME}:8080
- NODE_API_URL=${node_api_url}
- CONTAINER_NAME=${LOGOS_CONTAINER_NAME}
- POLL_INTERVAL=15
pid: host
${exporter_host_block}
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /proc:/host/proc:ro
Expand All @@ -95,6 +112,7 @@ services:
- "4317" # OTLP gRPC (node pushes here)
- "4318" # OTLP HTTP
- "8889" # Prometheus scrape
${otel_host_ports}
logging:
driver: json-file
options:
Expand Down