Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Set a specific port if needed, otherwise a random available port will be used
# PORT=8080
32 changes: 20 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,33 @@ A minimal web server starter pack with:
- a top level error handler with developer friendly error response
- basic error page
- basic dark mode css
- minimal docker support
- basic docker compose setup

All requests for php files, directories, or sensitive files
are redirected to the public/index.php.
## Universal Structure.

An additional .htaccess and index.php outside the public folder allow the server to run form either folder
with the same behaviour.
The project structure is compatible with docker and native apache and shared hosting setups.

Requires php7 or higher and composer.
All requests are redirected to the public/index.php.

## Setup
Run `composer init` to create a composer.json.
Run the web server with docker `./docker/start-server.sh`.
An additional .htaccess and index.php outside the public folder allow the server to
run exactly the same when forced by shared hosting providers to put the entire repo in a public web root.

Additional htaccess for src/ blocks public access to source files regardless of server setup.

# Requires:
apache with mod_rewrite enabled and php 8.0+.

# Recommended development setup:
- Clone the repo and delete the .git folder
- Run `composer init` and set up PSR-4 autoloading with src/ as the namespace root (default setting).
- Run `composer dump-autoload` to generate the autoloader.
- Run `docker compose up` to start the server (random port will be assigned unlees you set the PORT env variable in a .env file).

## Licence
Do what you want with this code.

It comes as is with no warranty and the author
It comes as is with no warranty, and the author
accepts no liability in any way for how it is used.

You do not have to keep a copy this licence with the code,
once you delete it the code is yours and I expect no credit.
You do not have to keep a copy this license with the code,
once you delete it, the code is yours, and I expect no credit.
72 changes: 72 additions & 0 deletions bin/pull-template.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/bin/bash
set -e

TEMPLATE_GIT_URL="https://github.com/b-hayes/php-apache-template.git"
TEMPLATE_DIRS=("$HOME/repo/php-apache-template" "$HOME/repo/b-hayes/php-apache-template" "../php-apache-template")
TEMPLATE_DIR=""

# Check for directory argument
if [ -n "$1" ]; then
if [ -d "$1" ]; then
TEMPLATE_DIR="$1"
echo "Using specified directory: $TEMPLATE_DIR"
# If the specified directory is a git repo, exclude git-ignored files
if [ -d "$TEMPLATE_DIR/.git" ]; then
echo "Files to be copied from $TEMPLATE_DIR (excluding .git, README.md, and git-ignored):" >&2
git -C "$TEMPLATE_DIR" ls-files --others --ignored --exclude-standard --directory | sed "s|^|IGNORED: |" >&2
files_to_copy=$(git -C "$TEMPLATE_DIR" ls-files --cached --others --exclude-standard | grep -vE '^README.md$')
echo "$files_to_copy" >&2
read -p "Are you sure you want to copy these files and overwrite existing ones? (y/N): " confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
echo "Aborted."
exit 1
fi
# Copy only tracked and untracked (not ignored) files, excluding .git and README.md
(cd "$TEMPLATE_DIR" && tar --exclude=.git --exclude=README.md -cf - $files_to_copy) | tar -xf -
else
echo "Files to be copied from $TEMPLATE_DIR:" >&2
find "$TEMPLATE_DIR" -type f ! -path "$TEMPLATE_DIR/.git/*" ! -name 'README.md' | sed "s|^$TEMPLATE_DIR/||" >&2
read -p "Are you sure you want to copy these files and overwrite existing ones? (y/N): " confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
echo "Aborted."
exit 1
fi
# Copy files from specified directory
rsync -av --exclude='.git' --exclude='README.md' "$TEMPLATE_DIR"/ ./
fi
if git diff --quiet; then
echo "No changes detected."
else
git --no-pager diff
fi
exit 0
fi
fi

