Skip to content

Docker Production Root Identity Reset CN

JAVA-Lw edited this page Aug 21, 2026 · 1 revision

Docker 生产环境:保留 UID 重置 root 账号、邮箱和密码

这是一份面向拥有 PostgreSQL 管理权限的运维教程。它处理的是通用的身份恢复场景,不依赖系统备份:保留既有 root 用户的 UUID,只重置账号、邮箱和密码。

users.id 不变
  -> 业务资源 owner 不变
  -> 审计记录关联不变
  -> workspace membership / role binding 不变

不要删除 root 后重新创建。那会生成新的 UUID,并使依赖旧 user_id 的数据失去原 owner。

适用范围与风险

  • 数据库为 PostgreSQL,且已完成当前 1flowbase migration。
  • 操作对象是唯一的 default_display_role = 'root' 用户。
  • 操作会使该用户的旧登录会话失效。
  • 账号和邮箱必须在系统中唯一;若与其他用户的本地登录 identity 冲突,脚本会回滚。

先停止入口容器,避免在身份记录更新的中间状态接受登录请求;内置数据库的 db 与 plugin-runner 可以保持运行:

cd /srv/1flowbase/docker
docker compose stop api web

外部 PostgreSQL 部署将每个命令替换为 docker compose -f docker-compose.external-db.yaml ...。

先确认目标 UID。内置 PostgreSQL 示例:

docker compose exec db psql -U "${POSTGRES_USER:-postgres}" -d "${POSTGRES_DB:-1flowbase}" \
  -c "select id, account, email from users where default_display_role = 'root';"

结果必须恰好一行。若为零行或多行,停止操作并先排查数据库状态。

推荐:受控 Node 脚本

.env 里的 BOOTSTRAP_ROOT_PASSWORD 是明文输入,不能直接写入 users.password_hash。应用使用 Argon2id 哈希;以下脚本会生成哈希,并在单个事务中同步 users 和 user_auth_identities。

在可信运维机器上安装一次依赖(Node 20+):

mkdir -p ~/1flowbase-ops && cd ~/1flowbase-ops
npm install pg argon2

保存为 reset-root-identity.mjs:

import { readFile } from 'node:fs/promises';
import { randomUUID } from 'node:crypto';
import process from 'node:process';
import argon2 from 'argon2';
import pg from 'pg';

const envFile = process.argv[2];
if (!envFile || !process.env.DATABASE_URL) {
  throw new Error('usage: DATABASE_URL=postgres://... node reset-root-identity.mjs /path/to/docker/.env');
}

const entries = (await readFile(envFile, 'utf8'))
  .split(/\r?\n/)
  .filter((line) => line && !line.startsWith('#') && line.includes('='))
  .map((line) => {
    const index = line.indexOf('=');
    return [line.slice(0, index), line.slice(index + 1)];
  });
const env = Object.fromEntries(entries);
const account = env.BOOTSTRAP_ROOT_ACCOUNT?.trim();
const email = env.BOOTSTRAP_ROOT_EMAIL?.trim();
const password = env.BOOTSTRAP_ROOT_PASSWORD;
if (!account || !email || !password) {
  throw new Error('docker/.env must contain BOOTSTRAP_ROOT_ACCOUNT, BOOTSTRAP_ROOT_EMAIL, and BOOTSTRAP_ROOT_PASSWORD');
}

const passwordLocalAuthenticatorId = '00000000-0000-0000-0000-000000000001';
const client = new pg.Client({ connectionString: process.env.DATABASE_URL });
await client.connect();
try {
  await client.query('begin');
  const roots = await client.query(
    "select id from users where default_display_role = 'root' for update",
  );
  if (roots.rowCount !== 1) {
    throw new Error(`expected exactly one root user, found ${roots.rowCount}`);
  }
  const userId = roots.rows[0].id;

  const conflict = await client.query(
    `select user_id from user_auth_identities
      where authenticator_id = $1
        and lower(subject_value) = any($2::text[])
        and user_id <> $3
      limit 1`,
    [passwordLocalAuthenticatorId, [account.toLowerCase(), email.toLowerCase()], userId],
  );
  if (conflict.rowCount) {
    throw new Error('the requested account or email already belongs to another local-login user');
  }

  const passwordHash = await argon2.hash(password, { type: argon2.argon2id });
  await client.query(
    `update users
        set account = $2, email = $3, password_hash = $4,
            session_version = session_version + 1,
            updated_by = $1, updated_at = now()
      where id = $1`,
    [userId, account, email, passwordHash],
  );
  await client.query(
    `delete from user_auth_identities
      where user_id = $1 and authenticator_id = $2
        and subject_type in ('account', 'email')`,
    [userId, passwordLocalAuthenticatorId],
  );
  for (const [subjectType, subjectValue] of [['account', account], ['email', email]]) {
    await client.query(
      `insert into user_auth_identities
          (id, user_id, authenticator_id, subject_type, subject_value, metadata, created_by, updated_by)
       values ($1, $2, $3, $4, $5, '{}'::jsonb, $2, $2)`,
      [randomUUID(), userId, passwordLocalAuthenticatorId, subjectType, subjectValue],
    );
  }
  await client.query('commit');
  console.log(`root identity reset for existing user ${userId}`);
} catch (error) {
  await client.query('rollback');
  throw error;
} finally {
  await client.end();
}

在 .env 中设置目标账号、邮箱和密码。不要把密码放进命令行、shell history 或脚本源码。然后从能访问 PostgreSQL 的可信机器执行:

DATABASE_URL='postgres://flowbase:***@db.example.internal:5432/1flowbase?sslmode=require' \
  node ~/1flowbase-ops/reset-root-identity.mjs /srv/1flowbase/docker/.env

内置 PostgreSQL 的 DATABASE_URL 应使用运维侧能访问的主机地址、映射端口及 docker/.env 中的数据库账号、密码和库名;不要把容器内部地址 db:5432 用在宿主机 Node 进程上。

成功后重启入口服务:

cd /srv/1flowbase/docker
docker compose up -d api web

使用新账号和新密码完成一次登录验证。旧密码和旧浏览器 session 应失效。

紧急手工 SQL:仅改密码

如果账号与邮箱都不变,且你已经在安全环境中生成了兼容的 Argon2id hash,可以只更新密码。不要使用 PostgreSQL 的 MD5、crypt() 或明文;这些格式不符合 1flowbase 的本地密码校验。

begin;

select id, account, email
from users
where default_display_role = 'root'
for update;

update users
set password_hash = '<预先生成的 Argon2id hash>',
    session_version = session_version + 1,
    updated_at = now()
where id = '<上一步确认的 root UUID>';

commit;

不要用这段 SQL 改账号或邮箱。账号/邮箱登录依赖 user_auth_identities,只改 users 会造成登录 identity 与用户资料不一致;请使用上面的事务脚本。

停止条件

立即停止并回滚,而不是猜测修复方式,如果发生以下任一情况:

  • root 查询不是恰好一行;
  • 新账号或邮箱已被其他用户占用;
  • 数据库连接、事务、identity 写入或登录验证失败;
  • 需要同时修改多个 root 用户,或不确定哪个 UID 是目标。

Clone this wiki locally