diff --git a/ct/langfuse.sh b/ct/langfuse.sh new file mode 100644 index 0000000000..2c39d0fcd5 --- /dev/null +++ b/ct/langfuse.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/DevScripts/main" +_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func" +source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func") +# Copyright (c) 2021-2026 community-scripts ORG +# Author: Stephen Chin (steveonjava) +# License: MIT | https://github.com/community-scripts/DevScripts/raw/main/LICENSE +# Source: https://github.com/langfuse/langfuse + +APP="Langfuse" +var_tags="${var_tags:-ai;monitoring;analytics}" +var_cpu="${var_cpu:-4}" +var_ram="${var_ram:-16384}" +var_disk="${var_disk:-100}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +#var_arm64="${var_arm64:-no}" # unset = ask the user; set yes/no only when verified +var_unprivileged="${var_unprivileged:-1}" + +export var_admin_email="${var_admin_email:-}" +export var_admin_pass="${var_admin_pass:-}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + + if [[ ! -d /opt/langfuse || ! -f /opt/langfuse_data/.env ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + if check_for_gh_release "seaweedfs" "seaweedfs/seaweedfs"; then + msg_info "Stopping SeaweedFS" + systemctl stop langfuse-storage + msg_ok "Stopped SeaweedFS" + + fetch_and_deploy_gh_release "seaweedfs" "seaweedfs/seaweedfs" "prebuild" "latest" "/opt/seaweedfs" "linux_$(arch_resolve).tar.gz" + + msg_info "Starting SeaweedFS" + systemctl start langfuse-storage + $STD curl --retry 30 --retry-delay 2 --retry-connrefused -fsS http://127.0.0.1:8333/healthz + msg_ok "Started SeaweedFS" + fi + + if check_for_gh_release "migrate" "golang-migrate/migrate"; then + fetch_and_deploy_gh_release "migrate" "golang-migrate/migrate" "binary" + fi + + if check_for_gh_release "langfuse" "langfuse/langfuse"; then + msg_warn "Take a container snapshot before updating Langfuse; database migrations cannot be rolled back by this script." + msg_info "Stopping Langfuse" + systemctl stop langfuse-web langfuse-worker + msg_ok "Stopped Langfuse" + + # Keep the old version marker until the new services pass their health checks. + create_backup "$HOME/.langfuse" + CLEAN_INSTALL=1 fetch_and_deploy_gh_release "langfuse" "langfuse/langfuse" "tarball" + restore_backup + NODE_VERSION="24" NODE_MODULE="pnpm@$(jq -r '.engines.pnpm' /opt/langfuse/package.json)" setup_nodejs + RUST_TOOLCHAIN="$(sed -n 's/^channel = "\(.*\)"/\1/p' /opt/langfuse/packages/native/rust-toolchain.toml)" RUST_PROFILE="minimal" setup_rust + + msg_info "Building Langfuse" + cd /opt/langfuse + HUSKY=0 $STD pnpm install --frozen-lockfile + # DOCKER_BUILD=1 only skips Langfuse's runtime env validation while compiling; no container is involved. + DOCKER_BUILD=1 NEXT_TELEMETRY_DISABLED=1 CI=true pnpm_config_verify_deps_before_run=false $STD pnpm turbo run build --filter=web... --filter=worker... --concurrency=1 + cp -r web/.next/static web/.next/standalone/web/.next/ + cp -r web/public web/.next/standalone/web/ + msg_ok "Built Langfuse" + + msg_info "Starting Langfuse" + systemctl restart langfuse-migrate + systemctl start langfuse-web langfuse-worker + $STD curl --retry 60 --retry-delay 2 --retry-connrefused -fsS http://127.0.0.1:3000/api/public/ready + $STD curl --retry 30 --retry-delay 2 --retry-connrefused -fsS http://127.0.0.1:3030/api/health + cat <"$HOME/.langfuse" +$(jq -r '.version' /opt/langfuse/package.json) +EOF + msg_ok "Started Langfuse" + msg_ok "Updated successfully!" + fi + exit +} + +start +build_container +description + +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW}Access it using the following URL:${CL}" +echo -e "${GATEWAY}${BGN}http://${IP}:3000${CL}" diff --git a/install/langfuse-install.sh b/install/langfuse-install.sh new file mode 100644 index 0000000000..fa6e972d01 --- /dev/null +++ b/install/langfuse-install.sh @@ -0,0 +1,257 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 community-scripts ORG +# Author: Stephen Chin (steveonjava) +# License: MIT | https://github.com/community-scripts/DevScripts/raw/main/LICENSE +# Source: https://github.com/langfuse/langfuse + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +if [[ -z "${var_admin_email:-}" ]]; then + var_admin_email=$(prompt_input "Langfuse admin email:" "admin@example.com" 60) +fi +var_admin_email="${var_admin_email:-admin@example.com}" +if [[ ! "$var_admin_email" =~ ^[A-Za-z0-9_+-]+(\.[A-Za-z0-9_+-]+)*@([A-Za-z0-9][A-Za-z0-9-]*\.)+[A-Za-z]{2,}$ ]]; then + msg_warn "Invalid email '${var_admin_email}', falling back to admin@example.com" + var_admin_email="admin@example.com" +fi +var_admin_pass="${var_admin_pass:-}" +if [[ -n "$var_admin_pass" ]] && [[ ${#var_admin_pass} -lt 8 || "$var_admin_pass" == *[[:space:][:cntrl:]\"\'\\]* ]]; then + msg_warn "Admin password needs at least 8 characters and no spaces, quotes or backslashes, generating one" + var_admin_pass="" +fi +if [[ -z "$var_admin_pass" ]]; then + var_admin_pass=$(random_password 24) +fi + +msg_info "Installing Dependencies" +$STD apt install -y build-essential redis-server +msg_ok "Installed Dependencies" + +PG_VERSION="17" setup_postgresql +PG_DB_NAME="langfuse" PG_DB_USER="langfuse" setup_postgresql_db +setup_clickhouse +fetch_and_deploy_gh_release "langfuse" "langfuse/langfuse" "tarball" +NODE_VERSION="24" NODE_MODULE="pnpm@$(jq -r '.engines.pnpm' /opt/langfuse/package.json)" setup_nodejs +RUST_TOOLCHAIN="$(sed -n 's/^channel = "\(.*\)"/\1/p' /opt/langfuse/packages/native/rust-toolchain.toml)" RUST_PROFILE="minimal" setup_rust +fetch_and_deploy_gh_release "migrate" "golang-migrate/migrate" "binary" +fetch_and_deploy_gh_release "seaweedfs" "seaweedfs/seaweedfs" "prebuild" "latest" "/opt/seaweedfs" "linux_$(arch_resolve).tar.gz" + +msg_info "Building Langfuse" +cd /opt/langfuse +HUSKY=0 $STD pnpm install --frozen-lockfile +# DOCKER_BUILD=1 only skips Langfuse's runtime env validation while compiling; no container is involved. +DOCKER_BUILD=1 NEXT_TELEMETRY_DISABLED=1 CI=true pnpm_config_verify_deps_before_run=false $STD pnpm turbo run build --filter=web... --filter=worker... --concurrency=1 +cp -r web/.next/static web/.next/standalone/web/.next/ +cp -r web/public web/.next/standalone/web/ +msg_ok "Built Langfuse" + +msg_info "Configuring Langfuse" +mkdir -p /opt/langfuse_data/storage +chmod 700 /opt/langfuse_data +REDIS_PASS=$(random_password 32) +CLICKHOUSE_PASS=$(random_password 32) +S3_SECRET=$(random_password 48) +PUBLIC_KEY="pk-lf-$(random_password 32)" +SECRET_KEY="sk-lf-$(random_password 32)" +cat </opt/langfuse_data/.env +NODE_ENV=production +NEXTAUTH_URL=http://${LOCAL_IP}:3000 +NEXTAUTH_SECRET=$(random_password 48) +SALT=$(random_password 48) +ENCRYPTION_KEY=$(openssl rand -hex 32) +DATABASE_URL=postgresql://langfuse:${PG_DB_PASS}@127.0.0.1:5432/langfuse +DIRECT_URL=postgresql://langfuse:${PG_DB_PASS}@127.0.0.1:5432/langfuse +CLICKHOUSE_URL=http://127.0.0.1:8123 +CLICKHOUSE_MIGRATION_URL=clickhouse://127.0.0.1:9000 +CLICKHOUSE_USER=langfuse +CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASS} +CLICKHOUSE_DB=default +CLICKHOUSE_CLUSTER_ENABLED=false +REDIS_HOST=127.0.0.1 +REDIS_PORT=6379 +REDIS_AUTH=${REDIS_PASS} +LANGFUSE_S3_EVENT_UPLOAD_BUCKET=langfuse +LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/ +LANGFUSE_S3_EVENT_UPLOAD_REGION=us-east-1 +LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=http://127.0.0.1:8333 +LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=langfuse +LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=${S3_SECRET} +LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true +AUTH_DISABLE_SIGNUP=true +TELEMETRY_ENABLED=false +NEXT_TELEMETRY_DISABLED=1 +LANGFUSE_INIT_ORG_ID=default-org +LANGFUSE_INIT_ORG_NAME=Default +LANGFUSE_INIT_PROJECT_ID=default-project +LANGFUSE_INIT_PROJECT_NAME=Default +LANGFUSE_INIT_PROJECT_PUBLIC_KEY=${PUBLIC_KEY} +LANGFUSE_INIT_PROJECT_SECRET_KEY=${SECRET_KEY} +LANGFUSE_INIT_USER_EMAIL=${var_admin_email} +LANGFUSE_INIT_USER_NAME=Admin +LANGFUSE_INIT_USER_PASSWORD=${var_admin_pass} +EOF +chmod 600 /opt/langfuse_data/.env +cat </etc/redis/langfuse.conf +bind 127.0.0.1 ::1 +protected-mode yes +requirepass ${REDIS_PASS} +maxmemory-policy noeviction +appendonly yes +EOF +chown root:redis /etc/redis/langfuse.conf +chmod 640 /etc/redis/langfuse.conf +cat <>/etc/redis/redis.conf +include /etc/redis/langfuse.conf +EOF +cat </etc/clickhouse-server/users.d/langfuse.xml + + + + + $(printf '%s' "${CLICKHOUSE_PASS}" | sha256sum | cut -d ' ' -f1) + 127.0.0.1::1 + default + default + 1 + + + +EOF +chown clickhouse:clickhouse /etc/clickhouse-server/users.d/langfuse.xml +chmod 600 /etc/clickhouse-server/users.d/langfuse.xml +cat </etc/clickhouse-server/config.d/langfuse.xml + + 127.0.0.1 + UTC + +EOF +cat </opt/langfuse_data/s3.json +{ + "identities": [ + { + "name": "langfuse", + "credentials": [{"accessKey": "langfuse", "secretKey": "${S3_SECRET}"}], + "actions": ["Admin", "Read", "Write", "List", "Tagging"] + } + ] +} +EOF +chmod 600 /opt/langfuse_data/s3.json +msg_ok "Configured Langfuse" + +msg_info "Creating Services" +cat </etc/systemd/system/langfuse-storage.service +[Unit] +Description=Langfuse S3 Storage (SeaweedFS) +After=network.target + +[Service] +Type=simple +WorkingDirectory=/opt/langfuse_data/storage +ExecStart=/opt/seaweedfs/weed server -dir=/opt/langfuse_data/storage -ip=127.0.0.1 -ip.bind=127.0.0.1 -s3 -s3.port=8333 -s3.port.iceberg=0 -s3.port.lance=0 -s3.config=/opt/langfuse_data/s3.json -master.telemetry=false +Restart=on-failure +RestartSec=5 +UMask=0077 + +[Install] +WantedBy=multi-user.target +EOF +cat </etc/systemd/system/langfuse-migrate.service +[Unit] +Description=Langfuse Database Migrations +After=network.target postgresql.service clickhouse-server.service redis-server.service langfuse-storage.service +Wants=postgresql.service clickhouse-server.service redis-server.service langfuse-storage.service +Before=langfuse-web.service langfuse-worker.service + +[Service] +Type=oneshot +WorkingDirectory=/opt/langfuse +EnvironmentFile=/opt/langfuse_data/.env +ExecStart=/usr/bin/pnpm --filter @langfuse/shared exec prisma db execute --schema=./prisma/schema.prisma --file=./scripts/cleanup.sql +ExecStart=/usr/bin/pnpm --filter @langfuse/shared exec prisma migrate deploy --schema=./prisma/schema.prisma +ExecStart=/bin/sh /opt/langfuse/packages/shared/clickhouse/scripts/up.sh +RemainAfterExit=yes +TimeoutStartSec=1800 +UMask=0077 +EOF +cat </etc/systemd/system/langfuse-web.service +[Unit] +Description=Langfuse Web +After=network.target langfuse-migrate.service +Requires=langfuse-migrate.service + +[Service] +Type=simple +WorkingDirectory=/opt/langfuse +EnvironmentFile=/opt/langfuse_data/.env +Environment=HOSTNAME=0.0.0.0 +Environment=PORT=3000 +ExecStart=/usr/bin/node /opt/langfuse/web/.next/standalone/web/server.js +SuccessExitStatus=143 +Restart=on-failure +RestartSec=5 +UMask=0077 + +[Install] +WantedBy=multi-user.target +EOF +cat </etc/systemd/system/langfuse-worker.service +[Unit] +Description=Langfuse Worker +After=network.target langfuse-migrate.service +Requires=langfuse-migrate.service + +[Service] +Type=simple +WorkingDirectory=/opt/langfuse +EnvironmentFile=/opt/langfuse_data/.env +Environment=HOSTNAME=127.0.0.1 +Environment=PORT=3030 +ExecStart=/usr/bin/node /opt/langfuse/worker/dist/index.js +Restart=on-failure +RestartSec=5 +UMask=0077 + +[Install] +WantedBy=multi-user.target +EOF +systemctl restart redis-server clickhouse-server +systemctl enable -q --now langfuse-storage +$STD curl --retry 30 --retry-delay 2 --retry-connrefused -fsS http://127.0.0.1:9333/cluster/status +$STD /opt/seaweedfs/weed shell -master=127.0.0.1:9333 <<<"s3.bucket.create -name langfuse" +# Pass S3 authentication over stdin, not process arguments or build logs. +$STD curl --config - --retry 30 --retry-delay 2 --retry-connrefused -fsS http://127.0.0.1:8333/langfuse <>~/langfuse.creds + +Langfuse Admin +Email: ${var_admin_email} +Password: ${var_admin_pass} + +Langfuse API Keys (project Default) +Public Key: ${PUBLIC_KEY} +Secret Key: ${SECRET_KEY} +EOF +chmod 600 ~/langfuse.creds +msg_ok "Saved Credentials" + +motd_ssh +customize +cleanup_lxc diff --git a/json/langfuse.json b/json/langfuse.json new file mode 100644 index 0000000000..3af12db29b --- /dev/null +++ b/json/langfuse.json @@ -0,0 +1,69 @@ +{ + "name": "Langfuse", + "slug": "langfuse", + "categories": [ + 9, + 20 + ], + "date_created": "2026-10-18", + "type": "ct", + "updateable": true, + "privileged": false, + "interface_port": 3000, + "documentation": "https://langfuse.com/self-hosting", + "website": "https://langfuse.com/", + "repository": "https://github.com/langfuse/langfuse", + "architectures": [ + "amd64" + ], + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/langfuse.webp", + "description": "Langfuse is an open-source LLM engineering platform. It records traces, token usage and costs from LLM applications and agents, and adds evaluations, prompt management and analytics dashboards.", + "install_methods": [ + { + "type": "default", + "script": "ct/langfuse.sh", + "config_path": "/opt/langfuse_data/.env", + "resources": { + "cpu": 4, + "ram": 16384, + "hdd": 100, + "os": "Debian", + "version": "13" + } + } + ], + "default_credentials": { + "username": "admin@example.com", + "password": null + }, + "app_vars": [ + { + "name": "var_admin_email", + "label": "Admin Email", + "type": "text", + "default": "admin@example.com", + "help": "Login for the generated admin account. Langfuse only uses it to send mail when SMTP is configured." + }, + { + "name": "var_admin_pass", + "label": "Admin Password", + "type": "password", + "secret": true, + "help": "Generated when left empty. At least 8 characters, without spaces, quotes or backslashes." + } + ], + "notes": [ + { + "text": "The admin login and the Default project's API keys are saved to ~/langfuse.creds inside the container. Sign-up is off, invited users included; set AUTH_DISABLE_SIGNUP=false in /opt/langfuse_data/.env and restart langfuse-web while adding users.", + "type": "info" + }, + { + "text": "Behind a reverse proxy, set NEXTAUTH_URL in /opt/langfuse_data/.env to the external URL and restart langfuse-web.", + "type": "info" + }, + { + "text": "Media uploads and batch exports are not configured; they need an S3 endpoint reachable by clients.", + "type": "info" + } + ] +}