diff --git a/apps/flipt/2.10.0/.env.sample b/apps/flipt/2.10.0/.env.sample new file mode 100644 index 000000000..884b74a5f --- /dev/null +++ b/apps/flipt/2.10.0/.env.sample @@ -0,0 +1,4 @@ +PANEL_APP_PORT_HTTP=8080 +PANEL_APP_PORT_GRPC=9000 +APP_DATA_DIR=./data +CONTAINER_NAME=flipt diff --git a/apps/flipt/2.10.0/data.yml b/apps/flipt/2.10.0/data.yml new file mode 100644 index 000000000..52ac09321 --- /dev/null +++ b/apps/flipt/2.10.0/data.yml @@ -0,0 +1,52 @@ +additionalProperties: + formFields: + - default: 8080 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: HTTP Port + labelZh: HTTP 端口 + label: + en: HTTP Port + zh: HTTP 端口 + zh-Hant: HTTP 連接埠 + ja: HTTP ポート + ko: HTTP 포트 + ru: Порт HTTP + ms: Port HTTP + pt-br: Porta HTTP + required: true + rule: paramPort + type: number + - default: 9000 + edit: true + envKey: PANEL_APP_PORT_GRPC + labelEn: gRPC Port + labelZh: gRPC 端口 + label: + en: gRPC Port + zh: gRPC 端口 + zh-Hant: gRPC 連接埠 + ja: gRPC ポート + ko: gRPC 포트 + ru: Порт gRPC + ms: Port gRPC + pt-br: Porta gRPC + required: true + rule: paramPort + type: number + - default: ./data + edit: true + envKey: APP_DATA_DIR + labelEn: Data Directory + labelZh: 数据目录 + label: + en: Data Directory + zh: 数据目录 + zh-Hant: 資料目錄 + ja: データディレクトリ + ko: 데이터 디렉터리 + ru: Каталог данных + ms: Direktori Data + pt-br: Diretório de dados + required: true + type: text diff --git a/apps/flipt/2.10.0/docker-compose.yml b/apps/flipt/2.10.0/docker-compose.yml new file mode 100644 index 000000000..ca33296a9 --- /dev/null +++ b/apps/flipt/2.10.0/docker-compose.yml @@ -0,0 +1,30 @@ +services: + flipt: + image: "docker.flipt.io/flipt/flipt:v2.10.0" + container_name: ${CONTAINER_NAME} + restart: unless-stopped + networks: + - 1panel-network + ports: + - "${PANEL_APP_PORT_HTTP}:8080" + - "${PANEL_APP_PORT_GRPC}:9000" + environment: + - FLIPT_STORAGE_DEFAULT_BACKEND_TYPE=local + - FLIPT_STORAGE_DEFAULT_BACKEND_PATH=/var/opt/flipt/storage + - FLIPT_META_STATE_DIRECTORY=/var/opt/flipt/state + - FLIPT_META_CHECK_FOR_UPDATES=false + - FLIPT_META_TELEMETRY_ENABLED=false + volumes: + - "${APP_DATA_DIR}:/var/opt/flipt" + healthcheck: + test: ["CMD-SHELL", "wget --spider -q http://127.0.0.1:8080/health || exit 1"] + interval: 10s + timeout: 5s + start_period: 20s + retries: 10 + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/flipt/2.10.0/scripts/init.sh b/apps/flipt/2.10.0/scripts/init.sh new file mode 100755 index 000000000..a85557e48 --- /dev/null +++ b/apps/flipt/2.10.0/scripts/init.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}" + +fail() { + printf '%s\n' "$1" >&2 + exit 1 +} + +strip_matching_quotes() { + local value="$1" + + if [[ ${#value} -ge 2 ]]; then + if [[ "${value:0:1}" == '"' && "${value: -1}" == '"' ]]; then + value="${value:1:${#value}-2}" + elif [[ "${value:0:1}" == "'" && "${value: -1}" == "'" ]]; then + value="${value:1:${#value}-2}" + fi + fi + printf '%s\n' "$value" +} + +read_env_value() { + local key="$1" + local value="" + + if [[ -f "$ENV_FILE" ]]; then + value="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2- || true)" + fi + strip_matching_quotes "$value" +} + +path_is_dotenv_safe() { + local value="$1" + + case "$value" in + *$'\n'* | *$'\r'* | *\\* | *'$'* | *'#'* | *'"'* | *"'"*) return 1 ;; + *) return 0 ;; + esac +} + +[[ -f "$ENV_FILE" ]] || fail "$ENV_FILE not found" +[[ ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must not be a symbolic link" + +if [[ ${APP_DATA_DIR+x} ]]; then + APP_DATA_DIR_RAW="$APP_DATA_DIR" +else + APP_DATA_DIR_RAW="$(read_env_value APP_DATA_DIR)" +fi +APP_DATA_DIR_RAW="$(strip_matching_quotes "${APP_DATA_DIR_RAW:-./data}")" + +[[ -n "$APP_DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be empty" +path_is_dotenv_safe "$APP_DATA_DIR_RAW" || fail "APP_DATA_DIR contains unsupported dotenv characters" + +case "$APP_DATA_DIR_RAW" in + /*) + APP_DATA_DIR_ABS="$(realpath -m -- "$APP_DATA_DIR_RAW")" + ;; + *) + APP_DATA_DIR_ABS="$(realpath -m -- "${ROOT_DIR}/${APP_DATA_DIR_RAW#./}")" + case "$APP_DATA_DIR_ABS" in + "${ROOT_DIR}" | "${ROOT_DIR}"/*) ;; + *) fail "Relative APP_DATA_DIR must stay inside the application directory" ;; + esac + ;; +esac + +[[ "$APP_DATA_DIR_ABS" != "/" ]] || fail "APP_DATA_DIR must not be the filesystem root" +[[ ! -L "$APP_DATA_DIR_ABS" ]] || fail "APP_DATA_DIR must not be a symbolic link" +if [[ -e "$APP_DATA_DIR_ABS" && ! -d "$APP_DATA_DIR_ABS" ]]; then + fail "APP_DATA_DIR must be a directory" +fi +mkdir -p -- "$APP_DATA_DIR_ABS" + +for directory in storage state; do + target="${APP_DATA_DIR_ABS}/${directory}" + [[ ! -L "$target" ]] || fail "$target must not be a symbolic link" + if [[ -e "$target" && ! -d "$target" ]]; then + fail "$target must be a directory" + fi + mkdir -p -- "$target" + chown 100:1000 -- "$target" + chmod 700 -- "$target" +done diff --git a/apps/flipt/2.10.0/scripts/uninstall.sh b/apps/flipt/2.10.0/scripts/uninstall.sh new file mode 100755 index 000000000..5a3f6bd6b --- /dev/null +++ b/apps/flipt/2.10.0/scripts/uninstall.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +if command -v docker-compose >/dev/null 2>&1; then + docker-compose down --volumes --remove-orphans +else + docker compose down --volumes --remove-orphans +fi diff --git a/apps/flipt/2.10.0/scripts/upgrade.sh b/apps/flipt/2.10.0/scripts/upgrade.sh new file mode 100755 index 000000000..593b31667 --- /dev/null +++ b/apps/flipt/2.10.0/scripts/upgrade.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +exec bash "${SCRIPT_DIR}/init.sh" diff --git a/apps/flipt/README.md b/apps/flipt/README.md new file mode 100644 index 000000000..2adb62c85 --- /dev/null +++ b/apps/flipt/README.md @@ -0,0 +1,67 @@ +# Flipt + +## 产品介绍 + +Flipt 是一个 Git 原生的功能开关管理平台,可通过 Web UI、HTTP API 和 gRPC API 管理与评估功能开关。此应用使用 Flipt 官方镜像,以一个 Flipt service 和本地 Git 存储运行。 + +## 主要功能 + +- 在 Web UI 中管理命名空间、功能开关、变体、规则和分群 +- 通过 HTTP 与 gRPC API 在应用中评估功能开关 +- 将功能开关状态保存为本地 Git 数据,保留可审计的修订历史 + +## 访问说明 + +- HTTP/UI 容器端口为 `8080`,安装表单可修改对应主机端口。 +- gRPC 容器端口为 `9000`,安装表单可修改对应主机端口。 +- 默认安装不启用身份认证。不要把管理界面直接暴露到不受信任的公网;应使用 1Panel 反向代理、访问控制或上游支持的认证配置保护实例。 + +## 部署拓扑 + +- 默认包只有一个 Flipt service,不依赖数据库、Redis 或 1Panel Runtime。 +- Flipt v2 未显式配置存储时使用易失的 `in-memory` 后端。本包设置 `FLIPT_STORAGE_DEFAULT_BACKEND_TYPE=local`,将功能开关状态持久化到本地 Git 存储。 +- 本包关闭上游的版本检查与遥测:`FLIPT_META_CHECK_FOR_UPDATES=false`、`FLIPT_META_TELEMETRY_ENABLED=false`。 +- Flipt 可连接外部 Git 仓库并支持更复杂的环境配置,但这些能力不属于当前单机默认包。 + +## 数据与升级 + +安装表单选择的数据目录会挂载到 `/var/opt/flipt`,其中: + +- `storage` → `/var/opt/flipt/storage`,保存本地 Git 仓库及功能开关状态 +- `state` → `/var/opt/flipt/state`,保存实例级状态文件 + +容器以 UID `100`、GID `1000` 运行。安装和升级脚本会创建上述目录、设置所有者并收紧为 `0700`。升级前请备份完整数据目录。生产环境建议使用固定版本;`latest` 包跟随上游 `v2` 浮动标签,适合明确接受自动获取新 minor/patch 版本的场景。 + +## 许可证 + +- Flipt v2 服务端使用 source-available 的 Fair Core License `FCL-1.0-MIT`,不是标准 OSI 开源许可证。 +- 许可证允许内部使用、非商业教育与研究等用途,但禁止构成 **Competing Use** 的商业产品或服务。使用者应自行确认用途符合上游条款。 +- 每个版本在发布日的 second anniversary 起获得 MIT future license;在对应日期之前,不能把当前服务端版本视为 MIT 软件。 +- RPC 客户端代码和 Go SDK 使用 MIT,并不改变当前服务端的 FCL 授权状态。 + +## 安全扫描快照 + +维护侧于 2026-07-15 使用 Trivy 0.72.0 扫描固定镜像 `v2.10.0` 与浮动镜像 `v2`;两者当时解析到同一 digest,结果均为 Critical=0、High=6。High 项包括上游二进制依赖中的 Moby、Go 标准库,以及 Alpine OpenSSL 软件包;其中部分已有修复版本,部分扫描时尚无修复版本。该结果是随漏洞库和镜像变化的时间点快照,部署者应持续跟踪上游镜像更新并在生产使用前重新扫描。 + +## Introduction + +Flipt is a Git-native feature flag management platform with a Web UI, HTTP API, and gRPC API. This package runs the official image as one service and explicitly selects persistent local Git storage instead of Flipt v2's default in-memory backend. + +The selected data directory is mounted at `/var/opt/flipt`; flag state is stored under `/var/opt/flipt/storage` and instance state under `/var/opt/flipt/state`. Update checks and telemetry are disabled by default. The package does not depend on a database, Redis, or a 1Panel Runtime. + +The Flipt v2 server is source-available under `FCL-1.0-MIT`, including a Competing Use restriction and an MIT future license effective on the second anniversary of each version's release. Review the upstream license before deployment. + +## Features + +- Manage namespaces, feature flags, variants, rules, and segments from the Web UI +- Evaluate flags through HTTP and gRPC APIs +- Persist flag state in a local Git repository with auditable revisions + +## 参考资料 + +- 文档: +- 配置说明: +- 许可证说明: +- 许可证全文: +- 源码仓库: +- 官方 Docker 用法: diff --git a/apps/flipt/data.yml b/apps/flipt/data.yml new file mode 100644 index 000000000..2a169b316 --- /dev/null +++ b/apps/flipt/data.yml @@ -0,0 +1,32 @@ +name: Flipt +tags: + - DevOps +title: Git 原生的自托管功能开关管理平台 +description: Git 原生的自托管功能开关管理平台 +additionalProperties: + key: flipt + name: Flipt + tags: + - DevOps + shortDescZh: Git 原生的自托管功能开关管理平台 + shortDescEn: Git-native, self-hosted feature flag management + description: + en: Git-native, self-hosted feature flag management for applications and services + zh: 面向应用与服务的 Git 原生自托管功能开关管理平台 + zh-Hant: 面向應用程式與服務的 Git 原生自託管功能開關管理平台 + ja: アプリケーションとサービス向けの Git ネイティブなセルフホスト型機能フラグ管理 + ko: 애플리케이션과 서비스를 위한 Git 네이티브 자체 호스팅 기능 플래그 관리 + ru: Git-ориентированное самостоятельное управление функциональными флагами для приложений и сервисов + ms: Pengurusan bendera ciri hos sendiri berasaskan Git untuk aplikasi dan perkhidmatan + pt-br: Gerenciamento de feature flags auto-hospedado e nativo de Git para aplicativos e serviços + type: tool + crossVersionUpdate: true + limit: 0 + recommend: 0 + memoryRequired: 256 + website: https://www.flipt.io/ + github: https://github.com/flipt-io/flipt + document: https://docs.flipt.io/v2/ + architectures: + - amd64 + - arm64 diff --git a/apps/flipt/latest/.env.sample b/apps/flipt/latest/.env.sample new file mode 100644 index 000000000..884b74a5f --- /dev/null +++ b/apps/flipt/latest/.env.sample @@ -0,0 +1,4 @@ +PANEL_APP_PORT_HTTP=8080 +PANEL_APP_PORT_GRPC=9000 +APP_DATA_DIR=./data +CONTAINER_NAME=flipt diff --git a/apps/flipt/latest/data.yml b/apps/flipt/latest/data.yml new file mode 100644 index 000000000..52ac09321 --- /dev/null +++ b/apps/flipt/latest/data.yml @@ -0,0 +1,52 @@ +additionalProperties: + formFields: + - default: 8080 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: HTTP Port + labelZh: HTTP 端口 + label: + en: HTTP Port + zh: HTTP 端口 + zh-Hant: HTTP 連接埠 + ja: HTTP ポート + ko: HTTP 포트 + ru: Порт HTTP + ms: Port HTTP + pt-br: Porta HTTP + required: true + rule: paramPort + type: number + - default: 9000 + edit: true + envKey: PANEL_APP_PORT_GRPC + labelEn: gRPC Port + labelZh: gRPC 端口 + label: + en: gRPC Port + zh: gRPC 端口 + zh-Hant: gRPC 連接埠 + ja: gRPC ポート + ko: gRPC 포트 + ru: Порт gRPC + ms: Port gRPC + pt-br: Porta gRPC + required: true + rule: paramPort + type: number + - default: ./data + edit: true + envKey: APP_DATA_DIR + labelEn: Data Directory + labelZh: 数据目录 + label: + en: Data Directory + zh: 数据目录 + zh-Hant: 資料目錄 + ja: データディレクトリ + ko: 데이터 디렉터리 + ru: Каталог данных + ms: Direktori Data + pt-br: Diretório de dados + required: true + type: text diff --git a/apps/flipt/latest/docker-compose.yml b/apps/flipt/latest/docker-compose.yml new file mode 100644 index 000000000..377a30ccf --- /dev/null +++ b/apps/flipt/latest/docker-compose.yml @@ -0,0 +1,30 @@ +services: + flipt: + image: "docker.flipt.io/flipt/flipt:v2" + container_name: ${CONTAINER_NAME} + restart: unless-stopped + networks: + - 1panel-network + ports: + - "${PANEL_APP_PORT_HTTP}:8080" + - "${PANEL_APP_PORT_GRPC}:9000" + environment: + - FLIPT_STORAGE_DEFAULT_BACKEND_TYPE=local + - FLIPT_STORAGE_DEFAULT_BACKEND_PATH=/var/opt/flipt/storage + - FLIPT_META_STATE_DIRECTORY=/var/opt/flipt/state + - FLIPT_META_CHECK_FOR_UPDATES=false + - FLIPT_META_TELEMETRY_ENABLED=false + volumes: + - "${APP_DATA_DIR}:/var/opt/flipt" + healthcheck: + test: ["CMD-SHELL", "wget --spider -q http://127.0.0.1:8080/health || exit 1"] + interval: 10s + timeout: 5s + start_period: 20s + retries: 10 + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/flipt/latest/scripts/init.sh b/apps/flipt/latest/scripts/init.sh new file mode 100755 index 000000000..a85557e48 --- /dev/null +++ b/apps/flipt/latest/scripts/init.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}" + +fail() { + printf '%s\n' "$1" >&2 + exit 1 +} + +strip_matching_quotes() { + local value="$1" + + if [[ ${#value} -ge 2 ]]; then + if [[ "${value:0:1}" == '"' && "${value: -1}" == '"' ]]; then + value="${value:1:${#value}-2}" + elif [[ "${value:0:1}" == "'" && "${value: -1}" == "'" ]]; then + value="${value:1:${#value}-2}" + fi + fi + printf '%s\n' "$value" +} + +read_env_value() { + local key="$1" + local value="" + + if [[ -f "$ENV_FILE" ]]; then + value="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2- || true)" + fi + strip_matching_quotes "$value" +} + +path_is_dotenv_safe() { + local value="$1" + + case "$value" in + *$'\n'* | *$'\r'* | *\\* | *'$'* | *'#'* | *'"'* | *"'"*) return 1 ;; + *) return 0 ;; + esac +} + +[[ -f "$ENV_FILE" ]] || fail "$ENV_FILE not found" +[[ ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must not be a symbolic link" + +if [[ ${APP_DATA_DIR+x} ]]; then + APP_DATA_DIR_RAW="$APP_DATA_DIR" +else + APP_DATA_DIR_RAW="$(read_env_value APP_DATA_DIR)" +fi +APP_DATA_DIR_RAW="$(strip_matching_quotes "${APP_DATA_DIR_RAW:-./data}")" + +[[ -n "$APP_DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be empty" +path_is_dotenv_safe "$APP_DATA_DIR_RAW" || fail "APP_DATA_DIR contains unsupported dotenv characters" + +case "$APP_DATA_DIR_RAW" in + /*) + APP_DATA_DIR_ABS="$(realpath -m -- "$APP_DATA_DIR_RAW")" + ;; + *) + APP_DATA_DIR_ABS="$(realpath -m -- "${ROOT_DIR}/${APP_DATA_DIR_RAW#./}")" + case "$APP_DATA_DIR_ABS" in + "${ROOT_DIR}" | "${ROOT_DIR}"/*) ;; + *) fail "Relative APP_DATA_DIR must stay inside the application directory" ;; + esac + ;; +esac + +[[ "$APP_DATA_DIR_ABS" != "/" ]] || fail "APP_DATA_DIR must not be the filesystem root" +[[ ! -L "$APP_DATA_DIR_ABS" ]] || fail "APP_DATA_DIR must not be a symbolic link" +if [[ -e "$APP_DATA_DIR_ABS" && ! -d "$APP_DATA_DIR_ABS" ]]; then + fail "APP_DATA_DIR must be a directory" +fi +mkdir -p -- "$APP_DATA_DIR_ABS" + +for directory in storage state; do + target="${APP_DATA_DIR_ABS}/${directory}" + [[ ! -L "$target" ]] || fail "$target must not be a symbolic link" + if [[ -e "$target" && ! -d "$target" ]]; then + fail "$target must be a directory" + fi + mkdir -p -- "$target" + chown 100:1000 -- "$target" + chmod 700 -- "$target" +done diff --git a/apps/flipt/latest/scripts/uninstall.sh b/apps/flipt/latest/scripts/uninstall.sh new file mode 100755 index 000000000..5a3f6bd6b --- /dev/null +++ b/apps/flipt/latest/scripts/uninstall.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +if command -v docker-compose >/dev/null 2>&1; then + docker-compose down --volumes --remove-orphans +else + docker compose down --volumes --remove-orphans +fi diff --git a/apps/flipt/latest/scripts/upgrade.sh b/apps/flipt/latest/scripts/upgrade.sh new file mode 100755 index 000000000..593b31667 --- /dev/null +++ b/apps/flipt/latest/scripts/upgrade.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +exec bash "${SCRIPT_DIR}/init.sh" diff --git a/apps/flipt/logo.png b/apps/flipt/logo.png new file mode 100644 index 000000000..2d02c2528 Binary files /dev/null and b/apps/flipt/logo.png differ