# Determine possible template directories
for dir in "${TEMPLATE_DIRS[@]}"; do
if [ -d "$dir/.git" ]; then
TEMPLATE_DIR="$dir"
break
fi
done
if [ -z "$TEMPLATE_DIR" ]; then
TEMPLATE_DIR="${TEMPLATE_DIRS[0]}"
git clone "$TEMPLATE_GIT_URL" "$TEMPLATE_DIR"
fi

# Pull latest changes from master
cd "$TEMPLATE_DIR"
git checkout master
git pull origin master
cd - > /dev/null

# Copy files from template to current project (excluding .git and README.md)
rsync -av --exclude='.git' --exclude='README.md' "$TEMPLATE_DIR"/ ./

# Show git diff
if git diff --quiet; then
echo "No changes detected."
else
git --no-pager diff
fi
135 changes: 135 additions & 0 deletions bin/update-template.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/bin/bash

set -e

TEMPLATE_GIT_URL="https://github.com/b-hayes/php-apache-template.git"
# Possible locations for the template directory based on my usual workflows
TEMPLATE_DIRS=("$HOME/repo/php-apache-template" "$HOME/repo/b-hayes/php-apache-template" "../php-apache-template")
TEMPLATE_DIR=""

# Determine possible template directories
for dir in "${TEMPLATE_DIRS[@]}"; do
if [ -d "$dir/.git" ]; then
TEMPLATE_DIR="$dir"
break
fi
done
if [ -z "$TEMPLATE_DIR" ]; then
TEMPLATE_DIR="${TEMPLATE_DIRS[0]}"
git clone "$TEMPLATE_GIT_URL" "$TEMPLATE_DIR"
fi

