diff --git a/Makefile b/Makefile index c02f8338c73..7e5813719af 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,7 @@ build-push-wrapper: .PHONY: build-audiusd-local build-push-audiusd build-audiusd-local: - docker build -t audius/audiusd:$(AD_TAG) -f ./cmd/audiusd/Dockerfile ./ + docker build -t audius/audiusd:$(AD_TAG) -t audius/audiusd:current -f ./cmd/audiusd/Dockerfile ./ build-push-audiusd: DOCKER_DEFAULT_PLATFORM=linux/amd64 docker build --push -t audius/audiusd:$(AD_TAG) -f ./cmd/audiusd/Dockerfile ./ diff --git a/cmd/audiusd/Dockerfile b/cmd/audiusd/Dockerfile index 13be1c76bdf..a7a8610b142 100644 --- a/cmd/audiusd/Dockerfile +++ b/cmd/audiusd/Dockerfile @@ -1,4 +1,9 @@ -FROM debian:bookworm AS cpp-builder +FROM debian:bullseye AS cpp-builder + +RUN apt-get update && \ + apt-get install -y curl gnupg2 lsb-release && \ + curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/postgresql-keyring.gpg && \ + echo "deb [signed-by=/usr/share/keyrings/postgresql-keyring.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/postgresql.list RUN apt-get update && apt-get install -y \ build-essential \ @@ -19,6 +24,7 @@ RUN apt-get update && apt-get install -y \ libavformat-dev \ libavutil-dev \ libswresample-dev \ + libavresample-dev \ libsamplerate0-dev \ libtag1-dev \ libchromaprint-dev \ @@ -73,11 +79,11 @@ RUN g++ -o /bin/analyze-key /app/cpp/keyfinder.cpp \ RUN g++ -o /bin/analyze-bpm /app/cpp/bpm-analyzer.cpp \ -I/usr/include/eigen3 -I/usr/local/include/essentia -I/usr/local/include \ -L/usr/local/lib \ - -lessentia -ltag -lyaml -lfftw3 -lfftw3f -lavcodec -lavformat -lavutil -lavfilter -lsamplerate -lswresample -lpthread -lz -lchromaprint && \ + -lessentia -ltag -lyaml -lfftw3 -lfftw3f -lavcodec -lavformat -lavutil -lavfilter -lsamplerate -lavresample -lpthread -lz -lchromaprint && \ chmod +x /bin/analyze-bpm -FROM golang:1.22-bookworm AS go-builder +FROM golang:1.22-bullseye AS go-builder WORKDIR /app @@ -99,7 +105,12 @@ COPY ./cmd/audiusd/env/dev.env ./cmd/audiusd/env/stage.env ./cmd/audiusd/env/pro COPY ./cmd/audiusd/entrypoint.sh /bin/entrypoint.sh RUN chmod +x /bin/entrypoint.sh -RUN mkdir -p /data && chown -R postgres:postgres /data +# Set up data directory structure with proper permissions +RUN mkdir -p /data && \ + mkdir -p /data/postgres && \ + chown -R postgres:postgres /data/postgres && \ + chmod -R 700 /data/postgres + RUN localedef -i en_US -f UTF-8 en_US.UTF-8 ARG git_sha @@ -109,6 +120,7 @@ EXPOSE 80 EXPOSE 443 EXPOSE 26656 +# Single volume mount point VOLUME ["/data"] ENTRYPOINT ["/bin/entrypoint.sh"] diff --git a/cmd/audiusd/README.md b/cmd/audiusd/README.md index abf63f2a639..39e8eb1e749 100644 --- a/cmd/audiusd/README.md +++ b/cmd/audiusd/README.md @@ -7,7 +7,7 @@ A golang implementation of the audius protocol. Minimal example to run a node and sync it to the audius mainnet. ```bash -docker run --rm -ti -p 80:80 audius/audiusd:latest +docker run --rm -ti -p 80:80 audius/audiusd:current open http://localhost/console/overview ``` @@ -17,9 +17,10 @@ open http://localhost/console/overview To operate a [registered](https://docs.audius.org/node-operator/setup/registration/) node requires the minimal config below. ```bash -# directory for data persistence -mkdir ~/.audiusd +# directory for data and configuration persistence +mkdir -p ~/.audiusd +# note that as on now, only creator nodes are supported cat < ~/.audiusd/override.env creatorNodeEndpoint=https:// delegateOwnerWallet= @@ -28,7 +29,13 @@ spOwnerWallet= ENABLE_STORAGE=true EOF -docker run -d -ti --env-file ~/.audiusd/override.env -v ~/.audiusd/data:/data -p 80:80 -p 443:443 -p 26656:26656 audius/audiusd:latest +docker run -d -ti --env-file ~/.audiusd/override.env -v ~/.audiusd/data:/data -p 80:80 -p 443:443 -p 26656:26656 audius/audiusd:current +``` + +If you are migrating from an **existing registered production node**, you will want to pay attention to the persistent volume mount point. Which will likely look something more like this: + +```bash +docker run -d -ti --env-file ~/.audiusd/override.env -v /var/k8s:/data -p 80:80 -p 443:443 -p 26656:26656 audius/audiusd:current ``` ### P2P Ports @@ -44,3 +51,26 @@ To enable TLS, set `ENABLE_TLS=true` in your environment. This will instruct `au For this to function correctly, the following conditions must be met: - Your service must be publicly accessible via the URL specified in the `creatorNodeEndpoint` environment variable. - Your service must be reachable on both port `:80` and port `:443` + +**CLOUDFLARE PROXY** + +If you are using Cloudflare Proxy, and want to use auto TLS, you will need to start with DNS-only mode: + - Configure Cloudflare in DNS-only mode initially (not proxied) + - Let the node obtain its LetsEncrypt certificate (requires HTTP access) + - Once certificate is obtained, you can enable Cloudflare proxy + +See Cloudflare [ssl-mode docs](https://developers.cloudflare.com/ssl/origin-configuration/ssl-modes/) for more details. + +## Development + +``` +make build-audiusd-local + +# sync a local node to stage +docker run --rm -ti -p 80:80 -e NETWORK=stage audius/audiusd:$(git rev-parse HEAD) +open http://localhost/console/overview + +# network defaults to prod out of box, for an unregistered, RPC node +# tag would be "current" after this PR merges +docker run --rm -ti -p 80:80 audius/audiusd:$(git rev-parse HEAD) +``` diff --git a/cmd/audiusd/entrypoint.sh b/cmd/audiusd/entrypoint.sh index 430f328c7b9..3c661693470 100644 --- a/cmd/audiusd/entrypoint.sh +++ b/cmd/audiusd/entrypoint.sh @@ -1,68 +1,104 @@ #!/bin/bash +# Set default network to prod if not specified +NETWORK="${NETWORK:-prod}" ENV_FILE="/env/${NETWORK}.env" OVERRIDE_ENV_FILE="/env/override.env" +# Validate environment files exist +if [ ! -f "$ENV_FILE" ]; then + echo "Error: Network environment file not found at $ENV_FILE" + exit 1 +fi + # source environment variables without overwriting existing ones source_env_file() { local file=$1 - if [ -f "$file" ]; then - echo "Sourcing environment variables from $file" - while IFS='=' read -r key value || [ -n "$key" ]; do - # skip lines that are comments or empty - [[ "$key" =~ ^#.*$ ]] && continue - [[ -z "$key" ]] && continue - # only set variables that are not already defined (prioritize docker-passed env) - if [ -z "${!key}" ]; then - export "$key"="$value" - fi - done < "$file" - else - echo "Environment file $file not found!" + if [ ! -f "$file" ]; then + echo "Environment file $file not found" + return fi + + echo "Loading environment from $file" + while IFS='=' read -r key value || [ -n "$key" ]; do + [[ "$key" =~ ^#.*$ ]] && continue + [[ -z "$key" ]] && continue + if [ -z "${!key}" ]; then + val="${value%\"}" + val="${val#\"}" + export "$key"="$val" + fi + done < "$file" } source_env_file "$ENV_FILE" source_env_file "$OVERRIDE_ENV_FILE" -# minimum values for a core node to just run -POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres} -POSTGRES_DB=${POSTGRES_DB:-audiusd} -POSTGRES_DATA_DIR=${POSTGRES_DATA_DIR:-/var/lib/postgresql/data} -export dbUrl=${dbUrl:-postgresql://postgres:postgres@localhost:5432/audius_creator_node?sslmode=disable} +# Set database name based on creatorNodeEndpoint +if [ -n "$creatorNodeEndpoint" ]; then + POSTGRES_DB="audius_creator_node" +else + POSTGRES_DB="audiusd" +fi + +# Set other defaults +POSTGRES_USER="postgres" +POSTGRES_PASSWORD="postgres" +POSTGRES_DATA_DIR=${POSTGRES_DATA_DIR:-/data/postgres} +export dbUrl="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?sslmode=disable" export uptimeDataDir=${uptimeDataDir:-/data/bolt} -export audius_core_root_dir=${audius_core_root_dir:-/data/audiusd} +export audius_core_root_dir=${audius_core_root_dir:-/data/core} export creatorNodeEndpoint=${creatorNodeEndpoint:-http://localhost} -if [ ! -d "$POSTGRES_DATA_DIR" ]; then - echo "Initializing PostgreSQL data directory at $POSTGRES_DATA_DIR..." - su - postgres -c "/usr/lib/postgresql/*/bin/initdb -D $POSTGRES_DATA_DIR" +setup_postgres() { + PG_BIN="/usr/lib/postgresql/15/bin" + + # Ensure directories exist with correct permissions + mkdir -p /data + mkdir -p "$POSTGRES_DATA_DIR" + chown -R postgres:postgres /data + chown -R postgres:postgres "$POSTGRES_DATA_DIR" + chmod -R 700 "$POSTGRES_DATA_DIR" - echo "Updating PostgreSQL configuration for password authentication..." - sed -i "s/peer/trust/g" "$POSTGRES_DATA_DIR/pg_hba.conf" - sed -i "s/md5/trust/g" "$POSTGRES_DATA_DIR/pg_hba.conf" -fi - -chown -R postgres:postgres "$POSTGRES_DATA_DIR" -chmod -R u+rwx,g-rwx,o-rwx "$POSTGRES_DATA_DIR" - -echo "Configuring PostgreSQL to log to stderr for docker capture..." -sed -i "s|#log_destination = 'stderr'|log_destination = 'stderr'|" "$POSTGRES_DATA_DIR/postgresql.conf" -sed -i "s|#logging_collector = on|logging_collector = off|" "$POSTGRES_DATA_DIR/postgresql.conf" + # Initialize if needed + if [ -z "$(ls -A $POSTGRES_DATA_DIR)" ] || ! [ -f "$POSTGRES_DATA_DIR/PG_VERSION" ]; then + echo "Initializing PostgreSQL data directory at $POSTGRES_DATA_DIR..." + su - postgres -c "$PG_BIN/initdb -D $POSTGRES_DATA_DIR" + + # Configure authentication and logging + sed -i "s/peer/trust/g; s/md5/trust/g" "$POSTGRES_DATA_DIR/pg_hba.conf" + sed -i "s|#log_destination = 'stderr'|log_destination = 'stderr'|; \ + s|#logging_collector = on|logging_collector = off|" \ + "$POSTGRES_DATA_DIR/postgresql.conf" -echo "Starting PostgreSQL service..." -su - postgres -c "/usr/lib/postgresql/*/bin/pg_ctl -D $POSTGRES_DATA_DIR -o '-c config_file=$POSTGRES_DATA_DIR/postgresql.conf' start" + # Only set up database and user on fresh initialization + echo "Setting up PostgreSQL user and database..." + # Start PostgreSQL temporarily to create user and database + su - postgres -c "$PG_BIN/pg_ctl -D $POSTGRES_DATA_DIR start" + until su - postgres -c "$PG_BIN/pg_isready -q"; do + sleep 1 + done + + su - postgres -c "psql -c \"ALTER USER ${POSTGRES_USER} WITH PASSWORD '${POSTGRES_PASSWORD}';\"" + su - postgres -c "psql -tc \"SELECT 1 FROM pg_database WHERE datname = '${POSTGRES_DB}'\" | grep -q 1 || \ + psql -c \"CREATE DATABASE ${POSTGRES_DB};\"" + + # Stop PostgreSQL to restart it properly + su - postgres -c "$PG_BIN/pg_ctl -D $POSTGRES_DATA_DIR stop" + fi -until su - postgres -c "pg_isready -q"; do - echo "Waiting for PostgreSQL to start..." - sleep 2 -done + # Always start PostgreSQL + echo "Starting PostgreSQL service..." + su - postgres -c "$PG_BIN/pg_ctl -D $POSTGRES_DATA_DIR start" -echo "Setting up PostgreSQL user and database..." -su - postgres -c "psql -c \"ALTER USER postgres WITH PASSWORD '$POSTGRES_PASSWORD';\"" -su - postgres -c "psql -tc \"SELECT 1 FROM pg_database WHERE datname = '$POSTGRES_DB'\" | grep -q 1 || psql -c 'CREATE DATABASE $POSTGRES_DB;'" + # Wait for PostgreSQL to be ready + until su - postgres -c "$PG_BIN/pg_isready -q"; do + echo "Waiting for PostgreSQL to start..." + sleep 2 + done +} -su - postgres -c "/usr/lib/postgresql/*/bin/pg_ctl -D $POSTGRES_DATA_DIR -o '-c config_file=$POSTGRES_DATA_DIR/postgresql.conf' restart" +setup_postgres echo "Starting audiusd..." exec /bin/audiusd "$@" diff --git a/cmd/audiusd/main.go b/cmd/audiusd/main.go index eb7fd845ebe..9ecb2eaf785 100644 --- a/cmd/audiusd/main.go +++ b/cmd/audiusd/main.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/hex" + "log/slog" "net/http" "net/http/httputil" "net/url" @@ -12,6 +13,7 @@ import ( "syscall" "log" + "net" "github.com/AudiusProject/audius-protocol/pkg/core" "github.com/AudiusProject/audius-protocol/pkg/core/common" @@ -29,7 +31,27 @@ func main() { tlsEnabled := getEnvBool("ENABLE_TLS", false) storageEnabled := getEnvBool("ENABLE_STORAGE", false) - logger := common.NewLogger(nil) + var slogLevel slog.Level + if logLevel := os.Getenv("audiusd_log_level"); logLevel != "" { + switch logLevel { + case "debug": + slogLevel = slog.LevelDebug + case "info": + slogLevel = slog.LevelInfo + case "warn": + slogLevel = slog.LevelWarn + case "error": + slogLevel = slog.LevelError + default: + slogLevel = slog.LevelWarn + } + } else { + slogLevel = slog.LevelInfo + } + + logger := common.NewLogger(&slog.HandlerOptions{ + Level: slogLevel, + }) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -91,6 +113,17 @@ func main() { } func startEchoProxyWithOptionalTLS(hostUrl *url.URL, enableTLS bool) error { + + httpPort := os.Getenv("AUDIUSD_HTTP_PORT") + if httpPort == "" { + httpPort = "80" + } + + httpsPort := os.Getenv("AUDIUSD_HTTPS_PORT") + if httpsPort == "" { + httpsPort = "443" + } + e := echo.New() e.Use(middleware.Logger()) @@ -126,18 +159,37 @@ func startEchoProxyWithOptionalTLS(hostUrl *url.URL, enableTLS bool) error { e.Any("/*", echo.WrapHandler(mediorumProxy)) if enableTLS { - e.AutoTLSManager.HostPolicy = autocert.HostWhitelist(hostUrl.Hostname()) - e.AutoTLSManager.Cache = autocert.DirCache("/data/var/www/.cache") + // Get server's IP addresses + addrs, err := net.InterfaceAddrs() + if err != nil { + e.Logger.Warn("Failed to get interface addresses:", err) + } + + // Build whitelist starting with hostname and localhost + whitelist := []string{hostUrl.Hostname(), "localhost"} + + // Add all non-loopback IPv4 addresses + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ip4 := ipnet.IP.To4(); ip4 != nil { + whitelist = append(whitelist, ip4.String()) + } + } + } + + e.Logger.Info("TLS host whitelist:", whitelist) + e.AutoTLSManager.HostPolicy = autocert.HostWhitelist(whitelist...) + e.AutoTLSManager.Cache = autocert.DirCache(getEnvString("audius_core_root_dir", "/audius-core") + "/echo/cache") e.Pre(middleware.HTTPSRedirect()) go func() { - if err := e.StartAutoTLS(":443"); err != nil && err != http.ErrServerClosed { + if err := e.StartAutoTLS(":" + httpsPort); err != nil && err != http.ErrServerClosed { e.Logger.Fatal("shutting down the server") } }() go func() { - if err := e.Start(":80"); err != nil && err != http.ErrServerClosed { + if err := e.Start(":" + httpPort); err != nil && err != http.ErrServerClosed { e.Logger.Fatal("HTTP server failed") } }() @@ -145,26 +197,15 @@ func startEchoProxyWithOptionalTLS(hostUrl *url.URL, enableTLS bool) error { return nil } - return e.Start(":80") + return e.Start(":" + httpPort) } -func keyGen() (pKey string, addr string) { - privateKey, err := crypto.GenerateKey() - if err != nil { - log.Fatalf("Failed to generate private key: %v", err) - } - privateKeyBytes := crypto.FromECDSA(privateKey) - privateKeyStr := hex.EncodeToString(privateKeyBytes) - address := crypto.PubkeyToAddress(privateKey.PublicKey) - return privateKeyStr, address.Hex() -} - -func getEnvBool(key string, defaultVal bool) bool { +func getEnv[T any](key string, defaultVal T, parse func(string) (T, error)) T { val, ok := os.LookupEnv(key) if !ok { return defaultVal } - parsed, err := strconv.ParseBool(val) + parsed, err := parse(val) if err != nil { log.Printf("Invalid value for %s: %v, defaulting to %v", key, val, defaultVal) return defaultVal @@ -172,6 +213,14 @@ func getEnvBool(key string, defaultVal bool) bool { return parsed } +func getEnvString(key, defaultVal string) string { + return getEnv(key, defaultVal, func(s string) (string, error) { return s, nil }) +} + +func getEnvBool(key string, defaultVal bool) bool { + return getEnv(key, defaultVal, strconv.ParseBool) +} + func getHostUrl() (*url.URL, error) { ep := os.Getenv("creatorNodeEndpoint") if ep == "" { @@ -182,3 +231,14 @@ func getHostUrl() (*url.URL, error) { } return url.Parse(ep) } + +func keyGen() (pKey string, addr string) { + privateKey, err := crypto.GenerateKey() + if err != nil { + log.Fatalf("Failed to generate private key: %v", err) + } + privateKeyBytes := crypto.FromECDSA(privateKey) + privateKeyStr := hex.EncodeToString(privateKeyBytes) + address := crypto.PubkeyToAddress(privateKey.PublicKey) + return privateKeyStr, address.Hex() +} diff --git a/pkg/core/core.go b/pkg/core/core.go index 3504e0ff661..47dec965b66 100644 --- a/pkg/core/core.go +++ b/pkg/core/core.go @@ -320,6 +320,7 @@ func setupNode(logger *common.Logger) (*config.Config, *cconfig.Config, error) { // https://docs.cometbft.com/main/references/config/config.toml#log_level cometConfig.LogLevel = envConfig.LogLevel + logger.Infof("Setting cometConfig.LogLevel = envConfig.LogLevel: %s, %s", cometConfig.LogLevel, envConfig.LogLevel) // postgres indexer config cometConfig.TxIndex.Indexer = "psql"