diff --git a/arty.yml b/arty.yml index b40a0a0..7f00df0 100644 --- a/arty.yml +++ b/arty.yml @@ -147,6 +147,20 @@ envs: JELLY_TRAEFIK_ENABLED: true JELLY_COMPOSE_PROJECT_NAME: jelly JELLY_TRAEFIK_HOST: jelly.pivoine.art + # Media Stack (Jellyfin, Filestash, Koel) + MEDIA_TRAEFIK_ENABLED: true + MEDIA_COMPOSE_PROJECT_NAME: media + MEDIA_JELLYFIN_IMAGE: jellyfin/jellyfin:latest + MEDIA_JELLYFIN_TRAEFIK_HOST: jellyfin.media.pivoine.art + MEDIA_FILESTASH_IMAGE: machines/filestash:latest + MEDIA_FILESTASH_TRAEFIK_HOST: filestash.media.pivoine.art + MEDIA_FILESTASH_CANARY: true + MEDIA_KOEL_IMAGE: phanan/koel:latest + MEDIA_KOEL_TRAEFIK_HOST: koel.media.pivoine.art + MEDIA_KOEL_DB_NAME: koel + MEDIA_KOEL_DEBUG: false + MEDIA_KOEL_MEMORY_LIMIT: 512M + MEDIA_KOEL_STREAMING_METHOD: x-sendfile # PairDrop DROP_TRAEFIK_ENABLED: true DROP_COMPOSE_PROJECT_NAME: drop diff --git a/compose.yaml b/compose.yaml index 6cb236c..896864e 100644 --- a/compose.yaml +++ b/compose.yaml @@ -7,12 +7,10 @@ include: - tandoor/compose.yaml - scrapy/compose.yaml - n8n/compose.yaml - - stash/compose.yaml - links/compose.yaml - vault/compose.yaml - joplin/compose.yaml - kit/compose.yaml - - jelly/compose.yaml - drop/compose.yaml - ai/compose.yaml - asciinema/compose.yaml @@ -22,6 +20,7 @@ include: - sablier/compose.yaml - proxy/compose.yaml - watch/compose.yaml + - media/compose.yaml networks: compose_network: diff --git a/core/postgres/init/01-init-databases.sh b/core/postgres/init/01-init-databases.sh index c501b11..364835a 100644 --- a/core/postgres/init/01-init-databases.sh +++ b/core/postgres/init/01-init-databases.sh @@ -11,35 +11,39 @@ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-E -- Create databases for compose services -- Main application database SELECT 'CREATE DATABASE directus' - WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'directus')\gexec + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'directus')\\gexec -- Umami analytics database SELECT 'CREATE DATABASE umami' - WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'umami')\gexec + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'umami')\\gexec -- n8n workflow automation database SELECT 'CREATE DATABASE n8n' - WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'n8n')\gexec + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'n8n')\\gexec -- Linkwarden bookmark manager database SELECT 'CREATE DATABASE linkwarden' - WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'linkwarden')\gexec + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'linkwarden')\\gexec -- Joplin note-taking server database SELECT 'CREATE DATABASE joplin' - WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'joplin')\gexec + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'joplin')\\gexec -- Mattermost chat platform database SELECT 'CREATE DATABASE mattermost' - WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'mattermost')\gexec + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'mattermost')\\gexec -- Tandoor recipe manager database SELECT 'CREATE DATABASE tandoor' - WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'tandoor')\gexec + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'tandoor')\\gexec -- Asciinema terminal recording server database SELECT 'CREATE DATABASE asciinema' - WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'asciinema')\gexec + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'asciinema')\\gexec + + -- Koel music streaming database + SELECT 'CREATE DATABASE koel' + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'koel')\\gexec -- Grant privileges to all databases GRANT ALL PRIVILEGES ON DATABASE directus TO $POSTGRES_USER; @@ -50,11 +54,12 @@ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-E GRANT ALL PRIVILEGES ON DATABASE mattermost TO $POSTGRES_USER; GRANT ALL PRIVILEGES ON DATABASE tandoor TO $POSTGRES_USER; GRANT ALL PRIVILEGES ON DATABASE asciinema TO $POSTGRES_USER; + GRANT ALL PRIVILEGES ON DATABASE koel TO $POSTGRES_USER; -- Log success SELECT 'Compose databases initialized:' AS status; SELECT datname FROM pg_database - WHERE datname IN ('directus', 'umami', 'n8n', 'linkwarden', 'joplin', 'mattermost', 'tandoor', 'asciinema') + WHERE datname IN ('directus', 'umami', 'n8n', 'linkwarden', 'joplin', 'mattermost', 'tandoor', 'asciinema', 'koel') ORDER BY datname; EOSQL @@ -71,4 +76,5 @@ echo " • joplin - Note-taking server database" echo " • mattermost - Chat platform database" echo " • tandoor - Recipe manager database" echo " • asciinema - Terminal recording server database" +echo " • koel - Music streaming server database" echo "" diff --git a/jelly/compose.yaml b/jelly/compose.yaml deleted file mode 100644 index 5ff0fb7..0000000 --- a/jelly/compose.yaml +++ /dev/null @@ -1,43 +0,0 @@ -services: - jellyfin: - image: jellyfin/jellyfin:latest - container_name: ${JELLY_COMPOSE_PROJECT_NAME}_app - restart: unless-stopped - volumes: - - jellyfin_config:/config - - jellyfin_cache:/cache - - /mnt/hidrive/users/valknar/Pictures:/media/pictures:ro - - /mnt/hidrive/users/valknar/Videos:/media/videos:ro - environment: - TZ: ${TIMEZONE:-Europe/Berlin} - networks: - - compose_network - labels: - - 'traefik.enable=${JELLY_TRAEFIK_ENABLED}' - # HTTP to HTTPS redirect - - 'traefik.http.middlewares.${JELLY_COMPOSE_PROJECT_NAME}-redirect-web-secure.redirectscheme.scheme=https' - - 'traefik.http.routers.${JELLY_COMPOSE_PROJECT_NAME}-web.middlewares=${JELLY_COMPOSE_PROJECT_NAME}-redirect-web-secure' - - 'traefik.http.routers.${JELLY_COMPOSE_PROJECT_NAME}-web.rule=Host(`${JELLY_TRAEFIK_HOST}`)' - - 'traefik.http.routers.${JELLY_COMPOSE_PROJECT_NAME}-web.entrypoints=web' - # HTTPS router - - 'traefik.http.routers.${JELLY_COMPOSE_PROJECT_NAME}-web-secure.rule=Host(`${JELLY_TRAEFIK_HOST}`)' - - 'traefik.http.routers.${JELLY_COMPOSE_PROJECT_NAME}-web-secure.tls.certresolver=resolver' - - 'traefik.http.routers.${JELLY_COMPOSE_PROJECT_NAME}-web-secure.entrypoints=web-secure' - - 'traefik.http.middlewares.${JELLY_COMPOSE_PROJECT_NAME}-web-secure-compress.compress=true' - - 'traefik.http.routers.${JELLY_COMPOSE_PROJECT_NAME}-web-secure.middlewares=${JELLY_COMPOSE_PROJECT_NAME}-web-secure-compress,security-headers@file' - # Service - - 'traefik.http.services.${JELLY_COMPOSE_PROJECT_NAME}-web-secure.loadbalancer.server.port=8096' - - 'traefik.docker.network=${NETWORK_NAME}' - # Watchtower - - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' - -volumes: - jellyfin_config: - name: ${JELLY_COMPOSE_PROJECT_NAME}_config - jellyfin_cache: - name: ${JELLY_COMPOSE_PROJECT_NAME}_cache - -networks: - compose_network: - name: ${NETWORK_NAME} - external: true diff --git a/media/compose.yaml b/media/compose.yaml new file mode 100644 index 0000000..e3f7359 --- /dev/null +++ b/media/compose.yaml @@ -0,0 +1,154 @@ +services: + # Jellyfin - Media streaming server + jellyfin: + image: ${MEDIA_JELLYFIN_IMAGE:-jellyfin/jellyfin:latest} + container_name: ${MEDIA_COMPOSE_PROJECT_NAME}_jellyfin + restart: unless-stopped + volumes: + - jellyfin_config:/config + - jellyfin_cache:/cache + - /mnt/hidrive/users/valknar/Pictures:/media/pictures:ro + - /mnt/hidrive/users/valknar/Videos:/media/videos:ro + environment: + TZ: ${TIMEZONE:-Europe/Berlin} + networks: + - compose_network + labels: + - 'traefik.enable=${MEDIA_TRAEFIK_ENABLED}' + # HTTP to HTTPS redirect + - 'traefik.http.middlewares.${MEDIA_COMPOSE_PROJECT_NAME}-jellyfin-redirect-web-secure.redirectscheme.scheme=https' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-jellyfin-web.middlewares=${MEDIA_COMPOSE_PROJECT_NAME}-jellyfin-redirect-web-secure' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-jellyfin-web.rule=Host(`${MEDIA_JELLYFIN_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-jellyfin-web.entrypoints=web' + # HTTPS router + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-jellyfin-web-secure.rule=Host(`${MEDIA_JELLYFIN_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-jellyfin-web-secure.tls.certresolver=resolver' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-jellyfin-web-secure.entrypoints=web-secure' + - 'traefik.http.middlewares.${MEDIA_COMPOSE_PROJECT_NAME}-jellyfin-web-secure-compress.compress=true' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-jellyfin-web-secure.middlewares=${MEDIA_COMPOSE_PROJECT_NAME}-jellyfin-web-secure-compress,security-headers@file' + # Service + - 'traefik.http.services.${MEDIA_COMPOSE_PROJECT_NAME}-jellyfin-web-secure.loadbalancer.server.port=8096' + - 'traefik.docker.network=${NETWORK_NAME}' + # Watchtower + - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' + + # Filestash - Web-based file manager + filestash: + image: ${MEDIA_FILESTASH_IMAGE:-machines/filestash:latest} + container_name: ${MEDIA_COMPOSE_PROJECT_NAME}_filestash + restart: unless-stopped + volumes: + - filestash_data:/app/data/state/ + tmpfs: + - /tmp:exec + environment: + TZ: ${TIMEZONE:-Europe/Berlin} + APPLICATION_URL: ${MEDIA_FILESTASH_TRAEFIK_HOST} + CANARY: ${MEDIA_FILESTASH_CANARY:-true} + networks: + - compose_network + labels: + - 'traefik.enable=${MEDIA_TRAEFIK_ENABLED}' + - 'traefik.http.middlewares.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-redirect-web-secure.redirectscheme.scheme=https' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web.middlewares=${MEDIA_COMPOSE_PROJECT_NAME}-filestash-redirect-web-secure' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web.rule=Host(`${MEDIA_FILESTASH_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web.entrypoints=web' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web-secure.rule=Host(`${MEDIA_FILESTASH_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web-secure.tls.certresolver=resolver' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web-secure.entrypoints=web-secure' + - 'traefik.http.middlewares.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web-secure-compress.compress=true' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web-secure.middlewares=${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web-secure-compress' + - 'traefik.http.services.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web-secure.loadbalancer.server.port=8334' + - 'traefik.docker.network=${NETWORK_NAME}' + - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' + + # Koel - Music streaming server + koel: + image: ${MEDIA_KOEL_IMAGE:-phanan/koel:latest} + container_name: ${MEDIA_COMPOSE_PROJECT_NAME}_koel + restart: unless-stopped + depends_on: + - koel_init + volumes: + - koel_covers:/var/www/html/public/img/covers + - koel_search_index:/var/www/html/storage/search-indexes + - /mnt/hidrive/users/valknar/Music:/music:ro + environment: + TZ: ${TIMEZONE:-Europe/Berlin} + APP_NAME: Koel + APP_ENV: production + APP_DEBUG: ${MEDIA_KOEL_DEBUG:-false} + APP_URL: https://${MEDIA_KOEL_TRAEFIK_HOST} + APP_KEY: ${MEDIA_KOEL_APP_KEY} + LOG_CHANNEL: stderr + DB_CONNECTION: pgsql + DB_HOST: ${CORE_DB_HOST} + DB_PORT: ${CORE_DB_PORT} + DB_DATABASE: ${MEDIA_KOEL_DB_NAME} + DB_USERNAME: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + MEMORY_LIMIT: ${MEDIA_KOEL_MEMORY_LIMIT:-512M} + STREAMING_METHOD: ${MEDIA_KOEL_STREAMING_METHOD:-x-sendfile} + networks: + - compose_network + labels: + - 'traefik.enable=${MEDIA_TRAEFIK_ENABLED}' + # HTTP to HTTPS redirect + - 'traefik.http.middlewares.${MEDIA_COMPOSE_PROJECT_NAME}-koel-redirect-web-secure.redirectscheme.scheme=https' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-koel-web.middlewares=${MEDIA_COMPOSE_PROJECT_NAME}-koel-redirect-web-secure' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-koel-web.rule=Host(`${MEDIA_KOEL_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-koel-web.entrypoints=web' + # HTTPS router + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-koel-web-secure.rule=Host(`${MEDIA_KOEL_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-koel-web-secure.tls.certresolver=resolver' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-koel-web-secure.entrypoints=web-secure' + - 'traefik.http.middlewares.${MEDIA_COMPOSE_PROJECT_NAME}-koel-web-secure-compress.compress=true' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-koel-web-secure.middlewares=${MEDIA_COMPOSE_PROJECT_NAME}-koel-web-secure-compress,security-headers@file' + # Service + - 'traefik.http.services.${MEDIA_COMPOSE_PROJECT_NAME}-koel-web-secure.loadbalancer.server.port=80' + - 'traefik.docker.network=${NETWORK_NAME}' + # Watchtower + - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' + + # Koel initialization container + koel_init: + image: ${MEDIA_KOEL_IMAGE:-phanan/koel:latest} + container_name: ${MEDIA_COMPOSE_PROJECT_NAME}_koel_init + restart: "no" + command: bash -c "php artisan koel:init --no-interaction && php artisan koel:admin --email=${ADMIN_EMAIL} --password=${ADMIN_PASSWORD} --no-interaction" + volumes: + - koel_covers:/var/www/html/public/img/covers + - koel_search_index:/var/www/html/storage/search-indexes + environment: + TZ: ${TIMEZONE:-Europe/Berlin} + APP_NAME: Koel + APP_ENV: production + APP_DEBUG: ${MEDIA_KOEL_DEBUG:-false} + APP_URL: https://${MEDIA_KOEL_TRAEFIK_HOST} + APP_KEY: ${MEDIA_KOEL_APP_KEY} + LOG_CHANNEL: stderr + DB_CONNECTION: pgsql + DB_HOST: ${CORE_DB_HOST} + DB_PORT: ${CORE_DB_PORT} + DB_DATABASE: ${MEDIA_KOEL_DB_NAME} + DB_USERNAME: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + networks: + - compose_network + +volumes: + jellyfin_config: + name: ${MEDIA_COMPOSE_PROJECT_NAME}_jellyfin_config + jellyfin_cache: + name: ${MEDIA_COMPOSE_PROJECT_NAME}_jellyfin_cache + filestash_data: + name: ${MEDIA_COMPOSE_PROJECT_NAME}_filestash_data + koel_covers: + name: ${MEDIA_COMPOSE_PROJECT_NAME}_koel_covers + koel_search_index: + name: ${MEDIA_COMPOSE_PROJECT_NAME}_koel_search_index + +networks: + compose_network: + name: ${NETWORK_NAME} + external: true diff --git a/stash/compose.yaml b/stash/compose.yaml deleted file mode 100644 index e97f711..0000000 --- a/stash/compose.yaml +++ /dev/null @@ -1,38 +0,0 @@ -services: - filestash: - image: ${STASH_IMAGE:-machines/filestash:latest} - container_name: ${STASH_COMPOSE_PROJECT_NAME}_app - restart: unless-stopped - volumes: - - filestash_data:/app/data/state/ - tmpfs: - - /tmp:exec - environment: - TZ: ${TIMEZONE:-Europe/Berlin} - APPLICATION_URL: ${STASH_TRAEFIK_HOST} - CANARY: ${STASH_CANARY:-true} - networks: - - compose_network - labels: - - 'traefik.enable=${STASH_TRAEFIK_ENABLED}' - - 'traefik.http.middlewares.${STASH_COMPOSE_PROJECT_NAME}-filestash-redirect-web-secure.redirectscheme.scheme=https' - - 'traefik.http.routers.${STASH_COMPOSE_PROJECT_NAME}-filestash-web.middlewares=${STASH_COMPOSE_PROJECT_NAME}-filestash-redirect-web-secure' - - 'traefik.http.routers.${STASH_COMPOSE_PROJECT_NAME}-filestash-web.rule=Host(`${STASH_TRAEFIK_HOST}`)' - - 'traefik.http.routers.${STASH_COMPOSE_PROJECT_NAME}-filestash-web.entrypoints=web' - - 'traefik.http.routers.${STASH_COMPOSE_PROJECT_NAME}-filestash-web-secure.rule=Host(`${STASH_TRAEFIK_HOST}`)' - - 'traefik.http.routers.${STASH_COMPOSE_PROJECT_NAME}-filestash-web-secure.tls.certresolver=resolver' - - 'traefik.http.routers.${STASH_COMPOSE_PROJECT_NAME}-filestash-web-secure.entrypoints=web-secure' - - 'traefik.http.middlewares.${STASH_COMPOSE_PROJECT_NAME}-filestash-web-secure-compress.compress=true' - - 'traefik.http.routers.${STASH_COMPOSE_PROJECT_NAME}-filestash-web-secure.middlewares=${STASH_COMPOSE_PROJECT_NAME}-filestash-web-secure-compress' - - 'traefik.http.services.${STASH_COMPOSE_PROJECT_NAME}-filestash-web-secure.loadbalancer.server.port=8334' - - 'traefik.docker.network=${NETWORK_NAME}' - - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' - -volumes: - filestash_data: - name: ${STASH_COMPOSE_PROJECT_NAME}_filestash_data - -networks: - compose_network: - name: ${NETWORK_NAME} - external: true