Compare commits

...
5 changed files with 94 additions and 20 deletions

No files matched your search

+5 -5
View File
@@ -215,7 +215,7 @@ x-fluxer-app-healthcheck: &fluxer-app-healthcheck
x-fluxer-svc-healthcheck: &fluxer-svc-healthcheck
<<: *fluxer-app-healthcheck
start_period: ${FLUXER_SVC_HEALTHCHECK_START_PERIOD:-60s}
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/8090 && printf 'GET /_health HTTP/1.0\\r\\n\\r\\n' >&3 && head -n 1 <&3 | grep -q ' 200 '"]
test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/127.0.0.1/8090 && printf \"GET /_health HTTP/1.0\\r\\n\\r\\n\" >&3 && head -n 1 <&3 | grep -q \" 200 \"'"]
services:
edge:
@@ -469,7 +469,7 @@ services:
<<: *fluxer-env
FLUXER_API_PORT: "8080"
FLUXER_SETUP_TOKEN: ${FLUXER_SETUP_TOKEN:-}
NODE_OPTIONS: --enable-source-maps${FLUXER_API_NODE_HEAP_MB:+ --max-old-space-size=$FLUXER_API_NODE_HEAP_MB}${FLUXER_API_NODE_OPTIONS:+ $FLUXER_API_NODE_OPTIONS}
NODE_OPTIONS: --enable-source-maps${FLUXER_API_NODE_HEAP_MB:+ --max-old-space-size=${FLUXER_API_NODE_HEAP_MB:-}}${FLUXER_API_NODE_OPTIONS:+ ${FLUXER_API_NODE_OPTIONS:-}}
NODE_EXTRA_CA_CERTS: ${FLUXER_NODE_EXTRA_CA_CERTS:-/etc/ssl/certs/ca-certificates.crt}
FLUXER_POSTGRES_MAX_CONNECTIONS: "${FLUXER_API_POSTGRES_MAX_CONNECTIONS:-25}"
FLUXER_API_PRESIGNED_ATTACHMENT_UPLOADS_ENABLED: "${FLUXER_API_PRESIGNED_ATTACHMENT_UPLOADS_ENABLED:-true}"
@@ -503,13 +503,13 @@ services:
command: ["node", "dist/WorkerEntrypoint.js"]
environment:
<<: *fluxer-env
NODE_OPTIONS: --enable-source-maps${FLUXER_WORKER_NODE_HEAP_MB:+ --max-old-space-size=$FLUXER_WORKER_NODE_HEAP_MB}${FLUXER_WORKER_NODE_OPTIONS:+ $FLUXER_WORKER_NODE_OPTIONS}
NODE_OPTIONS: --enable-source-maps${FLUXER_WORKER_NODE_HEAP_MB:+ --max-old-space-size=${FLUXER_WORKER_NODE_HEAP_MB:-}}${FLUXER_WORKER_NODE_OPTIONS:+ ${FLUXER_WORKER_NODE_OPTIONS:-}}
NODE_EXTRA_CA_CERTS: ${FLUXER_NODE_EXTRA_CA_CERTS:-/etc/ssl/certs/ca-certificates.crt}
FLUXER_API_WORKER_MODE: all_lanes
FLUXER_API_WORKER_ENABLE_CRON_SCHEDULER: "true"
FLUXER_POSTGRES_MAX_CONNECTIONS: "${FLUXER_WORKER_POSTGRES_MAX_CONNECTIONS:-25}"
healthcheck:
test: ["CMD", "node", "-e", "const age=Date.now()-require('node:fs').statSync('/tmp/fluxer-worker-heartbeat').mtimeMs;if(age>30000){console.error('worker heartbeat is '+Math.round(age)+'ms old');process.exit(1)}"]
test: ["CMD-SHELL", "node -e \"const age=Date.now()-require('node:fs').statSync('/tmp/fluxer-worker-heartbeat').mtimeMs;if(age>30000){console.error('worker heartbeat is '+Math.round(age)+'ms old');process.exit(1)}\""]
<<: *fluxer-app-healthcheck
retries: ${FLUXER_WORKER_HEALTHCHECK_RETRIES:-3}
depends_on:
@@ -867,7 +867,7 @@ services:
FLUXER_API_ENDPOINT: http://api:8080
FLUXER_STATIC_CDN_ENDPOINT: ${FLUXER_STATIC_CDN_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}}
healthcheck:
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/8080 && printf 'GET /_health HTTP/1.0\\r\\n\\r\\n' >&3 && head -n 1 <&3 | grep -q ' 200 '"]
test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/127.0.0.1/8080 && printf \"GET /_health HTTP/1.0\\r\\n\\r\\n\" >&3 && head -n 1 <&3 | grep -q \" 200 \"'"]
<<: *fluxer-app-healthcheck
start_period: ${FLUXER_SVC_HEALTHCHECK_START_PERIOD:-60s}
depends_on:
+1 -1
View File
@@ -297,7 +297,7 @@ const ACCEPTED_TABLE_FINDINGS = new Map<string, Readonly<Partial<Record<TableRul
['http-api/guild-members.mdx', {'table-identifier': 2}],
['http-api/guild-moderation.mdx', {'table-cell': 1}],
['http-api/guild-stickers.mdx', {'table-cell': 3}],
['http-api/guilds.mdx', {'table-fit': 1, 'table-identifier': 4}],
['http-api/guilds.mdx', {'table-fit': 1, 'table-identifier': 5}],
['http-api/instance.mdx', {'table-identifier': 8}],
['http-api/invites.mdx', {'table-cell': 6}],
['http-api/messages.mdx', {'table-fit': 2, 'table-cell': 20, 'table-identifier': 2}],
@@ -246,26 +246,74 @@ A resolved everyone mask that has `SEND_MESSAGES` and no [thread permission bit]
| id<sup>1</sup> | decimal string \| integer | Template-local channel ID |
| type<sup>2</sup> | integer | Source channel type |
| name? | ?string | Channel name, defaulting to the empty string |
| topic? | ?string | Channel topic |
| topic?<sup>6</sup> | ?string | Channel topic, or forum guidelines on a forum or media channel |
| position | integer | The sort position of the channel |
| parent_id?<sup>3</sup> | ?decimal string \| integer | Template-local parent category ID |
| bitrate?<sup>4</sup> | ?integer | Voice bitrate, defaulting to 64000 for a voice channel and capped at 96000 |
| user_limit?<sup>4</sup> | ?integer | Voice occupancy limit, defaulting to 0 for a voice channel |
| voice_connection_limit?<sup>4</sup> | ?integer | Per-user voice connection limit, defaulting to 5 for a voice channel |
| nsfw? | boolean | Whether the channel is age restricted (default false) |
| rate_limit_per_user? | integer | Slowmode interval in seconds, defaulting to 0 |
| permission_overwrites?<sup>5</sup> | array[[guild template role permission overwrite](#guild-template-role-permission-overwrite-object) object] | Role permission overwrites |
| nsfw? | ?boolean | Whether the channel is age restricted (default false) |
| rate_limit_per_user? | ?integer | Slowmode interval in seconds, defaulting to 0 |
| permission_overwrites?<sup>5</sup> | ?array[[guild template role permission overwrite](#guild-template-role-permission-overwrite-object) object] | Role permission overwrites |
| available_tags?<sup>7</sup> | ?array[[guild template forum tag](#guild-template-forum-tag-object) object] | Tags for a forum or media channel |
| default_reaction_emoji?<sup>8</sup> | ?[guild template default reaction](#guild-template-default-reaction-object) object | Default reaction shown on posts in a forum or media channel |
| default_sort_order?<sup>9</sup> | ?integer | Default [forum sort order](/http-api/forums/#forum-sort-orders) of a forum or media channel |
| default_forum_layout?<sup>9</sup> | ?integer | Default [forum layout](/http-api/forums/#forum-layouts) of a forum channel |
| default_tag_setting?<sup>9</sup> | ?string | Default [forum tag setting](/http-api/forums/#forum-tag-settings) of a forum or media channel |
| default_thread_rate_limit_per_user?<sup>10</sup> | ?integer | Slowmode interval in seconds copied onto new threads |
| default_auto_archive_duration?<sup>9</sup> | ?integer | Default [auto archive duration](/http-api/threads/#auto-archive-durations) in minutes for new threads |
| flags?<sup>11</sup> | ?integer | [Channel flags](/http-api/forums/#channel-flags) of a forum or media channel |
<sup>1</sup> A duplicate identifier in the same template rejects creation with 400 `GUILD_TEMPLATE_INVALID`
<sup>2</sup> The value 0 creates a text channel, 2 a voice channel, 4 a category, and 5 an announcement channel. The value 13, the stage channel type of another platform, creates a voice channel. Fluxer skips every other value, a forum channel (15) and a media channel (16) included, and creates no channel for it
<sup>2</sup> The value 0 creates a text channel, 2 a voice channel, 4 a category, 5 an announcement channel, 15 a forum channel, and 16 a media channel. The value 13, the stage channel type of another platform, creates a voice channel. Fluxer skips every other value and creates no channel for it
<sup>3</sup> The identifier is applied only when it resolves to a category in the same template, and every other value leaves the channel at the guild root
<sup>3</sup> The identifier is applied only when it resolves to a category in the same template and the channel is not itself a category, and every other value leaves the channel at the guild root
<sup>4</sup> These voice fields are stored only on a voice channel and are null on every other channel type
<sup>5</sup> An entry is applied only when its resolved `type` is 0 and its `id` resolves to a role in the same template, and the identifier `0` resolves to the everyone role. Fluxer skips every other entry, and a channel whose entries are all skipped is created with no overwrites
<sup>6</sup> A topic longer than 4096 characters on a forum or media channel, or 1024 characters on any other channel, is shortened to that limit
<sup>7</sup> Applied only to a forum or media channel. Each tag gets a new ID, its name is trimmed and shortened to 50 characters, a tag with an empty or repeated name is skipped, and Fluxer keeps at most 20 tags
<sup>8</sup> Applied only to a forum or media channel, and only when `emoji_name` is a single unicode emoji
<sup>9</sup> An unknown value is ignored and the channel keeps the default. `default_sort_order` and `default_tag_setting` apply only to a forum or media channel, `default_forum_layout` only to a forum channel, and `default_auto_archive_duration` to a text, announcement, forum or media channel
<sup>10</sup> Applied to a text, announcement, forum or media channel, capped at 21600, and a value of 0 or less is ignored
<sup>11</sup> Only the flags a forum or media channel can carry are kept, and `REQUIRE_TAG` is dropped when no imported tag can be applied by members without moderator permissions
### Guild template forum tag object
#### Structure
| Field | Type | Description |
| --- | --- | --- |
| name | string | Tag name |
| moderated? | ?boolean | Whether only moderators can apply the tag |
| emoji_id?<sup>1</sup> | ?string \| integer | Custom emoji of the tag |
| emoji_name?<sup>2</sup> | ?string | Unicode emoji of the tag |
<sup>1</sup> Ignored on import, because the custom emoji does not exist in the new guild
<sup>2</sup> Kept only when the value is a single unicode emoji
### Guild template default reaction object
#### Structure
| Field | Type | Description |
| --- | --- | --- |
| emoji_id?<sup>1</sup> | ?string \| integer | Custom emoji of the reaction |
| emoji_name?<sup>2</sup> | ?string | Unicode emoji of the reaction |
<sup>1</sup> Ignored on import, because the custom emoji does not exist in the new guild
<sup>2</sup> Kept only when the value is a single unicode emoji
### Guild template role permission overwrite object
#### Structure
+21 -4
View File
@@ -1454,6 +1454,17 @@ function Get-FluxerFreeKb([string]$Path) {
return [long]($drive.AvailableFreeSpace / 1024)
}
function Start-FluxerStopped([string[]]$Ids) {
if ($Ids.Count -eq 0) {
return 0
}
$result = Invoke-FluxerCapture (@('start') + $Ids)
if ($result.Code -ne 0 -and $result.Error.Length -gt 0) {
Write-FluxerProblem $result.Error
}
return $result.Code
}
# Step 2 continued: the volume copy.
#
# A live copy can catch a file mid-write, so the stack stops for it. The stack comes back up on
@@ -1461,9 +1472,10 @@ function Get-FluxerFreeKb([string]$Path) {
# working instance rather than a stopped one.
#
# By hand:
# docker compose ps -q > running
# docker compose stop
# docker run --rm -v fluxer_seaweedfs-data:/data -v "${PWD}\backups:/backup" alpine tar czf /backup/seaweedfs-data.tgz -C /data .
# docker compose up -d
# docker start (Get-Content running)
function Copy-FluxerVolumes([string]$Record, [string]$Project, [string]$TargetDir) {
if (-not (Test-FluxerStackDefinesService 'seaweedfs' $TargetDir)) {
Write-FluxerLine 'Skipping the uploads copy. This stack defines no seaweedfs service, so its objects are outside the stack and only the operator of that store can copy them.'
@@ -1497,6 +1509,11 @@ function Copy-FluxerVolumes([string]$Record, [string]$Project, [string]$TargetDi
$tarExtension = 'tar'
}
Write-FluxerLine 'Stopping the stack for a consistent copy of the uploads.'
$running = Invoke-FluxerCapture @('compose', 'ps', '-q')
if ($running.Code -ne 0) {
Stop-Fluxer "docker compose ps failed, so the containers to start again after the copy cannot be read. Compose printed:`n$(Get-FluxerIndented $running.Error ' ')" $FluxerExitBackup
}
$stopped = @($running.Text.Split("`n") | ForEach-Object {$_.Trim()} | Where-Object {$_.Length -gt 0})
if ((Invoke-FluxerDocker @('compose', 'stop')) -ne 0) {
Stop-Fluxer 'docker compose stop failed.' $FluxerExitBackup
}
@@ -1505,13 +1522,13 @@ function Copy-FluxerVolumes([string]$Record, [string]$Project, [string]$TargetDi
Write-FluxerLine "Copying $full."
$code = Invoke-FluxerDocker @('run', '--rm', '-v', "${full}:/data:ro", '-v', "${Record}:/backup", $FluxerHelperImage, 'tar', $tarFlags, "/backup/$volume.$tarExtension", '-C', '/data', '.')
if ($code -ne 0) {
[void](Invoke-FluxerDocker @('compose', 'up', '-d', '--remove-orphans'))
[void](Start-FluxerStopped $stopped)
Stop-Fluxer "Copying $full failed. The stack is started again on the images it was running." $FluxerExitBackup
}
}
Write-FluxerLine 'Starting the stack again before the upgrade continues.'
if ((Invoke-FluxerDocker @('compose', 'up', '-d', '--remove-orphans')) -ne 0) {
Stop-Fluxer 'docker compose up -d failed after the copy. Read docker compose logs.' $FluxerExitBackup
if ((Start-FluxerStopped $stopped) -ne 0) {
Stop-Fluxer 'docker start failed after the copy. Read docker compose logs.' $FluxerExitBackup
}
}
+13 -4
View File
@@ -1686,6 +1686,11 @@ fluxer_free_kb() {
df -Pk "$1" 2>/dev/null | awk 'NR==2 {print $4}'
}
fluxer_start_stopped() {
[ -s "$fluxer_scratch/stopped" ] || return 0
xargs $fluxer_engine start < "$fluxer_scratch/stopped" > /dev/null
}
# Step 2 continued: the volume copy.
#
# A live copy can catch a file mid-write, so the stack stops for it. The stack
@@ -1693,9 +1698,10 @@ fluxer_free_kb() {
# happens, so a failure here leaves a working instance rather than a stopped one.
#
# By hand:
# docker compose ps -q > running
# docker compose stop
# docker run --rm -v fluxer_seaweedfs-data:/data -v "$PWD/backups:/backup" alpine tar czf /backup/seaweedfs-data.tgz -C /data .
# docker compose up -d
# xargs docker start < running
fluxer_copy_volumes() {
if ! fluxer_stack_defines_service seaweedfs; then
fluxer_say 'Skipping the uploads copy. This stack defines no seaweedfs service, so its objects are outside the stack and only the operator of that store can copy them.'
@@ -1738,6 +1744,9 @@ $(fluxer_volume_error ' ')" ;;
fluxer_tar_ext='tgz'
fi
fluxer_say 'Stopping the stack for a consistent copy of the uploads.'
if ! $fluxer_engine compose ps -q > "$fluxer_scratch/stopped"; then
fluxer_fail 7 "$fluxer_engine compose ps failed in $opt_dir, so the containers to start again after the copy cannot be read."
fi
if ! $fluxer_engine compose stop; then
fluxer_fail 7 "$fluxer_engine compose stop failed in $opt_dir."
fi
@@ -1746,13 +1755,13 @@ $(fluxer_volume_error ' ')" ;;
fluxer_full="${fluxer_project}_${fluxer_volume}"
fluxer_say "Copying $fluxer_full."
if ! $fluxer_engine run --rm -v "$fluxer_full:/data:ro" -v "$fluxer_record:/backup" "$FLUXER_HELPER_IMAGE" tar "$fluxer_tar_flags" "/backup/$fluxer_volume.$fluxer_tar_ext" -C /data .; then
$fluxer_engine compose up -d --remove-orphans || true
fluxer_start_stopped || true
fluxer_fail 7 "Copying $fluxer_full failed. The stack is started again on the images it was running."
fi
done < "$fluxer_scratch/copy-volumes"
fluxer_say 'Starting the stack again before the upgrade continues.'
if ! $fluxer_engine compose up -d --remove-orphans; then
fluxer_fail 7 "$fluxer_engine compose up -d failed in $opt_dir after the copy. Read $fluxer_engine compose logs there."
if ! fluxer_start_stopped; then
fluxer_fail 7 "$fluxer_engine start failed in $opt_dir after the copy. Read $fluxer_engine compose logs there."
fi
}