diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..069a924 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +.dockerignore +docker/* +Makefile +docker*.yml +Dockerfile +bootstrap/cache/* +storage/logs/* +storage/framework/cache/data/* +storage/framework/sessions/* +storage/framework/testing/* +storage/framework/views/* +storage/*.cache +vendor +node_modules +*.log +.gitignore +.editorconfig +.idea +.vscode +.github +.git diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cc2c0f7..b8b1cbf 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,3 +11,8 @@ updates: interval: "daily" allow: - dependency-name: "jikan*" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + diff --git a/.github/workflows/container-base-image-release.yml b/.github/workflows/container-base-image-release.yml new file mode 100644 index 0000000..aee7416 --- /dev/null +++ b/.github/workflows/container-base-image-release.yml @@ -0,0 +1,58 @@ +name: Container Base Image Release +concurrency: production +on: + workflow_dispatch: +jobs: + release-base-image: + runs-on: ubuntu-latest + name: Release base container image + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up qemu + uses: docker/setup-qemu-action@v2 + + - name: Set up docker buildx + uses: docker/setup-buildx-action@v2 + with: + platforms: linux/amd64,linux/arm64 + + - name: Read metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ghcr.io/jikan-me/jikan-rest-php + jikanme/jikan-rest-php + tags: | + type=raw,value=latest + type=sha + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v3 + with: + push: true + context: ./docker/base_image/php-8.0 + # let's use github action cache storage + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + GITHUB_PERSONAL_TOKEN=${{ secrets.GITHUB_TOKEN }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/container-image-release.yml b/.github/workflows/container-image-release.yml new file mode 100644 index 0000000..7d4edb2 --- /dev/null +++ b/.github/workflows/container-image-release.yml @@ -0,0 +1,71 @@ +name: Container Image Release +concurrency: production +on: + release: + types: [published] + +jobs: + release-app-image: + runs-on: ubuntu-latest + name: Release App container image + steps: + - name: Check if base container image exists + id: baseImageExists + run: | + GHCR_TOKEN=$(echo ${{ secrets.GITHUB_TOKEN }} | base64) + curl --fail -H "Authorization: Bearer ${GHCR_TOKEN}" https://ghcr.io/v2/jikan-me/jikan-rest-php/tags/list | grep -q latest + + - name: Base image existance check failed + if: ${{ always() && steps.baseImageExists.outcome == 'failure' }} + run: echo "Base image doesn't exist yet. Please run the base image creation workflow first." + + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up qemu + uses: docker/setup-qemu-action@v2 + + - name: Set up docker buildx + uses: docker/setup-buildx-action@v2 + with: + platforms: linux/amd64,linux/arm64 + + - name: Read metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ghcr.io/jikan-me/jikan-rest + jikanme/jikan-rest + tags: | + type=raw,value=${{ github.ref_name }} + type=sha + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v3 + with: + push: true + context: . + # let's use github action cache storage + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + GITHUB_PERSONAL_TOKEN=${{ secrets.GITHUB_TOKEN }} + BASE_IMAGE_VERSION=latest + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.rr.local.unix.yaml b/.rr.local.unix.yaml new file mode 100644 index 0000000..4bb0a29 --- /dev/null +++ b/.rr.local.unix.yaml @@ -0,0 +1,220 @@ +# --------------------------------------------------------------------------------------------------- +# WARNING! This file should be used ONLY for local application development. NOT for production usage! +# --------------------------------------------------------------------------------------------------- + +# Hint: RR will replace any config options using reference to environment variables, +# eg.: `option_key: ${ENVIRONMENT_VARIABLE_NAME}`. + +# RR configuration version +version: "2.7" + +# Remote Procedures Calling (docs: https://roadrunner.dev/docs/beep-beep-rpc) +# Is used for connecting to RoadRunner server from your PHP workers. +rpc: + # TCP address:port for listening. + # + # Default: "tcp://127.0.0.1:6001" + listen: tcp://127.0.0.1:6001 + +# Application server settings (docs: https://roadrunner.dev/docs/php-worker) +server: + # Worker starting command, with any required arguments. + # + # This option is required. + command: "php -dxdebug.start_with_request=trigger -dxdebug.mode=debug -dxdebug.client_port=9000 -dxdebug.client_host=127.0.0.1 -didekey=PHPSTORM ./vendor/bin/rr-worker start --relay-dsn unix:///var/run/rr/rr-relay.sock" + env: + - XDEBUG_SESSION: "1" + + ## Environment variables for the worker processes. + ## + ## Default: + #env: + # - SOME_KEY: "SOME_VALUE" + # - SOME_KEY2: "SOME_VALUE2" + + # Worker relay can be: "pipes", TCP (eg.: tcp://127.0.0.1:6001), or socket (eg.: unix:///var/run/rr-relay.sock). + # + # Default: "pipes" + relay: "unix:///var/run/rr/rr-relay.sock" + + # Timeout for relay connection establishing (only for socket and TCP port relay). + # + # Default: 60s + relay_timeout: 60s + +# HTTP plugin settings. +http: + # Host and port to listen on (eg.: `127.0.0.1:8080`). + # + # This option is required. + address: 0.0.0.0:8080 + + # HTTP access logs + # + # Default: false + access_logs: true + + # Maximal incoming request size in megabytes. Zero means no limit. + # + # Default: 0 + max_request_size: 256 + + # Middlewares for the http plugin, order is important. Allowed values is: "headers", "gzip". + # + # Default value: [] + middleware: ["static", "headers", "gzip"] + + # File uploading settings. + uploads: + # Directory for file uploads. Empty value means to use $TEMP based on your OS. + # + # Default: "" + dir: "/tmp" + + # Deny files with the following extensions to upload. + # + # Default: [".php", ".exe", ".bat"] + forbid: [".php", ".exe", ".bat", ".sh"] + + # Settings for "headers" middleware (docs: https://roadrunner.dev/docs/http-headers). + headers: + # Automatically add headers to every response. + # + # Default: + response: + X-Powered-By: "RoadRunner" + + # Settings for serving static content (docs: https://roadrunner.dev/docs/http-static). + static: + # Path to the directory with static assets. + # + # This option is required. + dir: "./public" + + # File extensions to forbid. + # + # Default: [] + forbid: [".htaccess", ".php"] + + # Automatically add headers to every response. + # + # Default: + response: + X-Powered-By: "RoadRunner" + + # Workers pool settings. + pool: + debug: true + # How many worker processes will be started. Zero (or nothing) means the number of logical CPUs. + # + # Default: 0 + num_workers: 4 + + # Maximal count of worker executions. Zero (or nothing) means no limit. + # + # Default: 0 + max_jobs: 64 + + # Timeout for worker allocation. Zero means no limit. + # + # Default: 60s + allocate_timeout: 10s + + # Timeout for worker destroying before process killing. Zero means no limit. + # + # Default: 60s + destroy_timeout: 10s + + # Supervisor is used to control http workers (previous name was "limit", docs: + # https://roadrunner.dev/docs/php-limit). "Soft" limits will not interrupt current request processing. "Hard" + # limit on the contrary - interrupts the execution of the request. + supervisor: + # Maximal worker memory usage in megabytes (soft limit). Zero means no limit. + # + # Default: 0 + max_worker_memory: 128 + + # Maximal job lifetime (hard limit). Zero means no limit. + # + # Default: 0s + exec_ttl: 60s + + # HTTP/2 settings. + http2: + # HTTP/2 over non-encrypted TCP connection using H2C. + # + # Default: false + h2c: false + + # Maximal concurrent streams count. + # + # Default: 128 + max_concurrent_streams: 128 + +## Application metrics in Prometheus format (docs: https://roadrunner.dev/docs/beep-beep-metrics). Drop this section +## for this feature disabling. +#metrics: +# # Prometheus client address (path /metrics added automatically). +# # +# # Default: "127.0.0.1:2112" +# address: 127.0.0.1:8081 + +# Health check endpoint (docs: https://roadrunner.dev/docs/beep-beep-health). If response code is 200 - it means at +# least one worker ready to serve requests. 500 - there are no workers ready to service requests. +# Drop this section for this feature disabling. +status: + # Host and port to listen on (eg.: `127.0.0.1:2114`). Use the following URL: http://127.0.0.1:2114/health?plugin=http + # Multiple plugins must be separated using "&" - http://127.0.0.1:2114/health?plugin=http&plugin=rpc where "http" and + # "rpc" are active (connected) plugins. + # + # This option is required. + address: 127.0.0.1:2114 + + # Response status code if a requested plugin not ready to handle requests + # Valid for both /health and /ready endpoints + # + # Default: 503 + unavailable_status_code: 503 + +# Automatically detect PHP file changes and reload connected services (docs: +# https://roadrunner.dev/docs/beep-beep-reload). Drop this section for this feature disabling. +reload: + # Sync interval. + # + # Default: "1s" + interval: 1s + + # Global patterns to sync. + # + # Default: [".php"] + patterns: [".php"] + + # List of included for sync services (this is a map, where key name is a plugin name). + # + # Default: + services: + http: + # Directories to sync. If recursive is set to true, recursive sync will be applied only to the directories in + # "dirs" section. Dot (.) means "current working directory". + # + # Default: [] + dirs: ["."] + + # Recursive search for file patterns to add. + # + # Default: false + recursive: true + + # Ignored folders. + # + # Default: [] + ignore: ["vendor"] + +# RoadRunner internal container configuration (docs: https://github.com/spiral/endure). +endure: + # Logging level. Possible values: "debug", "info", "warning", "error", "panic", "fatal". + # + # Default: "error" + log_level: error +app: + debug: true diff --git a/.rr.local.yaml b/.rr.local.yaml new file mode 100644 index 0000000..90d4523 --- /dev/null +++ b/.rr.local.yaml @@ -0,0 +1,220 @@ +# --------------------------------------------------------------------------------------------------- +# WARNING! This file should be used ONLY for local application development. NOT for production usage! +# --------------------------------------------------------------------------------------------------- + +# Hint: RR will replace any config options using reference to environment variables, +# eg.: `option_key: ${ENVIRONMENT_VARIABLE_NAME}`. + +# RR configuration version +version: "2.7" + +# Remote Procedures Calling (docs: https://roadrunner.dev/docs/beep-beep-rpc) +# Is used for connecting to RoadRunner server from your PHP workers. +rpc: + # TCP address:port for listening. + # + # Default: "tcp://127.0.0.1:6001" + listen: tcp://127.0.0.1:6001 + +# Application server settings (docs: https://roadrunner.dev/docs/php-worker) +server: + # Worker starting command, with any required arguments. + # + # This option is required. + command: "php -dxdebug.start_with_request=trigger -dxdebug.mode=debug -dxdebug.client_port=9000 -dxdebug.client_host=127.0.0.1 -didekey=PHPSTORM ./vendor/bin/rr-worker start" + env: + - XDEBUG_SESSION: "1" + + ## Environment variables for the worker processes. + ## + ## Default: + #env: + # - SOME_KEY: "SOME_VALUE" + # - SOME_KEY2: "SOME_VALUE2" + + # Worker relay can be: "pipes", TCP (eg.: tcp://127.0.0.1:6001), or socket (eg.: unix:///var/run/rr-relay.sock). + # + # Default: "pipes" + relay: "pipes" + + # Timeout for relay connection establishing (only for socket and TCP port relay). + # + # Default: 60s + relay_timeout: 60s + +# HTTP plugin settings. +http: + # Host and port to listen on (eg.: `127.0.0.1:8080`). + # + # This option is required. + address: 0.0.0.0:8080 + + # HTTP access logs + # + # Default: false + access_logs: true + + # Maximal incoming request size in megabytes. Zero means no limit. + # + # Default: 0 + max_request_size: 256 + + # Middlewares for the http plugin, order is important. Allowed values is: "headers", "gzip". + # + # Default value: [] + middleware: ["static", "headers", "gzip"] + + # File uploading settings. + uploads: + # Directory for file uploads. Empty value means to use $TEMP based on your OS. + # + # Default: "" + dir: "/tmp" + + # Deny files with the following extensions to upload. + # + # Default: [".php", ".exe", ".bat"] + forbid: [".php", ".exe", ".bat", ".sh"] + + # Settings for "headers" middleware (docs: https://roadrunner.dev/docs/http-headers). + headers: + # Automatically add headers to every response. + # + # Default: + response: + X-Powered-By: "RoadRunner" + + # Settings for serving static content (docs: https://roadrunner.dev/docs/http-static). + static: + # Path to the directory with static assets. + # + # This option is required. + dir: "./public" + + # File extensions to forbid. + # + # Default: [] + forbid: [".htaccess", ".php"] + + # Automatically add headers to every response. + # + # Default: + response: + X-Powered-By: "RoadRunner" + + # Workers pool settings. + pool: + debug: true + # How many worker processes will be started. Zero (or nothing) means the number of logical CPUs. + # + # Default: 0 + num_workers: 4 + + # Maximal count of worker executions. Zero (or nothing) means no limit. + # + # Default: 0 + max_jobs: 64 + + # Timeout for worker allocation. Zero means no limit. + # + # Default: 60s + allocate_timeout: 10s + + # Timeout for worker destroying before process killing. Zero means no limit. + # + # Default: 60s + destroy_timeout: 10s + + # Supervisor is used to control http workers (previous name was "limit", docs: + # https://roadrunner.dev/docs/php-limit). "Soft" limits will not interrupt current request processing. "Hard" + # limit on the contrary - interrupts the execution of the request. + supervisor: + # Maximal worker memory usage in megabytes (soft limit). Zero means no limit. + # + # Default: 0 + max_worker_memory: 128 + + # Maximal job lifetime (hard limit). Zero means no limit. + # + # Default: 0s + exec_ttl: 60s + + # HTTP/2 settings. + http2: + # HTTP/2 over non-encrypted TCP connection using H2C. + # + # Default: false + h2c: false + + # Maximal concurrent streams count. + # + # Default: 128 + max_concurrent_streams: 128 + +## Application metrics in Prometheus format (docs: https://roadrunner.dev/docs/beep-beep-metrics). Drop this section +## for this feature disabling. +#metrics: +# # Prometheus client address (path /metrics added automatically). +# # +# # Default: "127.0.0.1:2112" +# address: 127.0.0.1:8081 + +# Health check endpoint (docs: https://roadrunner.dev/docs/beep-beep-health). If response code is 200 - it means at +# least one worker ready to serve requests. 500 - there are no workers ready to service requests. +# Drop this section for this feature disabling. +status: + # Host and port to listen on (eg.: `127.0.0.1:2114`). Use the following URL: http://127.0.0.1:2114/health?plugin=http + # Multiple plugins must be separated using "&" - http://127.0.0.1:2114/health?plugin=http&plugin=rpc where "http" and + # "rpc" are active (connected) plugins. + # + # This option is required. + address: 127.0.0.1:2114 + + # Response status code if a requested plugin not ready to handle requests + # Valid for both /health and /ready endpoints + # + # Default: 503 + unavailable_status_code: 503 + +# Automatically detect PHP file changes and reload connected services (docs: +# https://roadrunner.dev/docs/beep-beep-reload). Drop this section for this feature disabling. +reload: + # Sync interval. + # + # Default: "1s" + interval: 1s + + # Global patterns to sync. + # + # Default: [".php"] + patterns: [".php"] + + # List of included for sync services (this is a map, where key name is a plugin name). + # + # Default: + services: + http: + # Directories to sync. If recursive is set to true, recursive sync will be applied only to the directories in + # "dirs" section. Dot (.) means "current working directory". + # + # Default: [] + dirs: ["."] + + # Recursive search for file patterns to add. + # + # Default: false + recursive: true + + # Ignored folders. + # + # Default: [] + ignore: ["vendor"] + +# RoadRunner internal container configuration (docs: https://github.com/spiral/endure). +endure: + # Logging level. Possible values: "debug", "info", "warning", "error", "panic", "fatal". + # + # Default: "error" + log_level: error +app: + debug: true diff --git a/.rr.yaml b/.rr.yaml new file mode 100644 index 0000000..b5d4e79 --- /dev/null +++ b/.rr.yaml @@ -0,0 +1,214 @@ +# Production usage guide: https://roadrunner.dev/docs/beep-beep-production + +# Hint: RR will replace any config options using reference to environment variables, +# eg.: `option_key: ${ENVIRONMENT_VARIABLE_NAME}`. +# Please note that this config is used within the docker container. + +# RR configuration version +version: "2.7" + +# Remote Procedures Calling (docs: https://roadrunner.dev/docs/beep-beep-rpc) +# Is used for connecting to RoadRunner server from your PHP workers. +rpc: + # TCP address:port for listening. + # + # Default: "tcp://127.0.0.1:6001" + listen: tcp://127.0.0.1:6001 + +# Application server settings (docs: https://roadrunner.dev/docs/php-worker) +server: + # Worker starting command, with any required arguments. + # + # This option is required. + command: "php ./vendor/bin/rr-worker start --relay-dsn unix:///var/run/rr/rr-relay.sock" + + ## Environment variables for the worker processes. + ## + ## Default: + #env: + # - SOME_KEY: "SOME_VALUE" + # - SOME_KEY2: "SOME_VALUE2" + + # Worker relay can be: "pipes", TCP (eg.: tcp://127.0.0.1:6001), or socket (eg.: unix:///var/run/rr-relay.sock). + # + # Default: "pipes" + relay: "unix:///var/run/rr/rr-relay.sock" + + # Timeout for relay connection establishing (only for socket and TCP port relay). + # + # Default: 60s + relay_timeout: 60s + +# Logging settings (docs: https://roadrunner.dev/docs/beep-beep-logging) +logs: + # Logging mode can be "development" or "production". Do not forget to change this value for production environment. + # + # Development mode (which makes DPanicLevel logs panic), uses a console encoder, writes to standard error, and + # disables sampling. Stacktraces are automatically included on logs of WarnLevel and above. + # + # Default: "development" + mode: production + + # Logging level can be "panic", "error", "warning", "info", "debug". + # + # Default: "debug" + level: info + + # Encoding format can be "console" or "json" (last is preferred for production usage). + # + # Default: "console" + encoding: json + + # we want to use docker's log drivers, so push logs to stdout + output: stdout + # we to use docker's log drivers, so push error logs to stdout + # this way it is possible for example to pipe logs to journald or to AWS Cloudwatch + err_output: stdout + +# HTTP plugin settings. +http: + # Host and port to listen on (eg.: `127.0.0.1:8080`). + # + # This option is required. + address: 0.0.0.0:8080 + + # HTTP access logs + # + # Default: false + access_logs: true + + # Maximal incoming request size in megabytes. Zero means no limit. + # + # Default: 0 + max_request_size: 256 + + # Middlewares for the http plugin, order is important. Allowed values is: "headers", "gzip". + # + # Default value: [] + middleware: ["static", "headers", "gzip"] + + # File uploading settings. + uploads: + # Directory for file uploads. Empty value means to use $TEMP based on your OS. + # + # Default: "" + dir: "/tmp" + + # Deny files with the following extensions to upload. + # + # Default: [".php", ".exe", ".bat"] + forbid: [".php", ".exe", ".bat", ".sh"] + + # Settings for "headers" middleware (docs: https://roadrunner.dev/docs/http-headers). + headers: + # Automatically add headers to every response. + # + # Default: + response: + X-Powered-By: "RoadRunner" + + # Settings for serving static content (docs: https://roadrunner.dev/docs/http-static). + static: + # Path to the directory with static assets. + # + # This option is required. + dir: "./public" + + # File extensions to forbid. + # + # Default: [] + forbid: [".htaccess", ".php"] + + # Automatically add headers to every response. + # + # Default: + response: + X-Powered-By: "RoadRunner" + + # Workers pool settings. + pool: + # How many worker processes will be started. Zero (or nothing) means the number of logical CPUs. + # + # Default: 0 + num_workers: 0 + + # Maximal count of worker executions. Zero (or nothing) means no limit. + # + # Default: 0 + max_jobs: 64 + + # Timeout for worker allocation. Zero means no limit. + # + # Default: 60s + allocate_timeout: 10s + + # Timeout for worker destroying before process killing. Zero means no limit. + # + # Default: 60s + destroy_timeout: 10s + + # Supervisor is used to control http workers (previous name was "limit", docs: + # https://roadrunner.dev/docs/php-limit). "Soft" limits will not interrupt current request processing. "Hard" + # limit on the contrary - interrupts the execution of the request. + supervisor: + # Maximal worker memory usage in megabytes (soft limit). Zero means no limit. + # + # Default: 0 + max_worker_memory: 128 + + # Maximal job lifetime (hard limit). Zero means no limit. + # + # Default: 0s + exec_ttl: 60s + + # HTTP/2 settings. + http2: + # HTTP/2 over non-encrypted TCP connection using H2C. + # + # Default: false + h2c: false + + # Maximal concurrent streams count. + # + # Default: 128 + max_concurrent_streams: 128 + +## Application metrics in Prometheus format (docs: https://roadrunner.dev/docs/beep-beep-metrics). Drop this section +## for this feature disabling. +#metrics: +# # Prometheus client address (path /metrics added automatically). +# # +# # Default: "127.0.0.1:2112" +# address: 127.0.0.1:8081 + +# Health check endpoint (docs: https://roadrunner.dev/docs/beep-beep-health). If response code is 200 - it means at +# least one worker ready to serve requests. 500 - there are no workers ready to service requests. +# Drop this section for this feature disabling. +status: + # Host and port to listen on (eg.: `127.0.0.1:2114`). Use the following URL: http://127.0.0.1:2114/health?plugin=http + # Multiple plugins must be separated using "&" - http://127.0.0.1:2114/health?plugin=http&plugin=rpc where "http" and + # "rpc" are active (connected) plugins. + # + # This option is required. + address: 127.0.0.1:2114 + + # Response status code if a requested plugin not ready to handle requests + # Valid for both /health and /ready endpoints + # + # Default: 503 + unavailable_status_code: 503 + +service: + laravel_queue_worker_1: + command: "php /app/artisan queue:work --queue=high,default" + process_num: 1 + restart_sec: 5 + supercronic: + command: "supercronic /etc/supercronic/laravel" + +# RoadRunner internal container configuration (docs: https://github.com/spiral/endure). +endure: + # Logging level. Possible values: "debug", "info", "warning", "error", "panic", "fatal". + # + # Default: "error" + log_level: error diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..41b8abc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,63 @@ +ARG BASE_IMAGE_VERSION="latest" +FROM jikanme/jikan-rest-php:${BASE_IMAGE_VERSION} +ARG GITHUB_PERSONAL_TOKEN +LABEL org.opencontainers.image.source=https://github.com/jikan-me/jikan-rest +RUN set -ex \ + && apt-get update && apt-get install -y --no-install-recommends \ + openssl \ + git \ + dos2unix \ + unzip \ + wget \ + # install supercronic (for laravel task scheduling), project page: + && wget -q "https://github.com/aptible/supercronic/releases/download/v0.1.12/supercronic-linux-amd64" \ + -O /usr/bin/supercronic \ + && chmod +x /usr/bin/supercronic \ + && mkdir /etc/supercronic \ + && echo '*/1 * * * * php /app/artisan schedule:run' > /etc/supercronic/laravel \ + && rm -rf /var/lib/apt/lists/* \ + # enable opcache for CLI and JIT, docs: + && echo -e "\nopcache.enable=1\nopcache.enable_cli=1\nopcache.jit_buffer_size=32M\nopcache.jit=1235\n" >> \ + ${PHP_INI_DIR}/conf.d/docker-php-ext-opcache.ini \ + # show installed modules + && php -m \ + # create unpriviliged user + && adduser --disabled-password --shell "/sbin/nologin" --home "/nonexistent" --no-create-home --uid "10001" --gecos "" "jikanapi" \ + && mkdir /app /var/run/rr \ + && chown -R jikanapi:jikanapi /app /var/run/rr /etc/supercronic/laravel \ + && chmod -R 777 /var/run/rr + +USER jikanapi:jikanapi + +WORKDIR /app + +# copy composer (json|lock) files for dependencies layer caching +COPY --chown=jikanapi:jikanapi ./composer.* /app/ + +# check if GITHUB_PERSONAL_TOKEN is set and configure it for composer +# it is recommended to set this for the build, otherwise the build might fail because of github's rate limits +RUN if [ -z "$GITHUB_PERSONAL_TOKEN" ]; then echo "** GITHUB_PERSONAL_TOKEN is not set. This build may fail due to github rate limits."; \ + else composer config github-oauth.github.com "$GITHUB_PERSONAL_TOKEN"; fi + +# install composer dependencies (autoloader MUST be generated later!) +RUN composer install -n --no-dev --no-cache --no-ansi --no-autoloader --no-scripts --prefer-dist + +# copy application sources into image (completely) +COPY --chown=jikanapi:jikanapi . /app/ + +RUN set -ex \ + && composer update jikan-me/jikan \ + && composer dump-autoload -n --optimize \ + && chmod -R 777 ${COMPOSER_HOME}/cache \ + && chmod -R a+w storage/ \ + && chown -R jikanapi:jikanapi /app \ + && chmod +x docker-entrypoint.php \ + && chmod +x docker-entrypoint.sh + +EXPOSE 8080 +EXPOSE 2114 + +HEALTHCHECK CMD curl --fail http://localhost:2114/health?plugin=http || exit 1 + +# unset default image entrypoint. +ENTRYPOINT ["/app/docker-entrypoint.sh"] diff --git a/README.MD b/README.MD index b6e75dc..c9d3bdc 100755 --- a/README.MD +++ b/README.MD @@ -25,7 +25,12 @@ Please read the [manual installation guide](https://github.com/jikan-me/jikan-re For any additional help, join our [Discord server](http://discord.jikan.moe/). ### 🐳 Docker Installation -If you don't want to install it manually, you can use the [docker image](https://github.com/jikan-me/jikan-docker) +We distribute the app as a container image so you can just run it: +```bash +docker run -d --name=jikan-rest -p 8080:8080 -v ./.env:/app/.env jikanme/jikan-rest:latest +``` + +For more information please refer to our [container usage guide](container_usage.md). ## Public REST API If you don't want to host your instance, there's a public API available. diff --git a/app/Anime.php b/app/Anime.php index f321af3..4469a7c 100644 --- a/app/Anime.php +++ b/app/Anime.php @@ -89,24 +89,26 @@ class Anime extends JikanApiSearchableModel public function getBroadcastAttribute() { - $broadcastStr = $this->attributes['broadcast']; + if (array_key_exists("broadcast", $this->attributes)) { + $broadcastStr = $this->attributes['broadcast']; - if (!preg_match('~(.*) at (.*) \(~', $broadcastStr, $matches)) { - return [ - 'day' => null, - 'time' => null, - 'timezone' => null, - 'string' => $broadcastStr - ]; - } + if (!preg_match('~(.*) at (.*) \(~', $broadcastStr, $matches)) { + return [ + 'day' => null, + 'time' => null, + 'timezone' => null, + 'string' => $broadcastStr + ]; + } - if (preg_match('~(.*) at (.*) \(~', $broadcastStr, $matches)) { - return [ - 'day' => $matches[1], - 'time' => $matches[2], - 'timezone' => 'Asia/Tokyo', - 'string' => $broadcastStr - ]; + if (preg_match('~(.*) at (.*) \(~', $broadcastStr, $matches)) { + return [ + 'day' => $matches[1], + 'time' => $matches[2], + 'timezone' => 'Asia/Tokyo', + 'string' => $broadcastStr + ]; + } } return [ diff --git a/app/Exceptions/GithubReport.php b/app/Exceptions/GithubReport.php index 0870c1f..fc2cc7a 100755 --- a/app/Exceptions/GithubReport.php +++ b/app/Exceptions/GithubReport.php @@ -52,7 +52,7 @@ class GithubReport /** * @var string|bool */ - private string|bool $redisRunning; + private string|bool $redisRunning = false; /** * @var string @@ -94,7 +94,12 @@ class GithubReport $report->instanceType = 'UNKNOWN'; if (env('APP_ENV') !== 'testing') { - $report->instanceType = $_SERVER['SERVER_NAME'] === 'api.jikan.moe' ? 'OFFICIAL' : 'HOSTED'; + if (array_key_exists('SERVER_NAME', $_SERVER)) { + $report->instanceType = $_SERVER['SERVER_NAME'] === 'api.jikan.moe' ? 'OFFICIAL' : 'HOSTED'; + } + else { + $report->instanceType = 'HOSTED-RR'; + } } return $report; diff --git a/app/Http/Middleware/Insights.php b/app/Http/Middleware/Insights.php index 332cd11..e454fd7 100644 --- a/app/Http/Middleware/Insights.php +++ b/app/Http/Middleware/Insights.php @@ -45,7 +45,7 @@ class Insights ->insert([ 'timestamp' => time(), 'url' => $request->getRequestUri(), - 'type' => + 'type' => "" ]); } diff --git a/app/Listeners/PsrWorkerErrorListener.php b/app/Listeners/PsrWorkerErrorListener.php new file mode 100644 index 0000000..7be4af5 --- /dev/null +++ b/app/Listeners/PsrWorkerErrorListener.php @@ -0,0 +1,29 @@ +exceptionHandler = new \App\Exceptions\Handler; + $this->logger = new Logger('psr-worker'); + $this->logger->pushHandler(new StreamHandler(storage_path().'/logs/psr-worker.log'), env('APP_DEBUG') ? Logger::DEBUG : Logger::WARNING); + } + + public function handle(LoopErrorOccurredEvent $event): void + { + try { + $this->exceptionHandler->report($event->exception()); + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + } + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 6672191..c1461c6 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -23,6 +23,7 @@ use App\Services\TypeSenseScoutSearchService; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Collection; use Laravel\Scout\Builder as ScoutBuilder; +use Typesense\LaravelTypesense\Typesense; class AppServiceProvider extends ServiceProvider { @@ -161,4 +162,28 @@ class AppServiceProvider extends ServiceProvider { return $app["config"]->get("scout.driver"); } + + public static function servicesToWarm(): array + { + $services = [ + ScoutSearchService::class, + AnimeSearchQueryBuilder::class, + MangaSearchQueryBuilder::class, + ClubSearchQueryBuilder::class, + CharacterSearchQueryBuilder::class, + PeopleSearchQueryBuilder::class, + TopAnimeQueryBuilder::class, + TopMangaQueryBuilder::class + ]; + + if (env("SCOUT_DRIVER") === "typesense") { + $services[] = Typesense::class; + } + + if (env("SCOUT_DRIVER") === "Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine") { + $services[] = \Elastic\Elasticsearch\Client::class; + } + + return $services; + } } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 0b8f393..23e7784 100755 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -3,6 +3,7 @@ namespace App\Providers; use Laravel\Lumen\Providers\EventServiceProvider as ServiceProvider; +use pushrbx\LumenRoadRunner\Events\LoopErrorOccurredEvent; class EventServiceProvider extends ServiceProvider { @@ -12,8 +13,8 @@ class EventServiceProvider extends ServiceProvider * @var array */ protected $listen = [ - 'App\Events\SomeEvent' => [ - 'App\Listeners\EventListener', - ], + LoopErrorOccurredEvent::class => [ + \App\Listeners\PsrWorkerErrorListener::class + ] ]; } diff --git a/bootstrap/app.php b/bootstrap/app.php index 2d92b35..bfeca7e 100755 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -112,10 +112,13 @@ $app->configure('database'); $app->configure('queue'); $app->configure('controller-to-table-mapping'); $app->configure('controller'); +$app->configure('roadrunner'); +$app->register(\pushrbx\LumenRoadRunner\ServiceProvider::class); $app->register(\SwaggerLume\ServiceProvider::class); $app->register(Flipbox\LumenGenerator\LumenGeneratorServiceProvider::class); $app->register(\App\Providers\SourceHeartbeatProvider::class); +$app->register(\App\Providers\EventServiceProvider::class); $app->register(Illuminate\Database\Eloquent\LegacyFactoryServiceProvider::class); $app->register(\App\Providers\AppServiceProvider::class); @@ -153,7 +156,7 @@ $app->register(Laravel\Scout\ScoutServiceProvider::class); // we support TypeSense and ElasticSearch as search indexes. if (env("SCOUT_DRIVER") === "typesense") { // in this case the TYPESENSE_HOST env var should be set too - $app->register(Typesense\LaravelTypesense\TypesenseServiceProvider::class); + $app->register(\Typesense\LaravelTypesense\TypesenseServiceProvider::class); } if (env("SCOUT_DRIVER") === "Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine") { // in this case the ELASTICSEARCH_HOST env var should be set too diff --git a/composer.json b/composer.json index e075539..0031613 100755 --- a/composer.json +++ b/composer.json @@ -4,6 +4,12 @@ "keywords": ["framework", "laravel", "lumen"], "license": "MIT", "type": "project", + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/pushrbx/lumen-roadrunner" + } + ], "require": { "php": "^8.0", "ext-json": "*", @@ -22,8 +28,10 @@ "laravel/scout": "^9.4", "league/flysystem": "^3.0", "matchish/laravel-scout-elasticsearch": "^6.0", + "mirazmac/dotenvwriter": "^0.1.0", "ocramius/package-versions": "^2.5", "predis/predis": "^1.1", + "pushrbx/lumen-roadrunner": "dev-master", "sentry/sentry-laravel": "^2.8", "symfony/yaml": "^4.1", "typesense/laravel-scout-typesense-driver": "^5.0", diff --git a/config/roadrunner.php b/config/roadrunner.php new file mode 100644 index 0000000..2873bac --- /dev/null +++ b/config/roadrunner.php @@ -0,0 +1,124 @@ + (bool)env('APP_FORCE_HTTPS', false), + + /* + |-------------------------------------------------------------------------- + | Event Listeners + |-------------------------------------------------------------------------- + | + | Worker provided by this package allows to interacts with request + | processing loop using application events. + | + | Feel free to add your own event listeners. + | + */ + + 'listeners' => [ + Events\BeforeLoopStartedEvent::class => [ + ...Defaults::beforeLoopStarted(), + ], + + Events\BeforeLoopIterationEvent::class => [ + ...Defaults::beforeLoopIteration(), + ], + + Events\BeforeRequestHandlingEvent::class => [ + ...Defaults::beforeRequestHandling(), + Listeners\InjectStatsIntoRequestListener::class + ], + + Events\AfterRequestHandlingEvent::class => [ + ...Defaults::afterRequestHandling(), + ], + + Events\AfterLoopIterationEvent::class => [ + ...Defaults::afterLoopIteration(), + Listeners\RunGarbageCollectorListener::class, // keep the memory usage low + // Listeners\CleanupUploadedFilesListener::class, // remove temporary files + ], + + Events\AfterLoopStoppedEvent::class => [ + ...Defaults::afterLoopStopped(), + ], + + Events\LoopErrorOccurredEvent::class => [ + ...Defaults::loopErrorOccurred(), + Listeners\SendExceptionToStderrListener::class, + Listeners\StopWorkerListener::class, + \App\Listeners\PsrWorkerErrorListener::class, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Containers Pre Resolving / Clearing + |-------------------------------------------------------------------------- + | + | The bindings listed below will be resolved before the events loop + | starting. Clearing a binding will force the container to resolve that + | binding again when asked. + | + | Feel free to add your own bindings here. + | + */ + + 'warm' => [ + ...Defaults::servicesToWarm(), + ...\App\Providers\AppServiceProvider::servicesToWarm() + ], + + 'clear' => [ + ...Defaults::servicesToClear(), + 'auth', // is not required for Laravel >= v8.35 + ], + + /* + |-------------------------------------------------------------------------- + | Reset Providers + |-------------------------------------------------------------------------- + | + | Providers that will be registered on every request. + | + | Feel free to add your service-providers here. + | + */ + + 'reset_providers' => [ + ...Defaults::providersToReset(), + Illuminate\Auth\AuthServiceProvider::class, // is not required for Laravel >= v8.35 + Illuminate\Pagination\PaginationServiceProvider::class, // is not required for Laravel >= v8.35 + ], + + /* + |-------------------------------------------------------------------------- + | Worker Classes + |-------------------------------------------------------------------------- + | + | Here you can override the worker class for processing different kinds of + | jobs, that received from the RoadRunner daemon. The key is a worker mode. + | + */ + + 'workers' => [ + Mode::MODE_HTTP => \pushrbx\LumenRoadRunner\Worker::class, + // Mode::MODE_JOBS => ..., + // Mode::MODE_TEMPORAL => ..., + ], +]; diff --git a/container_usage.md b/container_usage.md new file mode 100644 index 0000000..69be8f4 --- /dev/null +++ b/container_usage.md @@ -0,0 +1,71 @@ +# 🐳 Running Jikan API in a container + +```bash +docker run -d --name=jikan-rest -p 8080:8080 -v ./.env:/app/.env jikanme/jikan-rest:latest +``` +- Container listens on port `8080` for http requests +- By mounting your .env file on the container via `-v ./.env:/app/.env` command line option for `docker run` you can configure Jikan API. + +> **Important**: You need to either mount a `.env` file on the container or specify the configuration through environment variables to make Jikan API work in the container. Jikan API needs a MongoDB and optionally a search engine. In high load environments additionally a `redis` server is required too. The configuration should point to the correct address of these services. + +> **Tip**: If you run the container on a non-default network, you can use the container names in the configuration to specify the address of services like MongoDB and TypeSense/ElasticSearch. However this is not a concern if you use `docker-compose`. + +There is also a `Dockerfile` in the repo which you can use to build the container image and startup the app in a container: +```bash +docker build -t jikan-rest:nightly . +docker run -d --name=jikan-rest -p 8080:8080 -v ./.env:/app/.env jikan-rest:nightly +``` + +### Docker compose usage + +``` +docker-compose up +``` +Docker compose will use the `.env` file from the folder where you execute it from to load configurations for the services. If you don't have a `.env` file yet in the folder, copy the `.env.dist` file, and set the passwords. + +> **Please note**: The syntax rules of docker compose for `.env` applies here: https://docs.docker.com/compose/env-file/#syntax-rules + +#### Note for Podman + +If you build the container image yourself with podman, the resulting image format will be OCI by default. +To make the health checks work in that situation you need to run the container the following way: +```bash +podman run -d --name=jikan-rest -p 8080:8080 -v ./.env:/app/.env --health-start-period=5s --health-cmd="curl --fail http://localhost:2114/health?plugin=http || exit 1" jikan-rest:nightly +``` + +#### Configuration of the container + +You can change the settings of Jikan through setting environment variables via the `-e` command line argument option for the `docker run` command. +These environment variables are the same as the options found in the `.env` file. We also provide a sample file called `.env.dist`. +Additionally, you can use the `--env-file` option of `docker run` to specify configuration for Jikan, in which case you put all the configuration in the env file. +```bash +docker run -d --name=jikan-rest -p 8080:8080 --env-file ./env.list jikanme/jikan-rest:latest +``` +The env-file should contain env var value pairs line by line. +``` +VAR1=value1 +VAR2=value2 +``` +There are additional configuration options: + +| Name | Description | +|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| RR_MAX_WORKER_MEMORY | (Number) Configures the available memory in megabytes for the php scripts | +| RR_MAX_REQUEST_SIZE_MB | (Number) Configures the max allowed request body size in megabytes | + | JIKAN_QUEUE_WORKER_PROCESS_NUM | (Number) Configures the number of running queue worker processes. (You want to increase this if you experience huge load) | + | JIKAN_ENABLE_PERIODICAL_FULL_INDEXER | (Bool) Configures whether to run the anime/manga indexer every week, which would crawl all anime/manga at first then it would just grab the latest anime/manga entries from MAL. Defaults to false. | + +You can read more about additional configuration options on the [Configuration Wiki page](https://github.com/jikan-me/jikan-rest/wiki/Configuration). + +## Some facts about the container image + +- Jikan uses RoadRunner as an application server within the container. +- Both `wget` and `curl` exists in the container image. +- The script in `docker-entrypoint.php` sets safe defaults. Because of this by default the app won't behave the same way as the publicly available version of the app at [https://api.jikan.moe/v4](https://api.jikan.moe/v4). The default settings: + - No redis caching + - No search index usage (inaccurate search results) +- Via Roadrunner multiple processes are running in the container, and their logs are aggregated and forwarded to `stdout`. + - These processes are: + - the php processes ingesting the http requests + - [Supercronic](https://github.com/aptible/supercronic), which runs cron jobs. + - Queue workers for populating the search index and other background jobs. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f7815e2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,93 @@ +# For now this is just for local development. This is not production ready. +version: '3.8' +volumes: + mongo-data: {} + redis-data: {} + tmp-data: {} + typesense-data: {} + +services: + jikan_rest: &jikan_rest + build: + context: . + dockerfile: Dockerfile + user: "${APP_UID:-10001}:${APP_GID:-10001}" + environment: + PS1: '\[\033[1;32m\]\[\033[1;36m\][\u@\h] \[\033[1;34m\]\w\[\033[0;35m\] \[\033[1;36m\]# \[\033[0m\]' + HOME: /tmp + APP_DEBUG: 'true' + APP_ENV: local + REDIS_HOST: redis + REDIS_PASSWORD: "${REDIS_PASSWORD:-null}" + DB_CONNECTION: mongodb + DB_HOST: mongodb + DB_DATABASE: jikan + DB_PORT: 27017 + DB_ADMIN: jikan + DB_USERNAME: "${DB_USERNAME}" + DB_PASSWORD: "${DB_PASSWORD}" + volumes: + - /etc/passwd:/etc/passwd:ro + - /etc/group:/etc/group:ro + - tmp-data:/tmp:rw + - .:/app:rw + depends_on: + mongodb: {condition: service_healthy} + redis: {condition: service_healthy} + typesense: {condition: service_healthy} + + web: + <<: *jikan_rest + ports: + - '8080:8080/tcp' + healthcheck: + test: [ 'CMD-SHELL', 'wget --spider -q "http://127.0.0.1:2114/health?plugin=http"' ] + interval: 2s + timeout: 2s + + cron: + <<: *jikan_rest + command: supercronic /etc/supercronic/laravel # it runs artisan schedule:run + + mongodb: + image: mongo:focal + volumes: + - mongo-data:/data/db + ports: + - '27017/tcp' + command: --wiredTigerCacheSizeGB 1 + environment: + MONGO_INITDB_ROOT_USERNAME: "${DB_USERNAME:-root}" + MONGO_INITDB_ROOT_PASSWORD: "${DB_PASSWORD}" + healthcheck: + test: echo 'db.runCommand("ping").ok' | mongo mongodb://localhost:27017 --quiet + interval: 30s + timeout: 10s + retries: 5 + + redis: + image: redis:7-alpine + command: + - /bin/sh + - -c + - redis-server --requirepass "$${REDIS_PASSWORD:?REDIS_PASSWORD variable is not set}" + volumes: + - redis-data:/data:rw + ports: + - '6379/tcp' + healthcheck: + test: [ 'CMD', 'redis-cli', 'ping' ] + interval: 500ms + timeout: 1s + + typesense: + image: typesense/typesense:0.23.1 + entrypoint: /bin/sh + command: + - -c + - /opt/typesense-server --data-dir /data --api-key "$${TYPESENSE_API_KEY:?TYPESENSE_API_KEY variable is not set}" + restart: no + volumes: + - typesense-data:/data + ports: + - "8108/tcp" diff --git a/docker-entrypoint.php b/docker-entrypoint.php new file mode 100755 index 0000000..b8b8602 --- /dev/null +++ b/docker-entrypoint.php @@ -0,0 +1,56 @@ +#!/usr/bin/env php + "null", + "SCOUT_QUEUE" => false, + "THROTTLE" => false, + "QUEUE_CONNECTION" => "database", + "DB_CACHING" => true, + "DB_HOST" => "localhost", + "DB_PORT" => 27017, + "DB_DATABASE" => "jikan", + "DB_USERNAME" => "", + "DB_PASSWORD" => "" +]; + +if (!file_exists(".env")) { + copy(".env.dist", ".env"); + $writer = new \MirazMac\DotEnv\Writer(__DIR__ . '/' . '.env'); + + foreach ($safe_defaults as $env_var_name => $env_var_default) { + $writer->set("SCOUT_DRIVER", env($env_var_name, $env_var_default)); + } + $writer->write(); +} + + +$dotenv = Dotenv::createImmutable(__DIR__); +$dotenv->load(); +$current_env = $_ENV; + +if ($current_env["SCOUT_DRIVER"] === "typesense" && empty($current_env["TYPESENSE_API_KEY"])) { + echo "Please set the TYPESENSE_API_KEY environment variable when setting SCOUT_DRIVER to typesense."; + exit(1); +} + +$rrConfig = \Symfony\Component\Yaml\Yaml::parse(file_get_contents(".rr.yaml")); +$rrConfig["http"]["pool"]["supervisor"]["max_worker_memory"] = (int) env("RR_MAX_WORKER_MEMORY", 128); +$rrConfig["http"]["max_request_size"] = (int) env("RR_MAX_REQUEST_SIZE_MB", 256); +$rrConfig["service"]["laravel_queue_worker_1"]["process_num"] = (int) env("JIKAN_QUEUE_WORKER_PROCESS_NUM", 1); +$periodical_full_indexer_key = "JIKAN_ENABLE_PERIODICAL_FULL_INDEXER"; +if (array_key_exists($periodical_full_indexer_key, $current_env) && in_array($current_env[$periodical_full_indexer_key], [1, '1', 'true', 'True', 'TRUE'])) { + $supercronic_schedule = file_get_contents("/etc/supercronic/laravel"); + $supercronic_schedule .= PHP_EOL; + $supercronic_schedule .= "0 1 * * 1 php /app/artisan indexer:anime --fail && php /app/artisan indexer:anime --resume && php /app/artisan indexer:manga --fail && php /app/artisan indexer:manga --resume"; + $supercronic_schedule .= PHP_EOL; + file_put_contents("/etc/supercronic/laravel", $supercronic_schedule); + $current_time = time(); + echo json_encode(["level" => "info", "ts" => "$current_time.0", "logger" => "container_entrypoint", "msg" => "Full anime/manga indexer is enabled. They will run every Monday at 1am."]) . PHP_EOL; +} +file_put_contents(".rr.yaml", \Symfony\Component\Yaml\Yaml::dump($rrConfig, 8)); diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 0000000..2abac45 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -eo pipefail + +status=0 +if [[ $# -eq 0 ]] ; then + php /app/docker-entrypoint.php + status=$? +else + php /app/docker-entrypoint.php "$@" + status=$? +fi + +[[ $status -ne 0 ]] && exit $status + +exec rr serve -c .rr.yaml diff --git a/docker/base_image/php-8.0/Dockerfile b/docker/base_image/php-8.0/Dockerfile new file mode 100644 index 0000000..97bb986 --- /dev/null +++ b/docker/base_image/php-8.0/Dockerfile @@ -0,0 +1,12 @@ +FROM spiralscout/roadrunner:2.10.6 as roadrunner +FROM composer:2.3.9 as composer +FROM mlocati/php-extension-installer:1.5.29 as php-ext-installer +FROM php:8.0-bullseye as runtime +ARG GITHUB_PERSONAL_TOKEN +COPY --from=composer /usr/bin/composer /usr/bin/composer +COPY --from=php-ext-installer /usr/bin/install-php-extensions /usr/local/bin/ +ENV COMPOSER_HOME="/tmp/composer" +RUN install-php-extensions gd exif intl bz2 gettext mongodb-stable redis opcache sockets pcntl + +# install roadrunner +COPY --from=roadrunner /usr/bin/rr /usr/bin/rr diff --git a/docker/base_image/php-8.1/Dockerfile b/docker/base_image/php-8.1/Dockerfile new file mode 100644 index 0000000..e19e025 --- /dev/null +++ b/docker/base_image/php-8.1/Dockerfile @@ -0,0 +1,12 @@ +FROM spiralscout/roadrunner:2.10.6 as roadrunner +FROM composer:2.3.9 as composer +FROM mlocati/php-extension-installer:1.5.29 as php-ext-installer +FROM php:8.1-bullseye as runtime +ARG GITHUB_PERSONAL_TOKEN +COPY --from=composer /usr/bin/composer /usr/bin/composer +COPY --from=php-ext-installer /usr/bin/install-php-extensions /usr/local/bin/ +ENV COMPOSER_HOME="/tmp/composer" +RUN install-php-extensions gd exif intl bz2 gettext mongodb-stable redis opcache sockets pcntl + +# install roadrunner +COPY --from=roadrunner /usr/bin/rr /usr/bin/rr