Improve release pipeline, add phpstan, general refactoring.
This commit is contained in:
parent
40d816494b
commit
5226b33620
25 changed files with 4419 additions and 447 deletions
.github/workflows
.trunk/configs
DockerfileReadme.mdcomposer.jsoncomposer.lockdocker-compose.ymlphpstan.neon.distrector.phpsrc
test
tests/testsites
107
.github/workflows/bouncer.yml
vendored
107
.github/workflows/bouncer.yml
vendored
|
@ -1,107 +0,0 @@
|
|||
name: Build Bouncer
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
- cron: "0 14 * * 2" # 2pm Patch Tuesday
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Docker Swarm Loadbalancer
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Setup: Checkout Source"
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "Setup: Get Date"
|
||||
id: date
|
||||
run: |
|
||||
{
|
||||
echo "datetime=$(date +'%Y-%m-%d %H:%M:%S')"
|
||||
echo "date=$(date +'%Y-%m-%d')"
|
||||
echo "time=$(date +'%H:%M:%S')"
|
||||
echo "container_build_datetime=$(date -u +'%Y-%m-%dT%H:%M:%S.%3NZ')"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: "Setup: PHP"
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.2
|
||||
|
||||
- name: "Setup: Setup QEMU"
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: "Setup: Expose GitHub Runtime"
|
||||
uses: crazy-max/ghaction-github-runtime@v3
|
||||
|
||||
- name: "Setup: Setup Docker Buildx"
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: "Setup: Login to Docker Hub"
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: matthewbaggett
|
||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||
|
||||
- name: "Setup: Login to GHCR"
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: matthewbaggett
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: "Setup: Find Composer Cache Directory"
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: "Setup: Composer Cache"
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-bouncer-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-bouncer-composer-
|
||||
|
||||
- name: "Dependencies: Composer Install"
|
||||
run: composer install --ignore-platform-reqs
|
||||
|
||||
- name: "Build: Build & Push Image"
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
target: bouncer
|
||||
platforms: ${{ !env.ACT && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
|
||||
pull: true
|
||||
push: true
|
||||
build-args: |
|
||||
GIT_SHA=${{ github.sha }}
|
||||
GIT_BUILD_ID=${{ github.ref_name }}
|
||||
BUILD_DATE=${{ steps.date.outputs.container_build_datetime }}
|
||||
GIT_COMMIT_MESSAGE=${{ github.event.head_commit.message }}
|
||||
tags: |
|
||||
benzine/bouncer:latest
|
||||
ghcr.io/benzine-framework/bouncer:latest
|
||||
cache-from: ${{ !env.ACT && 'type=gha' || '' }}
|
||||
cache-to: ${{ !env.ACT && 'type=gha,mode=max' || '' }}
|
||||
build-contexts: |
|
||||
php:cli=docker-image://ghcr.io/benzine-framework/php:cli-8.2
|
||||
|
||||
- name: "Post-Build: Validate build"
|
||||
shell: bash
|
||||
run: |
|
||||
docker \
|
||||
run \
|
||||
--rm \
|
||||
ghcr.io/benzine-framework/bouncer:latest \
|
||||
/usr/bin/install-report
|
36
.github/workflows/build.yml
vendored
Normal file
36
.github/workflows/build.yml
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
name: Build Swarm Loadbalancer
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
- cron: "0 14 * * 2" # 2pm Patch Tuesday
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-container:
|
||||
uses: ./.github/workflows/docker.build.yml
|
||||
secrets: inherit
|
||||
check-php:
|
||||
uses: ./.github/workflows/php.check.yml
|
||||
secrets: inherit
|
||||
check-trunk:
|
||||
uses: ./.github/workflows/trunk.check.yml
|
||||
secrets: inherit
|
||||
release-container:
|
||||
needs:
|
||||
- build-container
|
||||
- check-php
|
||||
- check-trunk
|
||||
uses: ./.github/workflows/docker.release.yml
|
||||
secrets: inherit
|
72
.github/workflows/docker.build.yml
vendored
Normal file
72
.github/workflows/docker.build.yml
vendored
Normal file
|
@ -0,0 +1,72 @@
|
|||
name: Build Swarm Loadbalancer
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PLATFORMS: linux/amd64,linux/arm64
|
||||
CANDIDATE_IMAGE: ghcr.io/benzine-framework/bouncer:build-${{ github.sha }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Swarm Loadbalancer
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- id: date
|
||||
run: |
|
||||
{
|
||||
echo "datetime=$(date +'%Y-%m-%d %H:%M:%S')"
|
||||
echo "date=$(date +'%Y-%m-%d')"
|
||||
echo "time=$(date +'%H:%M:%S')"
|
||||
echo "container_build_datetime=$(date -u +'%Y-%m-%dT%H:%M:%S.%3NZ')"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
- id: read-php-version
|
||||
run: echo "php_version=$(jq -r '.require["php"]' composer.json | sed -E 's/[^0-9.]//g')" >> $GITHUB_OUTPUT
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ steps.read-php-version.outputs.php_version }}
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: crazy-max/ghaction-github-runtime@v3
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: matthewbaggett
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- id: composer-cache-find
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
- id: composer-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: "${{ runner.os }}-bouncer-composer-${{ hashFiles('**/composer.lock') }}"
|
||||
restore-keys: ${{ runner.os }}-bouncer-composer-
|
||||
- run: composer install --ignore-platform-reqs
|
||||
- name: "Build & Push Candidate Image as ${{ env.CANDIDATE_IMAGE }}"
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
target: bouncer
|
||||
build-contexts: |
|
||||
php:cli=docker-image://ghcr.io/benzine-framework/php:cli-${{ steps.read-php-version.outputs.php_version }}
|
||||
build-args: |
|
||||
GIT_SHA=${{ github.sha }}
|
||||
GIT_BUILD_ID=${{ github.ref_name }}
|
||||
BUILD_DATE=${{ steps.date.outputs.container_build_datetime }}
|
||||
GIT_COMMIT_MESSAGE=${{ github.event.head_commit.message }}
|
||||
platforms: ${{ !env.ACT && env.PLATFORMS || 'linux/amd64' }}
|
||||
pull: true
|
||||
push: true
|
||||
tags: ${{ env.CANDIDATE_IMAGE }}
|
||||
cache-from: ${{ !env.ACT && 'type=gha' || '' }}
|
||||
cache-to: ${{ !env.ACT && 'type=gha,mode=max' || '' }}
|
42
.github/workflows/docker.release.yml
vendored
Normal file
42
.github/workflows/docker.release.yml
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
name: Release Swarm Loadbalancer
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CANDIDATE_IMAGE: ghcr.io/benzine-framework/bouncer:build-${{ github.sha }}
|
||||
RELEASE_IMAGE_GHCR: ghcr.io/benzine-framework/bouncer:latest
|
||||
RELEASE_IMAGE_DOCKER: benzine/bouncer:latest
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release Swarm Loadbalancer
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
registry:
|
||||
- ghcr
|
||||
- docker
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: "Pull Candidate Image"
|
||||
run: docker pull ${{ env.CANDIDATE_IMAGE }}
|
||||
- name: "Login to Docker Hub"
|
||||
if: matrix.registry == 'docker'
|
||||
run: docker login -u matthewbaggett -p ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||
- name: "Login to GHCR"
|
||||
if: matrix.registry == 'ghcr'
|
||||
run: docker login ghcr.io -u matthewbaggett -p ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: "Tag Candidate Image"
|
||||
run: docker tag ${{ env.CANDIDATE_IMAGE }} ${{ matrix.registry == 'ghcr' && env.RELEASE_IMAGE_GHCR || env.RELEASE_IMAGE_DOCKER }}
|
||||
- name: "Push Release Image"
|
||||
run: docker push ${{ matrix.registry == 'ghcr' && env.RELEASE_IMAGE_GHCR || env.RELEASE_IMAGE_DOCKER }}
|
54
.github/workflows/docker.validate.yml
vendored
Normal file
54
.github/workflows/docker.validate.yml
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
name: Validate Swarm Loadbalancer
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
workflow_run:
|
||||
workflows: ["Build Swarm Loadbalancer"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CANDIDATE_IMAGE: ghcr.io/benzine-framework/bouncer:build-${{ github.sha }}
|
||||
|
||||
jobs:
|
||||
validate-install-report:
|
||||
name: Run Install Report
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Post-Build: Validate build"
|
||||
shell: bash
|
||||
run: |
|
||||
docker \
|
||||
run \
|
||||
--rm \
|
||||
${{ env.CANDIDATE_IMAGE }} \
|
||||
/usr/bin/install-report
|
||||
validate-dive-report:
|
||||
name: Run Dive
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Use Dive to inspect the image for junk
|
||||
- name: "Post-Build: Dive"
|
||||
uses: wagoodman/dive@v0.10.0
|
||||
with:
|
||||
args: ${{ env.CANDIDATE_IMAGE }}
|
||||
validate-vulnerability-report:
|
||||
name: Run Trivy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Inspect the container for security vulnerabilities
|
||||
- name: "Post-Build: Trivy"
|
||||
uses: aquasecurity/trivy-action@v0.3.0
|
||||
with:
|
||||
image-ref: ${{ env.CANDIDATE_IMAGE }}
|
||||
format: table
|
||||
exit-code: 1
|
69
.github/workflows/php.check.yml
vendored
Normal file
69
.github/workflows/php.check.yml
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
name: "QC: PHP"
|
||||
|
||||
permissions: read-all
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
- cron: "0 11 * * 2" # 11am Patch Tuesday
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
php-stan:
|
||||
name: PHPStan
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
checks: write # For trunk to post annotations
|
||||
contents: read # For repo checkout
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@v4
|
||||
- name: "Read PHP version from composer.json"
|
||||
id: read-php-version
|
||||
run: echo "php_version=$(jq -r '.require["php"]' composer.json | sed -E 's/[^0-9.]//g')" >> $GITHUB_OUTPUT
|
||||
- name: "Setup PHP"
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ steps.read-php-version.outputs.php_version }}
|
||||
tools: phpstan
|
||||
- name: Run PHPStan
|
||||
run: phpstan analyse src
|
||||
php-cs-fixer:
|
||||
name: PHP-CS-Fixer
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
checks: write # For trunk to post annotations
|
||||
contents: read # For repo checkout
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@v4
|
||||
- name: "Read PHP version from composer.json"
|
||||
id: read-php-version
|
||||
run: echo "php_version=$(jq -r '.require["php"]' composer.json | sed -E 's/[^0-9.]//g')" >> $GITHUB_OUTPUT
|
||||
- name: "Setup PHP"
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ steps.read-php-version.outputs.php_version }}
|
||||
tools: php-cs-fixer
|
||||
- name: "Run PHP CS Fixer"
|
||||
run: php-cs-fixer fix --config=.php-cs-fixer.php --diff --verbose
|
||||
# If there are changed files, create a PR, assign it to whom created the push and fail the build
|
||||
- name: "Create PR"
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
title: "Apply php-cs-fixer changes"
|
||||
commit-message: "Apply php-cs-fixer changes"
|
||||
branch: "php-cs-fixer-${{ github.sha }}"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
assignees: ${{ github.actor }}
|
||||
labels: "auto-apply"
|
||||
body: |
|
||||
This PR was automatically created to apply php-cs-fixer changes.
|
||||
Please review the changes and merge if they are correct.
|
8
.trunk/configs/phpstan.neon.dist
Normal file
8
.trunk/configs/phpstan.neon.dist
Normal file
|
@ -0,0 +1,8 @@
|
|||
parameters:
|
||||
level: 5
|
||||
paths:
|
||||
- src
|
||||
- bin
|
||||
- tests
|
||||
scanFiles:
|
||||
- bin/bouncer
|
21
Dockerfile
21
Dockerfile
|
@ -1,4 +1,4 @@
|
|||
# checkov:skip=CKV_DOCKER_3 user cannot be determined at this stage.
|
||||
# checkov:skip=CKV_DOCKER_3 I don't have time for rootless
|
||||
FROM php:cli as bouncer
|
||||
|
||||
LABEL maintainer="Matthew Baggett <matthew@baggett.me>" \
|
||||
|
@ -96,21 +96,8 @@ HEALTHCHECK --start-period=30s \
|
|||
|
||||
RUN ls -lah /app /app/bin
|
||||
|
||||
# checkov:skip=CKV_DOCKER_3 user cannot be determined at this stage.
|
||||
FROM php:nginx as test-app-a
|
||||
COPY test/public-web-a /app/public
|
||||
# checkov:skip=CKV_DOCKER_3 This is a test container.
|
||||
FROM php:nginx as test-app
|
||||
COPY tests/testsites /app/public
|
||||
HEALTHCHECK --start-period=30s \
|
||||
CMD curl -s -o /dev/null -w "200" http://localhost:80/ || exit 1
|
||||
|
||||
# checkov:skip=CKV_DOCKER_3 user cannot be determined at this stage.
|
||||
FROM php:nginx as test-app-b
|
||||
COPY test/public-web-b /app/public
|
||||
HEALTHCHECK --start-period=30s \
|
||||
CMD curl -s -o /dev/null -w "200" http://localhost:80/ || exit 1
|
||||
|
||||
# checkov:skip=CKV_DOCKER_3 user cannot be determined at this stage.
|
||||
FROM php:nginx as test-app-c
|
||||
COPY test/public-web-c /app/public
|
||||
HEALTHCHECK --start-period=30s \
|
||||
CMD curl -s -o /dev/null -w "200" http://localhost:80/ || exit 1
|
||||
|
||||
|
|
11
Readme.md
11
Readme.md
|
@ -1,5 +1,16 @@
|
|||
# Automatic Swarm Nginx Load Balancer
|
||||
|
||||
This is a non-production-ready automatic loadbalancer that works by sniffing the docker socket for changes to the running systems in a docker swarm.
|
||||
It probably works in k8s too, but I don't use k8s personally.
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
_\* this doesn't include the number of times it's been pulled from the github container registry, and theres no nice way to pull that from a REST API so there isn't a pretty badge for that._
|
||||
|
||||
## Environment variables
|
||||
|
||||
This container has its own environment variables, AS WELL AS scanning for some environment variables associated with your services.
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
{
|
||||
"name": "benzine/bouncer",
|
||||
"description": "Automated Docker-swarm aware Nginx configuration management",
|
||||
"type": "project",
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"license": "GPL-3.0-or-later",
|
||||
"type": "project",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Matthew Baggett",
|
||||
"email": "matthew@baggett.me"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"php": "^8.2",
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-openssl": "*",
|
||||
"adambrett/shell-wrapper": "~1.0",
|
||||
"bramus/monolog-colored-line-formatter": "~3.1",
|
||||
"adambrett/shell-wrapper": "^1.0",
|
||||
"bramus/monolog-colored-line-formatter": "^3.1",
|
||||
"guzzlehttp/guzzle": "^7.8",
|
||||
"kint-php/kint": "^3.3",
|
||||
"league/flysystem": "^2.5",
|
||||
|
@ -21,27 +24,40 @@
|
|||
"nesbot/carbon": "^2.72",
|
||||
"phpspec/php-diff": "^1.1",
|
||||
"spatie/emoji": "^2.3",
|
||||
"symfony/polyfill-php83": "^1.28",
|
||||
"symfony/yaml": "^6.4",
|
||||
"twig/twig": "^3.8"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Matthew Baggett",
|
||||
"email": "matthew@baggett.me"
|
||||
}
|
||||
],
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.46"
|
||||
"ergebnis/composer-normalize": "^2.42",
|
||||
"friendsofphp/php-cs-fixer": "^3.46",
|
||||
"php-static-analysis/rector-rule": "^0.2.2",
|
||||
"phpstan/extension-installer": "^1.2.0",
|
||||
"phpstan/phpstan": "^1.11",
|
||||
"phpunit/phpunit": "^11",
|
||||
"rawr/phpunit-data-provider": "^3.3",
|
||||
"rector/rector": "^1.0",
|
||||
"rregeer/phpunit-coverage-check": "^0.3.1",
|
||||
"squizlabs/php_codesniffer": "^3.7"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Bouncer\\": "src/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"fix": "php-cs-fixer fix"
|
||||
},
|
||||
"bin": [
|
||||
"bin/bouncer"
|
||||
]
|
||||
],
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"ergebnis/composer-normalize": true,
|
||||
"phpstan/extension-installer": true
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"scripts": {
|
||||
"fix": "php-cs-fixer fix",
|
||||
"stan": "phpstan analyse",
|
||||
"test": "phpunit"
|
||||
}
|
||||
}
|
||||
|
|
3852
composer.lock
generated
3852
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -27,12 +27,11 @@ services:
|
|||
additional_contexts:
|
||||
- php:nginx=docker-image://ghcr.io/benzine-framework/php:nginx-8.2
|
||||
volumes:
|
||||
- ./test/public-web-a:/app/public
|
||||
- ./tests/testsites:/app/public
|
||||
environment:
|
||||
- BOUNCER_DOMAIN=a.web.grey.ooo
|
||||
- BOUNCER_TARGET_PORT=80
|
||||
# - BOUNCER_LETSENCRYPT=true
|
||||
|
||||
- SITE_NAME=A
|
||||
web-b:
|
||||
image: test-app-b
|
||||
build:
|
||||
|
@ -40,8 +39,8 @@ services:
|
|||
additional_contexts:
|
||||
- php:nginx=docker-image://ghcr.io/benzine-framework/php:nginx-8.2
|
||||
volumes:
|
||||
- ./test/public-web-b:/app/public
|
||||
- ./tests/testsites:/app/public
|
||||
environment:
|
||||
- BOUNCER_DOMAIN=b.web.grey.ooo
|
||||
- BOUNCER_TARGET_PORT=80
|
||||
# - BOUNCER_LETSENCRYPT=true
|
||||
- SITE_NAME=B
|
||||
|
|
1
phpstan.neon.dist
Symbolic link
1
phpstan.neon.dist
Symbolic link
|
@ -0,0 +1 @@
|
|||
.trunk/configs/phpstan.neon.dist
|
62
rector.php
Normal file
62
rector.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PhpStaticAnalysis\RectorRule\AnnotationsToAttributesRector;
|
||||
use Rector\Config\RectorConfig;
|
||||
use Rector\Doctrine\Set\DoctrineSetList;
|
||||
use Rector\Php80\Rector\Class_\AnnotationToAttributeRector;
|
||||
use Rector\PHPUnit\AnnotationsToAttributes\Rector\Class_\AnnotationWithValueToAttributeRector;
|
||||
use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector;
|
||||
use Rector\PHPUnit\Rector\Class_\PreferPHPUnitSelfCallRector;
|
||||
use Rector\PHPUnit\Set\PHPUnitSetList;
|
||||
use Rector\Removing\Rector\FuncCall\RemoveFuncCallRector;
|
||||
use Rector\Renaming\Rector\Name\RenameClassRector;
|
||||
use Rector\Symfony\Set\SensiolabsSetList;
|
||||
use Rector\Symfony\Set\SymfonySetList;
|
||||
use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector;
|
||||
|
||||
$rectorConfig = RectorConfig::configure();
|
||||
$rectorConfig->withParallel(30);
|
||||
$rectorConfig->withPreparedSets(
|
||||
deadCode: true,
|
||||
codeQuality: true
|
||||
);
|
||||
$rectorConfig->withPaths([
|
||||
__DIR__ . '/bin',
|
||||
__DIR__ . '/src',
|
||||
__DIR__ . '/tests',
|
||||
]);
|
||||
// uncomment to reach your current PHP version
|
||||
$rectorConfig->withPhpSets();
|
||||
$rectorConfig->withSets([
|
||||
PHPUnitSetList::PHPUNIT_80,
|
||||
PHPUnitSetList::PHPUNIT_90,
|
||||
PHPUnitSetList::PHPUNIT_100,
|
||||
PHPUnitSetList::PHPUNIT_CODE_QUALITY,
|
||||
PHPUnitSetList::ANNOTATIONS_TO_ATTRIBUTES,
|
||||
// PhpStaticAnalysisSetList::ANNOTATIONS_TO_ATTRIBUTES,// Implied by PHPUNIT_100
|
||||
DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
|
||||
SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES,
|
||||
SensiolabsSetList::ANNOTATIONS_TO_ATTRIBUTES,
|
||||
__DIR__ . '/vendor/fakerphp/faker/rector-migrate.php',
|
||||
]);
|
||||
$rectorConfig->withConfiguredRule(RemoveFuncCallRector::class, [
|
||||
'var_dump',
|
||||
]);
|
||||
$rectorConfig->withRules([
|
||||
AddVoidReturnTypeWhereNoReturnRector::class,
|
||||
AnnotationToAttributeRector::class,
|
||||
AnnotationWithValueToAttributeRector::class,
|
||||
AnnotationsToAttributesRector::class,
|
||||
]);
|
||||
|
||||
// Prefer self::assert* over $this->assert* in PHPUnit tests
|
||||
$rectorConfig->withSkip([
|
||||
PreferPHPUnitThisCallRector::class,
|
||||
]);
|
||||
$rectorConfig->withRules([
|
||||
PreferPHPUnitSelfCallRector::class,
|
||||
]);
|
||||
|
||||
return $rectorConfig;
|
|
@ -7,6 +7,7 @@ namespace Bouncer;
|
|||
use AdamBrett\ShellWrapper\Command\Builder as CommandBuilder;
|
||||
use AdamBrett\ShellWrapper\Runners\Exec;
|
||||
use Aws\S3\S3Client;
|
||||
use Bouncer\Logger\AbstractLogger;
|
||||
use GuzzleHttp\Client as Guzzle;
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use GuzzleHttp\Exception\ServerException;
|
||||
|
@ -15,7 +16,7 @@ use League\Flysystem\FileAttributes;
|
|||
use League\Flysystem\Filesystem;
|
||||
use League\Flysystem\FilesystemException;
|
||||
use League\Flysystem\Local\LocalFilesystemAdapter;
|
||||
use Monolog\Logger;
|
||||
use Bouncer\Logger\Logger;
|
||||
use Bouncer\Logger\Formatter;
|
||||
use Spatie\Emoji\Emoji;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
@ -38,7 +39,7 @@ class Bouncer
|
|||
private Filesystem $certificateStoreLocal;
|
||||
private ?Filesystem $certificateStoreRemote = null;
|
||||
private Filesystem $providedCertificateStore;
|
||||
private Logger $logger;
|
||||
private AbstractLogger $logger;
|
||||
private array $previousContainerState = [];
|
||||
private array $previousSwarmState = [];
|
||||
private array $fileHashes;
|
||||
|
@ -61,7 +62,7 @@ class Bouncer
|
|||
|
||||
$this->settings = new Settings();
|
||||
|
||||
$this->logger = new \Bouncer\Logger\Logger(
|
||||
$this->logger = new Logger(
|
||||
settings: $this->settings,
|
||||
processIdProcessor: new Processor\ProcessIdProcessor(),
|
||||
memoryPeakUsageProcessor: new Processor\MemoryPeakUsageProcessor(),
|
||||
|
@ -306,6 +307,7 @@ class Bouncer
|
|||
|
||||
$bouncerTarget->setUseGlobalCert($this->isUseGlobalCert());
|
||||
|
||||
// @phpstan-ignore-next-line MB: I'm not sure you're right about ->hasCustomNginxConfig only returning false, Stan..
|
||||
if ($bouncerTarget->isEndpointValid() || $bouncerTarget->hasCustomNginxConfig()) {
|
||||
$bouncerTargets[] = $bouncerTarget;
|
||||
} else {
|
||||
|
@ -364,6 +366,7 @@ class Bouncer
|
|||
|
||||
exit(1);
|
||||
}
|
||||
// @phpstan-ignore-next-line Yes, I know this is a loop, that is desired.
|
||||
while (true) {
|
||||
$this->runLoop();
|
||||
}
|
||||
|
@ -471,24 +474,6 @@ class Bouncer
|
|||
return json_decode($this->docker->request('GET', "containers/{$id}/json")->getBody()->getContents(), true);
|
||||
}
|
||||
|
||||
private function dockerEnvHas(string $key, ?array $envs): bool
|
||||
{
|
||||
if ($envs === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($envs as $env) {
|
||||
if (stripos($env, '=') !== false) {
|
||||
[$envKey, $envVal] = explode('=', $env, 2);
|
||||
if ($envKey === $key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function dockerEnvFilter(?array $envs): array
|
||||
{
|
||||
if ($envs === null) {
|
||||
|
|
417
src/Logger/AbstractLogger.php
Normal file
417
src/Logger/AbstractLogger.php
Normal file
|
@ -0,0 +1,417 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bouncer\Logger;
|
||||
|
||||
use PhpStaticAnalysis\Attributes\Type;
|
||||
use PhpStaticAnalysis\Attributes\Param;
|
||||
use PhpStaticAnalysis\Attributes\Returns;
|
||||
use Monolog\DateTimeImmutable;
|
||||
use Monolog\Handler\HandlerInterface;
|
||||
use Monolog\Level;
|
||||
use Monolog\LogRecord;
|
||||
use Monolog\Processor\ProcessorInterface;
|
||||
use Monolog\ResettableInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
abstract class AbstractLogger implements LoggerInterface, ResettableInterface
|
||||
{
|
||||
protected array $handlers;
|
||||
protected bool $microsecondTimestamps = true;
|
||||
protected \DateTimeZone $timezone;
|
||||
protected ?\Closure $exceptionHandler = null;
|
||||
|
||||
/**
|
||||
* Keeps track of depth to prevent infinite logging loops.
|
||||
*/
|
||||
private int $logDepth = 0;
|
||||
|
||||
#[Type('\WeakMap<\Fiber<mixed, mixed, mixed, mixed>, int>')] // Keeps track of depth inside fibers to prevent infinite logging loops
|
||||
private \WeakMap $fiberLogDepth;
|
||||
|
||||
/**
|
||||
* Whether to detect infinite logging loops
|
||||
* This can be disabled via {@see useLoggingLoopDetection} if you have async handlers that do not play well with this.
|
||||
*/
|
||||
private bool $detectCycles = true;
|
||||
|
||||
#[Param(name: 'string')] // The logging channel, a simple descriptive name that is attached to all log records
|
||||
#[Param(handlers: 'HandlerInterface[]')] // optional stack of handlers, the first one in the array is called first, etc
|
||||
#[Param(processors: 'callable[]')] // Optional array of processors
|
||||
#[Param(timezone: 'null|\DateTimeZone')] // Optional timezone, if not provided date_default_timezone_get() will be used
|
||||
#[Param(processors: 'array<(callable(LogRecord):LogRecord | ProcessorInterface)>')]
|
||||
public function __construct(protected string $name, array $handlers = [], #[Type('array<(callable(LogRecord):LogRecord | ProcessorInterface)>')]
|
||||
protected array $processors = [], ?\DateTimeZone $timezone = null)
|
||||
{
|
||||
$this->setHandlers($handlers);
|
||||
$this->timezone = $timezone ?? new \DateTimeZone(date_default_timezone_get());
|
||||
$this->fiberLogDepth = new \WeakMap();
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new cloned instance with the name changed.
|
||||
*/
|
||||
#[Returns('static')]
|
||||
public function withName(string $name): self
|
||||
{
|
||||
$new = clone $this;
|
||||
$new->name = $name;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function pushHandler(HandlerInterface $handler): self
|
||||
{
|
||||
array_unshift($this->handlers, $handler);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setHandlers(array $handlers): self
|
||||
{
|
||||
$this->handlers = [];
|
||||
foreach (array_reverse($handlers) as $handler) {
|
||||
$this->pushHandler($handler);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
#[Param(callback: 'ProcessorInterface|callable(LogRecord):LogRecord')]
|
||||
#[Returns('$this')]
|
||||
public function pushProcessor(callable | ProcessorInterface $callback): self
|
||||
{
|
||||
array_unshift($this->processors, $callback);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
#[Returns('$this')]
|
||||
public function useLoggingLoopDetection(bool $detectCycles): self
|
||||
{
|
||||
$this->detectCycles = $detectCycles;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a log record.
|
||||
*/
|
||||
#[Param(level: 'Level')] // The logging level (a Monolog or RFC 5424 level)
|
||||
#[Param(message: 'string')] // The log message
|
||||
#[Param(context: 'mixed[]')] // The log context
|
||||
#[Param(datetime: 'null|DateTimeImmutable')] // Optional log date to log into the past or future
|
||||
#[Returns('bool')] // Whether the record has been processed
|
||||
#[Param(level: 'value-of<Level::VALUES>|Level')]
|
||||
public function addRecord(Level $level, string $message, array $context = [], ?DateTimeImmutable $datetime = null): bool
|
||||
{
|
||||
if ($this->detectCycles) {
|
||||
if (($fiber = \Fiber::getCurrent()) instanceof \Fiber) {
|
||||
$logDepth = $this->fiberLogDepth[$fiber] = ($this->fiberLogDepth[$fiber] ?? 0) + 1;
|
||||
} else {
|
||||
$logDepth = ++$this->logDepth;
|
||||
}
|
||||
} else {
|
||||
$logDepth = 0;
|
||||
}
|
||||
|
||||
if ($logDepth === 3) {
|
||||
$this->warning('A possible infinite logging loop was detected and aborted. It appears some of your handler code is triggering logging, see the previous log record for a hint as to what may be the cause.');
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($logDepth >= 5) { // log depth 4 is let through, so we can log the warning above
|
||||
return false;
|
||||
}
|
||||
|
||||
$trace = debug_backtrace();
|
||||
$context = array_merge(
|
||||
[
|
||||
'file' => basename($trace[1]['file']),
|
||||
'line' => $trace[1]['line'],
|
||||
'pid' => getmypid(),
|
||||
],
|
||||
$context
|
||||
);
|
||||
|
||||
try {
|
||||
$recordInitialized = $this->processors === [];
|
||||
|
||||
$record = new LogRecord(
|
||||
datetime: $datetime ?? new DateTimeImmutable($this->microsecondTimestamps, $this->timezone),
|
||||
channel: $this->name,
|
||||
level: $level,
|
||||
message: $message,
|
||||
context: $context,
|
||||
extra: [],
|
||||
);
|
||||
$handled = false;
|
||||
|
||||
foreach ($this->handlers as $handler) {
|
||||
if (false === $recordInitialized) {
|
||||
// skip initializing the record as long as no handler is going to handle it
|
||||
if (!$handler->isHandling($record)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($this->processors as $processor) {
|
||||
$record = $processor($record);
|
||||
}
|
||||
$recordInitialized = true;
|
||||
} catch (\Throwable $e) {
|
||||
$this->handleException($e, $record);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// once the record is initialized, send it to all handlers as long as the bubbling chain is not interrupted
|
||||
try {
|
||||
$handled = true;
|
||||
if (true === $handler->handle(clone $record)) {
|
||||
break;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$this->handleException($e, $record);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return $handled;
|
||||
} finally {
|
||||
if ($this->detectCycles) {
|
||||
if (isset($fiber)) {
|
||||
--$this->fiberLogDepth[$fiber];
|
||||
} else {
|
||||
--$this->logDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
foreach ($this->handlers as $handler) {
|
||||
$handler->close();
|
||||
}
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
foreach ($this->handlers as $handler) {
|
||||
if ($handler instanceof ResettableInterface) {
|
||||
$handler->reset();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->processors as $processor) {
|
||||
if ($processor instanceof ResettableInterface) {
|
||||
$processor->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Logger has a handler that listens on the given level.
|
||||
*/
|
||||
#[Param(level: 'Level')]
|
||||
public function isHandling(Level $level): bool
|
||||
{
|
||||
$record = new LogRecord(
|
||||
datetime: new DateTimeImmutable($this->microsecondTimestamps, $this->timezone),
|
||||
channel: $this->name,
|
||||
message: '',
|
||||
level: $level,
|
||||
);
|
||||
|
||||
foreach ($this->handlers as $handler) {
|
||||
if ($handler->isHandling($record)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a log record at an arbitrary level.
|
||||
*
|
||||
* This method allows for compatibility with common interfaces.
|
||||
*/
|
||||
#[Param(level: 'mixed')] // The log level (a Monolog, PSR-3 or RFC 5424 level)
|
||||
#[Param(message: 'string|\Stringable')] // The log message
|
||||
#[Param(context: 'mixed[]')] // The log context
|
||||
#[Param(level: 'Level|LogLevel::*')]
|
||||
public function log($level, string | \Stringable $message, array $context = []): void
|
||||
{
|
||||
if (!$level instanceof Level) {
|
||||
$level = Level::Critical;
|
||||
}
|
||||
|
||||
$this->addRecord($level, "A Level that wasn't valid was used to write to a Logger: {level}", ['level' => $level]);
|
||||
$this->addRecord($level, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a log record at the DEBUG level.
|
||||
*
|
||||
* This method allows for compatibility with common interfaces.
|
||||
*/
|
||||
#[Param(message: 'string|\Stringable')] // The log message
|
||||
#[Param(context: 'mixed[]')] // The log context
|
||||
public function debug(string | \Stringable $message, array $context = []): void
|
||||
{
|
||||
$this->addRecord(Level::Debug, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a log record at the INFO level.
|
||||
*
|
||||
* This method allows for compatibility with common interfaces.
|
||||
*/
|
||||
#[Param(message: 'string|\Stringable')] // The log message
|
||||
#[Param(context: 'mixed[]')] // The log context
|
||||
public function info(string | \Stringable $message, array $context = []): void
|
||||
{
|
||||
$this->addRecord(Level::Info, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a log record at the NOTICE level.
|
||||
*
|
||||
* This method allows for compatibility with common interfaces.
|
||||
*/
|
||||
#[Param(message: 'string|\Stringable')] // The log message
|
||||
#[Param(context: 'mixed[]')] // The log context
|
||||
public function notice(string | \Stringable $message, array $context = []): void
|
||||
{
|
||||
$this->addRecord(Level::Notice, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a log record at the WARNING level.
|
||||
*
|
||||
* This method allows for compatibility with common interfaces.
|
||||
*/
|
||||
#[Param(message: 'string|\Stringable')] // The log message
|
||||
#[Param(context: 'mixed[]')] // The log context
|
||||
public function warning(string | \Stringable $message, array $context = []): void
|
||||
{
|
||||
$this->addRecord(Level::Warning, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a log record at the ERROR level.
|
||||
*
|
||||
* This method allows for compatibility with common interfaces.
|
||||
*/
|
||||
#[Param(message: 'string|\Stringable')] // The log message
|
||||
#[Param(context: 'mixed[]')] // The log context
|
||||
public function error(string | \Stringable $message, array $context = []): void
|
||||
{
|
||||
$this->addRecord(Level::Error, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a log record at the CRITICAL level.
|
||||
*
|
||||
* This method allows for compatibility with common interfaces.
|
||||
*/
|
||||
#[Param(message: 'string|\Stringable')] // The log message
|
||||
#[Param(context: 'mixed[]')] // The log context
|
||||
public function critical(string | \Stringable $message, array $context = []): void
|
||||
{
|
||||
$this->addRecord(Level::Critical, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a log record at the ALERT level.
|
||||
*
|
||||
* This method allows for compatibility with common interfaces.
|
||||
*/
|
||||
#[Param(message: 'string|\Stringable')] // The log message
|
||||
#[Param(context: 'mixed[]')] // The log context
|
||||
public function alert(string | \Stringable $message, array $context = []): void
|
||||
{
|
||||
$this->addRecord(Level::Alert, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a log record at the EMERGENCY level.
|
||||
*
|
||||
* This method allows for compatibility with common interfaces.
|
||||
*/
|
||||
#[Param(message: 'string|\Stringable')] // The log message
|
||||
#[Param(context: 'mixed[]')] // The log context
|
||||
public function emergency(string | \Stringable $message, array $context = []): void
|
||||
{
|
||||
$this->addRecord(Level::Emergency, (string) $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timezone to be used for the timestamp of log records.
|
||||
*/
|
||||
#[Returns('$this')]
|
||||
public function setTimezone(\DateTimeZone $tz): self
|
||||
{
|
||||
$this->timezone = $tz;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timezone to be used for the timestamp of log records.
|
||||
*/
|
||||
public function getTimezone(): \DateTimeZone
|
||||
{
|
||||
return $this->timezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates exception management to the custom exception handler,
|
||||
* or throws the exception if no custom handler is set.
|
||||
*/
|
||||
protected function handleException(\Throwable $e, LogRecord $record): void
|
||||
{
|
||||
if (!$this->exceptionHandler instanceof \Closure) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
($this->exceptionHandler)($e, $record);
|
||||
}
|
||||
|
||||
#[Returns('array<string, mixed>')]
|
||||
public function __serialize(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'handlers' => $this->handlers,
|
||||
'processors' => $this->processors,
|
||||
'microsecondTimestamps' => $this->microsecondTimestamps,
|
||||
'timezone' => $this->timezone,
|
||||
'exceptionHandler' => $this->exceptionHandler,
|
||||
'logDepth' => $this->logDepth,
|
||||
'detectCycles' => $this->detectCycles,
|
||||
];
|
||||
}
|
||||
|
||||
#[Param(data: 'array<string, mixed>')]
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
foreach (['name', 'handlers', 'processors', 'microsecondTimestamps', 'timezone', 'exceptionHandler', 'logDepth', 'detectCycles'] as $property) {
|
||||
if (isset($data[$property])) {
|
||||
$this->{$property} = $data[$property];
|
||||
}
|
||||
}
|
||||
|
||||
$this->fiberLogDepth = new \WeakMap();
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ namespace Bouncer\Logger;
|
|||
use Bouncer\Settings\Settings;
|
||||
use Monolog\Processor;
|
||||
|
||||
class Logger extends \Monolog\Logger
|
||||
class Logger extends AbstractLogger
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Settings $settings,
|
||||
|
|
|
@ -4,8 +4,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace Bouncer;
|
||||
|
||||
use Bouncer\Logger\AbstractLogger;
|
||||
use Bouncer\Logger\Logger;
|
||||
use Bouncer\Settings\Settings;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Spatie\Emoji\Emoji;
|
||||
|
||||
class Target
|
||||
|
@ -27,14 +29,12 @@ class Target
|
|||
private ?int $proxyTimeoutSeconds = null;
|
||||
private ?string $username = null;
|
||||
private ?string $password = null;
|
||||
|
||||
private ?string $hostOverride = null;
|
||||
private ?string $customNginxConfig = null;
|
||||
|
||||
private bool $requiresForcedScanning = false;
|
||||
|
||||
public function __construct(
|
||||
private Logger $logger,
|
||||
private AbstractLogger $logger,
|
||||
private Settings $settings,
|
||||
) {
|
||||
$this->allowNonSSL = $this->settings->get('ssl/allow_non_ssl', true);
|
||||
|
@ -94,9 +94,6 @@ class Target
|
|||
return $this->username;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string
|
||||
*/
|
||||
public function setUsername(string $username): self
|
||||
{
|
||||
$this->username = $username;
|
||||
|
@ -298,9 +295,6 @@ class Target
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDomains(): array
|
||||
{
|
||||
return $this->domains;
|
||||
|
@ -420,10 +414,6 @@ class Target
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getLogger(): Logger
|
||||
{
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
public function updateLogger(): self
|
||||
{
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
<h1>Website A</h1>
|
Binary file not shown.
Before Width: 64px | Height: 64px | Size: 34 KiB |
|
@ -1 +0,0 @@
|
|||
<h1>Website B</h1>
|
Binary file not shown.
Before Width: 64px | Height: 64px | Size: 34 KiB |
|
@ -1 +0,0 @@
|
|||
<h1>Website C</h1>
|
Before Width: 64px | Height: 64px | Size: 34 KiB After Width: 64px | Height: 64px | Size: 34 KiB |
5
tests/testsites/index.php
Normal file
5
tests/testsites/index.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
$environment = array_merge($_ENV, $_SERVER);
|
||||
$site = $environment['SITE_NAME'] ?? 'unknown';
|
||||
$server = $environment['SERVER_NAME'] ?? gethostname();
|
||||
printf("<h1>Website %s</h1><p>Running on %s</p>", $site, $server);
|
Loading…
Reference in a new issue