if [[ "$1" == "--add" ]]; then
shift
if [ $# -eq 0 ]; then
echo "No files specified to add."
exit 1
fi
FILES_TO_UPDATE=("$@")
elif [[ "$1" == "--add-changed" ]]; then
mapfile -t FILES_TO_UPDATE < <(git status --porcelain | awk '{print $2}' | grep -v '^README.md$')
if [ ${#FILES_TO_UPDATE[@]} -eq 0 ]; then
echo "No changed or new files to add."
exit 0
fi
elif [[ "$1" == "--all-template-files" ]]; then
cd "$TEMPLATE_DIR"
FILES_TO_UPDATE=( $(git ls-files --others --exclude-standard --cached | grep -v -i '^README.md$') )
cd - > /dev/null
else
echo "Usage: $0 [--add <files...> | --add-changed | --all-template-files]"
echo " --add <files...> Add specific files to the template repo."
echo " --add-changed Add new/modified files in the current repo (excluding README.md)."
echo " --all-template-files Add all template files (excluding README.md)."
exit 0
fi

# Copy updated files
for file in "${FILES_TO_UPDATE[@]}"; do
src_file="$(pwd)/$file"
dest_file="$TEMPLATE_DIR/$file"
mkdir -p "$(dirname "$dest_file")"
cp -v "$src_file" "$dest_file"
done

# Commit and push changes
cd "$TEMPLATE_DIR"

# Intelligent commit message generation
status_output=$(git status --porcelain)
added_files=($(echo "$status_output" | awk '$1 ~ /^A/ {print $2}'))
modified_files=($(echo "$status_output" | awk '$1 ~ /^M/ {print $2}'))
deleted_files=($(echo "$status_output" | awk '$1 ~ /^D/ {print $2}'))

commit_msg=""
if [ ${#added_files[@]} -gt 0 ]; then
if [ ${#added_files[@]} -eq 1 ]; then
commit_msg="Added ${added_files[0]}"
else
commit_msg="Added ${added_files[0]} and others"
fi
fi
if [ ${#modified_files[@]} -gt 0 ]; then
if [ -n "$commit_msg" ]; then
commit_msg+="; "
fi
if [ ${#modified_files[@]} -eq 1 ]; then
commit_msg+="Updated ${modified_files[0]}"
else
commit_msg+="Updated ${modified_files[0]} and others"
fi
fi
if [ ${#deleted_files[@]} -gt 0 ]; then
if [ -n "$commit_msg" ]; then
commit_msg+="; "
fi
if [ ${#deleted_files[@]} -eq 1 ]; then
commit_msg+="Removed ${deleted_files[0]}"
else
commit_msg+="Removed ${deleted_files[0]} and others"
fi
fi
if [ -z "$commit_msg" ]; then
commit_msg="Update from $CURR_FOLDER_NAME"
fi

# Get current folder name for branch
CURR_FOLDER_NAME=$(basename "$(git rev-parse --show-toplevel)")
BRANCH_NAME="update-from-$CURR_FOLDER_NAME"

git fetch origin
if git ls-remote --exit-code --heads origin "$BRANCH_NAME" > /dev/null; then
git checkout "$BRANCH_NAME"
git pull origin "$BRANCH_NAME"
else
git checkout -b "$BRANCH_NAME"
fi

git add .

# Show staged changes and confirm before committing
if ! git diff --cached --quiet; then
echo -e "\033[0;32mThe following files are staged for commit:\033[0m" >&2
git diff --cached --name-status | sed $'s/^/\033[0;32m/;s/$/\033[0m/' >&2
# Use the intelligent commit_msg as the default
echo -e "\033[1;33mEnter commit message (default: '$commit_msg'):\033[0m" >&2
read -e -i "$commit_msg" user_commit_msg
commit_msg=${user_commit_msg:-$commit_msg}
git commit -m "$commit_msg"
PUSH_OUTPUT=$(git push --set-upstream origin "$BRANCH_NAME" 2>&1)
# Look for PR link in push output and open if present
PR_LINK=$(echo "$PUSH_OUTPUT" | grep -oE 'https://github.com/[^ ]+/pull/new/[^ ]+')
if [ -n "$PR_LINK" ]; then
if command -v xdg-open >/dev/null 2>&1; then
xdg-open "$PR_LINK" >/dev/null 2>&1 &
elif command -v open >/dev/null 2>&1; then
open "$PR_LINK" >/dev/null 2>&1 &
elif command -v explorer.exe >/dev/null 2>&1; then
explorer.exe "$PR_LINK" >/dev/null 2>&1 &
else
echo "Please open: $PR_LINK"
fi
fi
else
echo "No changes to commit."
fi
9 changes: 9 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
app:
image: php:8-apache
ports:
- "${PORT:-0}:80"
volumes:
- .:/var/www/html
- ./docker/apache.conf:/etc/apache2/sites-enabled/000-default.conf
user: "1000:1000"
35 changes: 8 additions & 27 deletions docker/apache.conf
Original file line number Diff line number Diff line change
@@ -1,33 +1,14 @@
LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so

<VirtualHost *:80>
# The ServerName directive sets the request scheme, hostname and port that
# the server uses to identify itself. This is used when creating
# redirection URLs. In the context of virtual hosts, the ServerName
# specifies what hostname must appear in the request's Host: header to
# match this virtual host. For the default virtual host (this file) this
# value is not decisive as it is used as a last resort host regardless.
# However, you must set it for any further virtual host explicitly.
#ServerName www.example.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html/public

ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
<Directory /var/www/html/public>
AllowOverride All
Require all granted
</Directory>

# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn

ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined

# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
16 changes: 0 additions & 16 deletions docker/start-server.sh

This file was deleted.

15 changes: 15 additions & 0 deletions public/403.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
http_response_code(403);
?>
<div style="width: 100%;text-align: center;">
<p style="font-size: xxx-large"><?=http_response_code()?></p>
<p style="font-size: xxx-large;">🤨🤚</p>
<h1>Sorry mate you cant be here.</h1>
</div>
<style>
body {
display: flex;
align-items: center; /* Vertical center alignment */
justify-content: center; /* Horizontal center alignment */
}
</style>
Loading