Compare commits

..

13 Commits

Author SHA1 Message Date
Kieran Eglin a9d5407071 Bumped version 2024-03-24 15:42:03 -07:00
Kieran 37c1da8014 Added GHCR to README (#115) 2024-03-24 15:40:16 -07:00
Kieran Eglin 937a14985f One more GHCR shot 2024-03-24 15:15:43 -07:00
Kieran Eglin a6a20c111f Added GHCR tags 2024-03-24 15:13:12 -07:00
Kieran Eglin 2bb96edc00 Merge branch 'master' of https://github.com/kieraneglin/pinchflat 2024-03-24 15:07:13 -07:00
Kieran Eglin 87f07ff634 Testing GHCR support 2024-03-24 15:07:00 -07:00
Kieran dd60cb5419 Improve preflight file permissions check (#114)
* Added startup permissions check with more helpful message

* Updated README

* Fixes faulty test
2024-03-24 15:06:08 -07:00
Kieran Eglin 610b00566b Added logging for job reset task 2024-03-24 09:28:05 -07:00
Kieran aba15ed886 Adds a method for retrying stuck jobs (#112) 2024-03-24 09:21:01 -07:00
Kieran Eglin 87da716fdf Adds platform option when building docker containers 2024-03-24 09:20:07 -07:00
Kieran Eglin 45f056c6e0 Enabled perf profiler for erlang JIT 2024-03-22 23:34:56 -07:00
Kieran Eglin 11d160008c Updated Docker image generation for GH action 2024-03-22 23:15:51 -07:00
Kieran Eglin ba0a1ba100 Out of alpha, into beta 2024-03-22 12:43:38 -07:00
11 changed files with 165 additions and 56 deletions
+15 -1
View File
@@ -7,6 +7,10 @@ on:
description: 'Docker Image Tag'
required: true
default: 'dev'
platforms:
description: 'Build Platforms'
required: true
default: 'linux/amd64,linux/arm64'
jobs:
docker:
@@ -24,9 +28,19 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
platforms: ${{ github.event.inputs.platforms }}
push: true
file: ./selfhosted.Dockerfile
tags: 'keglin/pinchflat:${{ github.event.inputs.image_tag }}'
tags: |
keglin/pinchflat:${{ github.event.inputs.image_tag }}
ghcr.io/${{ github.repository_owner }}/pinchflat:${{ github.event.inputs.image_tag }}
-28
View File
@@ -21,31 +21,3 @@ alias Pinchflat.FastIndexing.YoutubeRss
alias Pinchflat.Metadata.MetadataFileHelpers
alias Pinchflat.SlowIndexing.FileFollowerServer
defmodule IexHelpers do
def last_media_item do
Repo.one(from m in MediaItem, limit: 1)
end
def details(type) do
source =
case type do
:playlist -> playlist_url()
:channel -> channel_url()
end
YtDlpCollection.get_source_details(source)
end
def ids(type) do
source =
case type do
:playlist -> playlist_url()
:channel -> channel_url()
end
YtDlpCollection.get_media_attributes_for_collection(source)
end
end
import IexHelpers
+25 -4
View File
@@ -61,12 +61,29 @@ If it doesn't work for your use case, please make a feature request! You can als
Simply search for Pinchflat in the Community Apps store!
### Portainer
Docker Compose file:
```yaml
version: '3'
services:
pinchflat:
image: keglin/pinchflat:latest
ports:
- '8945:8945'
volumes:
- /host/path/to/config:/config
- /host/path/to/downloads:/downloads
```
### Docker
1. Create two directories on your host machine: one for storing config and one for storing downloaded media. Make sure they're both writable by the user running the Docker container.
2. Prepare the docker image in one of the two ways below:
- **From Docker Hub:** `docker pull keglin/pinchflat:latest`, or;
- **Building locally:** `docker build . --file selfhosted.Dockerfile -t keglin/pinchflat:latest`
- **From GHCR:** `docker pull ghcr.io/kieraneglin/pinchflat:latest`
- NOTE: also available on Docker Hub at `keglin/pinchflat:latest`
- **Building locally:** `docker build . --file selfhosted.Dockerfile -t ghcr.io/kieraneglin/pinchflat:latest`
3. Run the container:
```bash
@@ -76,10 +93,14 @@ docker run \
-p 8945:8945 \
-v /host/path/to/config:/config \
-v /host/path/to/downloads:/downloads \
keglin/pinchflat:latest
ghcr.io/kieraneglin/pinchflat:latest
```
NOTE: it's recommended to not run the container as root. Doing so can create permission issues if other apps need to work with the downloaded media. If you need to run any command as root, you can run `su` from the container's shell as there is no password set for the root user.
### IMPORTANT: File permissions
You _must_ ensure the host directories you've mounted are writable by the user running the Docker container. If you get a permission error follow the steps it suggests. See [#106](https://github.com/kieraneglin/pinchflat/issues/106) for more.
It's recommended to not run the container as root. Doing so can create permission issues if other apps need to work with the downloaded media. If you need to run any command as root, you can run `su` from the container's shell as there is no password set for the root user.
## Authentication
+13 -17
View File
@@ -14,7 +14,6 @@ defmodule Pinchflat.Boot.PreJobStartupTasks do
alias Pinchflat.Repo
alias Pinchflat.Settings
alias Pinchflat.Filesystem.FilesystemHelpers
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, %{}, opts)
@@ -31,33 +30,30 @@ defmodule Pinchflat.Boot.PreJobStartupTasks do
"""
@impl true
def init(state) do
reset_executing_jobs()
apply_default_settings()
ensure_directories_are_writeable()
rename_old_job_workers()
{:ok, state}
end
# If a node cannot gracefully shut down, the currently executing jobs get stuck
# in the "executing" state. This is a problem because the job runner will not
# pick them up again
defp reset_executing_jobs do
{count, _} =
Oban.Job
|> where(state: "executing")
|> Repo.update_all(set: [state: "retryable"])
Logger.info("Reset #{count} executing jobs")
end
defp apply_default_settings do
Settings.fetch!(:onboarding, true)
Settings.fetch!(:pro_enabled, false)
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
# 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
+16 -5
View File
@@ -20,6 +20,21 @@ defmodule Pinchflat.Filesystem.FilesystemHelpers do
filepath
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 """
Writes content to a file, creating directories as needed.
Takes the same args as File.write!/3.
@@ -27,11 +42,7 @@ defmodule Pinchflat.Filesystem.FilesystemHelpers do
Returns :ok | raises on error
"""
def write_p!(filepath, content, modes \\ []) do
filepath
|> Path.dirname()
|> File.mkdir_p!()
File.write!(filepath, content, modes)
:ok = write_p(filepath, content, modes)
end
@doc """
+61
View File
@@ -5,6 +5,10 @@ defmodule Pinchflat.Release do
"""
@app :pinchflat
require Logger
alias Pinchflat.Filesystem.FilesystemHelpers
def migrate do
load_app()
@@ -18,6 +22,36 @@ defmodule Pinchflat.Release do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
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
Application.fetch_env!(@app, :ecto_repos)
end
@@ -25,4 +59,31 @@ defmodule Pinchflat.Release do
defp load_app do
Application.load(@app)
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
+1 -1
View File
@@ -4,7 +4,7 @@ defmodule Pinchflat.MixProject do
def project do
[
app: :pinchflat,
version: "0.1.0-alpha.8",
version: "0.1.1",
elixir: "~> 1.16",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
cd -P -- "$(dirname -- "$0")"
exec ./pinchflat eval Pinchflat.Release.check_file_permissions
+7
View File
@@ -1,4 +1,11 @@
#!/bin/sh
/app/bin/check_file_permissions
if [ $? -ne 0 ]; then
echo "Filesystem error. Exiting."
exit 1
fi
/app/bin/migrate
cd -P -- "$(dirname -- "$0")"
+1
View File
@@ -39,6 +39,7 @@ RUN mix local.hex --force && \
# set build ENV
ENV MIX_ENV="prod"
ENV ERL_FLAGS="+JPperf true"
# install mix dependencies
COPY mix.exs mix.lock ./
@@ -34,6 +34,29 @@ defmodule Pinchflat.Filesystem.FilesystemHelpersTest do
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
File.rm!(filepath)
end
end
describe "write_p!/3" do
test "writes content to a file" do
filepath = FilesystemHelpers.generate_metadata_tmpfile(:json)