mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-19 05:55:34 +09:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c51800a8d6 | ||
|
|
f9e52da7b8 |
No files matched your search
@@ -98,6 +98,17 @@ describe('renderTrayIcon', () => {
|
||||
assert.deepEqual(pixel(bitmap, 32, 6, 16), [0xff, 0xff, 0xff, 0xff]);
|
||||
});
|
||||
|
||||
test('keeps the Linux monochrome pixmap within 256 pixels', () => {
|
||||
const result = renderTrayIcon(FakeNativeImage.fromBitmap(256), 'white', false, 'linux');
|
||||
const [representation] = result.representations.values();
|
||||
assert.equal(representation.width, 256);
|
||||
assert.equal(representation.height, 256);
|
||||
const bitmap = result.toBitmap();
|
||||
assert.deepEqual(pixel(bitmap, 256, 128, 50), [0xff, 0xff, 0xff, 0xff]);
|
||||
assert.deepEqual(pixel(bitmap, 256, 128, 128), [0, 0, 0, 0]);
|
||||
assert.deepEqual(pixel(bitmap, 256, 20, 128), [0, 0, 0, 0]);
|
||||
});
|
||||
|
||||
test('skips representations whose bitmap does not match their size', () => {
|
||||
const base = FakeNativeImage.fromBitmap(16);
|
||||
base.toBitmap = () => Buffer.alloc(3);
|
||||
|
||||
@@ -18,6 +18,7 @@ const MIN_GLYPH_CONTRAST = 32;
|
||||
const GLYPH_CUTOUT_START = 0.1;
|
||||
const GLYPH_CUTOUT_END = 0.5;
|
||||
const LINUX_MONOCHROME_CANVAS_RATIO = 22 / 16;
|
||||
const LINUX_MAX_PIXMAP_SIZE = 256;
|
||||
const PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
||||
const DOT_BGR: Bgr = [0x00, 0x00, 0xdd];
|
||||
const TEMPLATE_DOT_BGR: Bgr = [0x00, 0x00, 0x00];
|
||||
@@ -149,6 +150,36 @@ function cropTrayGlyph(buffer: Buffer, width: number, height: number): TrayBitma
|
||||
return {buffer: target, width: size, height: size};
|
||||
}
|
||||
|
||||
function downscaleTrayBitmap(bitmap: TrayBitmap, size: number): TrayBitmap {
|
||||
if (bitmap.width <= size) return bitmap;
|
||||
const scale = bitmap.width / size;
|
||||
const buffer = Buffer.alloc(size * size * 4);
|
||||
const sums = [0, 0, 0, 0];
|
||||
for (let y = 0; y < size; y++) {
|
||||
const top = y * scale;
|
||||
const bottom = top + scale;
|
||||
for (let x = 0; x < size; x++) {
|
||||
const left = x * scale;
|
||||
const right = left + scale;
|
||||
sums.fill(0);
|
||||
for (let sy = Math.floor(top); sy < Math.min(bitmap.height, Math.ceil(bottom)); sy++) {
|
||||
const weightY = Math.min(bottom, sy + 1) - Math.max(top, sy);
|
||||
for (let sx = Math.floor(left); sx < Math.min(bitmap.width, Math.ceil(right)); sx++) {
|
||||
const weight = weightY * (Math.min(right, sx + 1) - Math.max(left, sx));
|
||||
const offset = (sy * bitmap.width + sx) * 4;
|
||||
for (let channel = 0; channel < 4; channel++) {
|
||||
sums[channel] += bitmap.buffer[offset + channel] * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let channel = 0; channel < 4; channel++) {
|
||||
buffer[(y * size + x) * 4 + channel] = Math.round(sums[channel] / (scale * scale));
|
||||
}
|
||||
}
|
||||
}
|
||||
return {buffer, width: size, height: size};
|
||||
}
|
||||
|
||||
function addMonochromeTrayMargins(bitmap: TrayBitmap, size: number): TrayBitmap {
|
||||
const inset = Math.floor((size - bitmap.width) / 2);
|
||||
const buffer = Buffer.alloc(size * size * 4);
|
||||
@@ -210,7 +241,13 @@ export function renderTrayIcon(
|
||||
appearance === 'template' ? TEMPLATE_DOT_BGR : DOT_BGR,
|
||||
);
|
||||
}
|
||||
if (linuxMonochrome) bitmap = addMonochromeTrayMargins(bitmap, Math.ceil(logicalCanvasSize * scaleFactor));
|
||||
if (linuxMonochrome) {
|
||||
const canvasSize = Math.min(Math.ceil(logicalCanvasSize * scaleFactor), LINUX_MAX_PIXMAP_SIZE);
|
||||
bitmap = addMonochromeTrayMargins(
|
||||
downscaleTrayBitmap(bitmap, Math.floor(canvasSize / LINUX_MONOCHROME_CANVAS_RATIO)),
|
||||
canvasSize,
|
||||
);
|
||||
}
|
||||
result.addRepresentation({scaleFactor, ...bitmap});
|
||||
}
|
||||
if (result.isEmpty()) {
|
||||
|
||||
@@ -58,6 +58,8 @@ If both answer, skip ahead. On a Mac or a Windows PC, install [Docker Desktop](h
|
||||
|
||||
Debian and Ubuntu need Docker's apt repository from [Install Docker Engine](https://docs.docker.com/engine/install/), because the distribution's own `docker.io` package ships no Compose plugin. Follow the [post-installation steps](https://docs.docker.com/engine/install/linux-postinstall/) to add your user to the `docker` group, then log out and back in so the commands below run without `sudo`.
|
||||
|
||||
Podman 5 works too, with `docker-compose` installed so `podman compose` has a provider. The installer picks Podman when `docker` is missing or does not answer. Podman logs every healthcheck run to the journal by default, so with Podman the installer writes `fluxer-healthcheck-events.conf` to `~/.config/containers/containers.conf.d/` (or `/etc/containers/containers.conf.d/` as root) to turn that off. Health checks still run. If you set `healthcheck_events` yourself, the installer leaves it alone.
|
||||
|
||||
Linux also needs `curl` and `openssl`, which the steps below use:
|
||||
|
||||
```bash
|
||||
@@ -184,7 +186,7 @@ Leave `FLUXER_PUBLIC_ORIGIN` commented out, or keep it consistent with the publi
|
||||
| `--update` | Upgrade: record, back up, refresh, pull, recreate, verify |
|
||||
| `--rollback` | Restore the images and stack files that the newest `--update` run recorded |
|
||||
| `--allow-root` | Permit running as root |
|
||||
| `--engine <command>` | Container engine to drive. Default `docker`, or `podman` when `docker` is absent |
|
||||
| `--engine <command>` | Container engine to drive. Default `docker`, or `podman` when `docker` is absent or does not answer |
|
||||
|
||||
Run `--dry-run` first to see what a set of flags does. The PowerShell script takes the same flags as `-Domain`, `-Email` and so on, and has no `-AllowRoot`. [Upgrading](/operator/upgrading/#run-the-upgrade) has the flags that only `--update` and `--rollback` read, and the exit codes.
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ function Show-FluxerUsage {
|
||||
Write-FluxerLine ' -Domain <host> Hostname the instance answers on. Prompted when absent.'
|
||||
Write-FluxerLine ' -Email <address> Contact email for web push. Default: admin@<domain>.'
|
||||
Write-FluxerLine ' -Engine <command> Container engine to drive. Default: docker, or podman when'
|
||||
Write-FluxerLine ' docker is absent.'
|
||||
Write-FluxerLine ' docker is absent or does not answer.'
|
||||
Write-FluxerLine ' -Dir <path> Working directory. Default: the fluxer folder in the home'
|
||||
Write-FluxerLine ' directory, or the working directory itself when -Update or'
|
||||
Write-FluxerLine ' -Rollback is given and it holds an instance.'
|
||||
@@ -385,6 +385,14 @@ function Invoke-FluxerPreflight {
|
||||
$script:FluxerEngine = $Engine
|
||||
} elseif ($null -ne (Get-Command docker -ErrorAction SilentlyContinue)) {
|
||||
$script:FluxerEngine = 'docker'
|
||||
if ($null -ne (Get-Command podman -ErrorAction SilentlyContinue) -and (Invoke-FluxerCapture @('info')).Code -ne 0) {
|
||||
$script:FluxerEngine = 'podman'
|
||||
if ((Invoke-FluxerCapture @('info')).Code -eq 0) {
|
||||
Write-FluxerLine 'docker does not answer and podman does, so this run drives podman. Pass -Engine docker to use docker instead.'
|
||||
} else {
|
||||
$script:FluxerEngine = 'docker'
|
||||
}
|
||||
}
|
||||
} elseif ($null -ne (Get-Command podman -ErrorAction SilentlyContinue)) {
|
||||
$script:FluxerEngine = 'podman'
|
||||
} else {
|
||||
|
||||
@@ -214,7 +214,7 @@ Options:
|
||||
--domain <host> Hostname the instance answers on. Prompted when absent.
|
||||
--email <address> Contact email for web push. Default admin@<domain>.
|
||||
--engine <command> Container engine to drive. Default docker, or podman
|
||||
when docker is absent.
|
||||
when docker is absent or does not answer.
|
||||
--dir <path> Working directory. Default ~/fluxer, or the working
|
||||
directory itself when --update or --rollback is given
|
||||
and it holds an instance.
|
||||
@@ -561,6 +561,10 @@ fluxer_resolve_engine() {
|
||||
fluxer_engine=$opt_engine
|
||||
elif command -v docker >/dev/null 2>&1; then
|
||||
fluxer_engine='docker'
|
||||
if command -v podman >/dev/null 2>&1 && ! docker info >/dev/null 2>&1 && podman info >/dev/null 2>&1; then
|
||||
fluxer_engine='podman'
|
||||
fluxer_say 'docker does not answer and podman does, so this run drives podman. Pass --engine docker to use docker instead.'
|
||||
fi
|
||||
elif command -v podman >/dev/null 2>&1; then
|
||||
fluxer_engine='podman'
|
||||
else
|
||||
@@ -576,18 +580,11 @@ fluxer_preflight() {
|
||||
fluxer_fail 2 'Running as root. Use an account in the docker group, or pass --allow-root.'
|
||||
fi
|
||||
fluxer_resolve_engine
|
||||
if ! $fluxer_engine compose version >/dev/null 2>&1; then
|
||||
fluxer_fail 2 "$fluxer_engine has no compose subcommand. $(fluxer_docker_hint)"
|
||||
fi
|
||||
if $fluxer_engine compose version 2>/dev/null | grep -q '^podman-compose version'; then
|
||||
fluxer_fail 2 "$fluxer_engine compose runs podman-compose, which lacks compose ps -a and compose config --images that this script runs. $(fluxer_docker_hint)"
|
||||
fi
|
||||
fluxer_engine_report=$($fluxer_engine --version 2>/dev/null)
|
||||
fluxer_engine_kind=$(printf '%s\n' "$fluxer_engine_report" | sed -n 's/^\([A-Za-z][A-Za-z]*\) version .*/\1/p' | tr 'A-Z' 'a-z')
|
||||
fluxer_engine_version=$(printf '%s\n' "$fluxer_engine_report" | sed -n 's/^[A-Za-z][A-Za-z]* version v\{0,1\}\([0-9][0-9.]*\).*/\1/p')
|
||||
fluxer_compose_version=$($fluxer_engine compose version --short 2>/dev/null | sed -n 's/^v\{0,1\}\([0-9][0-9.]*\).*/\1/p')
|
||||
if [ -z "$fluxer_engine_version" ] || [ -z "$fluxer_compose_version" ]; then
|
||||
fluxer_fail 2 "Cannot read the engine and Compose versions from $fluxer_engine. It answered \"$fluxer_engine_report\" to --version."
|
||||
if [ -z "$fluxer_engine_version" ]; then
|
||||
fluxer_fail 2 "Cannot read the engine version from $fluxer_engine. It answered \"$fluxer_engine_report\" to --version."
|
||||
fi
|
||||
case $fluxer_engine_kind in
|
||||
podman)
|
||||
@@ -600,7 +597,17 @@ fluxer_preflight() {
|
||||
;;
|
||||
esac
|
||||
if ! fluxer_version_ge "$fluxer_engine_version" "$fluxer_min_engine"; then
|
||||
fluxer_fail 2 "$fluxer_engine_label $fluxer_engine_version with Compose $fluxer_compose_version. Fluxer needs $fluxer_engine_label $fluxer_min_engine or newer."
|
||||
fluxer_fail 2 "$fluxer_engine_label $fluxer_engine_version. Fluxer needs $fluxer_engine_label $fluxer_min_engine or newer. $(fluxer_docker_hint)"
|
||||
fi
|
||||
if ! $fluxer_engine compose version >/dev/null 2>&1; then
|
||||
fluxer_fail 2 "$fluxer_engine has no compose subcommand. $(fluxer_docker_hint)"
|
||||
fi
|
||||
if $fluxer_engine compose version 2>/dev/null | grep -q '^podman-compose version'; then
|
||||
fluxer_fail 2 "$fluxer_engine compose runs podman-compose, which lacks compose ps -a and compose config --images that this script runs. $(fluxer_docker_hint)"
|
||||
fi
|
||||
fluxer_compose_version=$($fluxer_engine compose version --short 2>/dev/null | sed -n 's/^v\{0,1\}\([0-9][0-9.]*\).*/\1/p')
|
||||
if [ -z "$fluxer_compose_version" ]; then
|
||||
fluxer_fail 2 "Cannot read the Compose version from $fluxer_engine compose version --short."
|
||||
fi
|
||||
if ! fluxer_version_ge "$fluxer_compose_version" "$FLUXER_MIN_COMPOSE"; then
|
||||
fluxer_fail 2 "$fluxer_engine_label $fluxer_engine_version with Compose $fluxer_compose_version. Fluxer needs Compose $FLUXER_MIN_COMPOSE or newer."
|
||||
@@ -837,7 +844,11 @@ fluxer_print_plan() {
|
||||
fluxer_say " email admin@$opt_domain, derived by compose"
|
||||
fluxer_plan_non_secret=$((fluxer_plan_non_secret - 1))
|
||||
fi
|
||||
fluxer_say ' action download the stack files, write .env, start the stack'
|
||||
if [ "$opt_no_start" -eq 1 ]; then
|
||||
fluxer_say ' action download the stack files, write .env, leave the stack stopped'
|
||||
else
|
||||
fluxer_say ' action download the stack files, write .env, start the stack'
|
||||
fi
|
||||
fluxer_say " .env keys $fluxer_plan_non_secret non-secret values and $(fluxer_secret_keys | wc -l | tr -d ' ') secrets"
|
||||
fluxer_say ' files docker-compose.yml docker-compose.proxy.yml tunnel.compose.yml Caddyfile .env.example'
|
||||
if [ -e "$opt_dir/.env" ]; then
|
||||
@@ -2195,9 +2206,29 @@ fluxer_run_rollback() {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Podman writes a health_status event to the journal for every healthcheck run,
|
||||
# with every label of the container in it, and this stack runs two dozen checks
|
||||
# every ten seconds. Compose and this script read health from inspect, so the
|
||||
# events have no reader here. An operator who set the key either way keeps it.
|
||||
fluxer_quiet_podman_health_events() {
|
||||
[ "$fluxer_engine_kind" = 'podman' ] || return 0
|
||||
[ "$opt_dry_run" -eq 0 ] || return 0
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
fluxer_conf_dir=/etc/containers/containers.conf.d
|
||||
else
|
||||
fluxer_conf_dir=${XDG_CONFIG_HOME:-$HOME/.config}/containers/containers.conf.d
|
||||
fi
|
||||
if grep -qs '^[[:space:]]*healthcheck_events[[:space:]]*=' /usr/share/containers/containers.conf /etc/containers/containers.conf /etc/containers/containers.conf.d/*.conf "${XDG_CONFIG_HOME:-$HOME/.config}/containers/containers.conf" "${XDG_CONFIG_HOME:-$HOME/.config}/containers/containers.conf.d/"*.conf; then
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "$fluxer_conf_dir" && printf '[engine]\nhealthcheck_events = false\n' > "$fluxer_conf_dir/fluxer-healthcheck-events.conf" || return 0
|
||||
fluxer_say "Wrote $fluxer_conf_dir/fluxer-healthcheck-events.conf, which stops Podman from logging every healthcheck run to the journal. Health checks still run."
|
||||
}
|
||||
|
||||
fluxer_preflight
|
||||
fluxer_validate_options
|
||||
fluxer_resolve_values
|
||||
fluxer_quiet_podman_health_events
|
||||
|
||||
if [ "$opt_update" -eq 1 ]; then
|
||||
fluxer_refresh_installer
|
||||
|
||||
Reference in new issue
Block a user