A collection of battle-tested Bash scripts to automate Git identity management, SSH key configuration, repository operations, system maintenance, and local WordPress setup β designed for developers managing multiple GitHub accounts on a single machine.
Important
Scripts are created without the .sh extension so they can be called directly by name from any terminal session. This follows the Unix convention for command-line tools.
- Architecture Overview
- SSH Key Convention
- Quick Install
- Script Reference
- Use Case Workflows
- Troubleshooting
~/.local/bin/ β Scripts directory (must be in $PATH)
~/.gitconfig β Main Git config (primary user + includeIf blocks)
~/.gitconfig-<name> β Per-user Git config (overrides inside specific dirs)
~/.ssh/config β SSH host aliases (gh-<keyname> β github.com)
~/.ssh/<keyname> β ed25519 private key
~/.ssh/<keyname>.pub β ed25519 public key (add to GitHub β Settings β SSH Keys)
Git's [includeIf "gitdir:~/dir/"] directive automatically activates a different [user] block when you are inside a matching directory. This means no manual git config commands are needed β identity switching is automatic and directory-scoped.
~/.gitconfig
βββ [user] name = Personal Dev β Used everywhere by default
βββ [includeIf "gitdir:~/work/"]
β βββ path = ~/.gitconfig-work β Overrides user when inside ~/work/
βββ [init] defaultBranch = main
~/.ssh/config
βββ Host gh-personal
β HostName github.com β Resolves to github.com
β IdentityFile ~/.ssh/personal β Uses personal key
βββ Host gh-work
HostName github.com
IdentityFile ~/.ssh/work β Uses work key
Clone URLs use the alias instead of github.com:
git@gh-personal:SofiDevO/my-repo.git β Uses personal SSH key
git@gh-work:MyCompany/project.git β Uses work SSH key
All scripts follow the same naming pattern consistently:
| Component | Pattern | Example |
|---|---|---|
| Private key file | ~/.ssh/<keyname> |
~/.ssh/personal |
| Public key file | ~/.ssh/<keyname>.pub |
~/.ssh/personal.pub |
| SSH config host | Host gh-<keyname> |
Host gh-personal |
| Clone URL format | git@gh-<keyname>:user/repo.git |
git@gh-personal:SofiDevO/repo.git |
The
gh-prefix is a deliberate namespace to avoid collisions with system-level SSH hosts and make all GitHub aliases immediately recognizable.
mkdir -p ~/.local/bin
git clone https://github.com/SofiDevO/sofi-scripts ~/.local/binTip
If you don't need a particular script, delete it from ~/.local/bin/ before running set-scripts. It won't affect the others.
chmod +x ~/.local/bin/set-scripts
~/.local/bin/set-scriptsThis is the only script you need to configure manually. It handles all others.
source ~/.zshrc # zsh users
# or
source ~/.bashrc # bash userswhich setup-git-users # β /home/<user>/.local/bin/setup-git-users
which clone-repo # β /home/<user>/.local/bin/clone-repo
which push-repo # β /home/<user>/.local/bin/push-repoConfigure all scripts as executable and register the directory in $PATH
The bootstrapper for the entire toolkit. It iterates over every file in ~/.local/bin, applies chmod +x, and ensures the directory is present in the $PATH of the detected shell.
- Reads all files in
$HOME/.local/bin - Skips itself,
.mdfiles, and subdirectories - Applies
chmod +xto each file - Detects the current shell via
$SHELL - If
~/.local/binis not in$PATH, appendsexport PATH="~/.local/bin:$PATH"to the appropriate config file
| Shell | Config file modified |
|---|---|
bash |
~/.bashrc |
zsh |
~/.zshrc |
fish |
~/.config/fish/config.fish |
| other | ~/.profile |
chmod +x ~/.local/bin/set-scripts
~/.local/bin/set-scriptsββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Configure Scripts as Executable β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π Looking for scripts in: /home/sofi/.local/bin
π§ Processing: add-git-user
β
add-git-user is now executable
π§ Processing: clone-repo
β
clone-repo is now executable
...
π Summary:
β’ Scripts processed: 11
β’ Directory: /home/sofi/.local/bin
β
Directory /home/sofi/.local/bin is already in PATH
π Setup complete!
π Available scripts:
β’ add-git-user
β’ add-repo
β’ clone-project
β’ clone-repo
...
Full Git + SSH environment setup from scratch
The primary onboarding script. Configures ~/.gitconfig, ~/.ssh/config, and generates one or more ed25519 SSH keys in a single interactive session.
| Prompt | Stored as | Notes |
|---|---|---|
| Username | [user] name in ~/.gitconfig |
Display name in commits |
[user] email in ~/.gitconfig |
Must be valid format | |
| SSH key name | ~/.ssh/<name>, Host gh-<name> |
Alphanumeric + hyphens only |
| SSH passphrase | Encrypts the private key | Confirmed twice |
| Additional users | Repeatable | Each gets a dir-scoped config |
~/.gitconfig
~/.ssh/config
~/.ssh/<main_key_name> (chmod 600)
~/.ssh/<main_key_name>.pub (chmod 644)
~/.ssh/<extra_key> (one per additional user)
~/.ssh/<extra_key>.pub
~/.gitconfig-<extra_dir> (one per additional user)
| File | Permission | Reason |
|---|---|---|
~/.ssh/ |
700 |
SSH daemon rejects world-readable dirs |
~/.ssh/config |
600 |
Sensitive host config |
~/.ssh/<key> |
600 |
Private key β never share this |
~/.ssh/<key>.pub |
644 |
Public key β safe to share |
setup-git-usersEnter the username for gitconfig: Sofia Dev
Enter the email for gitconfig: sofia@personal.com
Enter the SSH key name: personal
Do you want to add an additional user to gitconfig?
1) Yes, add another user
2) No, continue
Answer: 2
π Set a password for the SSH key of Sofia Dev
Password: β’β’β’β’β’β’β’β’
Confirm password: β’β’β’β’β’β’β’β’
β
Git and SSH configuration completed successfully.
π Configuration summary:
β’ Main user: Sofia Dev (sofia@personal.com)
π SSH keys:
β’ ~/.ssh/personal (gh-personal) for Sofia Dev
[user]
name = Sofia Dev
email = sofia@personal.com
[init]
defaultBranch = main# Configuration for Sofia Dev
Host gh-personal
HostName github.com
User git
IdentityFile ~/.ssh/personal
Enter the username for gitconfig: Sofia Dev
Enter the email for gitconfig: sofia@personal.com
Enter the SSH key name: personal
Do you want to add an additional user to gitconfig?
1) Yes, add another user
2) No, continue
Answer: 1
Enter the username: Sofia Corp
Enter the email: sofia@company.com
Enter the directory name for this user: work
Do you want to add an additional user to gitconfig?
1) Yes, add another user
2) No, continue
Answer: 2
~/.gitconfig
βββ [user] name = Sofia Dev
βββ [user] email = sofia@personal.com
βββ [includeIf "gitdir:~/work/"]
β path = ~/.gitconfig-work
βββ [init] defaultBranch = main
~/.gitconfig-work
βββ [user] name = Sofia Corp
[user] email = sofia@company.com
~/.ssh/config
βββ Host gh-personal β ~/.ssh/personal
βββ Host gh-work β ~/.ssh/work
Note
After running this script, copy the content of ~/.ssh/<key>.pub and add it to GitHub β Settings β SSH and GPG keys β New SSH key.
Add an additional SSH identity to an existing setup
Use this after setup-git-users when you need to onboard a new GitHub account without reconfiguring everything from scratch.
~/.gitconfigmust exist (created bysetup-git-users)
- Validates email format with regex
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ - Validates key name characters: only
[a-zA-Z0-9_-] - Checks for existing key conflicts and prompts to skip or rename
- Inserts
[includeIf "gitdir:~/dir/"]into~/.gitconfig(before[init]if present) - Creates
~/.gitconfig-<keyname> - Appends a new
Host gh-<keyname>block to~/.ssh/config - Generates an
ed25519key with passphrase confirmation - Applies correct permissions
- Prints the public key for immediate copy-paste into GitHub
add-git-userπ Enter the username: FreelanceClient
π§ Enter the user email: sofia@client.dev
π Enter the SSH key name (will be used as gh-<name>): client
π Enter the directory associated with this user (e.g. work): freelance
π Updating Git configuration...
β
includeIf added to ~/.gitconfig
β
Created ~/.gitconfig-client
π Updating SSH configuration...
β
Host gh-client added to ~/.ssh/config
π Generating SSH key...
SSH key password: β’β’β’β’β’β’β’β’
Confirm password: β’β’β’β’β’β’β’β’
β
Additional SSH user configured successfully.
π Summary:
β’ Name: FreelanceClient
β’ Email: sofia@client.dev
β’ SSH host: gh-client
β’ Key: ~/.ssh/client
β’ Directory: ~/freelance/
β’ gitconfig: ~/.gitconfig-client
π Remember to add the public key to GitHub:
ssh-ed25519 AAAAC3Nza... sofia@client.dev
If ~/.ssh/client already exists:
β οΈ Key ~/.ssh/client already exists.
1) Choose a different name
2) Continue (existing key will not be overwritten)
Answer: 2
The script will skip key generation but still update .gitconfig and ~/.ssh/config.
Clone a GitHub repository using a specific SSH identity
Dynamically lists available SSH keys from ~/.ssh/ and rewrites the clone URL to use the correct host alias.
Input: git@github.com:SofiDevO/my-app.git
Output: git@gh-personal:SofiDevO/my-app.git
This tells SSH to use the gh-personal host entry in ~/.ssh/config, which maps to github.com with the ~/.ssh/personal key. GitHub authenticates and serves the repo as usual.
# Navigate to the parent directory first
cd ~/projects
clone-repoπ Select the SSH key to use:
1) gh-personal
2) gh-work
Answer: 1
Enter the repository URL (e.g. git@github.com:user/repo.git):
git@github.com:SofiDevO/my-app.git
π Loading SSH key...
π Cloning from: git@gh-personal:SofiDevO/my-app.git
Cloning into 'my-app'...
β
Repository cloned successfully.
The script enforces that the URL starts with git@github.com: using Bash regex. HTTPS URLs will be rejected:
β URL format must start with git@github.com:
Clone a specific branch of a GitHub repository
Identical to clone-repo but adds branch selection and uses --single-branch for a lighter, faster clone.
| Feature | clone-repo |
clone-project |
|---|---|---|
| Clones all branches | β | β |
| Branch selection | β | β |
| Clone size | Full | Minimal |
| Use case | General cloning | Feature branch work |
cd ~/work
clone-projectπ Select the SSH key to use:
1) gh-personal
2) gh-work
Answer: 2
π¦ Enter the branch name you want to clone: feature/auth-module
Enter the repository URL (e.g. git@github.com:user/repo.git):
git@github.com:MyCompany/api.git
π Cloning from: git@gh-work:MyCompany/api.git
π± Branch: feature/auth-module
Cloning into 'api'...
β
Repository cloned successfully.
git clone -b feature/auth-module --single-branch git@gh-work:MyCompany/api.gitInitialize a local project and push to a completely empty GitHub repository
Important
Use this when the GitHub repo was created with no initial files (no README, no LICENSE, no .gitignore). If the remote already has commits, use push-repo instead.
git init
git add .
git commit -m "<message>"
git branch -M main
git remote add origin git@gh-<key>:<user>/<repo>.git
git push -u origin main
cd ~/my-new-project
add-repoπ Select the SSH key to use:
1) gh-personal
2) gh-work
Answer: 1
π Enter the first commit message: Initial commit β project scaffold
π Enter the repository URL (e.g. git@github.com:user/repo.git):
git@github.com:SofiDevO/new-project.git
π Loading SSH key...
π¦ Initializing Git repository...
π Staging all files...
πΎ Creating first commit...
πΏ Switching to main branch...
π€ Setting remote origin to: git@gh-personal:SofiDevO/new-project.git
π Pushing to repository...
β
Repository initialized and pushed successfully.
π Summary of actions performed:
β’ git init
β’ git add .
β’ git commit -m "Initial commit β project scaffold"
β’ git branch -M main
β’ git remote add origin git@gh-personal:SofiDevO/new-project.git
β’ git push -u origin main
Initialize and push a local project to a remote repository that already has commits
Important
Use this when the GitHub repo was created with a default README, LICENSE, or .gitignore. If the remote is empty, use add-repo instead.
push-repo includes an extra step:
git pull origin main --allow-unrelated-histories --no-rebase --no-editThis merges the remote's initial commit (e.g., a GitHub auto-generated README) with the local history before pushing β preventing the "refusing to push non-fast-forward" error.
cd ~/existing-local-project
push-repoπ Select the SSH key to use:
1) gh-personal
2) gh-work
Answer: 1
π Enter the first commit message: feat: add base project structure
π Enter the repository URL:
git@github.com:SofiDevO/project-with-readme.git
π Loading SSH key...
π¦ Initializing Git repository...
π Staging all files...
πΎ Creating first commit...
πΏ Switching to main branch...
π€ Setting remote origin...
π Pulling remote changes (README/LICENSE)...
π Pushing to repository...
β
Code pushed successfully to the repository.
| Scenario | Script |
|---|---|
| GitHub repo created empty (no files) | add-repo |
| GitHub repo created with README/LICENSE | push-repo |
| Repo already initialized, just push changes | gpush |
Create and switch to a new Git branch
A minimal wrapper around git checkout -b with input validation.
# Must be inside a Git repository
cd ~/my-project
new-branchπ¦ Enter the new branch name: feature/user-auth
β
Branch 'feature/user-auth' created and checked out successfully.
git checkout -b feature/user-auth- Exits with error if the branch name is empty
- Inherits Git's own branch name validation (special characters, spaces, etc. will fail at the
git checkoutlevel)
Pull the current branch from origin
A shorthand that auto-detects the current branch, eliminating the need to remember or type the branch name.
# Inside any Git repository
gpullPulling branch 'feature/user-auth' from origin...
From github.com:SofiDevO/my-app
* branch feature/user-auth -> FETCH_HEAD
Already up to date.
git pull origin $(git rev-parse --abbrev-ref HEAD)- Quick sync after a teammate pushes updates
- Daily pull before starting work
- CI/CD pre-scripts that need branch-agnostic pull
Push the current branch to origin
Mirror of gpull β auto-detects the current branch and pushes.
gpushPushing branch 'feature/user-auth' to origin...
Enumerating objects: 5, done.
...
To github.com:SofiDevO/my-app.git
a1b2c3d..e4f5g6h feature/user-auth -> feature/user-auth
git push origin $(git rev-parse --abbrev-ref HEAD)Note
gpush does not stage or commit files. Use git add and git commit before running it.
Clean apt cache, user cache, trash, and optionally kill background processes
A maintenance script for Debian/Ubuntu-based systems. Runs non-destructive cleanup automatically, then enters an interactive process manager if requested.
| Action | Command | Requires sudo |
|---|---|---|
| APT package cache | apt-get clean |
β |
| Remove orphaned packages | apt-get autoremove -y |
β |
| Remove old downloaded packages | apt-get autoclean |
β |
| Thumbnail cache | rm -rf ~/.cache/thumbnails/* |
β |
| Trash | rm -rf ~/.local/share/Trash/* |
β |
clean-systemStarting system cleanup...
Cleaning package cache (apt)...
Cleaning user cache (~/.cache/thumbnails)...
Emptying trash...
File cleanup complete.
--------------------------------
Do you want to close background processes? (Y/n): Y
Closing background processes...
Top 15 active processes for current user:
PID COMMAND %CPU %MEM
1234 chrome 12.3 8.1
5678 node 4.2 3.0
...
Enter the PID of the process to close (or press Enter to finish): 1234
Process 1234 closed successfully.
Enter the PID of the process to close (or press Enter to finish):
Process management complete.
Maintenance complete!
Do you want to close background processes? (Y/n): n
Operation cancelled. No processes were closed.
Maintenance complete!
Caution
kill <PID> sends SIGTERM by default. If a process doesn't stop, use kill -9 <PID> manually. The script does not force-kill.
Fully automated local WordPress installation
An end-to-end setup script that installs and configures a complete WordPress development environment: database creation, WordPress download, wp-config.php configuration, security keys, WP-CLI install, and a ready-to-run PHP development server.
| Dependency | Auto-installed? | Notes |
|---|---|---|
| PHP + extensions | β (if missing) | php-cli php-mysql php-curl php-gd php-mbstring php-xml php-xmlrpc php-zip |
| MariaDB or MySQL | β | Must be installed and running |
wget, curl |
β | Standard on most systems |
| Internet access | β | For WordPress download and WordPress.org salt API |
[1/6] Download WordPress (wordpress.org/latest.tar.gz β /tmp β $WP_DIR)
[2/6] Configure database (root password prompt, up to 3 retries)
[3/6] Configure wp-config.php (sed substitution + security salt from API)
[4/6] Download WP-CLI (/tmp/wp-cli.phar, reused if already present)
[5/6] Install WordPress (wp core install via WP-CLI)
[6/7] Install Adminer (lightweight DB web interface)
[7/7] Create start-server.sh (auto-detects available port β₯ 8080)
# Navigate to where you want the WordPress directory created
cd ~/projects
install-wordpressSelect the database system:
1) MariaDB
2) MySQL
Option (1 or 2): 1
WordPress administrator user configuration:
WordPress username: sofidev
WordPress password: β’β’β’β’β’β’β’β’
Confirm WordPress password: β’β’β’β’β’β’β’β’
WordPress administrator email: sofia@dev.com
WordPress site name (title): My Dev Blog
Installation directory: /home/sofi/projects/my-dev-blog
[1/6] Downloading WordPress...
WordPress downloaded and extracted to /home/sofi/projects/my-dev-blog
[2/6] Configuring MariaDB database...
Enter MariaDB root password: β’β’β’β’β’β’β’β’
Database created: my-dev-blog_db
Database user: my-dev-blog_user
[3/6] Configuring wp-config.php...
wp-config.php configured
[4/6] Installing WP-CLI...
WP-CLI installed
[5/6] Installing WordPress...
WordPress installed successfully
[6/7] Installing Adminer (database manager)...
Adminer installed successfully
[7/7] Creating server startup script...
Server script created
ββββββββββββββββββββββββββββββββββββββββββββββββββ
β WordPress installed successfully! β
ββββββββββββββββββββββββββββββββββββββββββββββββββ
Access information:
Directory: /home/sofi/projects/my-dev-blog
WordPress: http://localhost:8080
Admin user: sofidev
Admin password: β’β’β’β’β’β’β’β’
Database:
Web manager: http://localhost:8080/adminer.php
DB name: my-dev-blog_db
DB user: my-dev-blog_user
DB host: localhost
To start the server run:
cd /home/sofi/projects/my-dev-blog && ./start-server.sh
The site title is sanitized to create the directory name and database name:
| Input title | Generated slug | Directory | DB name |
|---|---|---|---|
My Dev Blog |
my-dev-blog |
./my-dev-blog/ |
my-dev-blog_db |
Client Project 2025! |
client-project-2025 |
./client-project-2025/ |
client-project-2025_db |
WordPress |
wordpress |
./wordpress/ |
wordpress_db |
my-dev-blog/
βββ wp-config.php β Configured with your DB credentials + API salts
βββ adminer.php β Database web UI (Adminer 4.8.1)
βββ start-server.sh β chmod +x, auto-detects port, runs php -S
βββ CREDENTIALS.txt β All access info saved locally
βββ ... β Full WordPress core files
cd ~/projects/my-dev-blog && ./start-server.shββββββββββββββββββββββββββββββββββββββββββββββββ
WordPress server started successfully
ββββββββββββββββββββββββββββββββββββββββββββββββ
π WordPress: http://localhost:8080
ποΈ Database: http://localhost:8080/adminer.php
π Directory: /home/sofi/projects/my-dev-blog
Press Ctrl+C to stop the server
Note
start-server.sh uses ss -tlnp to scan for occupied ports starting at 8080. If 8080 is in use, it automatically increments (8081, 8082...) until a free port is found.
The script registers a trap cleanup_on_error EXIT handler. If any step fails, it automatically removes the partially-created $WP_DIR directory, leaving a clean state for re-running.
If the root password is incorrect, the script retries up to 3 times before exiting with a diagnostic message:
β Error: Incorrect password or connection problem
Please try again...
Attempt 2 of 3
Enter MariaDB root password: β’β’β’β’β’β’β’β’
On exhausting retries:
ββββββββββββββββββββββββββββββββββββββββββββββββββ
β Error: Could not connect to MariaDB β
ββββββββββββββββββββββββββββββββββββββββββββββββββ
Please verify that:
1. MariaDB is running: sudo systemctl status mariadb
2. The root password is correct
3. The root user has adequate permissions
# Step 1: Install the scripts
git clone https://github.com/SofiDevO/sofi-scripts ~/.local/bin
chmod +x ~/.local/bin/set-scripts && ~/.local/bin/set-scripts
source ~/.zshrc
# Step 2: Configure Git identity + SSH key
setup-git-users
# β Enter: name, email, key name (e.g. "personal"), passphrase
# Step 3: Add the public key to GitHub
# Copy output of:
cat ~/.ssh/personal.pub
# Paste into: GitHub β Settings β SSH and GPG keys β New SSH key
# Step 4: Test the connection
ssh -T git@gh-personal
# β Hi SofiDevO! You've successfully authenticated...
# Step 5: Clone your first repo
cd ~/projects
clone-repo
# β Select key: gh-personal
# β URL: git@github.com:SofiDevO/my-app.git# Configure both identities in one run
setup-git-users
# β Main user: Personal Dev | sofia@personal.com | key: personal
# β Additional user: Work Dev | sofia@company.com | dir: work
# Add public keys to both GitHub accounts
cat ~/.ssh/personal.pub # β Add to personal GitHub account
cat ~/.ssh/work.pub # β Add to work GitHub account
# Test both connections
ssh -T git@gh-personal # β Hi PersonalAccount!
ssh -T git@gh-work # β Hi WorkAccount!
# Clone a personal repo
cd ~/projects
clone-repo
# β Select: gh-personal
# Clone a work repo (identity auto-switches because dir is ~/work/)
mkdir -p ~/work
cd ~/work
clone-repo
# β Select: gh-work# You already have personal + work set up
add-git-user
# β Name: Client Corp
# β Email: sofia@clientcorp.com
# β Key name: client
# β Directory: freelance
cat ~/.ssh/client.pub # β Add to client's GitHub org
mkdir -p ~/freelance
cd ~/freelance
clone-repo # β Select: gh-client# Scenario A: Empty GitHub repo (no files)
mkdir ~/projects/new-api && cd ~/projects/new-api
# ... create your files ...
add-repo
# β Select key, enter commit message, enter repo URL
# Scenario B: GitHub repo created with a default README
mkdir ~/projects/backend && cd ~/projects/backend
# ... create your files ...
push-repo
# β Same flow, but pulls remote README before pushingcd ~/projects/my-app
# Start of day β pull latest
gpull
# Create a feature branch
new-branch
# β feature/login-system
# ... write code ...
git add .
git commit -m "feat: add login form"
# Push the branch
gpush
# Switch to main for a hotfix
git checkout main
gpull
new-branch
# β hotfix/null-checkcd ~/projects
install-wordpress
# β DB system: MariaDB
# β Admin: sofidev | Password: MyP@ss | Email: me@dev.com
# β Site name: Dev Portfolio
# Start the server
cd ~/projects/dev-portfolio && ./start-server.sh
# β Open: http://localhost:8080/wp-admin
# β DB manager: http://localhost:8080/adminer.phpclean-system
# β Cleans apt cache, thumbnails, trash automatically
# β Prompt: close background processes? Y
# β Review top processes by CPU
# β Kill any memory hogs by PID# 1. Check executability
ls -la ~/.local/bin/ | grep -v "^d"
# 2. Check PATH
echo $PATH | tr ':' '\n' | grep local
# 3. Reload shell config
source ~/.zshrc # or ~/.bashrc
# 4. Re-run setup
set-scripts# 1. Verify the agent has the key loaded
ssh-add -l
# 2. Load the key manually
ssh-add ~/.ssh/personal
# 3. Test the connection
ssh -T git@gh-personal
# β Should say: Hi username! You've successfully authenticated...
# 4. Check the key is registered on GitHub
# GitHub β Settings β SSH and GPG keys# Check which config is active
git config user.email
# Verify includeIf is working
git config --show-origin user.email
# Make sure you're inside the correct directory
# e.g. work identity only applies inside ~/work/**# Check service status
sudo systemctl status mariadb
# or
sudo systemctl status mysql
# Start if stopped
sudo systemctl start mariadb
# Check root login
mariadb -u root -pThe start-server.sh script handles this automatically β but if you want to specify a port manually:
cd ~/projects/my-site
php -S localhost:9090