mirror of
https://github.com/ad-aures/castopod.git
synced 2026-03-13 09:31:04 +08:00
feat(docker): replace all-in-one image with FrankenPHP and Caddy based image + discard other images
- use serversideup/php as a base image - remove nginx unit base - remove app / webserver images - add bundle stage to remove pipeline dependency - update docker setup docs - edit gitlabci rules and release logic
This commit is contained in:
68
.dockerignore
Normal file
68
.dockerignore
Normal file
@@ -0,0 +1,68 @@
|
||||
.env
|
||||
|
||||
.git/
|
||||
node_modules/
|
||||
vendor/
|
||||
build/
|
||||
docs/
|
||||
scripts/
|
||||
tests/
|
||||
|
||||
#-------------------------
|
||||
# Temporary Files
|
||||
#-------------------------
|
||||
writable/cache/*
|
||||
!writable/cache/index.html
|
||||
|
||||
writable/logs/*
|
||||
!writable/logs/index.html
|
||||
|
||||
writable/session/*
|
||||
!writable/session/index.html
|
||||
|
||||
writable/temp/*
|
||||
!writable/temp/index.html
|
||||
|
||||
writable/uploads/*
|
||||
!writable/uploads/index.html
|
||||
|
||||
writable/debugbar/*
|
||||
!writable/debugbar/index.html
|
||||
|
||||
# public folder
|
||||
public/*
|
||||
!public/media
|
||||
!public/.htaccess
|
||||
!public/favicon.ico
|
||||
!public/icon*
|
||||
!public/castopod-banner*
|
||||
!public/castopod-avatar*
|
||||
!public/index.php
|
||||
!public/robots.txt
|
||||
!public/.well-known
|
||||
!public/.well-known/GDPR.yml
|
||||
|
||||
public/assets/*
|
||||
!public/assets/index.html
|
||||
|
||||
# public media folder
|
||||
!public/media/podcasts
|
||||
!public/media/persons
|
||||
!public/media/site
|
||||
|
||||
public/media/podcasts/*
|
||||
!public/media/podcasts/index.html
|
||||
|
||||
public/media/persons/*
|
||||
!public/media/persons/index.html
|
||||
|
||||
public/media/site/*
|
||||
!public/media/site/index.html
|
||||
|
||||
# Generated files
|
||||
modules/Admin/Language/*/PersonsTaxonomy.php
|
||||
|
||||
# Castopod bundle & packages
|
||||
castopod/
|
||||
castopod-*.zip
|
||||
castopod-*.tar.gz
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -175,15 +175,6 @@ public/media/site/*
|
||||
# Generated files
|
||||
modules/Admin/Language/*/PersonsTaxonomy.php
|
||||
|
||||
#-------------------------
|
||||
# Docker volumes
|
||||
#-------------------------
|
||||
|
||||
mariadb
|
||||
phpmyadmin
|
||||
sessions
|
||||
data
|
||||
|
||||
# Castopod bundle & packages
|
||||
castopod/
|
||||
castopod-*.zip
|
||||
|
||||
@@ -23,6 +23,10 @@ php-dependencies:
|
||||
expire_in: 30 mins
|
||||
paths:
|
||||
- vendor/
|
||||
rules:
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- when: on_success
|
||||
|
||||
js-dependencies:
|
||||
stage: prepare
|
||||
@@ -39,6 +43,10 @@ js-dependencies:
|
||||
expire_in: 30 mins
|
||||
paths:
|
||||
- node_modules/
|
||||
rules:
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- when: on_success
|
||||
|
||||
lint-commit-msg:
|
||||
stage: quality
|
||||
@@ -48,12 +56,10 @@ lint-commit-msg:
|
||||
- ./scripts/lint-commit-msg.sh
|
||||
dependencies:
|
||||
- js-dependencies
|
||||
only:
|
||||
- develop
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
- next
|
||||
rules:
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH =~ /^(develop|main|alpha|beta|next)$/
|
||||
|
||||
lint-php:
|
||||
stage: quality
|
||||
@@ -66,6 +72,10 @@ lint-php:
|
||||
- vendor/bin/rector process --dry-run --ansi
|
||||
dependencies:
|
||||
- php-dependencies
|
||||
rules:
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- when: on_success
|
||||
|
||||
lint-js:
|
||||
stage: quality
|
||||
@@ -76,6 +86,10 @@ lint-js:
|
||||
- pnpm run lint:css
|
||||
dependencies:
|
||||
- js-dependencies
|
||||
rules:
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- when: on_success
|
||||
|
||||
tests:
|
||||
stage: quality
|
||||
@@ -94,6 +108,10 @@ tests:
|
||||
- vendor/bin/phpunit --no-coverage
|
||||
dependencies:
|
||||
- php-dependencies
|
||||
rules:
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- when: on_success
|
||||
|
||||
bundle:
|
||||
stage: bundle
|
||||
@@ -114,14 +132,12 @@ bundle:
|
||||
name: "castopod-${CI_COMMIT_REF_SLUG}_${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- castopod
|
||||
only:
|
||||
variables:
|
||||
- $CI_PROJECT_NAMESPACE == "adaures"
|
||||
except:
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
- next
|
||||
rules:
|
||||
- if: $CI_PROJECT_NAMESPACE != "adaures"
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH =~ /^(main|alpha|beta|next)$/ || $CI_COMMIT_TAG
|
||||
when: never
|
||||
- when: on_success
|
||||
|
||||
release:
|
||||
stage: release
|
||||
@@ -145,40 +161,38 @@ release:
|
||||
artifacts:
|
||||
paths:
|
||||
- castopod
|
||||
- CP_VERSION.env
|
||||
only:
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
- next
|
||||
rules:
|
||||
- if: $CI_PROJECT_NAMESPACE != "adaures"
|
||||
when: never
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH =~ /^(main|alpha|beta|next)$/
|
||||
|
||||
website:
|
||||
stage: deploy
|
||||
trigger: adaures/castopod.org
|
||||
only:
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
rules:
|
||||
- if: $CI_PROJECT_NAMESPACE != "adaures"
|
||||
when: never
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/ && $CI_COMMIT_TAG
|
||||
|
||||
documentation:
|
||||
stage: deploy
|
||||
trigger:
|
||||
include: docs/.gitlab-ci.yml
|
||||
strategy: depend
|
||||
rules:
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- when: on_success
|
||||
|
||||
docker:
|
||||
stage: build
|
||||
trigger:
|
||||
include: docker/production/.gitlab-ci.yml
|
||||
strategy: depend
|
||||
variables:
|
||||
PARENT_PIPELINE_ID: $CI_PIPELINE_ID
|
||||
only:
|
||||
refs:
|
||||
- develop
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
- next
|
||||
variables:
|
||||
- $CI_PROJECT_NAMESPACE == "adaures"
|
||||
rules:
|
||||
- if: $CI_PROJECT_NAMESPACE != "adaures"
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH == "develop"
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/ && $CI_COMMIT_TAG
|
||||
|
||||
@@ -93,7 +93,8 @@
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
"CHANGELOG.md"
|
||||
]
|
||||
],
|
||||
"message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
|
||||
}
|
||||
],
|
||||
[
|
||||
|
||||
@@ -45,7 +45,7 @@ class MapController extends BaseController
|
||||
if (! ($found = cache($cacheName))) {
|
||||
$episodes = new EpisodeModel()
|
||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
||||
->where('location_geo is not', null)
|
||||
->where('location_geo is not')
|
||||
->findAll();
|
||||
$found = [];
|
||||
foreach ($episodes as $episode) {
|
||||
|
||||
@@ -81,14 +81,6 @@ class BaseClip extends Entity
|
||||
'updated_by' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $data
|
||||
*/
|
||||
public function __construct(?array $data = null)
|
||||
{
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
public function getJobDuration(): ?int
|
||||
{
|
||||
if ($this->job_duration === null && $this->job_started_at && $this->job_ended_at) {
|
||||
|
||||
@@ -30,22 +30,25 @@ if (! function_exists('set_podcast_metatags')) {
|
||||
$category .= $podcast->category->apple_category;
|
||||
|
||||
$schema = new Schema(
|
||||
new Thing('PodcastSeries', [
|
||||
'name' => $podcast->title,
|
||||
'headline' => $podcast->title,
|
||||
'url' => current_url(),
|
||||
'sameAs' => $podcast->link,
|
||||
'identifier' => $podcast->guid,
|
||||
'image' => $podcast->cover->feed_url,
|
||||
'description' => $podcast->description,
|
||||
'webFeed' => $podcast->feed_url,
|
||||
'accessMode' => 'auditory',
|
||||
'author' => $podcast->owner_name,
|
||||
'creator' => $podcast->owner_name,
|
||||
'publisher' => $podcast->publisher,
|
||||
'inLanguage' => $podcast->language_code,
|
||||
'genre' => $category,
|
||||
]),
|
||||
new Thing(
|
||||
props: [
|
||||
'name' => $podcast->title,
|
||||
'headline' => $podcast->title,
|
||||
'url' => current_url(),
|
||||
'sameAs' => $podcast->link,
|
||||
'identifier' => $podcast->guid,
|
||||
'image' => $podcast->cover->feed_url,
|
||||
'description' => $podcast->description,
|
||||
'webFeed' => $podcast->feed_url,
|
||||
'accessMode' => 'auditory',
|
||||
'author' => $podcast->owner_name,
|
||||
'creator' => $podcast->owner_name,
|
||||
'publisher' => $podcast->publisher,
|
||||
'inLanguage' => $podcast->language_code,
|
||||
'genre' => $category,
|
||||
],
|
||||
type: 'PodcastSeries',
|
||||
),
|
||||
);
|
||||
|
||||
/** @var HtmlHead $head */
|
||||
@@ -74,22 +77,31 @@ if (! function_exists('set_episode_metatags')) {
|
||||
function set_episode_metatags(Episode $episode): void
|
||||
{
|
||||
$schema = new Schema(
|
||||
new Thing('PodcastEpisode', [
|
||||
'url' => url_to('episode', esc($episode->podcast->handle), $episode->slug),
|
||||
'name' => $episode->title,
|
||||
'image' => $episode->cover->feed_url,
|
||||
'description' => $episode->description,
|
||||
'datePublished' => $episode->published_at->format(DATE_ATOM),
|
||||
'timeRequired' => iso8601_duration($episode->audio->duration),
|
||||
'duration' => iso8601_duration($episode->audio->duration),
|
||||
'associatedMedia' => new Thing('MediaObject', [
|
||||
'contentUrl' => $episode->audio_url,
|
||||
]),
|
||||
'partOfSeries' => new Thing('PodcastSeries', [
|
||||
'name' => $episode->podcast->title,
|
||||
'url' => $episode->podcast->link,
|
||||
]),
|
||||
]),
|
||||
new Thing(
|
||||
props: [
|
||||
'url' => url_to('episode', esc($episode->podcast->handle), $episode->slug),
|
||||
'name' => $episode->title,
|
||||
'image' => $episode->cover->feed_url,
|
||||
'description' => $episode->description,
|
||||
'datePublished' => $episode->published_at->format(DATE_ATOM),
|
||||
'timeRequired' => iso8601_duration($episode->audio->duration),
|
||||
'duration' => iso8601_duration($episode->audio->duration),
|
||||
'associatedMedia' => new Thing(
|
||||
props: [
|
||||
'contentUrl' => $episode->audio_url,
|
||||
],
|
||||
type: 'MediaObject',
|
||||
),
|
||||
'partOfSeries' => new Thing(
|
||||
props: [
|
||||
'name' => $episode->podcast->title,
|
||||
'url' => $episode->podcast->link,
|
||||
],
|
||||
type: 'PodcastSeries',
|
||||
),
|
||||
],
|
||||
type: 'PodcastEpisode',
|
||||
),
|
||||
);
|
||||
|
||||
/** @var HtmlHead $head */
|
||||
@@ -131,32 +143,50 @@ if (! function_exists('set_episode_metatags')) {
|
||||
if (! function_exists('set_post_metatags')) {
|
||||
function set_post_metatags(Post $post): void
|
||||
{
|
||||
$socialMediaPosting = new Thing('SocialMediaPosting', [
|
||||
'@id' => url_to('post', esc($post->actor->username), $post->id),
|
||||
'datePublished' => $post->published_at->format(DATE_ATOM),
|
||||
'author' => new Thing('Person', [
|
||||
'name' => $post->actor->display_name,
|
||||
'url' => $post->actor->uri,
|
||||
]),
|
||||
'text' => $post->message,
|
||||
]);
|
||||
$socialMediaPosting = new Thing(
|
||||
props: [
|
||||
'@id' => url_to('post', esc($post->actor->username), $post->id),
|
||||
'datePublished' => $post->published_at->format(DATE_ATOM),
|
||||
'author' => new Thing(
|
||||
props: [
|
||||
'name' => $post->actor->display_name,
|
||||
'url' => $post->actor->uri,
|
||||
],
|
||||
type: 'Person',
|
||||
),
|
||||
'text' => $post->message,
|
||||
],
|
||||
type: 'SocialMediaPosting',
|
||||
);
|
||||
|
||||
if ($post->episode_id !== null) {
|
||||
$socialMediaPosting->__set('sharedContent', new Thing('Audio', [
|
||||
'headline' => $post->episode->title,
|
||||
'url' => $post->episode->link,
|
||||
'author' => new Thing('Person', [
|
||||
'name' => $post->episode->podcast->owner_name,
|
||||
]),
|
||||
]));
|
||||
$socialMediaPosting->__set('sharedContent', new Thing(
|
||||
props: [
|
||||
'headline' => $post->episode->title,
|
||||
'url' => $post->episode->link,
|
||||
'author' => new Thing(
|
||||
props: [
|
||||
'name' => $post->episode->podcast->owner_name,
|
||||
],
|
||||
type: 'Person',
|
||||
),
|
||||
],
|
||||
type: 'Audio',
|
||||
));
|
||||
} elseif ($post->preview_card instanceof PreviewCard) {
|
||||
$socialMediaPosting->__set('sharedContent', new Thing('WebPage', [
|
||||
'headline' => $post->preview_card->title,
|
||||
'url' => $post->preview_card->url,
|
||||
'author' => new Thing('Person', [
|
||||
'name' => $post->preview_card->author_name,
|
||||
]),
|
||||
]));
|
||||
$socialMediaPosting->__set('sharedContent', new Thing(
|
||||
props: [
|
||||
'headline' => $post->preview_card->title,
|
||||
'url' => $post->preview_card->url,
|
||||
'author' => new Thing(
|
||||
props: [
|
||||
'name' => $post->preview_card->author_name,
|
||||
],
|
||||
type: 'Person',
|
||||
),
|
||||
],
|
||||
type: 'WebPage',
|
||||
));
|
||||
}
|
||||
|
||||
$schema = new Schema($socialMediaPosting);
|
||||
@@ -183,21 +213,27 @@ if (! function_exists('set_post_metatags')) {
|
||||
if (! function_exists('set_episode_comment_metatags')) {
|
||||
function set_episode_comment_metatags(EpisodeComment $episodeComment): void
|
||||
{
|
||||
$schema = new Schema(new Thing('SocialMediaPosting', [
|
||||
'@id' => url_to(
|
||||
'episode-comment',
|
||||
esc($episodeComment->actor->username),
|
||||
$episodeComment->episode->slug,
|
||||
$episodeComment->id,
|
||||
),
|
||||
'datePublished' => $episodeComment->created_at->format(DATE_ATOM),
|
||||
'author' => new Thing('Person', [
|
||||
'name' => $episodeComment->actor->display_name,
|
||||
'url' => $episodeComment->actor->uri,
|
||||
]),
|
||||
'text' => $episodeComment->message,
|
||||
'upvoteCount' => $episodeComment->likes_count,
|
||||
]));
|
||||
$schema = new Schema(new Thing(
|
||||
props: [
|
||||
'@id' => url_to(
|
||||
'episode-comment',
|
||||
esc($episodeComment->actor->username),
|
||||
$episodeComment->episode->slug,
|
||||
$episodeComment->id,
|
||||
),
|
||||
'datePublished' => $episodeComment->created_at->format(DATE_ATOM),
|
||||
'author' => new Thing(
|
||||
props: [
|
||||
'name' => $episodeComment->actor->display_name,
|
||||
'url' => $episodeComment->actor->uri,
|
||||
],
|
||||
type: 'Person',
|
||||
),
|
||||
'text' => $episodeComment->message,
|
||||
'upvoteCount' => $episodeComment->likes_count,
|
||||
],
|
||||
type: 'SocialMediaPosting',
|
||||
));
|
||||
|
||||
/** @var HtmlHead $head */
|
||||
$head = service('html_head');
|
||||
|
||||
@@ -82,10 +82,6 @@ class RssFeed extends SimpleXMLElement
|
||||
return $newChild;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return $newChild;
|
||||
}
|
||||
|
||||
$node->appendChild($no->createTextNode($value));
|
||||
|
||||
return $newChild;
|
||||
|
||||
@@ -122,7 +122,6 @@ class ClipModel extends Model
|
||||
$found[$key] = new VideoClip($videoClip->toArray());
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
return $found;
|
||||
}
|
||||
|
||||
|
||||
@@ -371,7 +371,7 @@ class EpisodeModel extends UuidModel
|
||||
$episodeCommentsCount = new EpisodeCommentModel()
|
||||
->builder()
|
||||
->select('episode_id, COUNT(*) as `comments_count`')
|
||||
->where('in_reply_to_id', null)
|
||||
->where('in_reply_to_id')
|
||||
->groupBy('episode_id')
|
||||
->getCompiledSelect();
|
||||
|
||||
@@ -379,8 +379,8 @@ class EpisodeModel extends UuidModel
|
||||
->builder()
|
||||
->select('fediverse_posts.episode_id as episode_id, COUNT(*) as `comments_count`')
|
||||
->join('fediverse_posts as fp', 'fediverse_posts.id = fp.in_reply_to_id')
|
||||
->where('fediverse_posts.in_reply_to_id', null)
|
||||
->where('fediverse_posts.episode_id IS NOT', null)
|
||||
->where('fediverse_posts.in_reply_to_id')
|
||||
->where('fediverse_posts.episode_id IS NOT')
|
||||
->groupBy('fediverse_posts.episode_id')
|
||||
->getCompiledSelect();
|
||||
|
||||
@@ -404,7 +404,7 @@ class EpisodeModel extends UuidModel
|
||||
$episodePostsCount = $this->builder()
|
||||
->select('episodes.id, COUNT(*) as `posts_count`')
|
||||
->join('fediverse_posts', 'episodes.id = fediverse_posts.episode_id')
|
||||
->where('in_reply_to_id', null)
|
||||
->where('in_reply_to_id')
|
||||
->groupBy('episodes.id')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
@@ -176,7 +176,7 @@ class PodcastModel extends Model
|
||||
'`' . $prefix . 'fediverse_posts`.`published_at` <= UTC_TIMESTAMP()',
|
||||
null,
|
||||
false,
|
||||
)->orWhere('fediverse_posts.published_at', null)
|
||||
)->orWhere('fediverse_posts.published_at')
|
||||
->groupEnd()
|
||||
->groupBy('podcasts.actor_id')
|
||||
->orderBy('max_published_at', 'DESC');
|
||||
|
||||
@@ -50,7 +50,7 @@ class PostModel extends FediversePostModel
|
||||
return $this->where([
|
||||
'episode_id' => $episodeId,
|
||||
])
|
||||
->where('in_reply_to_id', null)
|
||||
->where('in_reply_to_id')
|
||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
||||
->orderBy('published_at', 'DESC')
|
||||
->findAll();
|
||||
|
||||
@@ -10,38 +10,38 @@
|
||||
"adaures/castopod-plugins-manager": "dev-main",
|
||||
"adaures/ipcat-php": "^v1.0.0",
|
||||
"adaures/podcast-persons-taxonomy": "^v1.0.1",
|
||||
"aws/aws-sdk-php": "^3.356.33",
|
||||
"aws/aws-sdk-php": "^3.369.35",
|
||||
"chrisjean/php-ico": "^1.0.4",
|
||||
"cocur/slugify": "^v4.6.0",
|
||||
"codeigniter4/framework": "4.6.3",
|
||||
"cocur/slugify": "4.7.1",
|
||||
"codeigniter4/framework": "4.6.5",
|
||||
"codeigniter4/settings": "v2.2.0",
|
||||
"codeigniter4/shield": "1.2.0",
|
||||
"codeigniter4/tasks": "dev-develop",
|
||||
"geoip2/geoip2": "3.2.0",
|
||||
"geoip2/geoip2": "3.3.0",
|
||||
"james-heinrich/getid3": "^2.0.0-beta6",
|
||||
"league/commonmark": "^2.7.1",
|
||||
"league/commonmark": "^2.8.0",
|
||||
"league/html-to-markdown": "5.1.1",
|
||||
"melbahja/seo": "^v2.1.1",
|
||||
"michalsn/codeigniter4-uuid": "1.3.0",
|
||||
"melbahja/seo": "3.0.2",
|
||||
"michalsn/codeigniter4-uuid": "1.3.1",
|
||||
"mpratt/embera": "^2.0.42",
|
||||
"opawg/user-agents-v2-php": "dev-main",
|
||||
"phpseclib/phpseclib": "~2.0.49",
|
||||
"vlucas/phpdotenv": "5.6.2",
|
||||
"phpseclib/phpseclib": "~2.0.51",
|
||||
"vlucas/phpdotenv": "5.6.3",
|
||||
"whichbrowser/parser": "^v2.1.8",
|
||||
"yassinedoghri/codeigniter-vite": "^2.1.0",
|
||||
"yassinedoghri/php-icons": "1.3.0",
|
||||
"yassinedoghri/podcast-feed": "dev-main"
|
||||
},
|
||||
"require-dev": {
|
||||
"captainhook/captainhook": "^5.25.11",
|
||||
"captainhook/captainhook": "^5.28.3",
|
||||
"codeigniter/phpstan-codeigniter": "1.5.4",
|
||||
"mikey179/vfsstream": "^v1.6.12",
|
||||
"phpstan/extension-installer": "^1.4.3",
|
||||
"phpstan/phpstan": "^2.1.30",
|
||||
"phpunit/phpunit": "^12.4.0",
|
||||
"rector/rector": "^2.2.1",
|
||||
"symplify/coding-standard": "^12.4.3",
|
||||
"symplify/easy-coding-standard": "^12.6.0"
|
||||
"phpstan/phpstan": "^2.1.39",
|
||||
"phpunit/phpunit": "^13.0.3",
|
||||
"rector/rector": "^2.3.6",
|
||||
"symplify/coding-standard": "^13.0.0",
|
||||
"symplify/easy-coding-standard": "^13.0.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
||||
1404
composer.lock
generated
1404
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -4,9 +4,9 @@ stages:
|
||||
docker-build-rolling:
|
||||
stage: build
|
||||
image:
|
||||
name: docker.io/docker:23.0.3-dind
|
||||
name: docker.io/docker:29.2-dind
|
||||
services:
|
||||
- docker:23.0.3-dind
|
||||
- docker:29.2-dind
|
||||
variables:
|
||||
TAG: $CI_COMMIT_BRANCH
|
||||
DOCKER_BUILDKIT: 1
|
||||
@@ -17,22 +17,16 @@ docker-build-rolling:
|
||||
- cp ${DOCKER_HUB_CONFIG} /root/.docker/config.json
|
||||
- docker context create tls-environment
|
||||
- docker buildx create --use tls-environment
|
||||
- docker buildx build --push --platform=linux/amd64 --file=docker/production/castopod/Dockerfile --tag=${DOCKER_IMAGE_CASTOPOD}:${TAG} .
|
||||
- docker buildx build --push --platform=linux/amd64 --file=docker/production/web-server/Dockerfile --tag=${DOCKER_IMAGE_WEB_SERVER}:${TAG} .
|
||||
- docker buildx build --push --platform=linux/amd64 --file=docker/production/app/Dockerfile --tag=${DOCKER_IMAGE_APP}:${TAG} .
|
||||
needs:
|
||||
- pipeline: $PARENT_PIPELINE_ID
|
||||
job: bundle
|
||||
only:
|
||||
refs:
|
||||
- develop
|
||||
- docker buildx build --secret id=maxmind-licence-key,env=MAXMIND_LICENCE_KEY --push --platform=linux/amd64 --file=docker/production/Dockerfile --tag=${DOCKER_IMAGE_CASTOPOD}:${TAG} .
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == 'develop'
|
||||
|
||||
docker-build-main-release:
|
||||
docker-build-release:
|
||||
stage: build
|
||||
image:
|
||||
name: docker.io/docker:23.0.3-dind
|
||||
name: docker.io/docker:29.2-dind
|
||||
services:
|
||||
- docker:23.0.3-dind
|
||||
- docker:29.2-dind
|
||||
variables:
|
||||
DOCKER_BUILDKIT: 1
|
||||
DOCKER_HOST: tcp://docker:2376
|
||||
@@ -40,50 +34,15 @@ docker-build-main-release:
|
||||
script:
|
||||
- mkdir -p /root/.docker
|
||||
- cp ${DOCKER_HUB_CONFIG} /root/.docker/config.json
|
||||
- export CP_VERSION=$(cat CP_VERSION.env)
|
||||
# extract Castopod version from tag (remove "v" prefix)
|
||||
- export CP_VERSION=$(echo "$CI_COMMIT_TAG" | sed 's/^v//')
|
||||
# extract pre release identifier (eg. alpha, beta, next, ...) from CP_VERSION or "latest" if none exists
|
||||
- export CP_TAG=$(echo "$CP_VERSION" | sed 's/^[^-]*-\([^.]*\)\..*/\1/; t; s/.*/latest/')
|
||||
- docker context create tls-environment
|
||||
- docker buildx create --use tls-environment
|
||||
- docker buildx build --push --platform=linux/amd64 --file=docker/production/castopod/Dockerfile --tag=${DOCKER_IMAGE_CASTOPOD}:${CP_VERSION} --tag=${DOCKER_IMAGE_CASTOPOD}:latest .
|
||||
- docker buildx build --push --platform=linux/amd64 --file=docker/production/web-server/Dockerfile --tag=${DOCKER_IMAGE_WEB_SERVER}:${CP_VERSION} --tag=${DOCKER_IMAGE_WEB_SERVER}:latest .
|
||||
- docker buildx build --push --platform=linux/amd64 --file=docker/production/app/Dockerfile --tag=${DOCKER_IMAGE_APP}:${CP_VERSION} --tag=${DOCKER_IMAGE_APP}:latest .
|
||||
- docker buildx build --secret id=maxmind-licence-key,env=MAXMIND_LICENCE_KEY --push --platform=linux/amd64 --file=docker/production/Dockerfile --tag=${DOCKER_IMAGE_CASTOPOD}:${CP_VERSION} --tag=${DOCKER_IMAGE_CASTOPOD}:${CP_TAG} .
|
||||
# when --platform=linux/amd64,linux/arm64: amd64 image takes too long to be pushed as it needs to wait for arm64 to be built
|
||||
# --> build and push amd64 image to be pushed first, then overwrite manifest after building arm64
|
||||
- docker buildx build --push --platform=linux/amd64,linux/arm64 --file=docker/production/castopod/Dockerfile --tag=${DOCKER_IMAGE_CASTOPOD}:${CP_VERSION} --tag=${DOCKER_IMAGE_CASTOPOD}:latest .
|
||||
needs:
|
||||
- pipeline: $PARENT_PIPELINE_ID
|
||||
job: release
|
||||
only:
|
||||
refs:
|
||||
- main
|
||||
|
||||
docker-build-prerelease:
|
||||
stage: build
|
||||
image:
|
||||
name: docker.io/docker:23.0.3-dind
|
||||
services:
|
||||
- docker:23.0.3-dind
|
||||
variables:
|
||||
TAG: $CI_COMMIT_BRANCH
|
||||
DOCKER_BUILDKIT: 1
|
||||
DOCKER_HOST: tcp://docker:2376
|
||||
DOCKER_TLS_CERTDIR: "/certs"
|
||||
script:
|
||||
- mkdir -p /root/.docker
|
||||
- cp ${DOCKER_HUB_CONFIG} /root/.docker/config.json
|
||||
- export CP_VERSION=$(cat CP_VERSION.env)
|
||||
- docker context create tls-environment
|
||||
- docker buildx create --use tls-environment
|
||||
- docker buildx build --push --platform=linux/amd64 --file=docker/production/castopod/Dockerfile --tag=${DOCKER_IMAGE_CASTOPOD}:${CP_VERSION} --tag=${DOCKER_IMAGE_CASTOPOD}:${TAG} .
|
||||
- docker buildx build --push --platform=linux/amd64 --file=docker/production/web-server/Dockerfile --tag=${DOCKER_IMAGE_WEB_SERVER}:${CP_VERSION} --tag=${DOCKER_IMAGE_WEB_SERVER}:${TAG} .
|
||||
- docker buildx build --push --platform=linux/amd64 --file=docker/production/app/Dockerfile --tag=${DOCKER_IMAGE_APP}:${CP_VERSION} --tag=${DOCKER_IMAGE_APP}:${TAG} .
|
||||
# when --platform=linux/amd64,linux/arm64: amd64 image takes too long to be pushed as it needs to wait for arm64 to be built
|
||||
# --> build and push amd64 image to be pushed first, then overwrite manifest after building arm64
|
||||
- docker buildx build --push --platform=linux/amd64,linux/arm64 --file=docker/production/castopod/Dockerfile --tag=${DOCKER_IMAGE_CASTOPOD}:${CP_VERSION} --tag=${DOCKER_IMAGE_CASTOPOD}:${TAG} .
|
||||
needs:
|
||||
- pipeline: $PARENT_PIPELINE_ID
|
||||
job: release
|
||||
only:
|
||||
refs:
|
||||
- alpha
|
||||
- beta
|
||||
- next
|
||||
# --> build and push amd64 image first, then overwrite manifest after building arm64
|
||||
- docker buildx build --secret id=maxmind-licence-key,env=MAXMIND_LICENCE_KEY --push --platform=linux/amd64,linux/arm64 --file=docker/production/Dockerfile --tag=${DOCKER_IMAGE_CASTOPOD}:${CP_VERSION} --tag=${DOCKER_IMAGE_CASTOPOD}:${CP_TAG} .
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
|
||||
135
docker/production/Dockerfile
Normal file
135
docker/production/Dockerfile
Normal file
@@ -0,0 +1,135 @@
|
||||
####################################################
|
||||
# Castopod's Production Dockerfile
|
||||
####################################################
|
||||
# An optimized Dockerfile for production using
|
||||
# multi-stage builds:
|
||||
# 1. BUNDLE castopod
|
||||
# 2. BUILD the FrankenPHP/debian based prod image
|
||||
#---------------------------------------------------
|
||||
|
||||
ARG PHP_VERSION="8.4"
|
||||
|
||||
####################################################
|
||||
# BUNDLE STAGE
|
||||
# -------------------------------------------------
|
||||
# Bundle castopod for production using
|
||||
# a PHP / Alpine image
|
||||
#---------------------------------------------------
|
||||
FROM php:${PHP_VERSION}-alpine3.23 AS bundle
|
||||
|
||||
LABEL maintainer="Yassine Doghri <yassine@doghri.fr>"
|
||||
|
||||
COPY . /castopod-src
|
||||
WORKDIR /castopod-src
|
||||
|
||||
COPY --from=composer:2.9 /usr/bin/composer /usr/local/bin/composer
|
||||
|
||||
RUN \
|
||||
# download GeoLite2-City archive and extract it to writable/uploads
|
||||
--mount=type=secret,id=maxmind-licence-key,env=MAXMIND_LICENCE_KEY \
|
||||
wget -c "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=$MAXMIND_LICENCE_KEY&suffix=tar.gz" -O - | tar -xz -C ./writable/uploads/ \
|
||||
# rename extracted archives' folders
|
||||
&& mv ./writable/uploads/GeoLite2-City* ./writable/uploads/GeoLite2-City
|
||||
|
||||
RUN \
|
||||
# install composer globally
|
||||
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
|
||||
# install node and pnpm
|
||||
&& apk add --no-cache \
|
||||
nodejs \
|
||||
pnpm \
|
||||
git \
|
||||
rsync \
|
||||
# install production dependencies only using the --no-dev option
|
||||
&& composer install --no-dev --prefer-dist --no-ansi --no-interaction --no-progress --ignore-platform-reqs \
|
||||
# install js dependencies based on lockfile
|
||||
&& pnpm install --frozen-lockfile \
|
||||
# build all production static assets (css, js, images, icons, fonts, etc.)
|
||||
&& pnpm run build \
|
||||
# create castopod folder bundle: uses .rsync-filter (-F) file to copy only needed files
|
||||
&& rsync -aF . /castopod
|
||||
|
||||
|
||||
####################################################
|
||||
# BUILD STAGE
|
||||
# -------------------------------------------------
|
||||
# Define production image based on FrankenPHP /
|
||||
# Debian with services managed by s6-overlay
|
||||
#---------------------------------------------------
|
||||
FROM serversideup/php:${PHP_VERSION}-frankenphp-trixie AS build
|
||||
|
||||
LABEL maintainer="Yassine Doghri <yassine@doghri.fr>"
|
||||
|
||||
USER root
|
||||
|
||||
# Latest releases available at https://github.com/aptible/supercronic/releases
|
||||
ARG SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.2.42/supercronic-linux-amd64 \
|
||||
SUPERCRONIC_SHA1SUM=b444932b81583b7860849f59fdb921217572ece2 \
|
||||
SUPERCRONIC=supercronic-linux-amd64
|
||||
|
||||
# add supercronic to handle cron jobs
|
||||
RUN \
|
||||
curl -fsSLO "$SUPERCRONIC_URL" \
|
||||
&& echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - \
|
||||
&& chmod +x "$SUPERCRONIC" \
|
||||
&& mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" \
|
||||
&& ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic
|
||||
|
||||
ARG S6_OVERLAY_VERSION=3.2.2.0
|
||||
|
||||
# add s6-overlay process manager
|
||||
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp
|
||||
RUN tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz
|
||||
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-x86_64.tar.xz /tmp
|
||||
RUN tar -C / -Jxpf /tmp/s6-overlay-x86_64.tar.xz
|
||||
|
||||
# copy s6-overlay services
|
||||
COPY --chown=www-data:www-data docker/production/s6-rc.d /etc/s6-overlay/s6-rc.d
|
||||
|
||||
# make prepare-environment executable for bootstrapping the Castopod environment
|
||||
RUN chmod +x /etc/s6-overlay/s6-rc.d/bootstrap/prepare-environment.sh
|
||||
|
||||
RUN \
|
||||
apt-get update \
|
||||
&& apt-get install -y \
|
||||
ffmpeg \
|
||||
libfreetype6-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
libpng-dev \
|
||||
libwebp-dev \
|
||||
libicu-dev \
|
||||
&& install-php-extensions \
|
||||
intl \
|
||||
mysqli \
|
||||
exif \
|
||||
gd \
|
||||
# As of PHP 7.4 we don't need to add --with-png
|
||||
&& docker-php-ext-configure gd --with-webp --with-jpeg --with-freetype
|
||||
|
||||
# copy castopod bundle from bundle stage
|
||||
COPY --from=bundle --chown=www-data:www-data /castopod /app
|
||||
|
||||
RUN \
|
||||
chmod -R 550 /app/ \
|
||||
&& chmod -R 770 /app/public/media/ \
|
||||
&& chmod -R 770 /app/writable/ \
|
||||
&& chmod 750 /app/
|
||||
|
||||
ARG \
|
||||
PHP_MEMORY_LIMIT=512M \
|
||||
PHP_MAX_EXECUTION_TIME=300 \
|
||||
PHP_UPLOAD_MAX_FILE_SIZE=512M \
|
||||
PHP_POST_MAX_SIZE=512M \
|
||||
PHP_OPCACHE_ENABLE=1
|
||||
|
||||
ENV \
|
||||
PHP_MEMORY_LIMIT=${PHP_MEMORY_LIMIT} \
|
||||
PHP_MAX_EXECUTION_TIME=${PHP_MAX_EXECUTION_TIME} \
|
||||
PHP_UPLOAD_MAX_FILE_SIZE=${PHP_UPLOAD_MAX_FILE_SIZE} \
|
||||
PHP_POST_MAX_SIZE=${PHP_POST_MAX_SIZE} \
|
||||
PHP_OPCACHE_ENABLE=${PHP_OPCACHE_ENABLE}
|
||||
|
||||
USER www-data
|
||||
|
||||
ENTRYPOINT ["docker-php-serversideup-entrypoint"]
|
||||
CMD ["/init"]
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
ENV_FILE_LOCATION=/var/www/castopod/.env
|
||||
|
||||
# Fix ownership and permissions of castopod folders
|
||||
chmod -R 750 /var/www/castopod
|
||||
chown -R root:www-data /var/www/castopod
|
||||
chown -R www-data:www-data /var/www/castopod/writable /var/www/castopod/public/media
|
||||
|
||||
. /prepare_environment.sh
|
||||
|
||||
supervisord
|
||||
@@ -1,21 +0,0 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
|
||||
[program:supercronic]
|
||||
user=www-data
|
||||
command=supercronic /crontab.txt
|
||||
autostart=true
|
||||
autorestart=unexpected
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:fpm]
|
||||
command=/usr/local/sbin/php-fpm
|
||||
autostart=true
|
||||
autorestart=unexpected
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
@@ -1,60 +0,0 @@
|
||||
{
|
||||
"listeners": {
|
||||
"*:8000": {
|
||||
"pass": "routes"
|
||||
}
|
||||
},
|
||||
"routes": [
|
||||
{
|
||||
"match": {
|
||||
"uri": "~^.+\\.(css|js|jpg|jpeg|gif|png|ico|gz|svg|svgz|ttf|otf|woff|woff2|eot|mp4|ogg|ogv|webm|webp|zip|swf|map)$"
|
||||
},
|
||||
"action": {
|
||||
"share": "/var/www/castopod/public$uri",
|
||||
"response_headers": {
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Cache-Control": "max-age=604800"
|
||||
},
|
||||
"fallback": {
|
||||
"pass": "applications/castopod"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"share": "/var/www/castopod/public$uri",
|
||||
"response_headers": {
|
||||
"X-Frame-Options": "sameorigin",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
"fallback": {
|
||||
"pass": "applications/castopod"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"applications": {
|
||||
"castopod": {
|
||||
"type": "php",
|
||||
"root": "/var/www/castopod/public/",
|
||||
"script": "index.php"
|
||||
}
|
||||
},
|
||||
"access_log": {
|
||||
"path": "/dev/stdout"
|
||||
},
|
||||
"settings": {
|
||||
"http": {
|
||||
"body_read_timeout": $CP_TIMEOUT,
|
||||
"max_body_size": $CP_MAX_BODY_SIZE_BYTES,
|
||||
"static": {
|
||||
"mime_types": {
|
||||
"text/vtt": [".vtt"],
|
||||
"text/srt": [".srt"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
ENV_FILE_LOCATION=/var/www/castopod/.env
|
||||
|
||||
. /prepare_environment.sh
|
||||
cat /config.template.json | envsubst '$CP_MAX_BODY_SIZE_BYTES$CP_TIMEOUT' > /usr/local/var/lib/unit/conf.json
|
||||
|
||||
supervisord
|
||||
@@ -1,20 +0,0 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
|
||||
[program:supercronic]
|
||||
user=www-data
|
||||
command=supercronic /crontab.txt
|
||||
autostart=true
|
||||
autorestart=unexpected
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:unit]
|
||||
command=unitd --no-daemon
|
||||
autostart=true
|
||||
autorestart=unexpected
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile_maxbytes=0
|
||||
@@ -1 +0,0 @@
|
||||
* * * * * /usr/local/bin/php /var/www/castopod/spark tasks:run >> /dev/null 2>&1
|
||||
@@ -1,6 +0,0 @@
|
||||
file_uploads = On
|
||||
memory_limit = $CP_PHP_MEMORY_LIMIT
|
||||
upload_max_filesize = $CP_MAX_BODY_SIZE
|
||||
post_max_size = $CP_MAX_BODY_SIZE
|
||||
max_execution_time = $CP_TIMEOUT
|
||||
max_input_time = $CP_TIMEOUT
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/bin/sh
|
||||
#!/command/with-contenv sh
|
||||
|
||||
ENV_FILE_LOCATION=/app/.env
|
||||
|
||||
log_error() {
|
||||
printf "\033[0;31mERROR:\033[0m $1\n"
|
||||
@@ -9,6 +11,13 @@ log_warning() {
|
||||
printf "\033[0;33mWARNING:\033[0m $1\n"
|
||||
}
|
||||
|
||||
log_info() {
|
||||
printf "\033[0;34mINFO:\033[0m $1\n"
|
||||
}
|
||||
|
||||
# Remove .env file if exists to recreate it.
|
||||
rm -f $ENV_FILE_LOCATION
|
||||
|
||||
if [ -z "${CP_BASEURL}" ]
|
||||
then
|
||||
log_error "CP_BASEURL must be set"
|
||||
@@ -16,19 +25,19 @@ fi
|
||||
|
||||
if [ -z "${CP_MEDIA_BASEURL}" ]
|
||||
then
|
||||
echo "CP_MEDIA_BASEURL is empty, using CP_BASEURL by default"
|
||||
log_info "CP_MEDIA_BASEURL is empty, using CP_BASEURL by default"
|
||||
CP_MEDIA_BASEURL=$CP_BASEURL
|
||||
fi
|
||||
|
||||
if [ -z "${CP_ADMIN_GATEWAY}" ]
|
||||
then
|
||||
echo "CP_ADMIN_GATEWAY is empty, using default"
|
||||
log_info "CP_ADMIN_GATEWAY is empty, using default \"cp-admin\""
|
||||
CP_ADMIN_GATEWAY="cp-admin"
|
||||
fi
|
||||
|
||||
if [ -z "${CP_AUTH_GATEWAY}" ]
|
||||
then
|
||||
echo "CP_AUTH_GATEWAY is empty, using default"
|
||||
log_info "CP_AUTH_GATEWAY is empty, using default \"cp-auth\""
|
||||
CP_AUTH_GATEWAY="cp-auth"
|
||||
fi
|
||||
|
||||
@@ -39,13 +48,13 @@ fi
|
||||
|
||||
if [ -z "${CP_DATABASE_HOSTNAME}" ]
|
||||
then
|
||||
log_warning "CP_DATABASE_HOSTNAME is empty, using default"
|
||||
log_warning "CP_DATABASE_HOSTNAME is empty, using default \"mariadb\""
|
||||
CP_DATABASE_HOSTNAME="mariadb"
|
||||
fi
|
||||
|
||||
if [ -z "${CP_DATABASE_PREFIX}" ]
|
||||
then
|
||||
echo "CP_DATABASE_PREFIX is empty, using default"
|
||||
log_info "CP_DATABASE_PREFIX is empty, using default \"cp_\""
|
||||
CP_DATABASE_PREFIX="cp_"
|
||||
fi
|
||||
|
||||
@@ -84,29 +93,28 @@ fi
|
||||
|
||||
if [ ! -z "${CP_REDIS_HOST}" ]
|
||||
then
|
||||
echo "Using redis cache handler"
|
||||
log_info "Using redis cache handler"
|
||||
CP_CACHE_HANDLER="redis"
|
||||
if [ -z "${CP_REDIS_PASSWORD}" ]
|
||||
then
|
||||
echo "CP_REDIS_PASSWORD is empty, using default"
|
||||
CP_REDIS_PASSWORD="null"
|
||||
log_error "You must set CP_REDIS_PASSWORD when using redis as a cache handler."
|
||||
else
|
||||
CP_REDIS_PASSWORD="\"${CP_REDIS_PASSWORD}\""
|
||||
fi
|
||||
|
||||
if [ -z "${CP_REDIS_PORT}" ]
|
||||
then
|
||||
echo "CP_REDIS_PORT is empty, using default"
|
||||
log_info "CP_REDIS_PORT is empty, using default port \"6379\""
|
||||
CP_REDIS_PORT="6379"
|
||||
fi
|
||||
|
||||
if [ -z "${CP_REDIS_DATABASE}" ]
|
||||
then
|
||||
echo "CP_REDIS_DATABASE is empty, using default"
|
||||
log_info "CP_REDIS_DATABASE is empty, using default \"0\""
|
||||
CP_REDIS_DATABASE="0"
|
||||
fi
|
||||
else
|
||||
echo "Using file cache handler"
|
||||
log_info "Using file cache handler"
|
||||
CP_CACHE_HANDLER="file"
|
||||
fi
|
||||
|
||||
@@ -134,28 +142,6 @@ then
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "${CP_PHP_MEMORY_LIMIT}" ]
|
||||
then
|
||||
export CP_PHP_MEMORY_LIMIT="512M"
|
||||
fi
|
||||
|
||||
if [ -z "${CP_MAX_BODY_SIZE}" ]
|
||||
then
|
||||
export CP_MAX_BODY_SIZE="512M"
|
||||
fi
|
||||
|
||||
CP_MAX_BODY_SIZE_BYTES=$(numfmt --from=iec "$CP_MAX_BODY_SIZE")
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
log_error "Failed to parse CP_MAX_BODY_SIZE ($CP_MAX_BODY_SIZE) as human readable number"
|
||||
fi
|
||||
export CP_MAX_BODY_SIZE_BYTES=$CP_MAX_BODY_SIZE_BYTES
|
||||
|
||||
if [ -z "${CP_TIMEOUT}" ]
|
||||
then
|
||||
export CP_TIMEOUT=900
|
||||
fi
|
||||
|
||||
cat << EOF > $ENV_FILE_LOCATION
|
||||
app.baseURL="${CP_BASEURL}"
|
||||
media.baseURL="${CP_MEDIA_BASEURL}"
|
||||
@@ -238,20 +224,17 @@ if [ ! -z "${CP_EMAIL_SMTP_HOST}" ]
|
||||
then
|
||||
if [ -z "${CP_EMAIL_SMTP_USERNAME}" ]
|
||||
then
|
||||
echo "When CP_EMAIL_SMTP_HOST is provided, CP_EMAIL_SMTP_USERNAME must be set"
|
||||
exit 1
|
||||
log_error "When CP_EMAIL_SMTP_HOST is provided, CP_EMAIL_SMTP_USERNAME must be set"
|
||||
fi
|
||||
|
||||
if [ -z "${CP_EMAIL_SMTP_PASSWORD}" ]
|
||||
then
|
||||
echo "When CP_EMAIL_SMTP_HOST is provided, CP_EMAIL_SMTP_PASSWORD must be set"
|
||||
exit 1
|
||||
log_error "When CP_EMAIL_SMTP_HOST is provided, CP_EMAIL_SMTP_PASSWORD must be set"
|
||||
fi
|
||||
|
||||
if [ -z "${CP_EMAIL_FROM}" ]
|
||||
then
|
||||
echo "When CP_EMAIL_SMTP_HOST is provided, CP_EMAIL_FROM must be set"
|
||||
exit 1
|
||||
log_error "When CP_EMAIL_SMTP_HOST is provided, CP_EMAIL_FROM must be set"
|
||||
fi
|
||||
|
||||
cat << EOF >> $ENV_FILE_LOCATION
|
||||
@@ -273,8 +256,7 @@ EOF
|
||||
then
|
||||
if [ "${CP_EMAIL_SMTP_CRYPTO}" != "ssl" ] && [ "${CP_EMAIL_SMTP_CRYPTO}" != "tls" ]
|
||||
then
|
||||
echo "CP_EMAIL_SMTP_CRYPTO must be ssl or tls"
|
||||
exit 1
|
||||
log_error "CP_EMAIL_SMTP_CRYPTO must be ssl or tls"
|
||||
fi
|
||||
cat << EOF >> $ENV_FILE_LOCATION
|
||||
email.SMTPCrypto=${CP_EMAIL_SMTP_CRYPTO}
|
||||
@@ -282,14 +264,14 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Using config:"
|
||||
log_info "Using config:"
|
||||
cat $ENV_FILE_LOCATION
|
||||
|
||||
#Run database migrations after 10 seconds (to wait for the database to be started)
|
||||
(sleep 10 && php spark castopod:database-update) &
|
||||
# prevent .env from being writable
|
||||
chmod -w $ENV_FILE_LOCATION
|
||||
|
||||
#Run database migrations
|
||||
/usr/local/bin/php /var/www/html/spark castopod:database-update
|
||||
|
||||
# clear cache to account for new assets and any change in data structure
|
||||
php spark cache:clear
|
||||
|
||||
#Apply php configuration
|
||||
cat /uploads.template.ini | envsubst '$CP_MAX_BODY_SIZE$CP_MAX_BODY_SIZE_BYTES$CP_TIMEOUT$CP_PHP_MEMORY_LIMIT' > /usr/local/etc/php/conf.d/uploads.ini
|
||||
/usr/local/bin/php /var/www/html/spark cache:clear
|
||||
1
docker/production/s6-rc.d/bootstrap/type
Normal file
1
docker/production/s6-rc.d/bootstrap/type
Normal file
@@ -0,0 +1 @@
|
||||
oneshot
|
||||
2
docker/production/s6-rc.d/bootstrap/up
Normal file
2
docker/production/s6-rc.d/bootstrap/up
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/command/with-contenv sh
|
||||
/etc/s6-overlay/s6-rc.d/bootstrap/prepare-environment.sh
|
||||
2
docker/production/s6-rc.d/frankenphp/run
Normal file
2
docker/production/s6-rc.d/frankenphp/run
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/command/with-contenv sh
|
||||
frankenphp run --config /etc/frankenphp/Caddyfile --adapter caddyfile
|
||||
1
docker/production/s6-rc.d/frankenphp/type
Normal file
1
docker/production/s6-rc.d/frankenphp/type
Normal file
@@ -0,0 +1 @@
|
||||
longrun
|
||||
1
docker/production/s6-rc.d/supercronic/crontab
Normal file
1
docker/production/s6-rc.d/supercronic/crontab
Normal file
@@ -0,0 +1 @@
|
||||
* * * * * /usr/local/bin/php /var/www/html/spark tasks:run >> /dev/null 2>&1
|
||||
2
docker/production/s6-rc.d/supercronic/run
Normal file
2
docker/production/s6-rc.d/supercronic/run
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/command/with-contenv sh
|
||||
supercronic /etc/s6-overlay/s6-rc.d/supercronic/crontab
|
||||
1
docker/production/s6-rc.d/supercronic/type
Normal file
1
docker/production/s6-rc.d/supercronic/type
Normal file
@@ -0,0 +1 @@
|
||||
longrun
|
||||
0
docker/production/s6-rc.d/user/contents.d/bootstrap
Normal file
0
docker/production/s6-rc.d/user/contents.d/bootstrap
Normal file
@@ -1,18 +0,0 @@
|
||||
FROM docker.io/nginx:1.29
|
||||
|
||||
COPY docker/production/web-server/entrypoint.sh /entrypoint.sh
|
||||
COPY docker/production/web-server/nginx.template.conf /nginx.template.conf
|
||||
COPY castopod/public /var/www/html
|
||||
|
||||
RUN chmod +x /entrypoint.sh && \
|
||||
apt-get update && \
|
||||
apt-get install -y curl gettext-base && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
usermod -aG www-data nginx
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=3s CMD curl --fail http://localhost || exit 1
|
||||
VOLUME /var/www/html/media
|
||||
EXPOSE 80
|
||||
WORKDIR /var/www/html
|
||||
|
||||
CMD ["/entrypoint.sh"]
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/sh
|
||||
if [ -z "${CP_APP_HOSTNAME}" ]
|
||||
then
|
||||
echo "CP_APP_HOSTNAME is empty, using default"
|
||||
export CP_APP_HOSTNAME="app"
|
||||
fi
|
||||
|
||||
if [ -z "${CP_MAX_BODY_SIZE}" ]
|
||||
then
|
||||
export CP_MAX_BODY_SIZE=512M
|
||||
fi
|
||||
|
||||
if [ -z "${CP_TIMEOUT}" ]
|
||||
then
|
||||
export CP_TIMEOUT=900
|
||||
fi
|
||||
|
||||
cat /nginx.template.conf | envsubst '$CP_APP_HOSTNAME$CP_MAX_BODY_SIZE$CP_TIMEOUT' > /etc/nginx/nginx.conf
|
||||
|
||||
nginx -g "daemon off;"
|
||||
@@ -1,80 +0,0 @@
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
types {
|
||||
text/vtt vtt;
|
||||
text/srt srt;
|
||||
}
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
|
||||
keepalive_timeout 65;
|
||||
|
||||
set_real_ip_from 10.0.0.0/8;
|
||||
set_real_ip_from 172.16.0.0/12;
|
||||
set_real_ip_from 192.168.0.0/16;
|
||||
real_ip_header X-Real-IP;
|
||||
|
||||
upstream php-handler {
|
||||
server $CP_APP_HOSTNAME:9000;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
root /var/www/html;
|
||||
|
||||
server_tokens off;
|
||||
add_header X-Frame-Options sameorigin always;
|
||||
add_header Permissions-Policy interest-cohort=();
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload;";
|
||||
client_max_body_size $CP_MAX_BODY_SIZE;
|
||||
client_body_timeout ${CP_TIMEOUT}s;
|
||||
|
||||
fastcgi_buffers 64 4K;
|
||||
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_comp_level 4;
|
||||
gzip_min_length 256;
|
||||
gzip_types application/atom+xml application/javascript application/rss+xml image/bmp image/svg+xml image/x-icon text/css text/plain text/html;
|
||||
|
||||
try_files $uri $uri/ /index.php?$args;
|
||||
index index.php index.html;
|
||||
|
||||
location ~ \.php$ {
|
||||
include fastcgi_params;
|
||||
fastcgi_intercept_errors on;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SERVER_NAME $host;
|
||||
fastcgi_pass php-handler;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/castopod/public/$fastcgi_script_name;
|
||||
try_files $uri =404;
|
||||
fastcgi_read_timeout 3600;
|
||||
fastcgi_send_timeout 3600;
|
||||
}
|
||||
|
||||
location ~* ^.+\.(css|js|jpg|jpeg|gif|png|ico|gz|svg|svgz|ttf|otf|woff|woff2|eot|mp4|ogg|ogv|webm|webp|zip|swf|map)$ {
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
expires max;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -28,12 +28,10 @@ build:
|
||||
stage: build
|
||||
script:
|
||||
- pnpm run build
|
||||
except:
|
||||
- develop
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
- next
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH =~ /^(develop|main|alpha|beta|next)$/
|
||||
when: never
|
||||
- when: on_success
|
||||
|
||||
build-production:
|
||||
extends: .documentation-setup
|
||||
@@ -47,12 +45,8 @@ build-production:
|
||||
paths:
|
||||
- docs/dist/$CI_COMMIT_REF_SLUG
|
||||
expire_in: 30 mins
|
||||
only:
|
||||
- develop
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
- next
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH =~ /^(develop|main|alpha|beta|next)$/
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
@@ -78,9 +72,5 @@ deploy:
|
||||
script:
|
||||
- rsync -avzuh -e "ssh -p $SSH_PORT" $SOURCE_FOLDER $USER@$HOST:$TEMP_DIRECTORY --progress
|
||||
- ssh $USER@$HOST -p $SSH_PORT "rsync -rtv $TEMP_DIRECTORY $DIRECTORY"
|
||||
only:
|
||||
- develop
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
- next
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH =~ /^(develop|main|alpha|beta|next)$/
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
"prepare": "astro telemetry disable"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/starlight": "^0.36.0",
|
||||
"@astrojs/starlight": "^0.37.6",
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@fontsource/rubik": "^5.2.8",
|
||||
"astro": "^5.14.1",
|
||||
"sharp": "^0.34.4",
|
||||
"starlight-openapi": "^0.20.0"
|
||||
"astro": "^5.17.2",
|
||||
"sharp": "^0.34.5",
|
||||
"starlight-openapi": "^0.22.0"
|
||||
}
|
||||
}
|
||||
|
||||
2514
docs/pnpm-lock.yaml
generated
2514
docs/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,18 @@
|
||||
---
|
||||
title: Official Docker images
|
||||
title: Official Docker image
|
||||
---
|
||||
|
||||
Castopod pushes 3 Docker images to the Docker Hub during its automated build
|
||||
process:
|
||||
Castopod publishes a single official Docker image to the Docker Hub as part of
|
||||
its automated build process:
|
||||
|
||||
- [**`castopod/castopod`**](https://hub.docker.com/r/castopod/castopod): an all
|
||||
in one castopod image using nginx unit
|
||||
- [**`castopod/app`**](https://hub.docker.com/r/castopod/app): the app bundle
|
||||
with all of Castopod dependencies
|
||||
- [**`castopod/web-server`**](https://hub.docker.com/r/castopod/web-server): an
|
||||
Nginx configuration for Castopod
|
||||
- [**`castopod/castopod`**](https://hub.docker.com/r/castopod/castopod): an
|
||||
all-in-one image integrating [FrankenPHP](https://frankenphp.dev/) and
|
||||
[Caddy](https://caddyserver.com/), optimized for production environments. It
|
||||
is based on
|
||||
[serversideup/php](https://serversideup.net/open-source/docker-php/docs/image-variations/frankenphp).
|
||||
|
||||
Additionally, Castopod requires a MySQL-compatible database. A Redis database
|
||||
can be added as a cache handler.
|
||||
Castopod requires a MySQL-compatible database to function. Optionally, a Redis
|
||||
service can be configured as the caching layer.
|
||||
|
||||
## Supported tags
|
||||
|
||||
@@ -25,18 +24,16 @@ can be added as a cache handler.
|
||||
## Example usage
|
||||
|
||||
1. Install [docker](https://docs.docker.com/get-docker/) and
|
||||
[docker-compose](https://docs.docker.com/compose/install/)
|
||||
2. Create a `docker-compose.yml` file with the following:
|
||||
[docker compose](https://docs.docker.com/compose/install/)
|
||||
2. Create a `compose.yml` file with the following:
|
||||
|
||||
```yml
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
castopod:
|
||||
image: castopod/castopod:latest
|
||||
container_name: "castopod"
|
||||
volumes:
|
||||
- castopod-media:/var/www/castopod/public/media
|
||||
- castopod-media:/app/public/media
|
||||
environment:
|
||||
MYSQL_DATABASE: castopod
|
||||
MYSQL_USER: castopod
|
||||
@@ -47,14 +44,28 @@ can be added as a cache handler.
|
||||
CP_REDIS_HOST: redis
|
||||
CP_REDIS_PASSWORD: changeme
|
||||
networks:
|
||||
- castopod
|
||||
- castopod-app
|
||||
- castopod-db
|
||||
ports:
|
||||
- 8000:8000
|
||||
- "8080:8080" # HTTP
|
||||
- "8443:8443" # HTTPS
|
||||
- "8443:8443/udp" # HTTP/3
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s # allows bootstrap/migrations time
|
||||
depends_on:
|
||||
mariadb:
|
||||
condition: service_healthy
|
||||
restart: true
|
||||
redis:
|
||||
condition: service_started
|
||||
|
||||
mariadb:
|
||||
image: mariadb:11.2
|
||||
image: mariadb:12.1
|
||||
container_name: "castopod-mariadb"
|
||||
networks:
|
||||
- castopod-db
|
||||
@@ -66,15 +77,21 @@ can be added as a cache handler.
|
||||
MYSQL_USER: castopod
|
||||
MYSQL_PASSWORD: changeme
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
start_period: 10s
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
redis:
|
||||
image: redis:7.2-alpine
|
||||
image: redis:8.4-alpine
|
||||
container_name: "castopod-redis"
|
||||
command: --requirepass changeme
|
||||
volumes:
|
||||
- castopod-cache:/data
|
||||
networks:
|
||||
- castopod
|
||||
- castopod-app
|
||||
|
||||
volumes:
|
||||
castopod-media:
|
||||
@@ -82,8 +99,9 @@ can be added as a cache handler.
|
||||
castopod-cache:
|
||||
|
||||
networks:
|
||||
castopod:
|
||||
castopod-app:
|
||||
castopod-db:
|
||||
internal: true
|
||||
```
|
||||
|
||||
You have to adapt some variables to your needs (e.g. `CP_BASEURL`,
|
||||
@@ -97,61 +115,53 @@ can be added as a cache handler.
|
||||
```
|
||||
#castopod
|
||||
castopod.example.com {
|
||||
reverse_proxy localhost:8000
|
||||
reverse_proxy localhost:8080
|
||||
}
|
||||
```
|
||||
|
||||
4. Run `docker-compose up -d`, wait for it to initialize and head on to
|
||||
4. Run `docker compose up -d`, wait for it to initialize and head on to
|
||||
`https://castopod.example.com/cp-install` to finish setting up Castopod!
|
||||
|
||||
5. You're all set, start podcasting! 🎙️🚀
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- **castopod/castopod** and **castopod/app**
|
||||
|
||||
| Variable name | Type (`default`) | Default |
|
||||
| ------------------------------------- | ----------------------- | ---------------- |
|
||||
| **`CP_BASEURL`** | string | `undefined` |
|
||||
| **`CP_MEDIA_BASEURL`** | ?string | `CP_BASEURL` |
|
||||
| **`CP_ADMIN_GATEWAY`** | ?string | `"cp-admin"` |
|
||||
| **`CP_AUTH_GATEWAY`** | ?string | `"cp-auth"` |
|
||||
| **`CP_ANALYTICS_SALT`** | string | `undefined` |
|
||||
| **`CP_DATABASE_HOSTNAME`** | ?string | `"mariadb"` |
|
||||
| **`CP_DATABASE_NAME`** | ?string | `MYSQL_DATABASE` |
|
||||
| **`CP_DATABASE_USERNAME`** | ?string | `MYSQL_USER` |
|
||||
| **`CP_DATABASE_PASSWORD`** | ?string | `MYSQL_PASSWORD` |
|
||||
| **`CP_DATABASE_PREFIX`** | ?string | `"cp_"` |
|
||||
| **`CP_CACHE_HANDLER`** | [`"file"` or `"redis"`] | `"file"` |
|
||||
| **`CP_REDIS_HOST`** | ?string | `"localhost"` |
|
||||
| **`CP_REDIS_PASSWORD`** | ?string | `null` |
|
||||
| **`CP_REDIS_PORT`** | ?number | `6379` |
|
||||
| **`CP_REDIS_DATABASE`** | ?number | `0` |
|
||||
| **`CP_EMAIL_SMTP_HOST`** | ?string | `undefined` |
|
||||
| **`CP_EMAIL_FROM`** | ?string | `undefined` |
|
||||
| **`CP_EMAIL_SMTP_USERNAME`** | ?string | `"localhost"` |
|
||||
| **`CP_EMAIL_SMTP_PASSWORD`** | ?string | `null` |
|
||||
| **`CP_EMAIL_SMTP_PORT`** | ?number | `25` |
|
||||
| **`CP_EMAIL_SMTP_CRYPTO`** | [`"tls"` or `"ssl"`] | `"tls"` |
|
||||
| **`CP_ENABLE_2FA`** | ?boolean | `undefined` |
|
||||
| **`CP_MEDIA_FILE_MANAGER`** | ?string | `undefined` |
|
||||
| **`CP_MEDIA_S3_ENDPOINT`** | ?string | `undefined` |
|
||||
| **`CP_MEDIA_S3_KEY`** | ?string | `undefined` |
|
||||
| **`CP_MEDIA_S3_SECRET`** | ?string | `undefined` |
|
||||
| **`CP_MEDIA_S3_REGION`** | ?string | `undefined` |
|
||||
| **`CP_MEDIA_S3_BUCKET`** | ?string | `undefined` |
|
||||
| **`CP_MEDIA_S3_PROTOCOL`** | ?number | `undefined` |
|
||||
| **`CP_MEDIA_S3_PATH_STYLE_ENDPOINT`** | ?boolean | `undefined` |
|
||||
| **`CP_MEDIA_S3_KEY_PREFIX`** | ?string | `undefined` |
|
||||
| **`CP_DISABLE_HTTPS`** | ?[`0` or `1`] | `undefined` |
|
||||
| **`CP_MAX_BODY_SIZE`** | ?number (with suffix) | `512M` |
|
||||
| **`CP_PHP_MEMORY_LIMIT`** | ?number (with suffix) | `512M` |
|
||||
| **`CP_TIMEOUT`** | ?number | `900` |
|
||||
|
||||
- **castopod/web-server**
|
||||
|
||||
| Variable name | Type | Default |
|
||||
| ---------------------- | --------------------- | ------- |
|
||||
| **`CP_APP_HOSTNAME`** | ?string | `"app"` |
|
||||
| **`CP_MAX_BODY_SIZE`** | ?number (with suffix) | `512M` |
|
||||
| **`CP_TIMEOUT`** | ?number | `900` |
|
||||
| Variable name | Type (`default`) | Default |
|
||||
| ------------------------------------- | ----------------------- | ---------------- |
|
||||
| **`CP_BASEURL`** | string | `undefined` |
|
||||
| **`CP_MEDIA_BASEURL`** | ?string | `CP_BASEURL` |
|
||||
| **`CP_ADMIN_GATEWAY`** | ?string | `"cp-admin"` |
|
||||
| **`CP_AUTH_GATEWAY`** | ?string | `"cp-auth"` |
|
||||
| **`CP_ANALYTICS_SALT`** | string | `undefined` |
|
||||
| **`CP_DATABASE_HOSTNAME`** | ?string | `"mariadb"` |
|
||||
| **`CP_DATABASE_NAME`** | ?string | `MYSQL_DATABASE` |
|
||||
| **`CP_DATABASE_USERNAME`** | ?string | `MYSQL_USER` |
|
||||
| **`CP_DATABASE_PASSWORD`** | ?string | `MYSQL_PASSWORD` |
|
||||
| **`CP_DATABASE_PREFIX`** | ?string | `"cp_"` |
|
||||
| **`CP_CACHE_HANDLER`** | [`"file"` or `"redis"`] | `"file"` |
|
||||
| **`CP_REDIS_HOST`** | ?string | `"localhost"` |
|
||||
| **`CP_REDIS_PASSWORD`** | ?string | `null` |
|
||||
| **`CP_REDIS_PORT`** | ?number | `6379` |
|
||||
| **`CP_REDIS_DATABASE`** | ?number | `0` |
|
||||
| **`CP_EMAIL_SMTP_HOST`** | ?string | `undefined` |
|
||||
| **`CP_EMAIL_FROM`** | ?string | `undefined` |
|
||||
| **`CP_EMAIL_SMTP_USERNAME`** | ?string | `"localhost"` |
|
||||
| **`CP_EMAIL_SMTP_PASSWORD`** | ?string | `null` |
|
||||
| **`CP_EMAIL_SMTP_PORT`** | ?number | `25` |
|
||||
| **`CP_EMAIL_SMTP_CRYPTO`** | [`"tls"` or `"ssl"`] | `"tls"` |
|
||||
| **`CP_ENABLE_2FA`** | ?boolean | `undefined` |
|
||||
| **`CP_MEDIA_FILE_MANAGER`** | ?string | `undefined` |
|
||||
| **`CP_MEDIA_S3_ENDPOINT`** | ?string | `undefined` |
|
||||
| **`CP_MEDIA_S3_KEY`** | ?string | `undefined` |
|
||||
| **`CP_MEDIA_S3_SECRET`** | ?string | `undefined` |
|
||||
| **`CP_MEDIA_S3_REGION`** | ?string | `undefined` |
|
||||
| **`CP_MEDIA_S3_BUCKET`** | ?string | `undefined` |
|
||||
| **`CP_MEDIA_S3_PROTOCOL`** | ?number | `undefined` |
|
||||
| **`CP_MEDIA_S3_PATH_STYLE_ENDPOINT`** | ?boolean | `undefined` |
|
||||
| **`CP_MEDIA_S3_KEY_PREFIX`** | ?string | `undefined` |
|
||||
| **`CP_DISABLE_HTTPS`** | ?[`0` or `1`] | `undefined` |
|
||||
| **`PHP_MEMORY_LIMIT`** | ?number (with suffix) | `512M` |
|
||||
| **`PHP_UPLOAD_MAX_FILE_SIZE`** | ?number (with suffix) | `512M` |
|
||||
| **`PHP_POST_MAX_SIZE`** | ?number (with suffix) | `512M` |
|
||||
| **`PHP_MAX_EXECUTION_TIME`** | ?number | `300` |
|
||||
| **`PHP_OPCACHE_ENABLE`** | ?[`0` or `1`] | `1` |
|
||||
|
||||
@@ -5,10 +5,11 @@ title: Manage Podcast contributors
|
||||
The **Persons** section allows you to add podcast contributors. It is needed in
|
||||
the Podcast section to assign roles and is also used on the **Credits** page
|
||||
linked from your podcast's homepage. When Persons are assigned to a specific
|
||||
episode, there will be a link on the episode's page to list all persons assigned.
|
||||
episode, there will be a link on the episode's page to list all persons
|
||||
assigned.
|
||||
|
||||
A Person must be created in the **Persons** section before it can be [assigned
|
||||
to an episode](../podcast/episodes#persons).
|
||||
A Person must be created in the **Persons** section before it can be
|
||||
[assigned to an episode](../podcast/episodes#persons).
|
||||
|
||||
From the left hand navigation, press `Persons` to expand the menu. To view a
|
||||
list of all people that have been added to Castopod, press `All Persons`.
|
||||
|
||||
@@ -119,9 +119,9 @@ will be displayed.
|
||||
|
||||
You can add a transcript to your episode by choosing a file in SRT or VTT format
|
||||
to upload. Transcripts will be shown in a tab on the episode page and some
|
||||
podcast apps such as Apple Podcasts can display the transcript.
|
||||
Transcripts help users who may have a hearing disability and can also help with
|
||||
search engine optimization.
|
||||
podcast apps such as Apple Podcasts can display the transcript. Transcripts help
|
||||
users who may have a hearing disability and can also help with search engine
|
||||
optimization.
|
||||
|
||||
#### Chapters
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ class NotificationController extends BaseController
|
||||
{
|
||||
$notifications = new NotificationModel()
|
||||
->where('target_actor_id', $podcast->actor_id)
|
||||
->where('read_at', null)
|
||||
->where('read_at')
|
||||
->findAll();
|
||||
|
||||
foreach ($notifications as $notification) {
|
||||
|
||||
@@ -677,7 +677,7 @@ class PodcastController extends BaseController
|
||||
|
||||
$episodes = new EpisodeModel()
|
||||
->where('podcast_id', $podcast->id)
|
||||
->where('published_at !=', null)
|
||||
->where('published_at !=')
|
||||
->findAll();
|
||||
|
||||
foreach ($episodes as $episode) {
|
||||
@@ -846,7 +846,7 @@ class PodcastController extends BaseController
|
||||
|
||||
$episodes = new EpisodeModel()
|
||||
->where('podcast_id', $podcast->id)
|
||||
->where('published_at !=', null)
|
||||
->where('published_at !=')
|
||||
->findAll();
|
||||
|
||||
foreach ($episodes as $episode) {
|
||||
@@ -914,7 +914,7 @@ class PodcastController extends BaseController
|
||||
|
||||
$episodes = new EpisodeModel()
|
||||
->where('podcast_id', $podcast->id)
|
||||
->where('published_at !=', null)
|
||||
->where('published_at !=')
|
||||
->findAll();
|
||||
|
||||
foreach ($episodes as $episode) {
|
||||
|
||||
@@ -52,7 +52,7 @@ class EpisodeController extends BaseApiController
|
||||
(int) $this->request->getGet('offset'),
|
||||
);
|
||||
|
||||
array_map(static function ($episode): void {
|
||||
array_map(static function (Episode $episode): void {
|
||||
self::mapEpisode($episode);
|
||||
}, $data);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class PodcastController extends BaseApiController
|
||||
/** @var array<string,mixed> $data */
|
||||
$data = new PodcastModel()
|
||||
->findAll();
|
||||
array_map(static function ($podcast): void {
|
||||
array_map(static function (Podcast $podcast): void {
|
||||
self::mapPodcast($podcast);
|
||||
}, $data);
|
||||
return $this->respond($data);
|
||||
|
||||
@@ -283,7 +283,7 @@ if (! function_exists('get_actor_ids_with_unread_notifications')) {
|
||||
|
||||
$unreadNotifications = new NotificationModel()
|
||||
->whereIn('target_actor_id', array_column($userPodcasts, 'actor_id'))
|
||||
->where('read_at', null)
|
||||
->where('read_at')
|
||||
->findAll();
|
||||
|
||||
return array_column($unreadNotifications, 'target_actor_id');
|
||||
|
||||
@@ -42,7 +42,7 @@ abstract class AbstractObject
|
||||
}
|
||||
|
||||
// removes all NULL, FALSE and Empty Strings but leaves 0 (zero) values
|
||||
return array_filter($array, static fn ($value): bool => $value !== null && $value !== false && $value !== '');
|
||||
return array_filter($array, static fn ($value): bool => ! in_array($value, [null, false, ''], true));
|
||||
}
|
||||
|
||||
public function toJSON(): string
|
||||
|
||||
@@ -120,7 +120,7 @@ class SubscriptionModel extends Model
|
||||
'status' => 'active',
|
||||
])
|
||||
->groupStart()
|
||||
->where('expires_at', null)
|
||||
->where('expires_at')
|
||||
->orWhere('`expires_at` > UTC_TIMESTAMP()', null, false)
|
||||
->groupEnd()
|
||||
->first();
|
||||
|
||||
82
package.json
82
package.json
@@ -32,81 +32,81 @@
|
||||
"dependencies": {
|
||||
"@amcharts/amcharts4": "^4.10.40",
|
||||
"@amcharts/amcharts4-geodata": "^4.1.31",
|
||||
"@codemirror/commands": "^6.9.0",
|
||||
"@codemirror/commands": "^6.10.2",
|
||||
"@codemirror/lang-html": "^6.4.11",
|
||||
"@codemirror/lang-xml": "^6.1.0",
|
||||
"@codemirror/language": "^6.11.3",
|
||||
"@codemirror/state": "^6.5.2",
|
||||
"@codemirror/view": "^6.38.5",
|
||||
"@floating-ui/dom": "^1.7.4",
|
||||
"@codemirror/language": "^6.12.1",
|
||||
"@codemirror/state": "^6.5.4",
|
||||
"@codemirror/view": "^6.39.14",
|
||||
"@floating-ui/dom": "^1.7.5",
|
||||
"@github/clipboard-copy-element": "^1.3.0",
|
||||
"@github/hotkey": "^3.1.1",
|
||||
"@github/markdown-toolbar-element": "^2.2.3",
|
||||
"@github/relative-time-element": "^4.4.8",
|
||||
"@patternfly/elements": "^4.2.0",
|
||||
"@github/relative-time-element": "^5.0.0",
|
||||
"@patternfly/elements": "^4.3.1",
|
||||
"@vime/core": "^5.4.1",
|
||||
"choices.js": "^11.1.0",
|
||||
"codemirror": "^6.0.2",
|
||||
"flatpickr": "^4.6.13",
|
||||
"htmlfy": "^1.0.0",
|
||||
"htmlfy": "^1.0.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet.markercluster": "^1.5.3",
|
||||
"lit": "^3.3.1",
|
||||
"marked": "^16.4.0",
|
||||
"wavesurfer.js": "^7.11.0",
|
||||
"lit": "^3.3.2",
|
||||
"marked": "^17.0.2",
|
||||
"wavesurfer.js": "^7.12.1",
|
||||
"xml-formatter": "^3.6.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^20.1.0",
|
||||
"@commitlint/config-conventional": "^20.0.0",
|
||||
"@csstools/css-tokenizer": "^3.0.4",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.37.0",
|
||||
"@commitlint/cli": "^20.4.1",
|
||||
"@commitlint/config-conventional": "^20.4.1",
|
||||
"@csstools/css-tokenizer": "^4.0.0",
|
||||
"@eslint/eslintrc": "^3.3.3",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/exec": "^7.1.0",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/gitlab": "^13.2.9",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@semantic-release/gitlab": "^13.3.0",
|
||||
"@tailwindcss/forms": "^0.5.11",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@types/leaflet": "^1.9.20",
|
||||
"@types/leaflet": "^1.9.21",
|
||||
"all-contributors-cli": "^6.26.1",
|
||||
"commitizen": "^4.3.1",
|
||||
"conventional-changelog-conventionalcommits": "^9.1.0",
|
||||
"cross-env": "^10.1.0",
|
||||
"cssnano": "^7.1.1",
|
||||
"cssnano": "^7.1.2",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"eslint": "^9.37.0",
|
||||
"eslint": "^10.0.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"glob": "^11.0.3",
|
||||
"globals": "^16.4.0",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"glob": "^13.0.5",
|
||||
"globals": "^17.3.0",
|
||||
"husky": "^9.1.7",
|
||||
"is-ci": "^4.1.0",
|
||||
"lint-staged": "^16.2.3",
|
||||
"lint-staged": "^16.2.7",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-import": "^16.1.1",
|
||||
"postcss-nesting": "^13.0.2",
|
||||
"postcss-preset-env": "^10.4.0",
|
||||
"postcss-nesting": "^14.0.0",
|
||||
"postcss-preset-env": "^11.1.3",
|
||||
"postcss-reporter": "^7.1.0",
|
||||
"prettier": "3.6.2",
|
||||
"prettier": "3.8.1",
|
||||
"prettier-plugin-organize-imports": "^4.3.0",
|
||||
"semantic-release": "^24.2.9",
|
||||
"sharp": "^0.34.4",
|
||||
"stylelint": "^16.25.0",
|
||||
"stylelint-config-standard": "^39.0.1",
|
||||
"semantic-release": "^25.0.3",
|
||||
"sharp": "^0.34.5",
|
||||
"stylelint": "^17.3.0",
|
||||
"stylelint-config-standard": "^40.0.0",
|
||||
"svgo": "^4.0.0",
|
||||
"tailwindcss": "^3.4.18",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.46.0",
|
||||
"vite": "^7.1.9",
|
||||
"typescript-eslint": "^8.56.0",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-codeigniter": "^2.0.0",
|
||||
"vite-plugin-inspect": "^11.3.3",
|
||||
"vite-plugin-pwa": "^1.0.3",
|
||||
"vite-plugin-static-copy": "^3.1.3",
|
||||
"workbox-build": "^7.3.0",
|
||||
"workbox-core": "^7.3.0",
|
||||
"workbox-routing": "^7.3.0",
|
||||
"workbox-strategies": "^7.3.0"
|
||||
"vite-plugin-pwa": "^1.2.0",
|
||||
"vite-plugin-static-copy": "^3.2.0",
|
||||
"workbox-build": "^7.4.0",
|
||||
"workbox-core": "^7.4.0",
|
||||
"workbox-routing": "^7.4.0",
|
||||
"workbox-strategies": "^7.4.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,css,md,json}": "prettier --write",
|
||||
|
||||
6049
pnpm-lock.yaml
generated
6049
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
use Rector\CodeQuality\Rector\ClassMethod\ExplicitReturnNullRector;
|
||||
use Rector\CodingStyle\Rector\Encapsed\EncapsedStringsToSprintfRector;
|
||||
use Rector\CodingStyle\Rector\Stmt\NewlineAfterStatementRector;
|
||||
use Rector\CodingStyle\Rector\String_\SymplifyQuoteEscapeRector;
|
||||
use Rector\CodingStyle\Rector\String_\SimplifyQuoteEscapeRector;
|
||||
use Rector\Config\RectorConfig;
|
||||
use Rector\DeadCode\Rector\If_\UnwrapFutureCompatibleIfPhpVersionRector;
|
||||
use Rector\DeadCode\Rector\Stmt\RemoveUnreachableStatementRector;
|
||||
@@ -48,7 +48,7 @@ return RectorConfig::configure()
|
||||
__DIR__ . '/app/Language/*',
|
||||
__DIR__ . '/modules/*/Language/*',
|
||||
],
|
||||
SymplifyQuoteEscapeRector::class => [__DIR__ . '/app/Language/*', __DIR__ . '/modules/*/Language/*'],
|
||||
SimplifyQuoteEscapeRector::class => [__DIR__ . '/app/Language/*', __DIR__ . '/modules/*/Language/*'],
|
||||
|
||||
NewlineAfterStatementRector::class => [__DIR__ . '/app/Views'],
|
||||
|
||||
|
||||
@@ -191,26 +191,23 @@ function formatXML(contents: string) {
|
||||
return contents;
|
||||
}
|
||||
|
||||
let editorContents = "";
|
||||
try {
|
||||
editorContents = xmlFormat(contents, {
|
||||
return xmlFormat(contents, {
|
||||
indentation: " ",
|
||||
});
|
||||
} catch {
|
||||
// xml doesn't have a root node
|
||||
editorContents = xmlFormat("<root>" + contents + "</root>", {
|
||||
const editorContents = xmlFormat("<root>" + contents + "</root>", {
|
||||
indentation: " ",
|
||||
});
|
||||
// remove root, unnecessary lines and indents
|
||||
editorContents = editorContents
|
||||
return editorContents
|
||||
.replace(/^<root>/, "")
|
||||
.replace(/<\/root>$/, "")
|
||||
.replace(/^\s*[\r\n]/gm, "")
|
||||
.replace(/[\r\n] {2}/gm, "\r\n")
|
||||
.trim();
|
||||
}
|
||||
|
||||
return editorContents;
|
||||
}
|
||||
|
||||
function minifyXML(contents: string) {
|
||||
@@ -218,20 +215,15 @@ function minifyXML(contents: string) {
|
||||
return contents;
|
||||
}
|
||||
|
||||
let minifiedContent = "";
|
||||
try {
|
||||
minifiedContent = xmlFormat.minify(contents, {
|
||||
return xmlFormat.minify(contents, {
|
||||
collapseContent: true,
|
||||
});
|
||||
} catch {
|
||||
minifiedContent = xmlFormat.minify(`<root>${contents}</root>`, {
|
||||
const minifiedContent = xmlFormat.minify(`<root>${contents}</root>`, {
|
||||
collapseContent: true,
|
||||
});
|
||||
// remove root
|
||||
minifiedContent = minifiedContent
|
||||
.replace(/^<root>/, "")
|
||||
.replace(/<\/root>$/, "");
|
||||
return minifiedContent.replace(/^<root>/, "").replace(/<\/root>$/, "");
|
||||
}
|
||||
|
||||
return minifiedContent;
|
||||
}
|
||||
|
||||
@@ -11,9 +11,6 @@ echo "$( jq '.version = "'$COMPOSER_VERSION'"' composer.json )" > composer.json
|
||||
# replace CP_VERSION constant in app/config/constants
|
||||
sed -i "s/^defined('CP_VERSION').*/defined('CP_VERSION') || define('CP_VERSION', '$VERSION');/" ./app/Config/Constants.php
|
||||
|
||||
# fill CP_VERSION.env for docker build
|
||||
echo "$VERSION" > ./CP_VERSION.env
|
||||
|
||||
# download GeoLite2-City archive and extract it to writable/uploads
|
||||
wget -c "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=$MAXMIND_LICENCE_KEY&suffix=tar.gz" -O - | tar -xz -C ./writable/uploads/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user