feat: headless server mode with ExRatatui SSH daemon (issue #26, phase 1)
This commit is contained in:
@@ -25,6 +25,8 @@ config :bds, :desktop,
|
||||
window_min_size: {800, 600},
|
||||
title: "Blogging Desktop Server"
|
||||
|
||||
config :bds, :server, ssh_port: 2222
|
||||
|
||||
config :bds, BDS.Desktop.Endpoint,
|
||||
url: [host: "127.0.0.1"],
|
||||
adapter: Bandit.PhoenixAdapter,
|
||||
|
||||
@@ -5,6 +5,15 @@ defmodule BDS.Application do
|
||||
|
||||
@compiled_env Application.compile_env(:bds, :current_env, Mix.env())
|
||||
|
||||
# Children that depend on the boot mode (issue #26): desktop mode runs the
|
||||
# wx window shell, server mode runs headless behind the SSH daemon. Both
|
||||
# start the loopback HTTP endpoint and the CLI sync watcher.
|
||||
def mode_children(:server, _env) do
|
||||
[{BDS.Desktop.Server, []}, BDS.CliSync.Watcher, BDS.Server]
|
||||
end
|
||||
|
||||
def mode_children(:desktop, env), do: desktop_children(env)
|
||||
|
||||
def desktop_children(env \\ nil)
|
||||
|
||||
def desktop_children(:test) do
|
||||
@@ -41,7 +50,7 @@ defmodule BDS.Application do
|
||||
{Task.Supervisor, name: BDS.Scripting.TaskSupervisor},
|
||||
BDS.Scripting.JobSupervisor,
|
||||
BDS.Embeddings.Index
|
||||
] ++ embedding_children() ++ desktop_children(current_env())
|
||||
] ++ embedding_children() ++ mode_children(BDS.Server.mode(), current_env())
|
||||
|
||||
opts = [strategy: :one_for_one, name: BDS.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
|
||||
95
lib/bds/server.ex
Normal file
95
lib/bds/server.ex
Normal file
@@ -0,0 +1,95 @@
|
||||
defmodule BDS.Server do
|
||||
@moduledoc """
|
||||
Headless server mode (issue #26).
|
||||
|
||||
Boots the app without any wx window and serves clients over a single SSH
|
||||
daemon: TUI sessions run server-side via `ExRatatui.SSH` (only terminal
|
||||
cells cross the wire), and GUI clients tunnel to the loopback HTTP
|
||||
endpoint through the same daemon (`tcpip_tunnel_out`). Authentication is
|
||||
public-key only; host key and `authorized_keys` live in the private app
|
||||
data dir next to the database — never in the repo or a priv dir.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Resolves the boot mode from the `BDS_MODE` environment variable.
|
||||
Anything other than `"server"` (case-insensitive) is desktop mode.
|
||||
"""
|
||||
@spec mode(String.t() | nil) :: :desktop | :server
|
||||
def mode(value \\ System.get_env("BDS_MODE"))
|
||||
|
||||
def mode(value) when is_binary(value) do
|
||||
if String.downcase(value) == "server", do: :server, else: :desktop
|
||||
end
|
||||
|
||||
def mode(nil), do: :desktop
|
||||
|
||||
@doc "SSH key-material directory: `<data dir>/ssh` next to the database."
|
||||
@spec ssh_dir() :: Path.t()
|
||||
def ssh_dir do
|
||||
Application.get_env(:bds, BDS.Repo)[:database]
|
||||
|> Path.dirname()
|
||||
|> Path.join("ssh")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensures the SSH directory exists with an `authorized_keys` file (created
|
||||
empty and chmod 600 on first boot; existing keys are never touched).
|
||||
"""
|
||||
@spec ensure_ssh_dir!(Path.t()) :: Path.t()
|
||||
def ensure_ssh_dir!(dir) do
|
||||
File.mkdir_p!(dir)
|
||||
keys_path = Path.join(dir, "authorized_keys")
|
||||
|
||||
unless File.exists?(keys_path) do
|
||||
File.write!(keys_path, "")
|
||||
File.chmod!(keys_path, 0o600)
|
||||
end
|
||||
|
||||
dir
|
||||
end
|
||||
|
||||
@doc """
|
||||
Options for `ExRatatui.SSH.Daemon`: serves `BDS.TUI`, public-key auth
|
||||
only, and allows TCP/IP tunneling out so GUI clients can reach the
|
||||
loopback HTTP endpoint through the SSH connection.
|
||||
"""
|
||||
@spec daemon_opts(Path.t()) :: keyword()
|
||||
def daemon_opts(dir \\ ssh_dir()) do
|
||||
dir = ensure_ssh_dir!(dir)
|
||||
system_dir = ExRatatui.SSH.Daemon.ensure_host_key!(dir)
|
||||
|
||||
[
|
||||
mod: BDS.TUI,
|
||||
port: ssh_port(),
|
||||
system_dir: system_dir,
|
||||
user_dir: String.to_charlist(dir),
|
||||
auth_methods: ~c"publickey",
|
||||
tcpip_tunnel_out: true
|
||||
]
|
||||
end
|
||||
|
||||
@doc "SSH listen port: `BDS_SSH_PORT` env or `:bds, :server` config."
|
||||
@spec ssh_port() :: pos_integer()
|
||||
def ssh_port do
|
||||
case System.get_env("BDS_SSH_PORT") do
|
||||
value when is_binary(value) and value != "" -> String.to_integer(value)
|
||||
_other -> Application.get_env(:bds, :server)[:ssh_port] || 2222
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
# Child spec that defers all filesystem work (key generation) to process
|
||||
# start, so building a children list never touches the data dir.
|
||||
def child_spec(_opts) do
|
||||
%{
|
||||
id: __MODULE__,
|
||||
start: {__MODULE__, :start_link, []},
|
||||
type: :worker
|
||||
}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def start_link do
|
||||
ExRatatui.SSH.Daemon.start_link(daemon_opts())
|
||||
end
|
||||
end
|
||||
38
lib/bds/tui.ex
Normal file
38
lib/bds/tui.ex
Normal file
@@ -0,0 +1,38 @@
|
||||
defmodule BDS.TUI do
|
||||
@moduledoc """
|
||||
Terminal UI entry point (issue #26), served per SSH connection by
|
||||
`ExRatatui.SSH.Daemon` in server mode and runnable locally.
|
||||
|
||||
Placeholder shell for now: renders the app banner while the workbench
|
||||
renderer lands in a later phase. The UI locale is the server-side UI
|
||||
language setting, same as the GUI.
|
||||
"""
|
||||
|
||||
use ExRatatui.App
|
||||
use Gettext, backend: BDS.Gettext
|
||||
|
||||
alias BDS.Desktop.UILocale
|
||||
alias ExRatatui.Layout.Rect
|
||||
alias ExRatatui.Widgets.Paragraph
|
||||
|
||||
@impl true
|
||||
def mount(_opts) do
|
||||
UILocale.put(BDS.Desktop.ShellData.ui_language())
|
||||
{:ok, %{}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(_state, frame) do
|
||||
text =
|
||||
"bDS2 — " <>
|
||||
dgettext("ui", "Blogging Desktop Server") <>
|
||||
"\n\n" <>
|
||||
dgettext("ui", "Press q to quit.")
|
||||
|
||||
[{%Paragraph{text: text}, %Rect{x: 0, y: 0, width: frame.width, height: frame.height}}]
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event(%ExRatatui.Event.Key{code: "q"}, state), do: {:stop, state}
|
||||
def handle_event(_event, state), do: {:noreply, state}
|
||||
end
|
||||
1
mix.exs
1
mix.exs
@@ -40,6 +40,7 @@ defmodule BDS.MixProject do
|
||||
{:live_toast, "~> 0.8.0"},
|
||||
{:saxy, "~> 1.4"},
|
||||
{:desktop, "~> 1.5"},
|
||||
{:ex_ratatui, "~> 0.11"},
|
||||
{:image, "~> 0.67"},
|
||||
{:nx, "~> 0.10"},
|
||||
{:exla, "~> 0.10"},
|
||||
|
||||
1
mix.lock
1
mix.lock
@@ -25,6 +25,7 @@
|
||||
"esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"},
|
||||
"ex_dbus": {:hex, :ex_dbus, "0.1.4", "053df83d45b27ba0b9b6ef55a47253922069a3ace12a2a7dd30d3aff58301e17", [:mix], [{:dbus, "~> 0.8.0", [hex: :dbus, repo: "hexpm", optional: false]}, {:saxy, "~> 1.4.0", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "d8baeaf465eab57b70a47b70e29fdfef6eb09ba110fc37176eebe6ac7874d6d5"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.40.3", "4a972ffe64bc07dc605af487e98fc19b72a4185f55ca031b94c0552d6071c1d9", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2756e357742fecd9749b489b85d67c9ce99c465f2e75728d9e6dc8d704b973de"},
|
||||
"ex_ratatui": {:hex, :ex_ratatui, "0.11.1", "f3cc5c9827d957bc326d3b3b48338a927716fedc2575311d65a3af0dd03ed951", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b37b02b33e1e8f49874497d7b7f8872417b3807ec77d4bc1526c07c05c7e4cc9"},
|
||||
"ex_sni": {:hex, :ex_sni, "0.2.9", "81f9421035dd3edb6d69f1a4dd5f53c7071b41628130d32ba5ab7bb4bfdc2da0", [:mix], [{:debouncer, "~> 0.1", [hex: :debouncer, repo: "hexpm", optional: false]}, {:ex_dbus, "~> 0.1", [hex: :ex_dbus, repo: "hexpm", optional: false]}, {:saxy, "~> 1.4.0", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "921d67d913765ed20ea8354fd1798dabc957bf66990a6842d6aaa7cd5ee5bc06"},
|
||||
"ex_stemmers": {:hex, :ex_stemmers, "0.1.0", "63a84ae3a6f0c28a1d75768411f0ae15cfe8462fb70589b60977aa1b04c9372d", [:mix], [{:rustler, "~> 0.32.1", [hex: :rustler, repo: "hexpm", optional: false]}], "hexpm", "498826e2188e502f41d1a15f3d90e7738f0d94747e197367f03a2a44c09167c0"},
|
||||
"exla": {:hex, :exla, "0.10.0", "93e7d75a774fbc06ce05b96de20c4b01bda413b315238cb3c727c09a05d2bc3a", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:nx, "~> 0.10.0", [hex: :nx, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:xla, "~> 0.9.0", [hex: :xla, repo: "hexpm", optional: false]}], "hexpm", "16fffdb64667d7f0a3bc683fdcd2792b143a9b345e4b1f1d5cd50330c63d8119"},
|
||||
|
||||
@@ -71,8 +71,8 @@ msgid "AI Assistant"
|
||||
msgstr "KI-Assistent"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:208
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:193
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:211
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "AI Settings"
|
||||
msgstr "KI-Einstellungen"
|
||||
@@ -336,7 +336,7 @@ msgstr "Autor"
|
||||
msgid "Auto"
|
||||
msgstr "Automatisch"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:448
|
||||
#: lib/bds/desktop/shell_live.ex:442
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:234
|
||||
#: lib/bds/desktop/shell_live/media_editor.ex:171
|
||||
#: lib/bds/desktop/shell_live/media_editor.ex:364
|
||||
@@ -486,7 +486,7 @@ msgstr "Kategorie-Standards, Render-Flags und Template-Zuordnung"
|
||||
msgid "Category name is required"
|
||||
msgstr "Kategoriename ist erforderlich"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:771
|
||||
#: lib/bds/desktop/shell_live.ex:780
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:87
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:233
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:323
|
||||
@@ -1063,7 +1063,7 @@ msgstr "Galerie"
|
||||
msgid "Generate Site"
|
||||
msgstr "Website generieren"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:772
|
||||
#: lib/bds/desktop/shell_live.ex:781
|
||||
#: lib/bds/desktop/shell_live/socket_state.ex:109
|
||||
#: lib/bds/ui/sidebar.ex:789
|
||||
#, elixir-autogen, elixir-format
|
||||
@@ -1077,7 +1077,7 @@ msgid "Git Diff"
|
||||
msgstr "Git-Diff"
|
||||
|
||||
#: lib/bds/desktop/shell_data.ex:226
|
||||
#: lib/bds/desktop/shell_live.ex:768
|
||||
#: lib/bds/desktop/shell_live.ex:777
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:171
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Git Log"
|
||||
@@ -1200,9 +1200,9 @@ msgstr "Importdefinitionen"
|
||||
msgid "Import failed: %{error}"
|
||||
msgstr "Import fehlgeschlagen: %{error}"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:598
|
||||
#: lib/bds/desktop/shell_live.ex:884
|
||||
#: lib/bds/desktop/shell_live.ex:890
|
||||
#: lib/bds/desktop/shell_live.ex:605
|
||||
#: lib/bds/desktop/shell_live.ex:893
|
||||
#: lib/bds/desktop/shell_live.ex:899
|
||||
#: lib/bds/desktop/shell_live/sidebar_create.ex:47
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Import media"
|
||||
@@ -1784,7 +1784,7 @@ msgstr "Sonstige"
|
||||
msgid "Other (%{count})"
|
||||
msgstr "Andere (%{count})"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:767
|
||||
#: lib/bds/desktop/shell_live.ex:776
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:83
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Output"
|
||||
@@ -2538,7 +2538,7 @@ msgstr "Schlagwortname"
|
||||
msgid "Tags"
|
||||
msgstr "Tags"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:766
|
||||
#: lib/bds/desktop/shell_live.ex:775
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:54
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Tasks"
|
||||
@@ -3218,12 +3218,12 @@ msgstr "Willkommen beim KI-Assistenten"
|
||||
msgid "Comparing database and filesystem metadata"
|
||||
msgstr "Vergleicht Datenbank- und Dateisystem-Metadaten"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:674
|
||||
#: lib/bds/desktop/shell_live.ex:681
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Added %{count} images to post"
|
||||
msgstr "%{count} Bilder zum Beitrag hinzugefügt"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:642
|
||||
#: lib/bds/desktop/shell_live.ex:649
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Added %{title}"
|
||||
msgstr "%{title} hinzugefügt"
|
||||
@@ -3243,18 +3243,18 @@ msgstr "Endbenutzer-Anleitung für redaktionelle Arbeitsabläufe, Medien, Vorlag
|
||||
msgid "Image Import Concurrency"
|
||||
msgstr "Gleichzeitige Bildimporte"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:447
|
||||
#: lib/bds/desktop/shell_live.ex:460
|
||||
#: lib/bds/desktop/shell_live.ex:641
|
||||
#: lib/bds/desktop/shell_live.ex:673
|
||||
#: lib/bds/desktop/shell_live.ex:684
|
||||
#: lib/bds/desktop/shell_live.ex:695
|
||||
#: lib/bds/desktop/shell_live.ex:441
|
||||
#: lib/bds/desktop/shell_live.ex:454
|
||||
#: lib/bds/desktop/shell_live.ex:648
|
||||
#: lib/bds/desktop/shell_live.ex:680
|
||||
#: lib/bds/desktop/shell_live.ex:691
|
||||
#: lib/bds/desktop/shell_live.ex:702
|
||||
#: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Add Gallery Images"
|
||||
msgstr "Galerie-Bilder hinzufügen"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:696
|
||||
#: lib/bds/desktop/shell_live.ex:703
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Failed to process %{path}: %{reason}"
|
||||
msgstr "%{path} konnte nicht verarbeitet werden: %{reason}"
|
||||
@@ -3445,12 +3445,12 @@ msgstr "umbenannt"
|
||||
msgid "untracked"
|
||||
msgstr "nicht verfolgt"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:792
|
||||
#: lib/bds/desktop/shell_live.ex:801
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Blogmark"
|
||||
msgstr "Blogmark"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:835
|
||||
#: lib/bds/desktop/shell_live.ex:844
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open a project before importing a blogmark."
|
||||
msgstr "Öffnen Sie ein Projekt, bevor Sie ein Blogmark importieren."
|
||||
@@ -3491,7 +3491,7 @@ msgstr "Loeschen"
|
||||
msgid "Failed to copy bookmarklet to clipboard"
|
||||
msgstr "Bookmarklet konnte nicht in die Zwischenablage kopiert werden"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:804
|
||||
#: lib/bds/desktop/shell_live.ex:813
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The project this blogmark targets does not exist here."
|
||||
msgstr "Das Projekt, auf das dieses Blogmark verweist, existiert hier nicht."
|
||||
@@ -3578,12 +3578,12 @@ msgstr "KI-Einstellungen gespeichert."
|
||||
msgid "Could not load models"
|
||||
msgstr "Modelle konnten nicht geladen werden"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:187
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Could not save AI settings"
|
||||
msgstr "KI-Einstellungen konnten nicht gespeichert werden"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:182
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:184
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Loaded %{count} models."
|
||||
msgstr "%{count} Modelle geladen."
|
||||
@@ -3593,7 +3593,17 @@ msgstr "%{count} Modelle geladen."
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:173
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:174
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The endpoint returned no models. You can still type a model name manually."
|
||||
msgstr "Der Endpunkt hat keine Modelle zurückgegeben. Sie können einen Modellnamen weiterhin manuell eingeben."
|
||||
|
||||
#: lib/bds/tui.ex:28
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Blogging Desktop Server"
|
||||
msgstr "Blogging Desktop Server"
|
||||
|
||||
#: lib/bds/tui.ex:30
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Press q to quit."
|
||||
msgstr "Drücken Sie q zum Beenden."
|
||||
|
||||
@@ -71,8 +71,8 @@ msgid "AI Assistant"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:208
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:193
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:211
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "AI Settings"
|
||||
msgstr ""
|
||||
@@ -336,7 +336,7 @@ msgstr ""
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:448
|
||||
#: lib/bds/desktop/shell_live.ex:442
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:234
|
||||
#: lib/bds/desktop/shell_live/media_editor.ex:171
|
||||
#: lib/bds/desktop/shell_live/media_editor.ex:364
|
||||
@@ -486,7 +486,7 @@ msgstr ""
|
||||
msgid "Category name is required"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:771
|
||||
#: lib/bds/desktop/shell_live.ex:780
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:87
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:233
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:323
|
||||
@@ -1063,7 +1063,7 @@ msgstr ""
|
||||
msgid "Generate Site"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:772
|
||||
#: lib/bds/desktop/shell_live.ex:781
|
||||
#: lib/bds/desktop/shell_live/socket_state.ex:109
|
||||
#: lib/bds/ui/sidebar.ex:789
|
||||
#, elixir-autogen, elixir-format
|
||||
@@ -1077,7 +1077,7 @@ msgid "Git Diff"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_data.ex:226
|
||||
#: lib/bds/desktop/shell_live.ex:768
|
||||
#: lib/bds/desktop/shell_live.ex:777
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:171
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Git Log"
|
||||
@@ -1200,9 +1200,9 @@ msgstr ""
|
||||
msgid "Import failed: %{error}"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:598
|
||||
#: lib/bds/desktop/shell_live.ex:884
|
||||
#: lib/bds/desktop/shell_live.ex:890
|
||||
#: lib/bds/desktop/shell_live.ex:605
|
||||
#: lib/bds/desktop/shell_live.ex:893
|
||||
#: lib/bds/desktop/shell_live.ex:899
|
||||
#: lib/bds/desktop/shell_live/sidebar_create.ex:47
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Import media"
|
||||
@@ -1784,7 +1784,7 @@ msgstr ""
|
||||
msgid "Other (%{count})"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:767
|
||||
#: lib/bds/desktop/shell_live.ex:776
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:83
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Output"
|
||||
@@ -2538,7 +2538,7 @@ msgstr ""
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:766
|
||||
#: lib/bds/desktop/shell_live.ex:775
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:54
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Tasks"
|
||||
@@ -3218,12 +3218,12 @@ msgstr ""
|
||||
msgid "Comparing database and filesystem metadata"
|
||||
msgstr "Comparing database and filesystem metadata"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:674
|
||||
#: lib/bds/desktop/shell_live.ex:681
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Added %{count} images to post"
|
||||
msgstr "Added %{count} images to post"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:642
|
||||
#: lib/bds/desktop/shell_live.ex:649
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Added %{title}"
|
||||
msgstr "Added %{title}"
|
||||
@@ -3243,18 +3243,18 @@ msgstr ""
|
||||
msgid "Image Import Concurrency"
|
||||
msgstr "Image Import Concurrency"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:447
|
||||
#: lib/bds/desktop/shell_live.ex:460
|
||||
#: lib/bds/desktop/shell_live.ex:641
|
||||
#: lib/bds/desktop/shell_live.ex:673
|
||||
#: lib/bds/desktop/shell_live.ex:684
|
||||
#: lib/bds/desktop/shell_live.ex:695
|
||||
#: lib/bds/desktop/shell_live.ex:441
|
||||
#: lib/bds/desktop/shell_live.ex:454
|
||||
#: lib/bds/desktop/shell_live.ex:648
|
||||
#: lib/bds/desktop/shell_live.ex:680
|
||||
#: lib/bds/desktop/shell_live.ex:691
|
||||
#: lib/bds/desktop/shell_live.ex:702
|
||||
#: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Add Gallery Images"
|
||||
msgstr "Add Gallery Images"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:696
|
||||
#: lib/bds/desktop/shell_live.ex:703
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Failed to process %{path}: %{reason}"
|
||||
msgstr "Failed to process %{path}: %{reason}"
|
||||
@@ -3445,12 +3445,12 @@ msgstr ""
|
||||
msgid "untracked"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:792
|
||||
#: lib/bds/desktop/shell_live.ex:801
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
msgid "Blogmark"
|
||||
msgstr "Blogmark"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:835
|
||||
#: lib/bds/desktop/shell_live.ex:844
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open a project before importing a blogmark."
|
||||
msgstr ""
|
||||
@@ -3491,7 +3491,7 @@ msgstr ""
|
||||
msgid "Failed to copy bookmarklet to clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:804
|
||||
#: lib/bds/desktop/shell_live.ex:813
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The project this blogmark targets does not exist here."
|
||||
msgstr ""
|
||||
@@ -3578,12 +3578,12 @@ msgstr ""
|
||||
msgid "Could not load models"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:187
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Could not save AI settings"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:182
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:184
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Loaded %{count} models."
|
||||
msgstr ""
|
||||
@@ -3593,7 +3593,17 @@ msgstr ""
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:173
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:174
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The endpoint returned no models. You can still type a model name manually."
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/tui.ex:28
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Blogging Desktop Server"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/tui.ex:30
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Press q to quit."
|
||||
msgstr ""
|
||||
|
||||
@@ -71,8 +71,8 @@ msgid "AI Assistant"
|
||||
msgstr "Asistente de IA"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:208
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:193
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:211
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "AI Settings"
|
||||
msgstr "Configuración de IA"
|
||||
@@ -336,7 +336,7 @@ msgstr "Autor"
|
||||
msgid "Auto"
|
||||
msgstr "Automático"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:448
|
||||
#: lib/bds/desktop/shell_live.ex:442
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:234
|
||||
#: lib/bds/desktop/shell_live/media_editor.ex:171
|
||||
#: lib/bds/desktop/shell_live/media_editor.ex:364
|
||||
@@ -486,7 +486,7 @@ msgstr "Valores predeterminados de categoría, opciones de renderizado y conexi
|
||||
msgid "Category name is required"
|
||||
msgstr "El nombre de la categoría es obligatorio"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:771
|
||||
#: lib/bds/desktop/shell_live.ex:780
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:87
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:233
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:323
|
||||
@@ -1063,7 +1063,7 @@ msgstr "Galeria"
|
||||
msgid "Generate Site"
|
||||
msgstr "Generar sitio"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:772
|
||||
#: lib/bds/desktop/shell_live.ex:781
|
||||
#: lib/bds/desktop/shell_live/socket_state.ex:109
|
||||
#: lib/bds/ui/sidebar.ex:789
|
||||
#, elixir-autogen, elixir-format
|
||||
@@ -1077,7 +1077,7 @@ msgid "Git Diff"
|
||||
msgstr "Diff de Git"
|
||||
|
||||
#: lib/bds/desktop/shell_data.ex:226
|
||||
#: lib/bds/desktop/shell_live.ex:768
|
||||
#: lib/bds/desktop/shell_live.ex:777
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:171
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Git Log"
|
||||
@@ -1200,9 +1200,9 @@ msgstr "Definiciones de importación"
|
||||
msgid "Import failed: %{error}"
|
||||
msgstr "La importación falló: %{error}"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:598
|
||||
#: lib/bds/desktop/shell_live.ex:884
|
||||
#: lib/bds/desktop/shell_live.ex:890
|
||||
#: lib/bds/desktop/shell_live.ex:605
|
||||
#: lib/bds/desktop/shell_live.ex:893
|
||||
#: lib/bds/desktop/shell_live.ex:899
|
||||
#: lib/bds/desktop/shell_live/sidebar_create.ex:47
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Import media"
|
||||
@@ -1784,7 +1784,7 @@ msgstr "Otros"
|
||||
msgid "Other (%{count})"
|
||||
msgstr "Otros (%{count})"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:767
|
||||
#: lib/bds/desktop/shell_live.ex:776
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:83
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Output"
|
||||
@@ -2538,7 +2538,7 @@ msgstr "Nombre de la etiqueta"
|
||||
msgid "Tags"
|
||||
msgstr "Etiquetas"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:766
|
||||
#: lib/bds/desktop/shell_live.ex:775
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:54
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Tasks"
|
||||
@@ -3218,12 +3218,12 @@ msgstr "Bienvenido al asistente de IA"
|
||||
msgid "Comparing database and filesystem metadata"
|
||||
msgstr "Comparando metadatos de la base de datos y del sistema de archivos"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:674
|
||||
#: lib/bds/desktop/shell_live.ex:681
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Added %{count} images to post"
|
||||
msgstr "%{count} imágenes añadidas a la publicación"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:642
|
||||
#: lib/bds/desktop/shell_live.ex:649
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Added %{title}"
|
||||
msgstr "%{title} añadido"
|
||||
@@ -3243,18 +3243,18 @@ msgstr "Guía del usuario para flujos editoriales, medios, plantillas, traducci
|
||||
msgid "Image Import Concurrency"
|
||||
msgstr "Importación simultánea de imágenes"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:447
|
||||
#: lib/bds/desktop/shell_live.ex:460
|
||||
#: lib/bds/desktop/shell_live.ex:641
|
||||
#: lib/bds/desktop/shell_live.ex:673
|
||||
#: lib/bds/desktop/shell_live.ex:684
|
||||
#: lib/bds/desktop/shell_live.ex:695
|
||||
#: lib/bds/desktop/shell_live.ex:441
|
||||
#: lib/bds/desktop/shell_live.ex:454
|
||||
#: lib/bds/desktop/shell_live.ex:648
|
||||
#: lib/bds/desktop/shell_live.ex:680
|
||||
#: lib/bds/desktop/shell_live.ex:691
|
||||
#: lib/bds/desktop/shell_live.ex:702
|
||||
#: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Add Gallery Images"
|
||||
msgstr "Añadir imágenes a la galería"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:696
|
||||
#: lib/bds/desktop/shell_live.ex:703
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Failed to process %{path}: %{reason}"
|
||||
msgstr "No se pudo procesar %{path}: %{reason}"
|
||||
@@ -3445,12 +3445,12 @@ msgstr "renombrado"
|
||||
msgid "untracked"
|
||||
msgstr "sin seguimiento"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:792
|
||||
#: lib/bds/desktop/shell_live.ex:801
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Blogmark"
|
||||
msgstr "Blogmark"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:835
|
||||
#: lib/bds/desktop/shell_live.ex:844
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open a project before importing a blogmark."
|
||||
msgstr "Abre un proyecto antes de importar un blogmark."
|
||||
@@ -3491,7 +3491,7 @@ msgstr "Eliminar"
|
||||
msgid "Failed to copy bookmarklet to clipboard"
|
||||
msgstr "Error al copiar el bookmarklet al portapapeles"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:804
|
||||
#: lib/bds/desktop/shell_live.ex:813
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The project this blogmark targets does not exist here."
|
||||
msgstr "El proyecto al que apunta este blogmark no existe aquí."
|
||||
@@ -3578,12 +3578,12 @@ msgstr "Configuración de IA guardada."
|
||||
msgid "Could not load models"
|
||||
msgstr "No se pudieron cargar los modelos"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:187
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Could not save AI settings"
|
||||
msgstr "No se pudo guardar la configuración de IA"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:182
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:184
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Loaded %{count} models."
|
||||
msgstr "%{count} modelos cargados."
|
||||
@@ -3593,7 +3593,17 @@ msgstr "%{count} modelos cargados."
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:173
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:174
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The endpoint returned no models. You can still type a model name manually."
|
||||
msgstr "El endpoint no devolvió ningún modelo. Aún puedes escribir el nombre de un modelo manualmente."
|
||||
|
||||
#: lib/bds/tui.ex:28
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Blogging Desktop Server"
|
||||
msgstr "Blogging Desktop Server"
|
||||
|
||||
#: lib/bds/tui.ex:30
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Press q to quit."
|
||||
msgstr "Pulsa q para salir."
|
||||
|
||||
@@ -71,8 +71,8 @@ msgid "AI Assistant"
|
||||
msgstr "Assistant IA"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:208
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:193
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:211
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "AI Settings"
|
||||
msgstr "Paramètres IA"
|
||||
@@ -336,7 +336,7 @@ msgstr "Auteur"
|
||||
msgid "Auto"
|
||||
msgstr "Automatique"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:448
|
||||
#: lib/bds/desktop/shell_live.ex:442
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:234
|
||||
#: lib/bds/desktop/shell_live/media_editor.ex:171
|
||||
#: lib/bds/desktop/shell_live/media_editor.ex:364
|
||||
@@ -486,7 +486,7 @@ msgstr "Valeurs par défaut des catégories, options de rendu et liaison des mod
|
||||
msgid "Category name is required"
|
||||
msgstr "Le nom de la catégorie est requis"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:771
|
||||
#: lib/bds/desktop/shell_live.ex:780
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:87
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:233
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:323
|
||||
@@ -1063,7 +1063,7 @@ msgstr "Galerie"
|
||||
msgid "Generate Site"
|
||||
msgstr "Générer le site"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:772
|
||||
#: lib/bds/desktop/shell_live.ex:781
|
||||
#: lib/bds/desktop/shell_live/socket_state.ex:109
|
||||
#: lib/bds/ui/sidebar.ex:789
|
||||
#, elixir-autogen, elixir-format
|
||||
@@ -1077,7 +1077,7 @@ msgid "Git Diff"
|
||||
msgstr "Diff Git"
|
||||
|
||||
#: lib/bds/desktop/shell_data.ex:226
|
||||
#: lib/bds/desktop/shell_live.ex:768
|
||||
#: lib/bds/desktop/shell_live.ex:777
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:171
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Git Log"
|
||||
@@ -1200,9 +1200,9 @@ msgstr "Définitions d’import"
|
||||
msgid "Import failed: %{error}"
|
||||
msgstr "Échec de l’import : %{error}"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:598
|
||||
#: lib/bds/desktop/shell_live.ex:884
|
||||
#: lib/bds/desktop/shell_live.ex:890
|
||||
#: lib/bds/desktop/shell_live.ex:605
|
||||
#: lib/bds/desktop/shell_live.ex:893
|
||||
#: lib/bds/desktop/shell_live.ex:899
|
||||
#: lib/bds/desktop/shell_live/sidebar_create.ex:47
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Import media"
|
||||
@@ -1784,7 +1784,7 @@ msgstr "Autre"
|
||||
msgid "Other (%{count})"
|
||||
msgstr "Autres (%{count})"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:767
|
||||
#: lib/bds/desktop/shell_live.ex:776
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:83
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Output"
|
||||
@@ -2538,7 +2538,7 @@ msgstr "Nom du mot-clé"
|
||||
msgid "Tags"
|
||||
msgstr "Tags"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:766
|
||||
#: lib/bds/desktop/shell_live.ex:775
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:54
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Tasks"
|
||||
@@ -3218,12 +3218,12 @@ msgstr "Bienvenue dans l’assistant IA"
|
||||
msgid "Comparing database and filesystem metadata"
|
||||
msgstr "Comparaison des métadonnées entre la base et le système de fichiers"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:674
|
||||
#: lib/bds/desktop/shell_live.ex:681
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Added %{count} images to post"
|
||||
msgstr "%{count} images ajoutées à l'article"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:642
|
||||
#: lib/bds/desktop/shell_live.ex:649
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Added %{title}"
|
||||
msgstr "%{title} ajouté"
|
||||
@@ -3243,18 +3243,18 @@ msgstr "Guide utilisateur pour les flux éditoriaux, médias, modèles, traducti
|
||||
msgid "Image Import Concurrency"
|
||||
msgstr "Importation simultanée d'images"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:447
|
||||
#: lib/bds/desktop/shell_live.ex:460
|
||||
#: lib/bds/desktop/shell_live.ex:641
|
||||
#: lib/bds/desktop/shell_live.ex:673
|
||||
#: lib/bds/desktop/shell_live.ex:684
|
||||
#: lib/bds/desktop/shell_live.ex:695
|
||||
#: lib/bds/desktop/shell_live.ex:441
|
||||
#: lib/bds/desktop/shell_live.ex:454
|
||||
#: lib/bds/desktop/shell_live.ex:648
|
||||
#: lib/bds/desktop/shell_live.ex:680
|
||||
#: lib/bds/desktop/shell_live.ex:691
|
||||
#: lib/bds/desktop/shell_live.ex:702
|
||||
#: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Add Gallery Images"
|
||||
msgstr "Ajouter des images à la galerie"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:696
|
||||
#: lib/bds/desktop/shell_live.ex:703
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Failed to process %{path}: %{reason}"
|
||||
msgstr "Impossible de traiter %{path} : %{reason}"
|
||||
@@ -3445,12 +3445,12 @@ msgstr "renommé"
|
||||
msgid "untracked"
|
||||
msgstr "non suivi"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:792
|
||||
#: lib/bds/desktop/shell_live.ex:801
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Blogmark"
|
||||
msgstr "Blogmark"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:835
|
||||
#: lib/bds/desktop/shell_live.ex:844
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open a project before importing a blogmark."
|
||||
msgstr "Ouvrez un projet avant d’importer un blogmark."
|
||||
@@ -3491,7 +3491,7 @@ msgstr "Supprimer"
|
||||
msgid "Failed to copy bookmarklet to clipboard"
|
||||
msgstr "Échec de la copie du bookmarklet dans le presse-papiers"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:804
|
||||
#: lib/bds/desktop/shell_live.ex:813
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The project this blogmark targets does not exist here."
|
||||
msgstr "Le projet ciblé par ce blogmark n'existe pas ici."
|
||||
@@ -3578,12 +3578,12 @@ msgstr "Paramètres IA enregistrés."
|
||||
msgid "Could not load models"
|
||||
msgstr "Impossible de charger les modèles"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:187
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Could not save AI settings"
|
||||
msgstr "Impossible d'enregistrer les paramètres IA"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:182
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:184
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Loaded %{count} models."
|
||||
msgstr "%{count} modèles chargés."
|
||||
@@ -3593,7 +3593,17 @@ msgstr "%{count} modèles chargés."
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:173
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:174
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The endpoint returned no models. You can still type a model name manually."
|
||||
msgstr "Le point de terminaison n'a renvoyé aucun modèle. Vous pouvez toujours saisir un nom de modèle manuellement."
|
||||
|
||||
#: lib/bds/tui.ex:28
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Blogging Desktop Server"
|
||||
msgstr "Blogging Desktop Server"
|
||||
|
||||
#: lib/bds/tui.ex:30
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Press q to quit."
|
||||
msgstr "Appuyez sur q pour quitter."
|
||||
|
||||
@@ -71,8 +71,8 @@ msgid "AI Assistant"
|
||||
msgstr "Assistente IA"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:208
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:193
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:211
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "AI Settings"
|
||||
msgstr "Impostazioni IA"
|
||||
@@ -336,7 +336,7 @@ msgstr "Autore"
|
||||
msgid "Auto"
|
||||
msgstr "Automatico"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:448
|
||||
#: lib/bds/desktop/shell_live.ex:442
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:234
|
||||
#: lib/bds/desktop/shell_live/media_editor.ex:171
|
||||
#: lib/bds/desktop/shell_live/media_editor.ex:364
|
||||
@@ -486,7 +486,7 @@ msgstr "Valori predefiniti delle categorie, opzioni di rendering e collegamento
|
||||
msgid "Category name is required"
|
||||
msgstr "Il nome della categoria è obbligatorio"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:771
|
||||
#: lib/bds/desktop/shell_live.ex:780
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:87
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:233
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:323
|
||||
@@ -1063,7 +1063,7 @@ msgstr "Galleria"
|
||||
msgid "Generate Site"
|
||||
msgstr "Genera sito"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:772
|
||||
#: lib/bds/desktop/shell_live.ex:781
|
||||
#: lib/bds/desktop/shell_live/socket_state.ex:109
|
||||
#: lib/bds/ui/sidebar.ex:789
|
||||
#, elixir-autogen, elixir-format
|
||||
@@ -1077,7 +1077,7 @@ msgid "Git Diff"
|
||||
msgstr "Diff Git"
|
||||
|
||||
#: lib/bds/desktop/shell_data.ex:226
|
||||
#: lib/bds/desktop/shell_live.ex:768
|
||||
#: lib/bds/desktop/shell_live.ex:777
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:171
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Git Log"
|
||||
@@ -1200,9 +1200,9 @@ msgstr "Definizioni di importazione"
|
||||
msgid "Import failed: %{error}"
|
||||
msgstr "Importazione non riuscita: %{error}"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:598
|
||||
#: lib/bds/desktop/shell_live.ex:884
|
||||
#: lib/bds/desktop/shell_live.ex:890
|
||||
#: lib/bds/desktop/shell_live.ex:605
|
||||
#: lib/bds/desktop/shell_live.ex:893
|
||||
#: lib/bds/desktop/shell_live.ex:899
|
||||
#: lib/bds/desktop/shell_live/sidebar_create.ex:47
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Import media"
|
||||
@@ -1784,7 +1784,7 @@ msgstr "Altro"
|
||||
msgid "Other (%{count})"
|
||||
msgstr "Altro (%{count})"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:767
|
||||
#: lib/bds/desktop/shell_live.ex:776
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:83
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Output"
|
||||
@@ -2538,7 +2538,7 @@ msgstr "Nome del tag"
|
||||
msgid "Tags"
|
||||
msgstr "Tag"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:766
|
||||
#: lib/bds/desktop/shell_live.ex:775
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:54
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Tasks"
|
||||
@@ -3218,12 +3218,12 @@ msgstr "Benvenuto nell’assistente IA"
|
||||
msgid "Comparing database and filesystem metadata"
|
||||
msgstr "Confronto tra i metadati del database e del filesystem"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:674
|
||||
#: lib/bds/desktop/shell_live.ex:681
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Added %{count} images to post"
|
||||
msgstr "%{count} immagini aggiunte al post"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:642
|
||||
#: lib/bds/desktop/shell_live.ex:649
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Added %{title}"
|
||||
msgstr "%{title} aggiunto"
|
||||
@@ -3243,18 +3243,18 @@ msgstr "Guida per l'utente finale per flussi editoriali, media, modelli, traduzi
|
||||
msgid "Image Import Concurrency"
|
||||
msgstr "Importazione simultanea immagini"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:447
|
||||
#: lib/bds/desktop/shell_live.ex:460
|
||||
#: lib/bds/desktop/shell_live.ex:641
|
||||
#: lib/bds/desktop/shell_live.ex:673
|
||||
#: lib/bds/desktop/shell_live.ex:684
|
||||
#: lib/bds/desktop/shell_live.ex:695
|
||||
#: lib/bds/desktop/shell_live.ex:441
|
||||
#: lib/bds/desktop/shell_live.ex:454
|
||||
#: lib/bds/desktop/shell_live.ex:648
|
||||
#: lib/bds/desktop/shell_live.ex:680
|
||||
#: lib/bds/desktop/shell_live.ex:691
|
||||
#: lib/bds/desktop/shell_live.ex:702
|
||||
#: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Add Gallery Images"
|
||||
msgstr "Aggiungi immagini alla galleria"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:696
|
||||
#: lib/bds/desktop/shell_live.ex:703
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Failed to process %{path}: %{reason}"
|
||||
msgstr "Impossibile elaborare %{path}: %{reason}"
|
||||
@@ -3445,12 +3445,12 @@ msgstr "rinominato"
|
||||
msgid "untracked"
|
||||
msgstr "non tracciato"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:792
|
||||
#: lib/bds/desktop/shell_live.ex:801
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Blogmark"
|
||||
msgstr "Blogmark"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:835
|
||||
#: lib/bds/desktop/shell_live.ex:844
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open a project before importing a blogmark."
|
||||
msgstr "Apri un progetto prima di importare un blogmark."
|
||||
@@ -3491,7 +3491,7 @@ msgstr "Elimina"
|
||||
msgid "Failed to copy bookmarklet to clipboard"
|
||||
msgstr "Impossibile copiare il bookmarklet negli appunti"
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:804
|
||||
#: lib/bds/desktop/shell_live.ex:813
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The project this blogmark targets does not exist here."
|
||||
msgstr "Il progetto a cui punta questo blogmark non esiste qui."
|
||||
@@ -3578,12 +3578,12 @@ msgstr "Impostazioni IA salvate."
|
||||
msgid "Could not load models"
|
||||
msgstr "Impossibile caricare i modelli"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:187
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Could not save AI settings"
|
||||
msgstr "Impossibile salvare le impostazioni IA"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:182
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:184
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Loaded %{count} models."
|
||||
msgstr "Caricati %{count} modelli."
|
||||
@@ -3593,7 +3593,17 @@ msgstr "Caricati %{count} modelli."
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:173
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:174
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The endpoint returned no models. You can still type a model name manually."
|
||||
msgstr "L'endpoint non ha restituito alcun modello. Puoi comunque digitare manualmente il nome di un modello."
|
||||
|
||||
#: lib/bds/tui.ex:28
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Blogging Desktop Server"
|
||||
msgstr "Blogging Desktop Server"
|
||||
|
||||
#: lib/bds/tui.ex:30
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Press q to quit."
|
||||
msgstr "Premi q per uscire."
|
||||
|
||||
@@ -84,8 +84,8 @@ msgid "AI Assistant"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:208
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:193
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:211
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "AI Settings"
|
||||
msgstr ""
|
||||
@@ -349,7 +349,7 @@ msgstr ""
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:448
|
||||
#: lib/bds/desktop/shell_live.ex:442
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:234
|
||||
#: lib/bds/desktop/shell_live/media_editor.ex:171
|
||||
#: lib/bds/desktop/shell_live/media_editor.ex:364
|
||||
@@ -499,7 +499,7 @@ msgstr ""
|
||||
msgid "Category name is required"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:771
|
||||
#: lib/bds/desktop/shell_live.ex:780
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:87
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:233
|
||||
#: lib/bds/desktop/shell_live/chat_editor.ex:323
|
||||
@@ -1076,7 +1076,7 @@ msgstr ""
|
||||
msgid "Generate Site"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:772
|
||||
#: lib/bds/desktop/shell_live.ex:781
|
||||
#: lib/bds/desktop/shell_live/socket_state.ex:109
|
||||
#: lib/bds/ui/sidebar.ex:789
|
||||
#, elixir-autogen, elixir-format
|
||||
@@ -1090,7 +1090,7 @@ msgid "Git Diff"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_data.ex:226
|
||||
#: lib/bds/desktop/shell_live.ex:768
|
||||
#: lib/bds/desktop/shell_live.ex:777
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:171
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Git Log"
|
||||
@@ -1213,9 +1213,9 @@ msgstr ""
|
||||
msgid "Import failed: %{error}"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:598
|
||||
#: lib/bds/desktop/shell_live.ex:884
|
||||
#: lib/bds/desktop/shell_live.ex:890
|
||||
#: lib/bds/desktop/shell_live.ex:605
|
||||
#: lib/bds/desktop/shell_live.ex:893
|
||||
#: lib/bds/desktop/shell_live.ex:899
|
||||
#: lib/bds/desktop/shell_live/sidebar_create.ex:47
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Import media"
|
||||
@@ -1797,7 +1797,7 @@ msgstr ""
|
||||
msgid "Other (%{count})"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:767
|
||||
#: lib/bds/desktop/shell_live.ex:776
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:83
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Output"
|
||||
@@ -2551,7 +2551,7 @@ msgstr ""
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:766
|
||||
#: lib/bds/desktop/shell_live.ex:775
|
||||
#: lib/bds/desktop/shell_live/panel_renderer.ex:54
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Tasks"
|
||||
@@ -3231,12 +3231,12 @@ msgstr ""
|
||||
msgid "Comparing database and filesystem metadata"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:674
|
||||
#: lib/bds/desktop/shell_live.ex:681
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Added %{count} images to post"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:642
|
||||
#: lib/bds/desktop/shell_live.ex:649
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Added %{title}"
|
||||
msgstr ""
|
||||
@@ -3256,18 +3256,18 @@ msgstr ""
|
||||
msgid "Image Import Concurrency"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:447
|
||||
#: lib/bds/desktop/shell_live.ex:460
|
||||
#: lib/bds/desktop/shell_live.ex:641
|
||||
#: lib/bds/desktop/shell_live.ex:673
|
||||
#: lib/bds/desktop/shell_live.ex:684
|
||||
#: lib/bds/desktop/shell_live.ex:695
|
||||
#: lib/bds/desktop/shell_live.ex:441
|
||||
#: lib/bds/desktop/shell_live.ex:454
|
||||
#: lib/bds/desktop/shell_live.ex:648
|
||||
#: lib/bds/desktop/shell_live.ex:680
|
||||
#: lib/bds/desktop/shell_live.ex:691
|
||||
#: lib/bds/desktop/shell_live.ex:702
|
||||
#: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Add Gallery Images"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:696
|
||||
#: lib/bds/desktop/shell_live.ex:703
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Failed to process %{path}: %{reason}"
|
||||
msgstr ""
|
||||
@@ -3458,12 +3458,12 @@ msgstr ""
|
||||
msgid "untracked"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:792
|
||||
#: lib/bds/desktop/shell_live.ex:801
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Blogmark"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:835
|
||||
#: lib/bds/desktop/shell_live.ex:844
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open a project before importing a blogmark."
|
||||
msgstr ""
|
||||
@@ -3504,7 +3504,7 @@ msgstr ""
|
||||
msgid "Failed to copy bookmarklet to clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live.ex:804
|
||||
#: lib/bds/desktop/shell_live.ex:813
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The project this blogmark targets does not exist here."
|
||||
msgstr ""
|
||||
@@ -3591,12 +3591,12 @@ msgstr ""
|
||||
msgid "Could not load models"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:187
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Could not save AI settings"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:182
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:184
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Loaded %{count} models."
|
||||
msgstr ""
|
||||
@@ -3606,7 +3606,17 @@ msgstr ""
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:173
|
||||
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:174
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The endpoint returned no models. You can still type a model name manually."
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/tui.ex:28
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Blogging Desktop Server"
|
||||
msgstr ""
|
||||
|
||||
#: lib/bds/tui.ex:30
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Press q to quit."
|
||||
msgstr ""
|
||||
|
||||
62
specs/server.allium
Normal file
62
specs/server.allium
Normal file
@@ -0,0 +1,62 @@
|
||||
-- allium: 1
|
||||
-- Headless Server Mode (issue #26, phase 1)
|
||||
-- Scope: extension (client/server split — SSH transport for TUI and GUI)
|
||||
-- Distilled from: lib/bds/server.ex, lib/bds/application.ex, lib/bds/tui.ex
|
||||
|
||||
entity BootMode {
|
||||
kind: desktop | server
|
||||
-- Resolved from the BDS_MODE environment variable at application start.
|
||||
-- Anything other than "server" (case-insensitive) is desktop.
|
||||
}
|
||||
|
||||
entity SshKeyMaterial {
|
||||
dir: String -- <data dir>/ssh, next to the database
|
||||
host_key: String -- ssh_host_rsa_key, generated on first boot
|
||||
authorized_keys: String -- created empty (mode 600) on first boot
|
||||
|
||||
-- Invariant: key material lives in the private app data dir,
|
||||
-- never in the repo or an application priv dir.
|
||||
}
|
||||
|
||||
surface ServerRuntimeSurface {
|
||||
facing _: ServerRuntime
|
||||
|
||||
provides:
|
||||
ApplicationStarted(mode)
|
||||
SshClientConnected()
|
||||
}
|
||||
|
||||
rule ModeResolution {
|
||||
when: ApplicationStarted(mode)
|
||||
-- BDS_MODE environment variable decides the mode once per boot
|
||||
ensures: BootMode.created(kind: mode)
|
||||
}
|
||||
|
||||
rule DesktopBoot {
|
||||
when: ApplicationStarted(mode: desktop)
|
||||
-- Unchanged behavior: wx window shell, loopback HTTP endpoint.
|
||||
ensures: DesktopWindowStarted()
|
||||
}
|
||||
|
||||
rule ServerBoot {
|
||||
when: ApplicationStarted(mode: server)
|
||||
-- Headless: no wx children at all. Works on macOS, Windows, Linux.
|
||||
ensures: LoopbackEndpointStarted()
|
||||
-- HTTP endpoint stays bound to 127.0.0.1; never exposed directly
|
||||
ensures: CliSyncWatcherStarted()
|
||||
ensures: SshDaemonStarted()
|
||||
-- ExRatatui.SSH.Daemon: public-key auth only (no passwords),
|
||||
-- tcpip_tunnel_out enabled so GUI clients tunnel to the loopback
|
||||
-- endpoint through the same SSH connection and the same keys
|
||||
ensures: SshKeyMaterial.created()
|
||||
-- host key generated and authorized_keys touched on first boot only
|
||||
}
|
||||
|
||||
rule TuiSession {
|
||||
when: SshClientConnected()
|
||||
-- Each SSH client gets its own TUI app instance on the server;
|
||||
-- only terminal cells cross the wire.
|
||||
ensures: TuiAppMounted()
|
||||
-- UI locale comes from the server-side UI language setting,
|
||||
-- identical for every client
|
||||
}
|
||||
@@ -127,7 +127,9 @@ defmodule BDS.Scripting.JobTest do
|
||||
_running_job = wait_for_job(job.id, &(&1.status == :running))
|
||||
|
||||
assert :ok = BDS.Scripting.cancel_job(job.id)
|
||||
assert_receive :job_cleanup_ran, 1_000
|
||||
# Generous timeout: cleanup runs in a separate shutdown path and 1s
|
||||
# flakes when the full suite loads the machine.
|
||||
assert_receive :job_cleanup_ran, 5_000
|
||||
|
||||
cancelled_job = wait_for_job(job.id, &(&1.status == :cancelled))
|
||||
assert cancelled_job.finished_at != nil
|
||||
|
||||
111
test/bds/server_test.exs
Normal file
111
test/bds/server_test.exs
Normal file
@@ -0,0 +1,111 @@
|
||||
defmodule BDS.ServerTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
alias BDS.Server
|
||||
|
||||
describe "mode/1" do
|
||||
test "defaults to :desktop" do
|
||||
assert Server.mode(nil) == :desktop
|
||||
assert Server.mode("") == :desktop
|
||||
end
|
||||
|
||||
test "recognizes server mode" do
|
||||
assert Server.mode("server") == :server
|
||||
assert Server.mode("SERVER") == :server
|
||||
end
|
||||
|
||||
test "unknown values fall back to :desktop" do
|
||||
assert Server.mode("garbage") == :desktop
|
||||
end
|
||||
end
|
||||
|
||||
describe "ensure_ssh_dir!/1" do
|
||||
setup do
|
||||
dir = Path.join(System.tmp_dir!(), "bds-ssh-test-#{System.unique_integer([:positive])}")
|
||||
on_exit(fn -> File.rm_rf!(dir) end)
|
||||
{:ok, dir: dir}
|
||||
end
|
||||
|
||||
test "creates the directory and an empty authorized_keys file", %{dir: dir} do
|
||||
assert Server.ensure_ssh_dir!(dir) == dir
|
||||
assert File.dir?(dir)
|
||||
|
||||
keys_path = Path.join(dir, "authorized_keys")
|
||||
assert File.exists?(keys_path)
|
||||
assert File.read!(keys_path) == ""
|
||||
|
||||
%File.Stat{mode: mode} = File.stat!(keys_path)
|
||||
assert Bitwise.band(mode, 0o777) == 0o600
|
||||
end
|
||||
|
||||
test "leaves an existing authorized_keys file untouched", %{dir: dir} do
|
||||
File.mkdir_p!(dir)
|
||||
keys_path = Path.join(dir, "authorized_keys")
|
||||
File.write!(keys_path, "ssh-ed25519 AAAA... user@host\n")
|
||||
|
||||
Server.ensure_ssh_dir!(dir)
|
||||
|
||||
assert File.read!(keys_path) == "ssh-ed25519 AAAA... user@host\n"
|
||||
end
|
||||
end
|
||||
|
||||
describe "daemon_opts/1" do
|
||||
setup do
|
||||
dir = Path.join(System.tmp_dir!(), "bds-ssh-test-#{System.unique_integer([:positive])}")
|
||||
on_exit(fn -> File.rm_rf!(dir) end)
|
||||
{:ok, dir: dir}
|
||||
end
|
||||
|
||||
test "serves the TUI app over public-key auth only, with GUI tunneling", %{dir: dir} do
|
||||
opts = Server.daemon_opts(dir)
|
||||
|
||||
assert opts[:mod] == BDS.TUI
|
||||
assert opts[:auth_methods] == ~c"publickey"
|
||||
assert opts[:tcpip_tunnel_out] == true
|
||||
refute Keyword.has_key?(opts, :pwdfun)
|
||||
|
||||
assert opts[:system_dir] == String.to_charlist(dir)
|
||||
assert opts[:user_dir] == String.to_charlist(dir)
|
||||
end
|
||||
|
||||
test "generates a host key on first use and reuses it", %{dir: dir} do
|
||||
Server.daemon_opts(dir)
|
||||
key_path = Path.join(dir, "ssh_host_rsa_key")
|
||||
assert File.exists?(key_path)
|
||||
first = File.read!(key_path)
|
||||
|
||||
Server.daemon_opts(dir)
|
||||
assert File.read!(key_path) == first
|
||||
end
|
||||
|
||||
test "uses the configured SSH port", %{dir: dir} do
|
||||
assert Server.daemon_opts(dir)[:port] == Application.get_env(:bds, :server)[:ssh_port]
|
||||
end
|
||||
end
|
||||
|
||||
describe "BDS.Application.mode_children/2" do
|
||||
test "server mode starts endpoint, cli sync watcher, and the SSH daemon" do
|
||||
children = BDS.Application.mode_children(:server, :prod)
|
||||
|
||||
assert {BDS.Desktop.Server, []} in children
|
||||
assert BDS.CliSync.Watcher in children
|
||||
# BDS.Server wraps ExRatatui.SSH.Daemon, deferring key generation to
|
||||
# process start so building this list never touches the data dir.
|
||||
assert BDS.Server in children
|
||||
end
|
||||
|
||||
test "server mode starts no wx window children" do
|
||||
children = BDS.Application.mode_children(:server, :prod)
|
||||
|
||||
refute Enum.any?(children, fn
|
||||
{Desktop.Window, _} -> true
|
||||
_other -> false
|
||||
end)
|
||||
end
|
||||
|
||||
test "desktop mode delegates to desktop_children" do
|
||||
assert BDS.Application.mode_children(:desktop, :test) ==
|
||||
BDS.Application.desktop_children(:test)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user