Added startup permissions check with more helpful message

This commit is contained in:
Kieran Eglin 2024-03-24 14:43:38 -07:00
parent 610b00566b
commit 122aa6137f
No known key found for this signature in database
GPG key ID: 193984967FCF432D
6 changed files with 113 additions and 25 deletions

View file

@ -14,7 +14,6 @@ defmodule Pinchflat.Boot.PreJobStartupTasks do
alias Pinchflat.Repo alias Pinchflat.Repo
alias Pinchflat.Settings alias Pinchflat.Settings
alias Pinchflat.Filesystem.FilesystemHelpers
def start_link(opts \\ []) do def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, %{}, opts) GenServer.start_link(__MODULE__, %{}, opts)
@ -33,7 +32,6 @@ defmodule Pinchflat.Boot.PreJobStartupTasks do
def init(state) do def init(state) do
reset_executing_jobs() reset_executing_jobs()
apply_default_settings() apply_default_settings()
ensure_directories_are_writeable()
rename_old_job_workers() rename_old_job_workers()
{:ok, state} {:ok, state}
@ -56,21 +54,6 @@ defmodule Pinchflat.Boot.PreJobStartupTasks do
Settings.fetch!(:pro_enabled, false) Settings.fetch!(:pro_enabled, false)
end end
defp ensure_directories_are_writeable do
directories = [
Application.get_env(:pinchflat, :media_directory),
Application.get_env(:pinchflat, :tmpfile_directory),
Application.get_env(:pinchflat, :metadata_directory)
]
Enum.each(directories, fn dir ->
file = Path.join([dir, ".keep"])
# This will fail if the directory is not writeable, stopping boot
FilesystemHelpers.write_p!(file, "")
end)
end
# As part of a large refactor, I ended up moving a bunch of workers around. This # As part of a large refactor, I ended up moving a bunch of workers around. This
# is a problem because the workers are stored in the database and the runner # is a problem because the workers are stored in the database and the runner
# will try to run the OLD jobs. This is also why these tasks run before the job # will try to run the OLD jobs. This is also why these tasks run before the job

View file

@ -20,6 +20,21 @@ defmodule Pinchflat.Filesystem.FilesystemHelpers do
filepath filepath
end end
@doc """
Writes content to a file, creating directories as needed.
Takes the same args as File.write/3.
Returns :ok | {:error, any()}
"""
def write_p(file, content, modes \\ []) do
dirname = Path.dirname(file)
case File.mkdir_p(dirname) do
:ok -> File.write(file, content, modes)
err -> err
end
end
@doc """ @doc """
Writes content to a file, creating directories as needed. Writes content to a file, creating directories as needed.
Takes the same args as File.write!/3. Takes the same args as File.write!/3.
@ -27,11 +42,7 @@ defmodule Pinchflat.Filesystem.FilesystemHelpers do
Returns :ok | raises on error Returns :ok | raises on error
""" """
def write_p!(filepath, content, modes \\ []) do def write_p!(filepath, content, modes \\ []) do
filepath :ok = write_p(filepath, content, modes)
|> Path.dirname()
|> File.mkdir_p!()
File.write!(filepath, content, modes)
end end
@doc """ @doc """

View file

@ -5,6 +5,10 @@ defmodule Pinchflat.Release do
""" """
@app :pinchflat @app :pinchflat
require Logger
alias Pinchflat.Filesystem.FilesystemHelpers
def migrate do def migrate do
load_app() load_app()
@ -18,6 +22,36 @@ defmodule Pinchflat.Release do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
end end
def check_file_permissions do
load_app()
directories = [
"/config",
"/downloads",
Application.get_env(:pinchflat, :media_directory),
Application.get_env(:pinchflat, :tmpfile_directory),
Application.get_env(:pinchflat, :metadata_directory)
]
Enum.each(directories, fn dir ->
Logger.info("Checking permissions for #{dir}")
filepath = Path.join([dir, ".keep"])
case FilesystemHelpers.write_p(filepath, "") do
:ok ->
Logger.info("Permissions OK")
{:error, :eacces} ->
Logger.error(permission_denied_screed(dir))
raise "Permission denied"
err ->
Logger.error("Permissions check failed: #{inspect(err)}")
raise "Unknown error"
end
end)
end
defp repos do defp repos do
Application.fetch_env!(@app, :ecto_repos) Application.fetch_env!(@app, :ecto_repos)
end end
@ -25,4 +59,31 @@ defmodule Pinchflat.Release do
defp load_app do defp load_app do
Application.load(@app) Application.load(@app)
end end
defp permission_denied_screed(dir) do
"""
The directory "#{dir}" is not writeable by the Docker container.
Please ensure that the directory exists and is writeable by the Docker
container. All setups are different, but you may be able to run something
like this on the *host*:
chown nobody -R <host path that maps to #{dir}>
chmod 755 -R <host path that maps to #{dir}>
Swapping in your real host path. Then, you should set the user running
this container by editing your `docker run` command like so:
docker run --user 99:100 <rest of the command>
Or adding `user: '99:100'` to the Pinchflat service of your Docker Compose
file. Again, there are many ways to do this depending on your setup and
this is just one example. See issue #106 in the Pinchflat Github for more.
No matter the case, this _is_ a permissions error and allowing the container
to write to the directory is the only way to fix it. It is not recommended
to run the container as `root` because files created by Pinchflat may not
be accessible to other apps that want to modify them.
"""
end
end end

View file

@ -0,0 +1,3 @@
#!/bin/sh
cd -P -- "$(dirname -- "$0")"
exec ./pinchflat eval Pinchflat.Release.check_file_permissions

View file

@ -1,4 +1,11 @@
#!/bin/sh #!/bin/sh
/app/bin/check_file_permissions
if [ $? -ne 0 ]; then
echo "Filesystem error. Exiting."
exit 1
fi
/app/bin/migrate /app/bin/migrate
cd -P -- "$(dirname -- "$0")" cd -P -- "$(dirname -- "$0")"

View file

@ -34,12 +34,12 @@ defmodule Pinchflat.Filesystem.FilesystemHelpersTest do
end end
end end
describe "write_p!/3" do describe "write_p/3" do
test "writes content to a file" do test "writes content to a file" do
filepath = FilesystemHelpers.generate_metadata_tmpfile(:json) filepath = FilesystemHelpers.generate_metadata_tmpfile(:json)
content = "{}" content = "{}"
assert :ok = FilesystemHelpers.write_p!(filepath, content) assert :ok = FilesystemHelpers.write_p(filepath, content)
assert File.read!(filepath) == content assert File.read!(filepath) == content
File.rm!(filepath) File.rm!(filepath)
@ -50,7 +50,30 @@ defmodule Pinchflat.Filesystem.FilesystemHelpersTest do
filepath = Path.join([tmpfile_directory, "foo", "bar", "file.json"]) filepath = Path.join([tmpfile_directory, "foo", "bar", "file.json"])
content = "{}" content = "{}"
assert :ok = FilesystemHelpers.write_p!(filepath, content) assert :ok = FilesystemHelpers.write_p(filepath, content)
assert File.read!(filepath) == content
File.rm!(filepath)
end
end
describe "write_p!/3" do
test "writes content to a file" do
filepath = FilesystemHelpers.generate_metadata_tmpfile(:json)
content = "{}"
assert :ok = FilesystemHelpers.write_p(filepath, content)
assert File.read!(filepath) == content
File.rm!(filepath)
end
test "creates directories as needed" do
tmpfile_directory = Application.get_env(:pinchflat, :tmpfile_directory)
filepath = Path.join([tmpfile_directory, "foo", "bar", "file.json"])
content = "{}"
assert :ok = FilesystemHelpers.write_p(filepath, content)
assert File.read!(filepath) == content assert File.read!(filepath) == content
File.rm!(filepath) File.rm!(filepath)