feat: add Flipt

This commit is contained in:
okxlin
2026-07-15 08:38:22 +08:00
parent 8ade8fbbca
commit 9cd75fa959
15 changed files with 469 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
PANEL_APP_PORT_HTTP=8080
PANEL_APP_PORT_GRPC=9000
APP_DATA_DIR=./data
CONTAINER_NAME=flipt
+52
View File
@@ -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
+30
View File
@@ -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
+86
View File
@@ -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
+8
View File
@@ -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
+5
View File
@@ -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"
+67
View File
@@ -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
## 参考资料
- 文档:<https://docs.flipt.io/v2/>
- 配置说明:<https://docs.flipt.io/v2/configuration/overview>
- 许可证说明:<https://docs.flipt.io/v2/licensing>
- 许可证全文:<https://github.com/flipt-io/flipt/blob/v2/LICENSE>
- 源码仓库:<https://github.com/flipt-io/flipt>
- 官方 Docker 用法:<https://github.com/flipt-io/flipt#docker>
+32
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
PANEL_APP_PORT_HTTP=8080
PANEL_APP_PORT_GRPC=9000
APP_DATA_DIR=./data
CONTAINER_NAME=flipt
+52
View File
@@ -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
+30
View File
@@ -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
+86
View File
@@ -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
+8
View File
@@ -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
+5
View File
@@ -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"
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB