Compare commits

...
Author SHA1 Message Date
sonarly-bot 28f836200b fix(front-component-renderer): gate global hotkeys on input focus
https://sonarly.com/issue/38618?type=bug

Typing inside worker-rendered front-component inputs can trigger host global hotkeys (for example

Fix: Implemented a host-side focus bridge for worker-rendered front-component inputs so global hotkeys are suppressed while users type.

What changed:
1. `FrontComponentRendererProvider` now captures focus/blur events from descendants and detects editable targets (`input` text-like types, `textarea`, `contentEditable`).
2. On editable focus, it pushes a focus-stack item with:
   - a stable provider-scoped focus id
   - `enableGlobalHotkeysConflictingWithKeyboard: false`
3. On editable blur (unless focus is moving to another editable element inside the same provider), it removes that focus-stack item.
4. Added unmount cleanup to ensure no stale focus-stack item remains if renderer unmounts while focused.
5. Added tests covering:
   - push on editable focus
   - no remove when moving between editable fields
   - remove when leaving editable context

This directly addresses the root cause: front-component inputs were not participating in the host focus-stack hotkey gate, so global shortcuts remained active during typing.

Authored by Sonarly by autonomous analysis (run 43975).
2026-05-19 10:45:46 +00:00
db4a05301a i18n - website translations (#20712)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-19 12:27:49 +02:00
Abdullah.andGitHub f9e3683518 [Website] Change product hero to reveal tabs on scroll. (#20707)
Here's the video - it still needs refinement, but we want to hide
articles and product pages to push out a release today. Merging this as
a checkpoint so the next PR can hide these pages from navigation for the
release.


https://github.com/user-attachments/assets/eb5048b2-d3df-4920-a62a-5b2617d11e4a
2026-05-19 10:12:44 +00:00
fecea1bfae i18n - translations (#20710)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-19 12:13:33 +02:00
3281d37bdf Fix(twenty-front): BlockNote slash command shows empty state when no match (#20689)
Fixes: #20625
Original PR: #20626

New changes:
- Now the text says "Close menu" instead of "No command found".
- "Close menu" is interactive like other commands ( With keyboard and
with mouse ).
- It closes automatically when user type 3 extra character past the
point of no result.


https://github.com/user-attachments/assets/9c1315b4-5a63-424d-8da8-dc1283535725

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-05-19 09:47:18 +00:00
Félix MalfaitandGitHub 291ce5ccdb fix(filters): make filter dispatcher own relation-target resolution (#20670)
## Summary

Two relation-traversal bugs surfaced post-merge of #20533, both rooted
in the same architectural smell: the GraphQL filter dispatcher took a
flat `fields: FieldShared[]` array and silently dropped any filter whose
`relationTargetFieldMetadataId` wasn't in that array. Callers had to
remember to pre-augment the list with relation targets — and 16+ call
sites did not all know this.

This PR fixes both bugs and removes the smell.

### Bug 1 — Save as new view loses the relation target

`useCreateViewFromCurrentView` built the create-filter input without
`relationTargetFieldMetadataId`. The saved view's filter persisted
without the traversal — on reload the chip showed "Company contains
'air'" instead of "Company → Name contains 'air'". Discarded at save
time, not at read time.

Fix: include `relationTargetFieldMetadataId` in the create input.
(Commit 1.)

### Bug 2 — Workflow Search Records drops one-hop traversals

`FindRecordsWorkflowAction` built its fields list from
`flatObjectMetadata.fieldIds` only (source object's fields). The shared
dispatcher then couldn't resolve the relation target field on the
related object and silently dropped the filter — a configured "People
where Company → Name Contains 'Airbnb'" came through as `{ and: [] }`.

This was the same shape as bugs already fixed in 5 other call sites
(chart filters, view filters, record table, etc.). The pattern was:
caller forgets to augment fields → dispatcher silently drops the filter.

Fix (commit 2): change the dispatcher to take a
`findFieldMetadataItemById: (id) => FieldShared | undefined` resolver
callback. Both source-field and relation-target-field lookups go through
the same resolver, so callers no longer need to know about the
augmentation requirement. Frontend callers pass a workspace-wide
resolver built from `flattenedFieldMetadataItemsSelector`; server
callers wrap `findFlatEntityByIdInFlatEntityMaps` on
`flatFieldMetadataMaps`. In both cases relation-target lookups just
work, because the resolver can see fields on related objects.

## Why this matters

Before: "if you call the dispatcher, pre-augment your fields list with
relation targets, or filters get silently dropped." An invariant only
enforceable by code review, broken often enough to ship two user-visible
bugs in one week.

After: the dispatcher resolves field ids itself. There's no list to
forget to augment. The failure mode (filter silently dropped) becomes
structurally impossible at the dispatcher boundary.

Net diff: 240 insertions, 319 deletions. Removed
`augmentFieldsWithRelationTargets` (frontend) and the workflow
whack-a-mole code (server).

## Test plan
- [ ] Save view: create an advanced filter using a one-hop relation
traversal, click "Save as new view", reload, confirm the chip still
reads "Source → Target operator value"
- [ ] Workflow: configure a Search Records action with a
relation-traversal filter, run the workflow, confirm the filter is
actually applied
- [ ] Dashboard chart: configure a chart with a relation-traversal
filter, confirm the chart data respects it
- [ ] Record table, group-by, calendar, total count, footer aggregates:
all continue to work with both plain and relation-traversal filters
2026-05-19 11:55:22 +02:00
martmullandGitHub a9634b027e Stop bundling twenty-ui react cjs runtime code (#20703)
For this front component using Avatar from `twenty-sdk/ui`

```typescript
import { defineFrontComponent } from 'twenty-sdk/define';
import { Avatar } from 'twenty-sdk/ui';

// React component - implement your UI here
const Component = () => {
  return (
    <div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
      <Avatar avatarUrl={null} placeholder={'test'} />
      <h1>My new component!</h1>
      <p>This is your front component: fc-test</p>
    </div>
  );
};

export default defineFrontComponent({ ... });
```

## Before
<img width="3024" height="1964" alt="image"
src="https://github.com/user-attachments/assets/64479d9a-2316-4782-9552-c3982d85f97c"
/>

## After

<img width="1150" height="567" alt="image"
src="https://github.com/user-attachments/assets/67f34da2-a546-40e3-9f5d-62a5de6e4146"
/>
2026-05-19 09:26:41 +00:00
ae41751a8c i18n - docs translations (#20705)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-19 11:34:14 +02:00
11d8679f65 i18n - docs translations (#20702)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-19 09:40:04 +02:00
05f31c1837 docs(self-host): document ENCRYPTION_KEY, FALLBACK_ENCRYPTION_KEY and key rotation procedures (#20611)
## Summary
- Documents the new at-rest encryption envelope (`ENCRYPTION_KEY` /
`FALLBACK_ENCRYPTION_KEY`) introduced in v2.5+ and clarifies its
relationship to the legacy `APP_SECRET`-as-encryption-key path.
- Adds a new dedicated **Key rotation** guide covering manual /
Enterprise-cron JWT signing-key rotation, signing-key revocation, and
the online `ENCRYPTION_KEY` rotation procedure (including the new
\`secret-encryption:rotate\` CLI shipped in a follow-up PR).
- Updates the docker-compose quickstart to generate a dedicated
\`ENCRYPTION_KEY\` from day 1.
- Mentions the v2.5+ enc:v2 backfill in the upgrade guide.

English-only — the localized mirrors will be picked up by i18n CI.

## Test plan
- [ ] Mintlify build passes locally / in CI
- [ ] Sidebar entry renders under **Self-Host → Key rotation**
- [ ] Internal links to /developers/self-host/capabilities/key-rotation
resolve from setup.mdx, docker-compose.mdx and upgrade-guide.mdx

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-19 06:45:40 +00:00
2a92f34d06 chore: bump version to 2.7.0 (#20693)
## Summary

- Moves current version to previous versions array
- Sets TWENTY_CURRENT_VERSION to the new version
- Updates TWENTY_NEXT_VERSIONS with the next minor version

## Checklist

- [ ] Verify version constants are correct

Co-authored-by: Github Action Deploy <github-action-deploy@twenty.com>
2026-05-19 08:10:57 +02:00
d9b125efc5 i18n - website translations (#20694)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-19 08:10:35 +02:00
Charles BochetandGitHub e6b7f31fea fix(front): prevent standalone page layout crash from useTargetRecord (#20698)
## Context

Reported in production on `engineering.twenty.com` — standalone page
layouts (e.g. "Release overview") crash with the React error boundary
fallback ("Sorry, something went wrong"). The console shows:

```
Error: useTargetRecord must be used within a record page context (targetRecordIdentifier is required)
```

The minified stack trace points at `SidePanelToggleButto…`, but that's
just the bundle chunk name — the actual call site is
`PageLayoutTabsRenderer`.

## Root cause

#19296 added an unconditional `useTargetRecord()` call inside
`PageLayoutTabsRenderer` so it could read the target object's metadata
and hide tabs whose widgets reference deactivated relations:

```ts
const targetRecord = useTargetRecord();

const { objectMetadataItem } = useObjectMetadataItem({
  objectNameSingular: targetRecord.targetObjectNameSingular,
});
```

But `PageLayoutTabsRenderer` runs on **both** record pages and
standalone pages. On standalone pages, `StandalonePageLayoutPage`
intentionally sets `targetRecordIdentifier: undefined` in
`LayoutRenderingProvider`, which makes `useTargetRecord()` throw — and
the follow-up `useObjectMetadataItem()` would also throw on miss.
2026-05-19 00:40:37 +02:00
Charles BochetandGitHub bad1f20012 fix(server): handle legacy PK name in 2.6 rename-permission-flag upgrade (#20697)
## Summary

The 2.6 `RenamePermissionFlagToRolePermissionFlag` upgrade command
failed on staging and dev with:

```
[QueryFailedError] constraint "PK_a02789db60620a1e9f90147b50f" for table "rolePermissionFlag" does not exist
in RenamePermissionFlagToRolePermissionFlag1778235340020 (2.6.0) (instance fast)
```

### Root cause

TypeORM names PKs as `PK_<sha1(tableName_sortedColumnNames)[:27]>`. So:
- `permissionFlag_id` → `PK_a02789db60620a1e9f90147b50f`
- `settingPermission_id` → `PK_8c144a021030d7e3326835a04c8`
- `rolePermissionFlag_id` → `PK_76591adc8035c2e7b0cd6115136`

On databases initially migrated before the v1.5.5 migration squash
(#15183), the table was renamed `settingPermission` → `permissionFlag`
via the pre-squash migration
`1753149175945-renameSettingPermissionToPermissionFlag.ts`. That
migration renamed the table, the column, the unique index, and the role
FK, but **never renamed the PK constraint** — and Postgres does not
auto-rename constraints on `ALTER TABLE ... RENAME TO`. Those instances
therefore still carry the legacy PK name
`PK_8c144a021030d7e3326835a04c8`.

Fresh installs (squashed `setupMetadataTables` migration) instead have
the expected `PK_a02789db60620a1e9f90147b50f`.

The 2.6 upgrade only handled the fresh-install name, so it broke for any
DB that went through the historical rename chain.

### Fix

Replace the brittle `RENAME CONSTRAINT` with `DROP CONSTRAINT IF EXISTS`
for both historical PK names, followed by `ADD CONSTRAINT ... PRIMARY
KEY ("id")` with the canonical new name. The migration now converges to
the same PK name regardless of the DB's history.

The same pattern is applied symmetrically in `down()`.

### Why this is safe

- The whole instance command runs in a transaction
(`InstanceCommandRunnerService.runFastInstanceCommand`).
- The first statement (`ALTER TABLE ... RENAME TO`) takes `ACCESS
EXCLUSIVE` on the table, so the drop/add window for the PK is invisible
to any concurrent writer — they queue on the lock until commit.
- No FK references `rolePermissionFlag.id` at this point in the sequence
(migration 22 introduces an FK pointing at the new `permissionFlag`
catalog created in migration 21, not at the renamed grant table), so
dropping the PK does not cascade or block.
- `NOT NULL` and the `uuid_generate_v4()` default on `id` are
column-level and remain in place when the PK is dropped.

## Test plan

- [ ] Run 2.6 upgrade against a fresh-install database (PK =
`PK_a02789db60620a1e9f90147b50f`) — should succeed.
- [ ] Run 2.6 upgrade against a pre-squash database (PK =
`PK_8c144a021030d7e3326835a04c8`, reproducible on current staging/dev) —
should now succeed.
- [ ] Verify post-migration: `rolePermissionFlag` exists, PK is named
`PK_76591adc8035c2e7b0cd6115136`, all FKs and indexes named as expected.
- [ ] Run `down()` and verify table returns to `permissionFlag` with PK
`PK_a02789db60620a1e9f90147b50f`.
- [ ] Subsequent migrations (`1778235340021` permission-flag catalog,
`1778235340022` link, `1778235340023` backfill) still apply cleanly.
2026-05-18 23:04:15 +02:00
ce8ef261c1 Update pricing plan cards (#20614)
## Summary
- Update pricing top-card bullets to use workflow credits, keep full
customization, and show Organization-only features accurately.
- Make custom AI models self-host Organization-only and move custom
domain into the Cloud Organization card.
- Align pricing comparison rows for row-level permissions, encryption
key rotation, API call limits, custom domain availability, and self-host
custom objects/fields.

## Validation
- `lingui extract --overwrite --clean`
- `lingui compile --typescript`
- `node scripts/check-section-shape.mjs`
- `git diff --check`
- Playwright snapshot of `http://localhost:3002/pricing`

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-05-18 19:43:15 +00:00
Charles BochetandGitHub 1d3d3999e2 feat(server): upgrade-aware entity decorators for cross-version upgrades (#20686)
## What

When the same PR introduces a new core entity *and* adds a cache
provider that queries it, every workspace step from older versions that
runs before the introducing instance step hits `relation … does not
exist` — the cause of the failed [v2.6.0 staging-ci
run](https://github.com/twentyhq/twenty-infra/actions/runs/26042742000).
Same class of failure for renamed core entities and for new FK columns
hidden inside relation loads.

This PR adds **upgrade-aware entity decorators** + a runtime that adapts
TypeORM's view of the schema to the current `core.upgradeMigration`
cursor.

## Strategy

```
                    ┌────────────────────────────────┐
                    │  @Entity classes (final shape) │
                    │   + @WasIntroducedInUpgrade    │
                    │   + @WasRenamedInUpgrade       │
                    └───────────────┬────────────────┘
                                    │
              UpgradeSequenceRunner.run()
              ┌─────────────────────┴─────────────────────┐
              ▼                                           ▼
       step N+1 begins                          step N just completed
              │                                           │
              └────────► adapter.refresh() ◄──────────────┘
                            │
            reads core.upgradeMigration via
            UpgradeMigrationService.getLastAttemptedInstanceCommand
                            │
                            ▼
       ┌────────────────────────────────────────────────────────┐
       │  UpgradeAwareEntityMetadataAdapter                     │
       │  • mutates EntityMetadata.tableName / tablePath        │
       │     -> historical name for renames not yet applied     │
       │  • flips column.isSelect = false for not-yet-introduced│
       │     columns                                            │
       │  • tracks per-entity availability sidecar              │
       └─────────────────┬──────────────────────────────────────┘
                         │
                         ▼
       DataSource.getRepository wrapped at TypeOrmModule.forRoot:
       repo.find() / findOne() / count() / …
       ┌─────────────────────────────────────────┐
       │  wrapRepositoryWithUpgradeAwareProxy    │
       │  • entity unavailable -> short-circuit  │
       │     (find -> [], count -> 0,            │
       │      findOneOrFail -> EntityNotFound)   │
       │  • write -> Promise.reject(             │
       │      UpgradeUnavailableEntityWriteEx)   │
       │  • find({ relations: ['X'] }) with X    │
       │     unavailable -> X stripped           │
       └─────────────────────────────────────────┘
```

The decorator strings reference real `core.upgradeMigration.name` values
(`${version}_${className}_${timestamp}`). A boot-time validator walks
the actual `UpgradeSequenceReaderService.getUpgradeSequence()` and fails
fast on typos.

## Files

- New decorators:
`engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator.ts`,
`was-renamed-in-upgrade.decorator.ts`
- Runtime: `engine/twenty-orm/upgrade-aware/` (adapter, proxy, install
hook, state singleton, exceptions)
- Wired into `UpgradeSequenceRunnerService` (`refresh()` between steps)
and `TypeOrmModule.forRoot` (proxy install)
- 2-6 entity decorations: `RolePermissionFlagEntity` (rename history +
new `permissionFlagId` column), `PermissionFlagEntity` (new catalog)

## Validation

End-to-end local cross-version upgrade (v1.22 → HEAD): `28 workspace(s)
succeeded, 0 failed`; `upgrade:status → Instance: Up to date, 4 up to
date, 0 behind, 0 failed`. Full log excerpts and the
second-failure-found-and-fixed (`WorkspaceRolesPermissionsCacheService`
relation load) in [this
comment](https://github.com/twentyhq/twenty/pull/20686#issuecomment-4480036816).

## Test plan

- [x] Adapter spec covers rename mutation; proxy spec covers `find()`
short-circuit on unavailable entity. Resolver + validator + decorators
are covered by `resolve-entity-shape-at-upgrade-cursor.util.spec.ts`
(integration-level via real decorator application).
- [x] `nx lint:diff-with-main twenty-server` + `nx typecheck
twenty-server` clean
- [x] All 82 affected tests passing
- [ ] Cross-version-upgrade CI re-runs after this lands; v2.6.0 retag
once green

## Follow-ups deferred

- v2.7 `connectionProvider` rename repro as a permanent end-to-end test
artifact
- Extending the proxy to also cover `EntityManager.getRepository` and
`createQueryBuilder` if a non-`find()` upgrade-time consumer surfaces
2026-05-18 21:38:20 +02:00
EtienneandGitHub 89579f5225 fix(ai-chat) - upload files (#20681)
closes https://github.com/twentyhq/twenty/issues/20437

bonus : persist file filename for UI display
2026-05-18 15:50:02 +00:00
132d997474 i18n - translations (#20685)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-18 17:42:30 +02:00
7c252ff233 Fix 19026 deactivated relation unassignable (#19296)
PR to fix the bug #19026 

This PR will ensure that if an object has some relation deactivated, the
relation will not be visible in the side panel tab and will not be
assignable in the deactivated relation.

## Notes deactivated in People
<img width="1068" height="1106" alt="image"
src="https://github.com/user-attachments/assets/e8c2dbf3-5391-4dbc-8e40-79fcc44e8158"
/>

## Notes not visible in the side panel of people
<img width="2390" height="892" alt="image"
src="https://github.com/user-attachments/assets/308a78aa-2c6d-4d3d-b67c-b49795839aae"
/>

---------

Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
2026-05-18 15:25:51 +00:00
nitinandGitHub 4ba9c0ca0b [Navigation Drawer] Multiple fixes in settings and app drawer (#20634)
closes -
https://discord.com/channels/1130383047699738754/1487720717192527942



https://github.com/user-attachments/assets/6db2df8b-be01-4b5f-a958-575d87b41559

~~waiting on @Bonapara 's feedback!~~
2026-05-18 15:22:03 +00:00
Charles BochetandGitHub d03480472c perf(server): index messageChannel/calendarChannel for per-workspace sync crons (#20678)
## Summary

The messaging/calendar import crons each iterate every active workspace
and execute one `find` per workspace against `core."messageChannel"` /
`core."calendarChannel"` with the shape:

```
WHERE "workspaceId" = $1 AND "isSyncEnabled" = true AND "syncStage" = $2 [AND "type" <> $3]
```

There is currently no index supporting that shape, so the planner does a
seq scan on each table for every iteration. On prod-eu (RDS Performance
Insights, `rds-prod-eu-one`), these two queries are the top two by load
— together ~12 AAS, ~12 calls/sec — and have been the primary
contributor to the sustained 100% CPU since active workspace count grew.

This PR adds composite indexes on `(workspaceId, isSyncEnabled,
syncStage)` for both tables as an instance migration in 2.6.0.
2026-05-18 14:31:03 +00:00
8f3c336e62 i18n - docs translations (#20680)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-18 15:37:33 +02:00
234 changed files with 5993 additions and 1261 deletions
@@ -3263,7 +3263,7 @@ type Mutation {
updateWebhook(input: UpdateWebhookInput!): Webhook!
deleteWebhook(id: UUID!): Webhook!
createChatThread: AgentChatThread!
sendChatMessage(threadId: UUID!, text: String!, messageId: UUID!, browsingContext: JSON, modelId: String, fileIds: [UUID!]): SendChatMessageResult!
sendChatMessage(threadId: UUID!, text: String!, messageId: UUID!, browsingContext: JSON, modelId: String, fileAttachments: [FileAttachmentInput!]): SendChatMessageResult!
stopAgentChatStream(threadId: UUID!): Boolean!
renameChatThread(id: UUID!, title: String!): AgentChatThread!
archiveChatThread(id: UUID!): AgentChatThread!
@@ -4228,6 +4228,11 @@ input UpdateWebhookInputUpdates {
secret: String
}
input FileAttachmentInput {
id: UUID!
filename: String!
}
input CreateSkillInput {
id: UUID
name: String!
@@ -5872,7 +5872,7 @@ export interface MutationGenqlSelection{
updateWebhook?: (WebhookGenqlSelection & { __args: {input: UpdateWebhookInput} })
deleteWebhook?: (WebhookGenqlSelection & { __args: {id: Scalars['UUID']} })
createChatThread?: AgentChatThreadGenqlSelection
sendChatMessage?: (SendChatMessageResultGenqlSelection & { __args: {threadId: Scalars['UUID'], text: Scalars['String'], messageId: Scalars['UUID'], browsingContext?: (Scalars['JSON'] | null), modelId?: (Scalars['String'] | null), fileIds?: (Scalars['UUID'][] | null)} })
sendChatMessage?: (SendChatMessageResultGenqlSelection & { __args: {threadId: Scalars['UUID'], text: Scalars['String'], messageId: Scalars['UUID'], browsingContext?: (Scalars['JSON'] | null), modelId?: (Scalars['String'] | null), fileAttachments?: (FileAttachmentInput[] | null)} })
stopAgentChatStream?: { __args: {threadId: Scalars['UUID']} }
renameChatThread?: (AgentChatThreadGenqlSelection & { __args: {id: Scalars['UUID'], title: Scalars['String']} })
archiveChatThread?: (AgentChatThreadGenqlSelection & { __args: {id: Scalars['UUID']} })
@@ -6259,6 +6259,8 @@ update: UpdateWebhookInputUpdates}
export interface UpdateWebhookInputUpdates {targetUrl?: (Scalars['String'] | null),operations?: (Scalars['String'][] | null),description?: (Scalars['String'] | null),secret?: (Scalars['String'] | null)}
export interface FileAttachmentInput {id: Scalars['UUID'],filename: Scalars['String']}
export interface CreateSkillInput {id?: (Scalars['UUID'] | null),name: Scalars['String'],label: Scalars['String'],icon?: (Scalars['String'] | null),description?: (Scalars['String'] | null),content: Scalars['String']}
export interface UpdateSkillInput {id: Scalars['UUID'],name?: (Scalars['String'] | null),label?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),description?: (Scalars['String'] | null),content?: (Scalars['String'] | null),isActive?: (Scalars['Boolean'] | null)}
@@ -78,8 +78,8 @@ export default {
335,
342,
378,
454,
466
455,
467
],
"types": {
"BillingProductDTO": {
@@ -7876,9 +7876,9 @@ export default {
"modelId": [
1
],
"fileIds": [
3,
"[UUID!]"
"fileAttachments": [
440,
"[FileAttachmentInput!]"
]
}
],
@@ -7944,7 +7944,7 @@ export default {
290,
{
"input": [
440,
441,
"CreateSkillInput!"
]
}
@@ -7953,7 +7953,7 @@ export default {
290,
{
"input": [
441,
442,
"UpdateSkillInput!"
]
}
@@ -8011,7 +8011,7 @@ export default {
238,
{
"input": [
442,
443,
"GetAuthorizationUrlForSSOInput!"
]
}
@@ -8265,7 +8265,7 @@ export default {
196,
{
"input": [
443,
444,
"CreateApplicationRegistrationInput!"
]
}
@@ -8274,7 +8274,7 @@ export default {
7,
{
"input": [
444,
445,
"UpdateApplicationRegistrationInput!"
]
}
@@ -8301,7 +8301,7 @@ export default {
5,
{
"input": [
446,
447,
"CreateApplicationRegistrationVariableInput!"
]
}
@@ -8310,7 +8310,7 @@ export default {
5,
{
"input": [
447,
448,
"UpdateApplicationRegistrationVariableInput!"
]
}
@@ -8399,7 +8399,7 @@ export default {
6,
{
"input": [
449,
450,
"UpdateWorkspaceMemberSettingsInput!"
]
}
@@ -8433,7 +8433,7 @@ export default {
75,
{
"data": [
450,
451,
"ActivateWorkspaceInput!"
]
}
@@ -8442,7 +8442,7 @@ export default {
75,
{
"data": [
451,
452,
"UpdateWorkspaceInput!"
]
}
@@ -8469,7 +8469,7 @@ export default {
6,
{
"workspaceMigration": [
452,
453,
"WorkspaceMigrationInput!"
]
}
@@ -8487,7 +8487,7 @@ export default {
220,
{
"input": [
455,
456,
"SetupOIDCSsoInput!"
]
}
@@ -8496,7 +8496,7 @@ export default {
220,
{
"input": [
456,
457,
"SetupSAMLSsoInput!"
]
}
@@ -8505,7 +8505,7 @@ export default {
216,
{
"input": [
457,
458,
"DeleteSsoInput!"
]
}
@@ -8514,7 +8514,7 @@ export default {
217,
{
"input": [
458,
459,
"EditSsoInput!"
]
}
@@ -8545,7 +8545,7 @@ export default {
286,
{
"input": [
459,
460,
"SendEmailInput!"
]
}
@@ -8571,7 +8571,7 @@ export default {
"String!"
],
"connectionParameters": [
461,
462,
"EmailAccountConnectionParameters!"
],
"id": [
@@ -8583,7 +8583,7 @@ export default {
168,
{
"input": [
463,
464,
"UpdateLabPublicFeatureFlagInput!"
]
}
@@ -8665,7 +8665,7 @@ export default {
77,
{
"input": [
464,
465,
"CreateOneAppTokenInput!"
]
}
@@ -8728,7 +8728,7 @@ export default {
"String!"
],
"fileFolder": [
466,
467,
"FileFolder!"
],
"filePath": [
@@ -10746,6 +10746,17 @@ export default {
1
]
},
"FileAttachmentInput": {
"id": [
3
],
"filename": [
1
],
"__typename": [
1
]
},
"CreateSkillInput": {
"id": [
3
@@ -10828,7 +10839,7 @@ export default {
1
],
"update": [
445
446
],
"__typename": [
1
@@ -10876,7 +10887,7 @@ export default {
1
],
"update": [
448
449
],
"__typename": [
1
@@ -10994,7 +11005,7 @@ export default {
},
"WorkspaceMigrationInput": {
"actions": [
453
454
],
"__typename": [
1
@@ -11002,7 +11013,7 @@ export default {
},
"WorkspaceMigrationDeleteActionInput": {
"type": [
454
455
],
"metadataName": [
318
@@ -11097,7 +11108,7 @@ export default {
1
],
"files": [
460
461
],
"__typename": [
1
@@ -11116,13 +11127,13 @@ export default {
},
"EmailAccountConnectionParameters": {
"IMAP": [
462
463
],
"SMTP": [
462
463
],
"CALDAV": [
462
463
],
"__typename": [
1
@@ -11161,7 +11172,7 @@ export default {
},
"CreateOneAppTokenInput": {
"appToken": [
465
466
],
"__typename": [
1
@@ -11190,7 +11201,7 @@ export default {
230,
{
"input": [
468,
469,
"LogicFunctionLogsInput!"
]
}
+3 -1
View File
@@ -9,7 +9,9 @@ TAG=latest
SERVER_URL=http://localhost:3000
# Use openssl rand -base64 32 for each secret
# APP_SECRET=replace_me_with_a_random_string
# ENCRYPTION_KEY=replace_me_with_a_random_string
# FALLBACK_ENCRYPTION_KEY= # set to the previous ENCRYPTION_KEY during a rotation
# APP_SECRET= # legacy: only required for instances that pre-date ENCRYPTION_KEY
STORAGE_TYPE=local
+6 -2
View File
@@ -20,7 +20,9 @@ services:
STORAGE_S3_NAME: ${STORAGE_S3_NAME}
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
APP_SECRET: ${APP_SECRET:-replace_me_with_a_random_string}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
FALLBACK_ENCRYPTION_KEY: ${FALLBACK_ENCRYPTION_KEY}
APP_SECRET: ${APP_SECRET:-}
# MESSAGING_PROVIDER_GMAIL_ENABLED: ${MESSAGING_PROVIDER_GMAIL_ENABLED}
# CALENDAR_PROVIDER_GOOGLE_ENABLED: ${CALENDAR_PROVIDER_GOOGLE_ENABLED}
# AUTH_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID}
@@ -73,7 +75,9 @@ services:
STORAGE_S3_NAME: ${STORAGE_S3_NAME}
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
APP_SECRET: ${APP_SECRET:-replace_me_with_a_random_string}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
FALLBACK_ENCRYPTION_KEY: ${FALLBACK_ENCRYPTION_KEY}
APP_SECRET: ${APP_SECRET:-}
# MESSAGING_PROVIDER_GMAIL_ENABLED: ${MESSAGING_PROVIDER_GMAIL_ENABLED}
# CALENDAR_PROVIDER_GOOGLE_ENABLED: ${CALENDAR_PROVIDER_GOOGLE_ENABLED}
# AUTH_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID}
+1 -1
View File
@@ -91,7 +91,7 @@ fi
# Generate random strings for secrets
echo "# === Randomly generated secret ===" >> .env
echo "APP_SECRET=$(openssl rand -base64 32)" >> .env
echo "ENCRYPTION_KEY=$(openssl rand -base64 32)" >> .env
echo "" >> .env
echo "PG_DATABASE_PASSWORD=$(openssl rand -hex 32)" >> .env
@@ -47,22 +47,25 @@ Follow these steps for a manual setup.
curl -o .env https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/.env.example
```
2. **Generate Secret Tokens**
2. **Generate an Encryption Key**
Run the following command to generate a unique random string:
```bash
openssl rand -base64 32
```
**Important:** Keep this value secret / do not share it.
**Important:** Keep this value secret / do not share it. Losing `ENCRYPTION_KEY` means losing access to every secret stored in the database (OAuth tokens, application variables, TOTP secrets, etc.).
3. **Update the `.env`**
Replace the placeholder value in your .env file with the generated token:
```ini
APP_SECRET=first_random_string
ENCRYPTION_KEY=random_string
```
See the [Key rotation guide](/developers/self-host/capabilities/key-rotation) for instructions on rotating it without downtime.
4. **Set the Postgres Password**
Update the `PG_DATABASE_PASSWORD` value in the .env file with a strong password without special characters.
@@ -0,0 +1,57 @@
---
title: Key rotation
icon: "rotate"
---
Twenty has two independent key families:
- **JWT signing keys** — asymmetric ES256 keypairs (`kid`-tagged) stored in `core."signingKey"`, used to sign and verify access / refresh tokens.
- **At-rest encryption key** — `ENCRYPTION_KEY`, used to encrypt OAuth tokens, application variables, signing-key private keys, sensitive config values and TOTP secrets inside an `enc:v2:` envelope.
`APP_SECRET` is a legacy secret kept for backward compatibility: when `ENCRYPTION_KEY` is unset it acts as the at-rest encryption / session cookie fallback, and it still verifies pre-existing HS256 access tokens. It will be deprecated.
## JWT signing keys
Each key carries a `publicKey` (kept indefinitely so it can verify previously issued tokens), an encrypted `privateKey` (used only while the key is current), an `isCurrent` flag (exactly one row at a time) and an optional `revokedAt`.
### Rotate the current key
- **Manual** — **Settings → Admin Panel → Signing keys → Revoke** on the current row. Revoking wipes its encrypted private material and demotes it; the next sign call automatically mints a fresh ES256 keypair as the new current. Tokens signed under any other (non-revoked) `kid` keep verifying until they expire.
- **Enterprise (automatic)** — a daily cron (`'15 3 * * *'` UTC) issues a new current key once the existing one has been current for `SIGNING_KEY_ROTATION_DAYS` (default `90`). The previous key is *not* revoked, so tokens signed under it keep verifying. Register it once with `yarn command:prod cron:register:all`.
<Note>The Enterprise cron and `SIGNING_KEY_ROTATION_DAYS` ship in v2.6+.</Note>
### Revoke a key (leak / emergency only)
**Settings → Admin Panel → Signing keys → Revoke** on a non-current row. Wipes the encrypted private material, sets `revokedAt`, and rejects every existing token signed under that `kid`.
## Rotate `ENCRYPTION_KEY`
<Note>The `secret-encryption:rotate` command described below ships in v2.6+.</Note>
Every encrypted value is wrapped as `enc:v2:<keyId>:<payload>`, where `<keyId>` is an 8-hex prefix derived from the raw key. Rotation is online and resumable.
1. **Generate a new key**: `openssl rand -base64 32`.
2. **Configure both keys side-by-side** in `.env`, then restart:
```ini
ENCRYPTION_KEY=NEW_VALUE
FALLBACK_ENCRYPTION_KEY=OLD_VALUE
```
New writes use the new key, existing rows still decrypt via the fallback.
3. **Re-encrypt existing rows**:
```bash
docker exec -it {server_container} yarn command:prod secret-encryption:rotate
```
The command walks six sites (`connected-account-tokens`, `application-variable`, `application-registration-variable`, `signing-key-private-keys`, `sensitive-config-storage`, `totp-secrets`). A SQL filter skips rows already on the new `<keyId>`, so the command is idempotent: interrupt and re-run as needed. Exits non-zero if any row fails — re-run to retry.
| Flag | Description |
| --- | --- |
| `-s, --site <site>` | Limit to a single site. |
| `-b, --batch-size <n>` | Rows per batch (default `200`, max `5000`). |
| `-d, --dry-run` | Decrypt + re-encrypt in memory, skip the `UPDATE`. |
4. **Drop the fallback** once `--dry-run` shows zero remaining rows: remove `FALLBACK_ENCRYPTION_KEY` and restart.
## Legacy `APP_SECRET` support
Older instances that never set `ENCRYPTION_KEY` use `APP_SECRET` as the at-rest encryption key (and as the session-cookie secret, derived from it). This path is preserved for backward compatibility but is **deprecated** — set a dedicated `ENCRYPTION_KEY` and follow the rotation procedure above to migrate off it. `APP_SECRET` itself stays in use to verify legacy HS256 access tokens.
@@ -42,11 +42,26 @@ IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # default
<Warning>
Each variable is documented with descriptions in your admin panel at **Settings → Admin Panel → Configuration Variables**.
Some infrastructure settings like database connections (`PG_DATABASE_URL`), server URLs (`SERVER_URL`), and app secrets (`APP_SECRET`) can only be configured via `.env` file.
Some infrastructure settings like database connections (`PG_DATABASE_URL`), server URLs (`SERVER_URL`), and secrets (`ENCRYPTION_KEY`, `FALLBACK_ENCRYPTION_KEY`) can only be configured via `.env` file.
[Complete technical reference →](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)
</Warning>
## Encryption keys
Twenty uses two env-only encryption keys:
| Variable | Purpose | Required |
| --- | --- | --- |
| `ENCRYPTION_KEY` | Primary key used to encrypt secrets at rest (OAuth tokens, application variables, signing-key private keys, TOTP secrets, sensitive config values). | Yes for new installs (legacy installs may instead rely on `APP_SECRET` — see below) |
| `FALLBACK_ENCRYPTION_KEY` | Verification-only key. Set during a rotation to the *previous* `ENCRYPTION_KEY` so existing rows remain decryptable. | Only during rotation |
For backward compatibility, if `ENCRYPTION_KEY` is unset, Twenty falls back to `APP_SECRET` for at-rest encryption — matching the legacy behaviour for older deployments. New installs should always set a dedicated `ENCRYPTION_KEY`.
Generate values with `openssl rand -base64 32` and store them somewhere safe (a secrets manager, sealed config, etc.). Losing `ENCRYPTION_KEY` means losing access to every secret stored in the database.
To rotate `ENCRYPTION_KEY` without downtime, see the [Key rotation guide](/developers/self-host/capabilities/key-rotation).
## 2. Environment-Only Configuration
```bash
@@ -31,6 +31,18 @@ Starting from **v1.22**, Twenty supports cross-version upgrades. You can jump di
For example, upgrading from v1.22 straight to v2.0 is fully supported.
## Upgrading to v2.5+ — at-rest encryption envelope
Starting in **v2.5**, Twenty stores at-rest secrets (OAuth tokens, application variables, signing-key private keys, sensitive config values, TOTP secrets) inside a versioned `enc:v2:` envelope encrypted with `ENCRYPTION_KEY` (or `APP_SECRET` if `ENCRYPTION_KEY` is unset).
The first boot on v2.5 runs slow upgrade commands that **backfill** existing rows into the new envelope. They are idempotent — interrupting and restarting the server resumes from where it left off — but they can take a while on large databases. You can monitor progress with `upgrade:status`.
You should set a dedicated `ENCRYPTION_KEY` **before** the v2.5 upgrade so the backfill writes rows under it from the start. Switching keys after the backfill requires a [rotation](/developers/self-host/capabilities/key-rotation).
## Rotating secrets and signing keys
For day-to-day operational tasks like rotating `ENCRYPTION_KEY`, rotating the JWT signing key, or revoking a leaked signing key, see the dedicated [Key rotation guide](/developers/self-host/capabilities/key-rotation).
## Checking upgrade status
The `upgrade:status` command lets you inspect the current state of your instance and workspace migrations. It is useful for debugging upgrade issues or when filing a support request.
@@ -40,7 +40,8 @@ export default defineRole({
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
fieldUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name
.universalIdentifier,
canReadFieldValue: false,
canUpdateFieldValue: false,
},
@@ -51,7 +51,7 @@ Befolgen Sie diese Schritte für eine manuelle Einrichtung.
curl -o .env https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/.env.example
```
2. **Erstellen Sie geheime Tokens**
2. **Einen Verschlüsselungsschlüssel generieren**
Führen Sie den folgenden Befehl aus, um eine eindeutige Zufallszeichenfolge zu generieren:
@@ -59,16 +59,18 @@ Befolgen Sie diese Schritte für eine manuelle Einrichtung.
openssl rand -base64 32
```
**Wichtig:** Bewahren Sie diesen Wert geheim auf / teilen Sie ihn nicht.
**Wichtig:** Bewahren Sie diesen Wert geheim auf / teilen Sie ihn nicht. Der Verlust von `ENCRYPTION_KEY` bedeutet den Verlust des Zugriffs auf alle in der Datenbank gespeicherten Secrets (OAuth-Tokens, Anwendungsvariablen, TOTP-Secrets usw.).
3. **Aktualisieren Sie die `.env`**
Ersetzen Sie den Platzhalterwert in Ihrer .env-Datei durch das generierte Token:
```ini
APP_SECRET=erster_zufälliger_string
ENCRYPTION_KEY=random_string
```
Weitere Informationen findest du im [Leitfaden zur Schlüsselrotation](/l/de/developers/self-host/capabilities/key-rotation) mit Anweisungen zur Rotation ohne Ausfallzeit.
4. **Setzen Sie das Postgres-Passwort**
Aktualisieren Sie den Wert `PG_DATABASE_PASSWORD` in der .env-Datei mit einem starken Passwort ohne Sonderzeichen.
@@ -0,0 +1,61 @@
---
title: Schlüsselrotation
icon: rotate
---
Twenty hat zwei unabhängige Schlüsselfamilien:
* **JWT-Signaturschlüssel** — asymmetrische ES256-Schlüsselpaare (`kid`-markiert), die in `core."signingKey"` gespeichert sind und zum Signieren und Verifizieren von Access-/Refresh-Tokens verwendet werden.
* **Verschlüsselungsschlüssel im Ruhezustand** — `ENCRYPTION_KEY`, wird verwendet, um OAuth-Tokens, Anwendungsvariablen, die privaten Schlüssel der Signierschlüssel, sensible Konfigurationswerte und TOTP-Geheimnisse innerhalb eines `enc:v2:`-Umschlags zu verschlüsseln.
`APP_SECRET` ist ein veraltetes Secret, das aus Gründen der Abwärtskompatibilität beibehalten wird: Wenn `ENCRYPTION_KEY` nicht gesetzt ist, fungiert es als Fallback für At-Rest-Verschlüsselung / Session-Cookie und verifiziert weiterhin bereits bestehende HS256-Access-Tokens. Es wird als veraltet markiert werden.
## JWT-Signierschlüssel
Jeder Schlüssel enthält einen `publicKey` (unbefristet aufbewahrt, damit er zuvor ausgestellte Tokens verifizieren kann), einen verschlüsselten `privateKey` (nur verwendet, solange der Schlüssel aktuell ist), ein `isCurrent`-Flag (zu jedem Zeitpunkt genau eine Zeile) und ein optionales `revokedAt`.
### Aktuellen Schlüssel rotieren
* **Manuell** — **Settings → Admin Panel → Signing keys → Revoke** in der aktuellen Zeile. Das Widerrufen löscht das verschlüsselte private Material und stuft es herab; der nächste Signieraufruf erstellt automatisch ein neues ES256-Schlüsselpaar als neuen aktuellen Schlüssel. Tokens, die unter einem anderen (nicht widerrufenen) `kid` signiert wurden, werden weiterhin verifiziert, bis sie ablaufen.
* **Enterprise (automatisch)** — ein täglicher Cron-Job (`'15 3 * * *'` UTC) erstellt einen neuen aktuellen Schlüssel, sobald der vorhandene Schlüssel seit `SIGNING_KEY_ROTATION_DAYS` (Standard `90`) aktuell ist. Der vorherige Schlüssel wird *nicht* widerrufen, daher werden Tokens, die darunter signiert wurden, weiterhin verifiziert. Registriere ihn einmalig mit `yarn command:prod cron:register:all`.
<Note>Der Enterprise-Cron-Job und `SIGNING_KEY_ROTATION_DAYS` sind ab v2.6+ enthalten.</Note>
### Einen Schlüssel widerrufen (nur bei Leak / Notfall)
**Settings → Admin Panel → Signing keys → Revoke** in einer nicht aktuellen Zeile. Löscht das verschlüsselte private Material, setzt `revokedAt` und weist jedes vorhandene Token zurück, das unter diesem `kid` signiert wurde.
## `ENCRYPTION_KEY` rotieren
<Note>Der unten beschriebene Befehl `secret-encryption:rotate` ist ab v2.6+ enthalten.</Note>
Jeder verschlüsselte Wert wird als `enc:v2:\<keyId>:\<payload>` verpackt, wobei `\<keyId>` ein 8-stelliges Hex-Präfix ist, das aus dem Rohschlüssel abgeleitet wird. Die Rotation erfolgt online und ist fortsetzbar.
1. **Einen neuen Schlüssel generieren**: `openssl rand -base64 32`.
2. **Beide Schlüssel nebeneinander konfigurieren** in `.env` und dann neu starten:
```ini
ENCRYPTION_KEY=NEW_VALUE
FALLBACK_ENCRYPTION_KEY=OLD_VALUE
```
Neue Schreibvorgänge verwenden den neuen Schlüssel, vorhandene Zeilen werden weiterhin über den Fallback entschlüsselt.
3. **Vorhandene Zeilen erneut verschlüsseln**:
```bash
docker exec -it {server_container} yarn command:prod secret-encryption:rotate
```
Der Befehl durchläuft sechs Sites (`connected-account-tokens`, `application-variable`, `application-registration-variable`, `signing-key-private-keys`, `sensitive-config-storage`, `totp-secrets`). Ein SQL-Filter überspringt Zeilen, die bereits auf dem neuen `\<keyId>` sind, sodass der Befehl idempotent ist: Unterbrechen und bei Bedarf erneut ausführen. Beendet sich mit einem von Null verschiedenen Exit-Code, wenn irgendeine Zeile fehlschlägt — zum Wiederholen erneut ausführen.
| Flag | Beschreibung |
| ---------------------------------------- | ---------------------------------------------------------------------------- |
| `-s, --site \<site>` | Auf eine einzelne Site beschränken. |
| `-b, --batch-size \<n>` | Zeilen pro Batch (Standard `200`, Maximum `5000`). |
| `-d, --dry-run` | Entschlüsseln + erneut im Speicher verschlüsseln, das `UPDATE` überspringen. |
4. **Den Fallback entfernen**, sobald `--dry-run` null verbleibende Zeilen anzeigt: `FALLBACK_ENCRYPTION_KEY` entfernen und neu starten.
## Legacy-Unterstützung für `APP_SECRET`
Ältere Instanzen, die niemals `ENCRYPTION_KEY` gesetzt haben, verwenden `APP_SECRET` als At-Rest-Verschlüsselungsschlüssel (und als Session-Cookie-Secret, das daraus abgeleitet wird). Dieser Pfad wird aus Gründen der Abwärtskompatibilität beibehalten, ist aber **veraltet** — setze einen dedizierten `ENCRYPTION_KEY` und folge dem oben beschriebenen Rotationsverfahren, um davon zu migrieren. `APP_SECRET` selbst bleibt in Verwendung, um Legacy-HS256-Access-Tokens zu verifizieren.
@@ -43,11 +43,26 @@ IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # Standard
<Warning>
Jede Variable ist mit Beschreibungen in Ihrem Admin-Panel dokumentiert unter **Einstellungen → Admin-Panel → Konfigurationsvariablen**.
Einige Infrastruktureinstellungen wie Datenbankverbindungen (`PG_DATABASE_URL`), Server-URLs (`SERVER_URL`) und Anwendungsgeheimnisse (`APP_SECRET`) können nur über die `.env`-Datei konfiguriert werden.
Einige Infrastruktureinstellungen wie Datenbankverbindungen (`PG_DATABASE_URL`), Server-URLs (`SERVER_URL`) und Secrets (`ENCRYPTION_KEY`, `FALLBACK_ENCRYPTION_KEY`) können nur über die `.env`-Datei konfiguriert werden.
[Vollständige technische Referenz →](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)
</Warning>
## Verschlüsselungsschlüssel
Twenty verwendet zwei ausschließlich über Umgebungsvariablen konfigurierbare Verschlüsselungsschlüssel:
| Variable | Zweck | Erforderlich |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `ENCRYPTION_KEY` | Primärer Schlüssel, der für die Verschlüsselung von Secrets im Ruhezustand verwendet wird (OAuth-Tokens, Anwendungsvariablen, private Schlüssel von Signierschlüsseln, TOTP-Secrets, sensible Konfigurationswerte). | Ja, für neue Installationen (Legacy-Installationen können stattdessen auf `APP_SECRET` zurückgreifen siehe unten). |
| `FALLBACK_ENCRYPTION_KEY` | Nur zur Verifizierung verwendeter Schlüssel. Während einer Rotation auf den *vorherigen* `ENCRYPTION_KEY` gesetzt, damit vorhandene Zeilen weiterhin entschlüsselt werden können. | Nur während der Rotation |
Aus Gründen der Abwärtskompatibilität greift Twenty, falls `ENCRYPTION_KEY` nicht gesetzt ist, für die Verschlüsselung im Ruhezustand auf `APP_SECRET` zurück entsprechend dem Legacy-Verhalten älterer Deployments. Neue Installationen sollten immer einen eigenen `ENCRYPTION_KEY` setzen.
Werte mit `openssl rand -base64 32` generieren und sie an einem sicheren Ort aufbewahren (einen Secrets-Manager, eine versiegelte Konfiguration usw.). Der Verlust des `ENCRYPTION_KEY` bedeutet den Verlust des Zugriffs auf jedes in der Datenbank gespeicherte Secret.
Um `ENCRYPTION_KEY` ohne Downtime zu rotieren, siehe die [Anleitung zur Schlüsselrotation](/l/de/developers/self-host/capabilities/key-rotation).
## 2. Nur-Umgebungs-Konfiguration
```bash
@@ -31,6 +31,18 @@ Ab **v1.22** unterstützt Twenty versionsübergreifende Upgrades. Sie können di
Zum Beispiel wird ein Upgrade direkt von v1.22 auf v2.0 vollständig unterstützt.
## Upgrade auf v2.5+ Verschlüsselungsumschlag für ruhende Daten
Ab **v2.5** speichert Twenty Secrets im Ruhezustand (OAuth-Tokens, Anwendungsvariablen, private Signaturschlüssel, sensible Konfigurationswerte, TOTP-Secrets) in einem versionierten Umschlag `enc:v2:`, der mit `ENCRYPTION_KEY` (oder `APP_SECRET`, falls `ENCRYPTION_KEY` nicht gesetzt ist) verschlüsselt wird.
Beim ersten Start mit v2.5 werden langsame Upgrade-Befehle ausgeführt, die den neuen Umschlag mit bestehenden Zeilen befüllen. Sie sind idempotent ein Unterbrechen und Neustarten des Servers setzt an der Stelle fort, an der er aufgehört hat , aber sie können bei großen Datenbanken eine Weile dauern. Sie können den Fortschritt mit `upgrade:status` überwachen.
Sie sollten **vor** dem Upgrade auf v2.5 einen dedizierten `ENCRYPTION_KEY` setzen, damit beim Nachbefüllen die Zeilen von Anfang an unter diesem Schlüssel geschrieben werden. Ein Schlüsselwechsel nach der Nachbefüllung erfordert eine [Rotation](/l/de/developers/self-host/capabilities/key-rotation).
## Rotation von Secrets und Signaturschlüsseln
Für alltägliche operative Aufgaben wie das Rotieren von `ENCRYPTION_KEY`, das Rotieren des JWT-Signaturschlüssels oder das Widerrufen eines kompromittierten Signaturschlüssels siehe den dedizierten [Leitfaden zur Schlüsselrotation](/l/de/developers/self-host/capabilities/key-rotation).
## Upgrade-Status prüfen
Mit dem Befehl `upgrade:status` können Sie den aktuellen Status Ihrer Instanz und der Workspace-Migrationen prüfen. Er ist nützlich zum Debuggen von Upgrade-Problemen oder beim Erstellen einer Support-Anfrage.
@@ -40,7 +40,8 @@ export default defineRole({
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
fieldUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name
.universalIdentifier,
canReadFieldValue: false,
canUpdateFieldValue: false,
},
@@ -51,7 +51,7 @@ Siga estas etapas para uma configuração manual.
curl -o .env https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/.env.example
```
2. **Gere Tokens Secretos**
2. **Gerar uma chave de criptografia**
Execute o seguinte comando para gerar uma string aleatória única:
@@ -59,16 +59,18 @@ Siga estas etapas para uma configuração manual.
openssl rand -base64 32
```
**Importante:** Mantenha este valor em segredo / não o compartilhe.
**Importante:** Mantenha este valor em segredo / não o compartilhe. Perder `ENCRYPTION_KEY` significa perder o acesso a todos os segredos armazenados no banco de dados (tokens OAuth, variáveis de aplicação, segredos TOTP, etc.).
3. **Atualize o `.env`**
Substitua o valor do espaço reservado no seu arquivo .env pelo token gerado:
```ini
APP_SECRET=first_random_string
ENCRYPTION_KEY=random_string
```
Consulte o [guia de rotação de chaves](/l/pt/developers/self-host/capabilities/key-rotation) para obter instruções sobre como fazer a rotação sem tempo de inatividade.
4. **Defina a Senha do Postgres**
Atualize o valor `PG_DATABASE_PASSWORD` no arquivo .env com uma senha forte sem caracteres especiais.
@@ -0,0 +1,61 @@
---
title: Rotação de chaves
icon: rotate
---
Twenty possui duas famílias de chaves independentes:
* **Chaves de assinatura JWT** — pares de chaves assimétricas ES256 (com `kid`) armazenados em `core."signingKey"`, usados para assinar e verificar tokens de acesso/atualização.
* **Chave de criptografia em repouso** — `ENCRYPTION_KEY`, usada para criptografar tokens OAuth, variáveis de aplicação, chaves privadas de chaves de assinatura, valores confidenciais de configuração e segredos TOTP dentro de um envelope `enc:v2:`.
`APP_SECRET` é um segredo legado mantido para compatibilidade retroativa: quando `ENCRYPTION_KEY` não está definido, ele funciona como fallback de criptografia em repouso / cookie de sessão e ainda verifica tokens de acesso HS256 já existentes. Ele será descontinuado.
## Chaves de assinatura JWT
Cada chave carrega uma `publicKey` (mantida indefinidamente para que possa verificar tokens emitidos anteriormente), uma `privateKey` criptografada (usada apenas enquanto a chave é atual), um sinalizador `isCurrent` (exatamente uma linha por vez) e um `revokedAt` opcional.
### Rotacionar a chave atual
* **Manual** — **Settings → Admin Panel → Signing keys → Revoke** na linha atual. A revogação apaga o material privado criptografado e o rebaixa; a próxima chamada de assinatura gera automaticamente um novo par de chaves ES256 como o novo atual. Tokens assinados sob qualquer outro `kid` (não revogado) continuam sendo verificados até expirarem.
* **Enterprise (automático)** — um cron diário (`'15 3 * * *'` UTC) emite uma nova chave atual assim que a existente tiver sido atual por `SIGNING_KEY_ROTATION_DAYS` (padrão `90`). A chave anterior *não* é revogada, então tokens assinados sob ela continuam sendo verificados. Registre-o uma vez com `yarn command:prod cron:register:all`.
<Note>O cron do Enterprise e `SIGNING_KEY_ROTATION_DAYS` são disponibilizados a partir da v2.6+.</Note>
### Revogar uma chave (apenas vazamento / emergência)
**Settings → Admin Panel → Signing keys → Revoke** em uma linha que não seja a atual. Apaga o material privado criptografado, define `revokedAt` e rejeita todos os tokens existentes assinados sob aquele `kid`.
## Rotacionar `ENCRYPTION_KEY`
<Note>O comando `secret-encryption:rotate` descrito abaixo é disponibilizado a partir da v2.6+.</Note>
Cada valor criptografado é encapsulado como `enc:v2:\<keyId>:\<payload>`, em que `\<keyId>` é um prefixo hexadecimal de 8 caracteres derivado da chave bruta. A rotação é online e retomável.
1. **Gerar uma nova chave**: `openssl rand -base64 32`.
2. **Configurar ambas as chaves lado a lado** em `.env`, depois reinicie:
```ini
ENCRYPTION_KEY=NEW_VALUE
FALLBACK_ENCRYPTION_KEY=OLD_VALUE
```
Novas gravações usam a nova chave; linhas existentes ainda são descriptografadas por meio do fallback.
3. **Recriptografar linhas existentes**:
```bash
docker exec -it {server_container} yarn command:prod secret-encryption:rotate
```
O comando percorre seis sites (`connected-account-tokens`, `application-variable`, `application-registration-variable`, `signing-key-private-keys`, `sensitive-config-storage`, `totp-secrets`). Um filtro SQL ignora as linhas que já estão no novo `\<keyId>`, portanto o comando é idempotente: interrompa e execute novamente conforme necessário. Sai com código diferente de zero se alguma linha falhar — execute novamente para tentar de novo.
| Opção | Descrição |
| ---------------------------------------- | -------------------------------------------------------------- |
| `-s, --site \<site>` | Limitar a um único site. |
| `-b, --batch-size \<n>` | Linhas por lote (padrão `200`, máximo `5000`). |
| `-d, --dry-run` | Descriptografar + recriptografar em memória, pular o `UPDATE`. |
4. **Remova o fallback** assim que `--dry-run` mostrar zero linhas restantes: remova `FALLBACK_ENCRYPTION_KEY` e reinicie.
## Suporte legado a `APP_SECRET`
Instâncias antigas que nunca definiram `ENCRYPTION_KEY` usam `APP_SECRET` como chave de criptografia em repouso (e como segredo de cookie de sessão, derivado dela). Esse caminho é mantido para compatibilidade retroativa, mas está **descontinuado** — defina uma `ENCRYPTION_KEY` dedicada e siga o procedimento de rotação acima para migrar para fora dele. O próprio `APP_SECRET` continua em uso para verificar tokens de acesso HS256 legados.
@@ -43,11 +43,26 @@ IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # default
<Warning>
Cada variável é documentada com descrições no seu painel de administração em **Configurações → Painel de Administração → Variáveis de Configuração**.
Algumas configurações de infraestrutura como conexões de banco de dados (`PG_DATABASE_URL`), URLs de servidor (`SERVER_URL`) e segredos do app (`APP_SECRET`) só podem ser configuradas via arquivo `.env`.
Algumas configurações de infraestrutura como conexões de banco de dados (`PG_DATABASE_URL`), URLs de servidor (`SERVER_URL`) e segredos (`ENCRYPTION_KEY`, `FALLBACK_ENCRYPTION_KEY`) só podem ser configuradas via arquivo `.env`.
[Referência técnica completa →](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)
</Warning>
## Chaves de criptografia
A Twenty usa duas chaves de criptografia definidas apenas por variáveis de ambiente:
| Variável | Finalidade | Obrigatório |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| `ENCRYPTION_KEY` | Chave primária usada para criptografar segredos em repouso (tokens OAuth, variáveis do aplicativo, chaves privadas de assinatura, segredos TOTP, valores de configuração sensíveis). | Sim para novas instalações (instalações legadas podem, em vez disso, depender de `APP_SECRET` — veja abaixo) |
| `FALLBACK_ENCRYPTION_KEY` | Chave somente para verificação. Definida durante uma rotação para a *chave `ENCRYPTION_KEY` anterior* para que as linhas existentes permaneçam descriptografáveis. | Apenas durante a rotação |
Para compatibilidade retroativa, se `ENCRYPTION_KEY` não estiver definida, Twenty recorre a `APP_SECRET` para criptografia em repouso — correspondendo ao comportamento legado de implantações mais antigas. Novas instalações devem sempre definir uma `ENCRYPTION_KEY` dedicada.
Gere valores com `openssl rand -base64 32` e armazene-os em um local seguro (um gerenciador de segredos, configuração selada, etc.). Perder a `ENCRYPTION_KEY` significa perder o acesso a todos os segredos armazenados no banco de dados.
Para rotacionar a `ENCRYPTION_KEY` sem tempo de inatividade, consulte o [guia de rotação de chaves](/l/pt/developers/self-host/capabilities/key-rotation).
## 2. Configuração Somente por Ambiente
```bash
@@ -31,6 +31,18 @@ A partir da **v1.22**, o Twenty oferece suporte a atualizações entre versões.
Por exemplo, a atualização da v1.22 diretamente para a v2.0 é totalmente suportada.
## Atualizando para a v2.5+ — envelope de criptografia em repouso
A partir da **v2.5**, Twenty armazena segredos em repouso (tokens OAuth, variáveis de aplicação, chaves privadas de chaves de assinatura, valores de configuração sigilosos, segredos TOTP) dentro de um envelope versionado `enc:v2:` criptografado com `ENCRYPTION_KEY` (ou `APP_SECRET` se `ENCRYPTION_KEY` não estiver definido).
A primeira inicialização na v2.5 executa comandos de atualização lentos que **preenchem retroativamente** as linhas existentes no novo envelope. Eles são idempotentes — interromper e reiniciar o servidor retoma do ponto em que parou —, mas podem demorar em bancos de dados grandes. Você pode monitorar o progresso com `upgrade:status`.
Você deve definir uma `ENCRYPTION_KEY` dedicada **antes** da atualização para a v2.5, para que o preenchimento retroativo grave as linhas sob ela desde o início. Trocar as chaves após o preenchimento retroativo exige uma [rotação](/l/pt/developers/self-host/capabilities/key-rotation).
## Rotação de segredos e chaves de assinatura
Para tarefas operacionais do dia a dia, como rotacionar a `ENCRYPTION_KEY`, rotacionar a chave de assinatura JWT ou revogar uma chave de assinatura vazada, consulte o [guia de rotação de chaves](/l/pt/developers/self-host/capabilities/key-rotation) dedicado.
## Verificando o status da atualização
O comando `upgrade:status` permite inspecionar o estado atual da sua instância e das migrações dos espaços de trabalho. É útil para depurar problemas de atualização ou ao abrir uma solicitação de suporte.
@@ -40,7 +40,8 @@ export default defineRole({
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
fieldUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name
.universalIdentifier,
canReadFieldValue: false,
canUpdateFieldValue: false,
},
@@ -51,7 +51,7 @@ Urmați acești pași pentru o configurare manuală.
curl -o .env https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/.env.example
```
2. **Generați Tokenuri Secrete**
2. **Generează o cheie de criptare**
Rulați următoarea comandă pentru a genera un șir unic aleatoriu:
@@ -59,16 +59,18 @@ Urmați acești pași pentru o configurare manuală.
openssl rand -base64 32
```
**Important:** Păstrați această valoare secretă / nu o împărtășiți.
**Important:** Păstrați această valoare secretă / nu o împărtășiți. Pierderea `ENCRYPTION_KEY` înseamnă pierderea accesului la toate secretele stocate în baza de date (tokenuri OAuth, variabile de aplicație, secrete TOTP etc.).
3. **Actualizați `.env`**
Înlocuiți valoarea substituentului în fișierul dvs. .env cu tokenul generat:
```ini
APP_SECRET=first_random_string
ENCRYPTION_KEY=random_string
```
Vezi [ghidul pentru rotația cheii](/l/ro/developers/self-host/capabilities/key-rotation) pentru instrucțiuni despre cum să o rotești fără întreruperi.
4. **Setați Parola pentru Postgres**
Actualizați valoarea `PG_DATABASE_PASSWORD` în fișierul .env cu o parolă puternică fără caractere speciale.
@@ -0,0 +1,61 @@
---
title: Rotirea cheilor
icon: rotate
---
Twenty are două familii de chei independente:
* **Chei de semnare JWT** — perechi de chei asimetrice ES256 (etichetate cu `kid`) stocate în `core."signingKey"`, folosite pentru a semna și verifica tokenurile de acces / reîmprospătare.
* **Cheia de criptare pentru date în repaus** — `ENCRYPTION_KEY`, folosită pentru a cripta token-urile OAuth, variabilele aplicației, cheile private de semnare, valorile de configurare sensibile și secretele TOTP în interiorul unui înveliș `enc:v2:`.
`APP_SECRET` este un secret vechi păstrat pentru compatibilitate retroactivă: când `ENCRYPTION_KEY` nu este setată, acționează ca mecanism de rezervă pentru criptarea datelor în repaus / cookie-urile de sesiune și încă verifică token-urile de acces HS256 existente anterior. Va fi marcat ca învechit.
## Chei de semnare JWT
Fiecare cheie are un `publicKey` (păstrat pe termen nelimitat pentru a putea verifica token-urile emise anterior), un `privateKey` criptat (folosit doar cât timp cheia este curentă), un indicator `isCurrent` (exact un rând la un moment dat) și un câmp opțional `revokedAt`.
### Rotește cheia curentă
* **Manual** — **Settings → Admin Panel → Signing keys → Revoke** pe rândul curent. Revocarea șterge materialul privat criptat și o retrogradează; următorul apel de semnare generează automat o nouă pereche de chei ES256 ca noua cheie curentă. Token-urile semnate sub orice alt `kid` (nerevocat) continuă să fie verificate până la expirare.
* **Enterprise (automat)** — un cron zilnic (`'15 3 * * *'` UTC) emite o nouă cheie curentă după ce cea existentă a fost curentă timp de `SIGNING_KEY_ROTATION_DAYS` (implicit `90`). Cheia anterioară *nu* este revocată, astfel încât token-urile semnate cu ea continuă să fie verificate. Înregistrează-l o singură dată cu `yarn command:prod cron:register:all`.
<Note>Cron-ul Enterprise și `SIGNING_KEY_ROTATION_DAYS` sunt disponibile în v2.6+.</Note>
### Revocă o cheie (numai în caz de scurgere / urgență)
**Settings → Admin Panel → Signing keys → Revoke** pe un rând care nu este curent. Șterge materialul privat criptat, setează `revokedAt` și respinge fiecare token existent semnat sub acel `kid`.
## Rotește `ENCRYPTION_KEY`
<Note>Comanda `secret-encryption:rotate` descrisă mai jos este disponibilă în v2.6+.</Note>
Fiecare valoare criptată este încapsulată ca `enc:v2:\<keyId>:\<payload>`, unde `\<keyId>` este un prefix hexazecimal de 8 caractere derivat din cheia brută. Rotirea are loc online și poate fi reluată.
1. **Generează o cheie nouă**: `openssl rand -base64 32`.
2. **Configurează ambele chei în paralel** în `.env`, apoi repornește:
```ini
ENCRYPTION_KEY=NEW_VALUE
FALLBACK_ENCRYPTION_KEY=OLD_VALUE
```
Noile scrieri folosesc noua cheie, rândurile existente încă sunt decriptate prin mecanismul de rezervă.
3. **Re-criptează rândurile existente**:
```bash
docker exec -it {server_container} yarn command:prod secret-encryption:rotate
```
Comanda parcurge șase locații (`connected-account-tokens`, `application-variable`, `application-registration-variable`, `signing-key-private-keys`, `sensitive-config-storage`, `totp-secrets`). Un filtru SQL omite rândurile care sunt deja pe noul `\<keyId>`, astfel încât comanda este idempotentă: întrerupe și rulează din nou după nevoie. Iese cu un cod de eroare nenul dacă vreun rând eșuează — rulează din nou pentru a reîncerca.
| Opțiune | Descriere |
| ---------------------------------------- | ------------------------------------------------------ |
| `-s, --site \<site>` | Limitează la o singură locație. |
| `-b, --batch-size \<n>` | Rânduri per lot (implicit `200`, maxim `5000`). |
| `-d, --dry-run` | Decriptează + re-criptează în memorie, omite `UPDATE`. |
4. **Elimină mecanismul de rezervă** odată ce `--dry-run` arată zero rânduri rămase: elimină `FALLBACK_ENCRYPTION_KEY` și repornește.
## Compatibilitate veche pentru `APP_SECRET`
Instanțele mai vechi care nu au setat niciodată `ENCRYPTION_KEY` folosesc `APP_SECRET` ca cheie de criptare pentru date în repaus (și ca secret pentru cookie-urile de sesiune, derivat din aceasta). Acest mecanism este păstrat pentru compatibilitate retroactivă, dar este **învechit** — setează o `ENCRYPTION_KEY` dedicată și urmează procedura de rotire de mai sus pentru a migra de la el. `APP_SECRET` în sine rămâne în uz pentru a verifica token-urile de acces HS256 vechi.
@@ -43,11 +43,26 @@ IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # implicit
<Warning>
Fiecare variabilă este documentată cu descrieri în panoul dvs. de administrare la **Setări → Panou Admin → Variabile de Configurare**.
Unele setări de infrastructură, cum ar fi conexiunile la baze de date (`PG_DATABASE_URL`), URL-urile serverului (`SERVER_URL`), și secretele aplicației (`APP_SECRET`) pot fi configurate doar prin fișierul `.env`.
Unele setări de infrastructură, cum ar fi conexiunile la baze de date (`PG_DATABASE_URL`), URL-urile serverului (`SERVER_URL`) și secretele (`ENCRYPTION_KEY`, `FALLBACK_ENCRYPTION_KEY`) pot fi configurate doar prin fișierul `.env`.
[Referință tehnică completă →](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)
</Warning>
## Chei de criptare
Twenty folosește două chei de criptare disponibile doar prin variabile de mediu:
| Variabilă | Scop | Obligatoriu |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `ENCRYPTION_KEY` | Cheia principală folosită pentru a cripta secretele în repaus (token-uri OAuth, variabile de aplicație, chei private de semnare, secrete TOTP, valori de configurare sensibile). | Da, pentru instalările noi (instalările vechi se pot baza în schimb pe `APP_SECRET` — vezi mai jos) |
| `FALLBACK_ENCRYPTION_KEY` | Cheie folosită doar pentru verificare. Setată în timpul unei rotații la *precedenta* `ENCRYPTION_KEY` astfel încât rândurile existente să rămână decriptabile. | Doar în timpul rotației |
Pentru compatibilitate cu versiunile anterioare, dacă `ENCRYPTION_KEY` nu este setată, Twenty revine la `APP_SECRET` pentru criptarea în repaus — respectând comportamentul moștenit pentru implementările mai vechi. Instalările noi ar trebui să seteze întotdeauna o `ENCRYPTION_KEY` dedicată.
Generează valori cu `openssl rand -base64 32` și stochează-le într-un loc sigur (un manager de secrete, configurație sigilată etc.). Pierderea `ENCRYPTION_KEY` înseamnă pierderea accesului la fiecare secret stocat în baza de date.
Pentru a roti `ENCRYPTION_KEY` fără timp de nefuncționare, vezi [Ghidul de rotație a cheilor](/l/ro/developers/self-host/capabilities/key-rotation).
## 2. Configurare Doar Mediu
```bash
@@ -31,6 +31,18 @@ Serverul rulează automat toate migrațiile de actualizare necesare la pornire.
De exemplu, actualizarea de la v1.22 direct la v2.0 este pe deplin acceptată.
## Actualizare la v2.5+ — înveliș de criptare pentru date în repaus
Începând cu **v2.5**, Twenty stochează secretele aflate în repaus (tokenuri OAuth, variabile de aplicație, chei private de semnare, valori de configurare sensibile, secrete TOTP) într-un înveliș versionat `enc:v2:` criptat cu `ENCRYPTION_KEY` (sau `APP_SECRET` dacă `ENCRYPTION_KEY` nu este setată).
Prima pornire în v2.5 rulează comenzi lente de upgrade care populează retroactiv rândurile existente în noul înveliș. Aceste comenzi sunt idempotente — întreruperea și repornirea serverului reiau procesul de unde a rămas — dar pot dura ceva timp pe baze de date mari. Poți monitoriza progresul cu `upgrade:status`.
Ar trebui să setezi un `ENCRYPTION_KEY` dedicat **înainte** de upgrade-ul la v2.5, astfel încât procesul de populare retroactivă să scrie rândurile sub acesta încă de la început. Schimbarea cheilor după popularea retroactivă necesită o [rotație](/l/ro/developers/self-host/capabilities/key-rotation).
## Rotația secretelor și a cheilor de semnare
Pentru sarcini operaționale de zi cu zi, precum rotația `ENCRYPTION_KEY`, rotația cheii de semnare JWT sau revocarea unei chei de semnare compromise, consultă [Ghidul de rotație a cheilor](/l/ro/developers/self-host/capabilities/key-rotation).
## Verificarea stării actualizării
Comanda `upgrade:status` vă permite să inspectați starea curentă a instanței și a migrațiilor spațiilor de lucru. Este utilă pentru depanarea problemelor de actualizare sau când trimiteți o solicitare de asistență.
@@ -40,7 +40,8 @@ export default defineRole({
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
fieldUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name
.universalIdentifier,
canReadFieldValue: false,
canUpdateFieldValue: false,
},
@@ -51,7 +51,7 @@ VERSION=vx.y.z BRANCH=branch-name bash <(curl -sL https://raw.githubusercontent.
curl -o .env https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/.env.example
```
2. **Создайте секретные токены**
2. **Сгенерируйте ключ шифрования**
Выполните следующую команду для генерации уникальной случайной строки:
@@ -59,16 +59,18 @@ VERSION=vx.y.z BRANCH=branch-name bash <(curl -sL https://raw.githubusercontent.
openssl rand -base64 32
```
**Внимание:** держите это значение в секрете / не делитесь им.
**Внимание:** держите это значение в секрете / не делитесь им. Потеря `ENCRYPTION_KEY` означает потерю доступа ко всем секретам, хранящимся в базе данных (OAuth-токены, переменные приложения, TOTP-секреты и т. д.).
3. **Обновите файл `.env`**
Замените значение плейсхолдера в вашем файле .env на сгенерированный токен:
```ini
APP_SECRET=first_random_string
ENCRYPTION_KEY=random_string
```
См. [руководство по ротации ключей](/l/ru/developers/self-host/capabilities/key-rotation) для получения инструкций по их ротации без простоя.
4. **Установите пароль для Postgres**
Обновите значение `PG_DATABASE_PASSWORD` в файле .env, используя надежный пароль без специальных символов.
@@ -0,0 +1,61 @@
---
title: Ротация ключей
icon: rotate
---
У Twenty есть два независимых семейства ключей:
* **Ключи подписи JWT** — асимметричные пары ключей ES256 (с тегами `kid`), хранятся в `core."signingKey"` и используются для подписи и проверки access/refresh-токенов.
* **Ключ шифрования данных на диске** — `ENCRYPTION_KEY`, используется для шифрования OAuth-токенов, переменных приложения, закрытых ключей подписи, конфиденциальных значений конфигурации и TOTP-секретов внутри оболочки `enc:v2:`.
`APP_SECRET` — устаревший секрет, сохранённый для обратной совместимости: когда `ENCRYPTION_KEY` не установлен, он используется как резерв для шифрования данных на диске/сеансовых cookie и по-прежнему проверяет ранее выданные HS256 access-токены. Он будет объявлен устаревшим.
## Ключи подписи JWT
Каждый ключ содержит `publicKey` (хранится бессрочно, чтобы можно было проверять ранее выданные токены), зашифрованный `privateKey` (используется только пока ключ является текущим), флаг `isCurrent` (в каждый момент времени ровно одна строка) и необязательное поле `revokedAt`.
### Выполнить ротацию текущего ключа
* **Вручную** — **Settings → Admin Panel → Signing keys → Revoke** в текущей строке. Отзыв стирает его зашифрованный закрытый материал и понижает его; следующий вызов подписи автоматически создает новую пару ключей ES256 как новый текущий ключ. Токены, подписанные любым другим (не отозванным) `kid`, продолжают успешно проходить проверку до истечения срока действия.
* **Enterprise (автоматически)** — ежедневный cron (`'15 3 * * *'` UTC) выпускает новый текущий ключ, как только существующий ключ находится в статусе текущего в течение `SIGNING_KEY_ROTATION_DAYS` (по умолчанию `90`). Предыдущий ключ *не* отзывается, поэтому токены, подписанные им, продолжают успешно проходить проверку. Зарегистрируйте его один раз с помощью `yarn command:prod cron:register:all`.
<Note>Enterprise-cron и `SIGNING_KEY_ROTATION_DAYS` поставляются начиная с v2.6+.</Note>
### Отозвать ключ (только при утечке / в экстренных случаях)
**Settings → Admin Panel → Signing keys → Revoke** в нетекущей строке. Стирает зашифрованный закрытый материал, устанавливает `revokedAt` и отклоняет все существующие токены, подписанные с этим `kid`.
## Выполнить ротацию `ENCRYPTION_KEY`
<Note>Команда `secret-encryption:rotate`, описанная ниже, поставляется начиная с v2.6+.</Note>
Каждое зашифрованное значение оборачивается как `enc:v2:\<keyId>:\<payload>`, где `\<keyId>` — это 8-символьный шестнадцатеричный префикс, полученный из исходного ключа. Ротация выполняется онлайн и может быть возобновлена.
1. **Сгенерировать новый ключ**: `openssl rand -base64 32`.
2. **Настройте оба ключа параллельно** в `.env`, затем перезапустите:
```ini
ENCRYPTION_KEY=NEW_VALUE
FALLBACK_ENCRYPTION_KEY=OLD_VALUE
```
Новые записи используют новый ключ, существующие строки по-прежнему расшифровываются через резервный ключ.
3. **Повторно зашифровать существующие строки**:
```bash
docker exec -it {server_container} yarn command:prod secret-encryption:rotate
```
Команда обходит шесть разделов (`connected-account-tokens`, `application-variable`, `application-registration-variable`, `signing-key-private-keys`, `sensitive-config-storage`, `totp-secrets`). SQL-фильтр пропускает строки, которые уже находятся на новом `\<keyId>`, поэтому команда идемпотентна: прерывайте и запускайте повторно по мере необходимости. Завершает работу с ненулевым кодом, если какая-либо строка завершилась с ошибкой — запустите повторно, чтобы повторить попытку.
| Флаг | Описание |
| ---------------------------------------- | ------------------------------------------------------------------ |
| `-s, --site \<site>` | Ограничить одним сайтом. |
| `-b, --batch-size \<n>` | Строк в партии (по умолчанию `200`, максимум `5000`). |
| `-d, --dry-run` | Расшифровать + повторно зашифровать в памяти, пропустить `UPDATE`. |
4. **Удалите резервный ключ** после того, как `--dry-run` покажет ноль оставшихся строк: удалите `FALLBACK_ENCRYPTION_KEY` и перезапустите.
## Устаревшая поддержка `APP_SECRET`
Более старые инстансы, в которых никогда не был установлен `ENCRYPTION_KEY`, используют `APP_SECRET` как ключ шифрования данных на диске (и как секрет для сеансовых cookie, производный от него). Этот путь сохранен для обратной совместимости, но **устарел** — установите отдельный `ENCRYPTION_KEY` и выполните описанную выше процедуру ротации, чтобы с него мигрировать. Сам `APP_SECRET` продолжает использоваться для проверки устаревших HS256 access-токенов.
@@ -43,11 +43,26 @@ IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # по умолчанию
<Warning>
Каждая переменная документирована с описаниями в вашей панели администратора в **Настройки → Панель администратора → Переменные конфигурации**.
Некоторые инфраструктурные настройки, такие как соединения с базой данных (`PG_DATABASE_URL`), URL сервера (`SERVER_URL`) и секреты приложения (`APP_SECRET`), могут быть настроены только через файл `.env`.
Некоторые инфраструктурные настройки, такие как соединения с базой данных (`PG_DATABASE_URL`), URL сервера (`SERVER_URL`) и секреты (`ENCRYPTION_KEY`, `FALLBACK_ENCRYPTION_KEY`), могут быть настроены только через файл `.env`.
[Полная техническая ссылка →](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)
</Warning>
## Ключи шифрования
Twenty использует два ключа шифрования, задаваемых только через переменные окружения:
| Переменная | Назначение | Обязательно |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `ENCRYPTION_KEY` | Основной ключ, используемый для шифрования секретов в состоянии покоя (OAuth-токены, переменные приложения, закрытые ключи для подписания, TOTP-секреты, конфиденциальные значения конфигурации). | Да, для новых установок (устаревшие установки могут вместо этого полагаться на `APP_SECRET` — см. ниже) |
| `FALLBACK_ENCRYPTION_KEY` | Ключ, используемый только для проверки. Во время ротации установите значение равным *предыдущему* `ENCRYPTION_KEY`, чтобы существующие строки оставались доступными для расшифровки. | Только во время ротации |
Для обеспечения обратной совместимости, если `ENCRYPTION_KEY` не задан, Twenty использует `APP_SECRET` для шифрования данных в состоянии покоя — что соответствует устаревшему поведению для более старых развертываний. Для новых установок всегда следует задавать отдельный `ENCRYPTION_KEY`.
Генерируйте значения с помощью `openssl rand -base64 32` и храните их в безопасном месте (менеджер секретов, защищённая конфигурация и т. п.). Потеря `ENCRYPTION_KEY` означает потерю доступа ко всем секретам, хранящимся в базе данных.
Чтобы выполнить ротацию `ENCRYPTION_KEY` без простоя, см. [руководство по ротации ключей](/l/ru/developers/self-host/capabilities/key-rotation).
## 2. Конфигурация только для среды
```bash
@@ -31,6 +31,18 @@ cat databases_backup.sql | docker exec -i {db_container_name_or_id} psql -U {pos
Например, полностью поддерживается обновление с v1.22 сразу до v2.0.
## Переход на v2.5+ — конверт шифрования данных в состоянии покоя
Начиная с **v2.5**, Twenty хранит секреты в состоянии покоя (токены OAuth, переменные приложения, закрытые ключи подписи, конфиденциальные значения конфигурации, TOTP‑секреты) внутри версионируемого конверта `enc:v2:`, зашифрованного с помощью `ENCRYPTION_KEY` (или `APP_SECRET`, если `ENCRYPTION_KEY` не задан).
Первый запуск на v2.5 выполняет медленные команды обновления, которые **дополняют** существующие строки, помещая их в новый конверт. Они идемпотентны — при прерывании и перезапуске сервера выполнение продолжается с того места, где остановилось, — но на больших базах данных это может занять продолжительное время. Вы можете отслеживать прогресс с помощью `upgrade:status`.
Вам следует задать отдельный `ENCRYPTION_KEY` **перед** обновлением до v2.5, чтобы процедура дополнения с самого начала записывала строки под этим ключом. Смена ключей после завершения дополнения требует [ротации](/l/ru/developers/self-host/capabilities/key-rotation).
## Ротация секретов и ключей подписи
Для повседневных операционных задач, таких как ротация `ENCRYPTION_KEY`, ротация ключа подписи JWT или отзыв скомпрометированного ключа подписи, см. специализированное руководство [Key rotation guide](/l/ru/developers/self-host/capabilities/key-rotation).
## Проверка статуса обновления
Команда `upgrade:status` позволяет просмотреть текущее состояние вашего экземпляра и миграций рабочих пространств. Это полезно для отладки проблем с обновлением или при обращении в поддержку.
@@ -40,7 +40,8 @@ export default defineRole({
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
fieldUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name
.universalIdentifier,
canReadFieldValue: false,
canUpdateFieldValue: false,
},
@@ -51,7 +51,7 @@ Manuel kurulum için bu adımları uygulayın.
curl -o .env https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/.env.example
```
2. **Gizli Jetonlar Oluşturun**
2. **Bir Şifreleme Anahtarı Oluşturun**
Benzersiz bir rastgele karakter dizisi oluşturmak için aşağıdaki komutu çalıştırın:
@@ -59,16 +59,18 @@ Manuel kurulum için bu adımları uygulayın.
openssl rand -base64 32
```
**Önemli:** Bu değeri gizli tutun / paylaşmayın.
**Önemli:** Bu değeri gizli tutun / paylaşmayın. `ENCRYPTION_KEY` kaybedilirse, veritabanında depolanan her gizli veriye (OAuth belirteçleri, uygulama değişkenleri, TOTP gizli anahtarları vb.) erişim de kaybedilir.
3. **`.env` Güncelleyin**
.env dosyanızdaki yer tutucu değeri oluşturulan jetonla değiştirin:
```ini
APP_SECRET=birinci_rastgele_dize
ENCRYPTION_KEY=random_string
```
Kesinti olmadan anahtarı döndürmeye ilişkin talimatlar için [Anahtar döndürme kılavuzuna](/l/tr/developers/self-host/capabilities/key-rotation) bakın.
4. **Postgres Şifresini Ayarlayın**
.env dosyasındaki `PG_DATABASE_PASSWORD` değerini özel karakter içermeyen güçlü bir şifre ile güncelleyin.
@@ -0,0 +1,61 @@
---
title: Anahtar döndürme
icon: rotate
---
Twenty'nin iki bağımsız anahtar ailesi vardır:
* **JWT imzalama anahtarları** — asimetrik ES256 anahtar çiftleri (`kid` etiketli) `core."signingKey"` içinde saklanır ve erişim/yenileme belirteçlerini imzalamak ve doğrulamak için kullanılır.
* **Bekleme hâlindeki şifreleme anahtarı** — OAuth belirteçlerini, uygulama değişkenlerini, imzalama anahtarlarının gizli anahtarlarını, hassas yapılandırma değerlerini ve `enc:v2:` zarfı içindeki TOTP gizli anahtarlarını şifrelemek için kullanılan `ENCRYPTION_KEY`.
`APP_SECRET`, geriye dönük uyumluluk için tutulan eski bir gizlidir: `ENCRYPTION_KEY` ayarlanmamış olduğunda bekleme hâlindeki şifreleme / oturum çerezi geri dönüşü olarak davranır ve hâlâ önceden var olan HS256 erişim belirteçlerini doğrular. Kullanımdan kaldırılacaktır.
## JWT imzalama anahtarları
Her anahtar bir `publicKey` (önceden verilmiş belirteçleri doğrulayabilmesi için süresiz olarak saklanır), şifrelenmiş bir `privateKey` (yalnızca anahtar geçerli iken kullanılır), bir `isCurrent` bayrağı (aynı anda tam olarak bir satır) ve isteğe bağlı bir `revokedAt` taşır.
### Geçerli anahtarı döndür
* **El ile** — geçerli satırda **Settings → Admin Panel → Signing keys → Revoke**. İptal etmek, şifrelenmiş özel materyalini siler ve onu geçerli olmaktan çıkarır; bir sonraki imzalama çağrısı, yeni geçerli olarak otomatik olarak yeni bir ES256 anahtar çifti oluşturur. Herhangi başka bir (iptal edilmemiş) `kid` ile imzalanan belirteçler, süreleri dolana kadar doğrulanmaya devam eder.
* **Kurumsal (otomatik)** — günlük bir cron (`'15 3 * * *'` UTC), mevcut anahtar `SIGNING_KEY_ROTATION_DAYS` (varsayılan `90`) kadar süredir geçerli olduğunda yeni bir geçerli anahtar verir. Önceki anahtar *iptal edilmez*, bu nedenle onunla imzalanan belirteçler doğrulanmaya devam eder. `yarn command:prod cron:register:all` ile bir kez kaydedin.
<Note>Kurumsal cron ve `SIGNING_KEY_ROTATION_DAYS` v2.6+ ile birlikte gelir.</Note>
### Bir anahtarı iptal et (yalnızca sızıntı / acil durum için)
Geçerli olmayan bir satırda **Settings → Admin Panel → Signing keys → Revoke**. Şifrelenmiş özel materyali siler, `revokedAt` değerini ayarlar ve o `kid` ile imzalanmış mevcut tüm belirteçleri reddeder.
## `ENCRYPTION_KEY` anahtarını döndür
<Note>Aşağıda açıklanan `secret-encryption:rotate` komutu v2.6+ ile birlikte gelir.</Note>
Her şifrelenmiş değer `enc:v2:\<keyId>:\<payload>` olarak sarılır; burada `\<keyId>`, ham anahtardan türetilen 8 onaltılık önekidir. Döndürme çevrimiçi ve kaldığı yerden devam ettirilebilir.
1. **Yeni bir anahtar oluşturun**: `openssl rand -base64 32`.
2. `.env` içinde **her iki anahtarı yan yana yapılandırın**, ardından yeniden başlatın:
```ini
ENCRYPTION_KEY=NEW_VALUE
FALLBACK_ENCRYPTION_KEY=OLD_VALUE
```
Yeni yazmalar yeni anahtarı kullanır, mevcut satırlar hâlâ geri dönüş anahtarıyla şifresi çözülerek okunur.
3. **Mevcut satırları yeniden şifreleyin**:
```bash
docker exec -it {server_container} yarn command:prod secret-encryption:rotate
```
Komut altı siteyi dolaşır (`connected-account-tokens`, `application-variable`, `application-registration-variable`, `signing-key-private-keys`, `sensitive-config-storage`, `totp-secrets`). Bir SQL filtresi, zaten yeni `\<keyId>` üzerinde olan satırları atlar, bu nedenle komut idempotenttir: gerektiğinde yarıda kesip yeniden çalıştırın. Herhangi bir satır başarısız olursa sıfır olmayan kodla çıkar — yeniden denemek için tekrar çalıştırın.
| Bayrak | Açıklama |
| ---------------------------------------- | ------------------------------------------------------------------ |
| `-s, --site \<site>` | Yalnızca tek bir siteyle sınırla. |
| `-b, --batch-size \<n>` | Her yığın başına satır sayısı (varsayılan `200`, en fazla `5000`). |
| `-d, --dry-run` | Şifresini çöz + bellekte yeniden şifrele, `UPDATE` işlemini atla. |
4. `--dry-run` sıfır kalan satır gösterdiğinde **geri dönüş anahtarını bırakın**: `FALLBACK_ENCRYPTION_KEY` değerini kaldırın ve yeniden başlatın.
## Eski `APP_SECRET` desteği
Hiç `ENCRYPTION_KEY` ayarlamamış eski örnekler, `APP_SECRET` değerini bekleme hâlindeki şifreleme anahtarı (ve ondan türetilen oturum çerezi gizli anahtarı) olarak kullanır. Bu yol, geriye dönük uyumluluk için korunmuştur ancak **kullanımdan kaldırılmıştır** — özel bir `ENCRYPTION_KEY` ayarlayın ve ondan çıkmak için yukarıdaki döndürme prosedürünü izleyin. `APP_SECRET`in kendisi eski HS256 erişim belirteçlerini doğrulamak için kullanılmaya devam eder.
@@ -43,11 +43,26 @@ IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # varsayılan
<Warning>
Her bir değişken, yönetici panelinizde **Ayarlar → Yönetici Paneli → Konfigürasyon Değişkenleri** altında açıklamalarla belgelenir.
Veritabanı bağlantıları (`PG_DATABASE_URL`), sunucu URL'leri (`SERVER_URL`) ve uygulama gizli anahtarları (`APP_SECRET`) gibi bazı altyapı ayarları yalnızca `.env` dosyası aracılığıyla yapılandırılabilir.
Veritabanı bağlantıları (`PG_DATABASE_URL`), sunucu URL'leri (`SERVER_URL`) ve gizli anahtarlar (`ENCRYPTION_KEY`, `FALLBACK_ENCRYPTION_KEY`) gibi bazı altyapı ayarları yalnızca `.env` dosyası aracılığıyla yapılandırılabilir.
[Tam teknik referans →](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)
</Warning>
## Şifreleme anahtarları
Twenty yalnızca ortam değişkenleriyle yapılandırılan iki şifreleme anahtarı kullanır:
| Değişken | Amaç | Zorunlu |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `ENCRYPTION_KEY` | Dinlenik durumdaki gizli verileri şifrelemek için kullanılan birincil anahtar (OAuth belirteçleri, uygulama değişkenleri, imzalama anahtarlarının özel anahtarları, TOTP sırları, hassas yapılandırma değerleri). | Yeni kurulumlar için evet (eski kurulumlar bunun yerine aşağıda açıklandığı gibi `APP_SECRET`'e dayanıyor olabilir) |
| `FALLBACK_ENCRYPTION_KEY` | Yalnızca doğrulama için kullanılan anahtar. Döndürme sırasında, mevcut satırların şifresinin çözülebilir kalması için *önceki* `ENCRYPTION_KEY` değerine ayarlanır. | Yalnızca döndürme sırasında |
Geriye dönük uyumluluk için, `ENCRYPTION_KEY` ayarlanmadıysa, Twenty dinlenik durumdaki şifreleme için `APP_SECRET`'e geri döner — bu, daha eski dağıtımlardaki eski davranışla eşleşir. Yeni kurulumlarda her zaman özel bir `ENCRYPTION_KEY` ayarlanmalıdır.
Değerleri `openssl rand -base64 32` ile oluşturun ve onları güvenli bir yerde saklayın (bir gizli yönetim sistemi, mühürlü yapılandırma vb.). `ENCRYPTION_KEY`'in kaybedilmesi, veritabanında saklanan tüm gizli bilgilere erişimin kaybedilmesi anlamına gelir.
`ENCRYPTION_KEY`'i kesinti olmadan döndürmek için [Anahtar döndürme rehberi](/l/tr/developers/self-host/capabilities/key-rotation) bölümüne bakın.
## 2. Yalnızca Ortam Değişkenleriyle Yapılandırma
```bash
@@ -31,6 +31,18 @@ Twenty, **v1.22** sürümünden itibaren sürümler arası yükseltmeleri destek
Örneğin, v1.22'den doğrudan v2.0'a yükseltme tamamen desteklenir.
## v2.5+ sürümüne yükseltme — bekleme hâlindeki şifreleme zarfı
**v2.5** ile birlikte, Twenty beklemede olan gizli verileri (OAuth belirteçleri, uygulama değişkenleri, imzalama için kullanılan özel anahtarlar, hassas yapılandırma değerleri, TOTP sırları) sürümlendirilmiş ve `ENCRYPTION_KEY` (veya `ENCRYPTION_KEY` ayarlı değilse `APP_SECRET`) ile şifrelenmiş bir `enc:v2:` zarfının içinde saklar.
v2.5 sürümündeki ilk açılış, mevcut satırları yeni zarfa **geri dolduran** yavaş yükseltme komutlarını çalıştırır. Bunlar idempotandır — sunucuyu durdurup yeniden başlattığınızda kaldığı yerden devam eder — ancak büyük veritabanlarında biraz zaman alabilirler. İlerlemeyi `upgrade:status` ile izleyebilirsiniz.
Geri doldurmanın, en başından itibaren satırları onun altında yazabilmesi için, v2.5 yükseltmesinden **önce** özel bir `ENCRYPTION_KEY` ayarlamalısınız. Geri doldurmadan sonra anahtarları değiştirmek bir [anahtar döndürme](/l/tr/developers/self-host/capabilities/key-rotation) gerektirir.
## Gizli verilerin ve imzalama anahtarlarının döndürülmesi
`ENCRYPTION_KEY` döndürme, JWT imzalama anahtarını döndürme veya sızdırılmış bir imzalama anahtarını iptal etme gibi günlük operasyonel görevler için özel [Anahtar döndürme kılavuzu](/l/tr/developers/self-host/capabilities/key-rotation) bölümüne bakın.
## Yükseltme durumunu kontrol etme
`upgrade:status` komutu, örneğinizin ve çalışma alanı migrasyonlarının mevcut durumunu incelemenizi sağlar. Yükseltme sorunlarında hata ayıklamak veya bir destek talebi oluştururken kullanışlıdır.
@@ -40,7 +40,8 @@ export default defineRole({
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
fieldUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name
.universalIdentifier,
canReadFieldValue: false,
canUpdateFieldValue: false,
},
@@ -51,7 +51,7 @@ VERSION=vx.y.z BRANCH=branch-name bash <(curl -sL https://raw.githubusercontent.
curl -o .env https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/.env.example
```
2. **生成密钥令牌**
2. **生成加密密钥**
运行以下命令以生成唯一的随机字符串:
@@ -59,16 +59,18 @@ VERSION=vx.y.z BRANCH=branch-name bash <(curl -sL https://raw.githubusercontent.
openssl rand -base64 32
```
**重要:** 保持该值秘密/不要共享。
**重要:** 保持该值秘密/不要共享。 丢失 `ENCRYPTION_KEY` 意味着将失去对数据库中存储的所有机密信息(OAuth 令牌、应用程序变量、TOTP 机密等)的访问权限。
3. **更新`.env`**
用生成的令牌替换.env文件中的占位符值:
```ini
APP_SECRET=first_random_string
ENCRYPTION_KEY=random_string
```
有关在不停机的情况下轮换密钥的说明,请参阅[密钥轮换指南](/l/zh/developers/self-host/capabilities/key-rotation)。
4. **设置Postgres密码**
用不含特殊字符的强密码更新.env文件中的`PG_DATABASE_PASSWORD`值。
@@ -0,0 +1,61 @@
---
title: 密钥轮换
icon: rotate
---
Twenty 具有两个相互独立的密钥族:
* **JWT 签名密钥** — 存储在 `core."signingKey"` 中、带有 `kid` 标签的非对称 ES256 密钥对,用于对访问令牌 / 刷新令牌进行签名和验证。
* **静态加密密钥** — `ENCRYPTION_KEY`,用于在 `enc:v2:` 封装中加密 OAuth 令牌、应用变量、签名密钥私钥、敏感配置值和 TOTP 密钥。
`APP_SECRET` 是为向后兼容而保留的旧版密钥:当未设置 `ENCRYPTION_KEY` 时,它充当静态加密 / 会话 Cookie 的后备密钥,并且仍会验证已有的 HS256 访问令牌。 它将被弃用。
## JWT 签名密钥
每个密钥都包含一个 `publicKey`(无限期保留,以便验证之前签发的令牌)、一个加密的 `privateKey`(仅在该密钥为当前密钥时使用)、一个 `isCurrent` 标志(同一时间只有一行为当前),以及一个可选的 `revokedAt`。
### 轮换当前密钥
* **手动** — 在当前行上执行 **Settings → Admin Panel → Signing keys → Revoke**。 吊销操作会清除其加密的私有材料并将其降级;下一次签名调用会自动生成一个新的 ES256 密钥对,作为新的当前密钥。 在任何其他(未被吊销的)`kid` 下签名的令牌会一直验证,直到过期。
* **Enterprise(自动)** — 每日 cron`'15 3 * * *'` UTC)会在现有密钥已作为当前密钥使用 `SIGNING_KEY_ROTATION_DAYS`(默认 `90`)后签发一个新的当前密钥。 先前的密钥*不会*被吊销,因此在其下签名的令牌仍然可以通过验证。 使用 `yarn command:prod cron:register:all` 注册一次。
<Note>Enterprise 的 cron 和 `SIGNING_KEY_ROTATION_DAYS` 从 v2.6+ 版本开始提供。</Note>
### 吊销密钥(仅限泄露 / 紧急情况)
在非当前行上执行 **Settings → Admin Panel → Signing keys → Revoke**。 会清除加密的私有材料,设置 `revokedAt`,并拒绝在该 `kid` 下签名的所有现有令牌。
## 轮换 `ENCRYPTION_KEY`
<Note>下面描述的 `secret-encryption:rotate` 命令从 v2.6+ 版本开始提供。</Note>
每个加密值都封装为 `enc:v2:\<keyId>:\<payload>`,其中 `\<keyId>` 是从原始密钥派生出的 8 位十六进制前缀。 轮换是在线且可恢复的。
1. **生成新密钥**`openssl rand -base64 32`。
2. **在 `.env` 中并排配置两个密钥**,然后重启:
```ini
ENCRYPTION_KEY=NEW_VALUE
FALLBACK_ENCRYPTION_KEY=OLD_VALUE
```
新的写入使用新密钥,现有行仍通过后备密钥解密。
3. **重新加密现有行**
```bash
docker exec -it {server_container} yarn command:prod secret-encryption:rotate
```
该命令会遍历六个位置(`connected-account-tokens`、`application-variable`、`application-registration-variable`、`signing-key-private-keys`、`sensitive-config-storage`、`totp-secrets`)。 SQL 过滤器会跳过已使用新 `\<keyId>` 的行,因此该命令是幂等的:可根据需要中断并重新运行。 如果任意一行失败则以非零状态退出 — 重新运行以重试。
| 标志 | 描述 |
| ---------------------------------------- | ---------------------------- |
| `-s, --site \<site>` | 仅限单个位置。 |
| `-b, --batch-size \<n>` | 每批处理的行数(默认 `200`,最大 `5000`)。 |
| `-d, --dry-run` | 在内存中解密并重新加密,跳过 `UPDATE`。 |
4. 一旦 `--dry-run` 显示没有剩余行,就**删除后备密钥**:移除 `FALLBACK_ENCRYPTION_KEY` 并重启。
## 旧版 `APP_SECRET` 支持
从未设置 `ENCRYPTION_KEY` 的旧实例会使用 `APP_SECRET` 作为静态加密密钥(以及会话 Cookie 密钥,从中派生)。 此路径为向后兼容而保留,但已被**弃用** — 请设置专用的 `ENCRYPTION_KEY`,并按照上述轮换流程迁移离开它。 `APP_SECRET` 本身仍用于验证旧版 HS256 访问令牌。
@@ -43,11 +43,26 @@ IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # default
<Warning>
每个变量都在您的管理面板的**设置 → 管理员面板 → 配置变量**中记录了说明。
某些基础设施设置如数据库连接 (`PG_DATABASE_URL`)、服务器 URL (`SERVER_URL`) 和应用程序密钥 (`APP_SECRET`) 只能通过 `.env` 文件配置。
某些基础设施设置如数据库连接 (`PG_DATABASE_URL`)、服务器 URL (`SERVER_URL`) 和密钥(`ENCRYPTION_KEY`、`FALLBACK_ENCRYPTION_KEY`只能通过 `.env` 文件配置。
[完整技术参考→](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)
</Warning>
## 加密密钥
Twenty 使用两个仅通过环境变量配置的加密密钥:
| 变量 | 目的 | 必填 |
| ------------------------- | ------------------------------------------------------------ | ------------------------------------- |
| `ENCRYPTION_KEY` | 用于对静态存储的机密信息(OAuth 令牌、应用程序变量、签名密钥的私钥、TOTP 密钥、敏感配置值)进行加密的主密钥。 | 新安装必须设置(旧安装可能依赖于 `APP_SECRET` —— 见下文) |
| `FALLBACK_ENCRYPTION_KEY` | 仅用于验证的密钥。 在轮换期间,将其设置为*先前的* `ENCRYPTION_KEY`,以便现有行仍可解密。 | 仅在轮换期间 |
为保持向后兼容性,如果未设置 `ENCRYPTION_KEY`Twenty 将回退为使用 `APP_SECRET` 进行静态数据加密——与旧部署的旧版行为一致。 新的安装应始终设置专用的 `ENCRYPTION_KEY`。
使用 `openssl rand -base64 32` 生成值,并将其安全存储(如密钥管理器、加密配置等)。 丢失 `ENCRYPTION_KEY` 意味着将失去对数据库中存储的所有机密的访问权限。
要在不停机的情况下轮换 `ENCRYPTION_KEY`,请参阅[密钥轮换指南](/l/zh/developers/self-host/capabilities/key-rotation)。
## 2. 仅限环境配置
```bash
@@ -31,6 +31,18 @@ cat databases_backup.sql | docker exec -i {db_container_name_or_id} psql -U {pos
例如,从 v1.22 直接升级到 v2.0 完全受支持。
## 升级到 v2.5+ — 静态数据加密信封
从 **v2.5** 开始,Twenty 会将静态存储的机密(OAuth 令牌、应用程序变量、签名密钥的私钥、敏感配置值、TOTP 机密)存储在带版本的 `enc:v2:` 加密信封中,并使用 `ENCRYPTION_KEY`(如果未设置 `ENCRYPTION_KEY`,则使用 `APP_SECRET`)进行加密。
在 v2.5 的首次启动时会运行较慢的升级命令,将现有行**回填**到新的加密信封中。 这些命令是幂等的——中断并重新启动服务器后会从上次停止的位置恢复——但在大型数据库上可能需要一段时间才能完成。 你可以使用 `upgrade:status` 来监控进度。
你应该在 v2.5 升级**之前**设置专用的 `ENCRYPTION_KEY`,这样回填从一开始就会在该密钥下写入行。 在回填完成后再更换密钥,则需要进行一次[轮换](/l/zh/developers/self-host/capabilities/key-rotation)。
## 轮换机密和签名密钥
对于日常运维任务,例如轮换 `ENCRYPTION_KEY`、轮换 JWT 签名密钥或吊销泄露的签名密钥,请参阅专门的[密钥轮换指南](/l/zh/developers/self-host/capabilities/key-rotation)。
## 检查升级状态
`upgrade:status` 命令可用于查看您的实例和工作区迁移的当前状态。 这在调试升级问题或提交支持请求时非常有用。
@@ -1792,6 +1792,11 @@ export type File = {
size: Scalars['Float'];
};
export type FileAttachmentInput = {
filename: Scalars['String'];
id: Scalars['UUID'];
};
export enum FileFolder {
AgentChat = 'AgentChat',
AppTarball = 'AppTarball',
@@ -3197,7 +3202,7 @@ export type MutationSaveImapSmtpCaldavAccountArgs = {
export type MutationSendChatMessageArgs = {
browsingContext?: InputMaybe<Scalars['JSON']>;
fileIds?: InputMaybe<Array<Scalars['UUID']>>;
fileAttachments?: InputMaybe<Array<FileAttachmentInput>>;
messageId: Scalars['UUID'];
modelId?: InputMaybe<Scalars['String']>;
text: Scalars['String'];
@@ -6148,7 +6153,7 @@ export type SendChatMessageMutationVariables = Exact<{
messageId: Scalars['UUID'];
browsingContext?: InputMaybe<Scalars['JSON']>;
modelId?: InputMaybe<Scalars['String']>;
fileIds?: InputMaybe<Array<Scalars['UUID']> | Scalars['UUID']>;
fileAttachments?: InputMaybe<Array<FileAttachmentInput> | FileAttachmentInput>;
}>;
@@ -7983,7 +7988,7 @@ export const EvaluateAgentTurnDocument = {"kind":"Document","definitions":[{"kin
export const RemoveRoleFromAgentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveRoleFromAgent"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"agentId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeRoleFromAgent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"agentId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"agentId"}}}]}]}}]} as unknown as DocumentNode<RemoveRoleFromAgentMutation, RemoveRoleFromAgentMutationVariables>;
export const RenameChatThreadDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RenameChatThread"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"title"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"renameChatThread"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"Argument","name":{"kind":"Name","value":"title"},"value":{"kind":"Variable","name":{"kind":"Name","value":"title"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<RenameChatThreadMutation, RenameChatThreadMutationVariables>;
export const RunEvaluationInputDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RunEvaluationInput"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"agentId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"runEvaluationInput"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"agentId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"agentId"}}},{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"threadId"}},{"kind":"Field","name":{"kind":"Name","value":"agentId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"evaluations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"score"}},{"kind":"Field","name":{"kind":"Name","value":"comment"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]}}]} as unknown as DocumentNode<RunEvaluationInputMutation, RunEvaluationInputMutationVariables>;
export const SendChatMessageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SendChatMessage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"text"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"messageId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"browsingContext"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fileIds"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sendChatMessage"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"threadId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}}},{"kind":"Argument","name":{"kind":"Name","value":"text"},"value":{"kind":"Variable","name":{"kind":"Name","value":"text"}}},{"kind":"Argument","name":{"kind":"Name","value":"messageId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"messageId"}}},{"kind":"Argument","name":{"kind":"Name","value":"browsingContext"},"value":{"kind":"Variable","name":{"kind":"Name","value":"browsingContext"}}},{"kind":"Argument","name":{"kind":"Name","value":"modelId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}}},{"kind":"Argument","name":{"kind":"Name","value":"fileIds"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fileIds"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"messageId"}},{"kind":"Field","name":{"kind":"Name","value":"queued"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}}]}}]}}]} as unknown as DocumentNode<SendChatMessageMutation, SendChatMessageMutationVariables>;
export const SendChatMessageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SendChatMessage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"text"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"messageId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"browsingContext"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fileAttachments"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"FileAttachmentInput"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sendChatMessage"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"threadId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}}},{"kind":"Argument","name":{"kind":"Name","value":"text"},"value":{"kind":"Variable","name":{"kind":"Name","value":"text"}}},{"kind":"Argument","name":{"kind":"Name","value":"messageId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"messageId"}}},{"kind":"Argument","name":{"kind":"Name","value":"browsingContext"},"value":{"kind":"Variable","name":{"kind":"Name","value":"browsingContext"}}},{"kind":"Argument","name":{"kind":"Name","value":"modelId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}}},{"kind":"Argument","name":{"kind":"Name","value":"fileAttachments"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fileAttachments"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"messageId"}},{"kind":"Field","name":{"kind":"Name","value":"queued"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}}]}}]}}]} as unknown as DocumentNode<SendChatMessageMutation, SendChatMessageMutationVariables>;
export const StopAgentChatStreamDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"StopAgentChatStream"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stopAgentChatStream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"threadId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}}}]}]}}]} as unknown as DocumentNode<StopAgentChatStreamMutation, StopAgentChatStreamMutationVariables>;
export const UnarchiveChatThreadDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UnarchiveChatThread"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unarchiveChatThread"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"deletedAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<UnarchiveChatThreadMutation, UnarchiveChatThreadMutationVariables>;
export const UpdateOneAgentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateOneAgent"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateAgentInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateOneAgent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AgentFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AgentFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Agent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"prompt"}},{"kind":"Field","name":{"kind":"Name","value":"modelId"}},{"kind":"Field","name":{"kind":"Name","value":"responseFormat"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"modelConfiguration"}},{"kind":"Field","name":{"kind":"Name","value":"evaluationInputs"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<UpdateOneAgentMutation, UpdateOneAgentMutationVariables>;
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Terug na {linkText}"
msgid "Back to content"
msgstr "Terug na inhoud"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Terug na Instellings"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Maak toe"
msgid "Close banner"
msgstr "Sluit banier"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "العودة إلى {linkText}"
msgid "Back to content"
msgstr "العودة إلى المحتوى"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "العودة إلى الإعدادات"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "إغلاق"
msgid "Close banner"
msgstr "إغلاق اللافتة"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Torna a {linkText}"
msgid "Back to content"
msgstr "Tornar al contingut"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Torna a Configuració"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Tanca"
msgid "Close banner"
msgstr "Tanca el bàner"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Zpět na {linkText}"
msgid "Back to content"
msgstr "Zpět k obsahu"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Zpět do nastavení"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Zavřít"
msgid "Close banner"
msgstr "Zavřít banner"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Tilbage til {linkText}"
msgid "Back to content"
msgstr "Tilbage til indhold"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Tilbage til Indstillinger"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Luk"
msgid "Close banner"
msgstr "Luk banner"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Zurück zu {linkText}"
msgid "Back to content"
msgstr "Zurück zum Inhalt"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Zurück zu den Einstellungen"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Schließen"
msgid "Close banner"
msgstr "Banner schließen"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Πίσω στο {linkText}"
msgid "Back to content"
msgstr "Επιστροφή στο περιεχόμενο"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Πίσω στις Ρυθμίσεις"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Κλείσιμο"
msgid "Close banner"
msgstr "Κλείσιμο banner"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2647,11 +2647,6 @@ msgstr "Back to {linkText}"
msgid "Back to content"
msgstr "Back to content"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Back to Settings"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3462,6 +3457,11 @@ msgstr "Close"
msgid "Close banner"
msgstr "Close banner"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr "Close menu"
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Volver a {linkText}"
msgid "Back to content"
msgstr "Volver al contenido"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Volver a Configuración"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Cerrar"
msgid "Close banner"
msgstr "Cerrar banner"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Takaisin kohteeseen {linkText}"
msgid "Back to content"
msgstr "Takaisin sisältöön"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Takaisin asetuksiin"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Sulje"
msgid "Close banner"
msgstr "Sulje banneri"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Retour à {linkText}"
msgid "Back to content"
msgstr "Retour au contenu"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Retour aux paramètres"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Fermer"
msgid "Close banner"
msgstr "Fermer la bannière"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "חזרה אל {linkText}"
msgid "Back to content"
msgstr "חזרה לתוכן"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "חזרה להגדרות"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "סגור"
msgid "Close banner"
msgstr "סגור באנר"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Vissza ide: {linkText}"
msgid "Back to content"
msgstr "Vissza a tartalomhoz"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Vissza a beállításokhoz"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Bezár"
msgid "Close banner"
msgstr "Banner bezárása"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Torna a {linkText}"
msgid "Back to content"
msgstr "Torna al contenuto"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Torna alle Impostazioni"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Chiudi"
msgid "Close banner"
msgstr "Chiudi banner"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "{linkText} に戻る"
msgid "Back to content"
msgstr "コンテンツに戻る"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "設定に戻る"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "閉じる"
msgid "Close banner"
msgstr "バナーを閉じる"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "{linkText}(으)로 돌아가기"
msgid "Back to content"
msgstr "콘텐츠로 돌아가기"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "설정으로 돌아가기"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "닫기"
msgid "Close banner"
msgstr "배너 닫기"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Terug naar {linkText}"
msgid "Back to content"
msgstr "Terug naar inhoud"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Terug naar Instellingen"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Sluiten"
msgid "Close banner"
msgstr "Banner sluiten"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Tilbake til {linkText}"
msgid "Back to content"
msgstr "Tilbake til innhold"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Tilbake til Innstillinger"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Lukk"
msgid "Close banner"
msgstr "Lukk banner"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Powrót do {linkText}"
msgid "Back to content"
msgstr "Powrót do treści"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Powrót do ustawień"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Zamknij"
msgid "Close banner"
msgstr "Zamknij baner"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
@@ -2647,11 +2647,6 @@ msgstr ""
msgid "Back to content"
msgstr ""
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr ""
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3462,6 +3457,11 @@ msgstr ""
msgid "Close banner"
msgstr ""
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Voltar para {linkText}"
msgid "Back to content"
msgstr "Voltar ao conteúdo"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Voltar para Configurações"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Fechar"
msgid "Close banner"
msgstr "Fechar banner"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Voltar a {linkText}"
msgid "Back to content"
msgstr "Voltar ao conteúdo"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Voltar às Definições"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Fechar"
msgid "Close banner"
msgstr "Fechar banner"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Înapoi la {linkText}"
msgid "Back to content"
msgstr "Înapoi la conținut"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Înapoi la Setări"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Închide"
msgid "Close banner"
msgstr "Închide bannerul"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
Binary file not shown.
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Назад на {linkText}"
msgid "Back to content"
msgstr "Назад на садржај"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Назад на Подешавања"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Затвори"
msgid "Close banner"
msgstr "Затвори банер"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Tillbaka till {linkText}"
msgid "Back to content"
msgstr "Tillbaka till innehåll"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Tillbaka till inställningar"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Stäng"
msgid "Close banner"
msgstr "Stäng banner"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "{linkText}'e geri dön"
msgid "Back to content"
msgstr "İçeriğe geri dön"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Ayarlar'a geri dön"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Kapat"
msgid "Close banner"
msgstr "Afişi kapat"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
+5 -5
View File
@@ -2652,11 +2652,6 @@ msgstr "Назад до {linkText}"
msgid "Back to content"
msgstr "Повернутися до вмісту"
#. js-lingui-id: OvBnOM
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
msgid "Back to Settings"
msgstr "Назад до налаштувань"
#. js-lingui-id: GtJbUa
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
msgid "Background"
@@ -3467,6 +3462,11 @@ msgstr "Закрити"
msgid "Close banner"
msgstr "Закрити банер"
#. js-lingui-id: vcpc5o
#: src/modules/blocknote-editor/components/BlockEditor.tsx
msgid "Close menu"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"

Some files were not shown because too many files have changed in this diff Show More