Skip to content

Commit 3b78755

Browse files
authored
Add option to use SSH key for authentication (#2)
1 parent c27b1a0 commit 3b78755

File tree

4 files changed

+154
-40
lines changed

4 files changed

+154
-40
lines changed

.github/workflows/example.yml

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Deploy to PythonAnywhere
1+
name: Redeploy to PythonAnywhere
22

33
on:
44
workflow_dispatch:
@@ -13,9 +13,23 @@ jobs:
1313
- name: Redeploy to PythonAnywhere
1414
uses: MiguelRizzi/pythonanywhere-deploy-ssh@v1.0.0
1515
with:
16-
ssh_host: ssh.eu.pythonanywhere.com # Optional - defaults to ssh.pythonanywhere.com
17-
username: ${{ secrets.PA_USERNAME }} # Your PythonAnywhere username
18-
password: ${{ secrets.PA_PASSWORD }} # Your PythonAnywhere password
19-
working_directory: ${{ secrets.PA_WORKING_DIRECTORY }} # Target working directory on PythonAnywhere
20-
venv_directory: ${{ secrets.PA_VENV_DIRECTORY }} # Path to your virtual environment
21-
wsgi_file: /var/www/webapp_name_wsgi.py # Path to your WSGI file
16+
# Optional: specify the SSH host (defaults to ssh.pythonanywhere.com)
17+
# ssh_host: ssh.eu.pythonanywhere.com
18+
19+
# Required: your PythonAnywhere username
20+
username: ${{ secrets.PA_USERNAME }}
21+
22+
# Required (if no ssh_private_key): your PythonAnywhere password
23+
# password: ${{ secrets.PA_PASSWORD }}
24+
25+
# Required (if no password): SSH private key
26+
ssh_private_key: ${{ secrets.PA_SSH_PRIVATE_KEY }}
27+
28+
# Required: target working directory on PythonAnywhere
29+
working_directory: ${{ secrets.PA_WORKING_DIRECTORY }}
30+
31+
# Required: path to your virtual environment
32+
venv_directory: ${{ secrets.PA_VENV_DIRECTORY }}
33+
34+
# Required: path to your WSGI file
35+
wsgi_file: /var/www/webapp_name_wsgi.py

README.md

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 🚀 PythonAnywhere Redeploy Action
22

3-
This GitHub Action automates the redeployment process of a Django application hosted on **PythonAnywhere** using SSH.
3+
This GitHub Action automates the redeployment process of a Django application hosted on [PythonAnywhere](https://www.pythonanywhere.com) using SSH.
44
It simplifies the workflow of pulling the latest code, installing dependencies, running migrations, and restarting the web server with minimal configuration.
55

66

@@ -27,7 +27,7 @@ Before using this action, make sure that:
2727
### Workflow example
2828

2929
```yaml
30-
name: Deploy to PythonAnywhere
30+
name: Redeploy to PythonAnywhere
3131

3232
on:
3333
push:
@@ -44,32 +44,52 @@ jobs:
4444
- name: Redeploy to PythonAnywhere
4545
uses: MiguelRizzi/pythonanywhere-deploy-ssh@v1.0.0
4646
with:
47-
ssh_host: ssh.eu.pythonanywhere.com # Optional - defaults to ssh.pythonanywhere.com
47+
# Optional: specify the SSH host (defaults to ssh.pythonanywhere.com)
48+
# ssh_host: ssh.eu.pythonanywhere.com
49+
50+
# Required: your PythonAnywhere username
4851
username: ${{ secrets.PA_USERNAME }}
49-
password: ${{ secrets.PA_PASSWORD }}
52+
53+
# Required (if no ssh_private_key): your PythonAnywhere password
54+
# password: ${{ secrets.PA_PASSWORD }}
55+
56+
# Required (if no password): SSH private key
57+
ssh_private_key: ${{ secrets.PA_SSH_PRIVATE_KEY }}
58+
59+
# Required: target working directory on PythonAnywhere
5060
working_directory: ${{ secrets.PA_WORKING_DIRECTORY }}
61+
62+
# Required: path to your virtual environment
5163
venv_directory: ${{ secrets.PA_VENV_DIRECTORY }}
64+
65+
# Required: path to your WSGI file
5266
wsgi_file: /var/www/webapp_name_wsgi.py
5367
```
68+
-💡 Use `ssh.pythonanywhere.com` for US accounts or `ssh.eu.pythonanywhere.com` for EU-based accounts.
69+
🔐 You must provide **at least one** of the following: `password` or `ssh_private_key`
70+
5471

5572
## Inputs
5673

5774
| Name | Description | Required | Example |
5875
|-----------|-------------|----------|---------|
5976
| `ssh_host` | Optional SSH host for PythonAnywhere (default: `ssh.pythonanywhere.com`) | No | `ssh.eu.pythonanywhere.com` |
6077
| `username` | Your PythonAnywhere username | Yes | `username` |
61-
| `password` | Your PythonAnywhere password | Yes | `password` |
78+
| `password` | Your PythonAnywhere password | No | `password` |
79+
| `ssh_private_key` | SSH private key (optional, but required if `password` is not provided) | No | *contents of your private key* |
6280
| `working_directory` | Target working directory on PythonAnywhere | Yes | `/home/username/webapp_name` |
6381
| `venv_directory` | Path to the Python virtual environment | Yes | `/home/username/webapp_name/.venv` |
6482
| `wsgi_file` | Path to the WSGI file to reload the app | Yes | `/var/www/webapp_name_wsgi.py` |
65-
83+
6684

6785
## 🔐 Security
6886

69-
- **Always** use secrets for:
87+
Always store sensitive data like credentials and SSH keys as [GitHub Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets):
88+
89+
- **Recommended secrets**:
7090
- `username`
7191
- `password`
72-
- any sensitive tokens or credentials
92+
- `ssh_private_key`
7393

7494
These values can expose your account if leaked, even in public forks or workflow logs.
7595

@@ -80,19 +100,69 @@ These values can expose your account if leaked, even in public forks or workflow
80100
- They include your username
81101
- You want to keep the configuration more private or flexible
82102

103+
## 📂 Creating GitHub Secrets
104+
105+
1. In your GitHub repository, go to **Settings**
106+
2. Navigate to **Secrets and variables → Actions**
107+
3. Click **New repository secret**
108+
4. Name the secret (e.g., `PA_USERNAME`, `PA_SSH_PRIVATE_KEY`, etc.)
109+
5. Paste the corresponding value
110+
111+
Repeat for each required secret.
112+
113+
> ⚠️ **Do not commit secrets directly into your repository.** Always use GitHub Secrets to store sensitive information.
114+
115+
116+
## 📋 Using an SSH Key (Recommended)
117+
118+
### 1. Generate an SSH key (if you don't have one):
119+
120+
If you don’t already have an SSH key pair for authentication, you can generate one using the following command:
121+
122+
```bash
123+
$ ssh-keygen -t ed25519
124+
```
125+
- When prompted, press Enter to accept the default file location (`~/.ssh/id_ed25519`).
126+
127+
- You may set a passphrase for added security (optional).
128+
129+
### 2. Add the **public key** to PythonAnywhere:
130+
131+
To add the public key to PythonAnywhere, run the following command:
132+
133+
```bash
134+
$ ssh-copy-id <username>@ssh.pythonanywhere.com
135+
```
136+
Enter your PythonAnywhere password when prompted.
137+
138+
> 💡 Use `ssh.eu.pythonanywhere.com` for EU-based accounts.
83139

84-
## 📁 Repository Structure
140+
141+
### 3. Store the **private key** securely:
142+
To store the private key securely, follow these steps:
143+
- Copy the contents of the private key file (.ssh/id_ed25519) running the command
144+
```bash
145+
cat ~/.ssh/id_ed25519
146+
```
147+
- Create a new secret in your GitHub repository following the instructions in the [Creating GitHub Secrets](#-creating-github-secrets) section.
148+
- Paste the contents of the private key into the secret field.
149+
150+
> 💡 Make sure to keep your private key secure and do not share it with anyone.
151+
152+
For more information on SSH setup, refer to: [SSH Access on PythonAnywhere](https://help.pythonanywhere.com/pages/SSHAccess/)
153+
154+
## ⚠️ Repository Structure
85155

86156
This action is composed of:
87157

88158
- `action.yml`
89159
Defines the input interface of the GitHub Action (required inputs) and specifies the execution environment. In this case, it sets up execution in a Docker container that runs the `deploy.sh` script.
90160

91161
- `deploy.sh`
92-
Main script executed inside the container. It uses `sshpass` to connect to PythonAnywhere via SSH, performs `git pull`, installs dependencies, runs migrations, and restarts the web server by touching the `WSGI.py` file.
162+
Main script executed inside the container. It uses SSH to connect to PythonAnywhere, performs `git pull`, installs dependencies, runs migrations, collects static files and restarts the web server by touching the `WSGI.py` file.
93163

94164
- `Dockerfile`
95-
Defines a custom Docker image based on `ubuntu`, installs `sshpass`, and copies the required scripts. This ensures the environment has all necessary tools to perform the redeploy process without relying on the host runner.
165+
Defines a custom Docker image based on ubuntu, installs the necessary tools for SSH connections, and copies the required scripts. This ensures the environment has all the necessary tools to perform the redeploy process without relying on the host runner.
96166

97167
- `.github/workflows/example.yml`
98168
Provides an example of how to use the action in a workflow. You can use it as a template or reference when setting up your own deployment workflow.
@@ -101,3 +171,8 @@ Provides an example of how to use the action in a workflow. You can use it as a
101171
## 📝 License
102172

103173
This GitHub Action is distributed under the [MIT License](https://github.com/MiguelRizzi/pythonanywhere-deploy-ssh/blob/main/LICENSE).
174+
175+
176+
---
177+
178+
Made with ❤️ to simplify PythonAnywhere deployments.

action.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ inputs:
1515
description: "Your PythonAnywhere username"
1616
required: true
1717
password:
18-
description: "Your PythonAnywhere password"
19-
required: true
18+
description: "Your PythonAnywhere password (required if no ssh_private_key)"
19+
required: false
20+
ssh_private_key:
21+
description: "SSH private key for authentication (required if no password)"
22+
required: false
2023
working_directory:
2124
description: "Target working directory on PythonAnywhere"
2225
required: true
@@ -34,6 +37,7 @@ runs:
3437
- ${{ inputs.ssh_host }}
3538
- ${{ inputs.username }}
3639
- ${{ inputs.password }}
40+
- ${{ inputs.ssh_private_key }}
3741
- ${{ inputs.working_directory }}
3842
- ${{ inputs.venv_directory }}
3943
- ${{ inputs.wsgi_file }}

deploy.sh

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,47 @@ set -e
44
SSH_HOST=$1
55
USERNAME=$2
66
PASSWORD=$3
7-
WORKING_DIRECTORY=$4
8-
VENV_DIRECTORY=$5
9-
WSGI_FILE=$6
7+
SSH_PRIVATE_KEY=$4
8+
WORKING_DIRECTORY=$5
9+
VENV_DIRECTORY=$6
10+
WSGI_FILE=$7
1011

11-
echo "Connecting to PythonAnywhere server..."
12+
if [ -z "$USERNAME" ] || [ -z "$WORKING_DIRECTORY" ] || [ -z "$VENV_DIRECTORY" ] || [ -z "$WSGI_FILE" ]; then
13+
echo "Error: Debes proporcionar USERNAME, WORKING_DIRECTORY, VENV_DIRECTORY y WSGI_FILE."
14+
exit 1
15+
fi
1216

13-
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no "$USERNAME@$SSH_HOST" << EOF
14-
echo "Changing to working directory..."
15-
cd "$WORKING_DIRECTORY"
16-
echo "Activating virtual environment..."
17-
source "$VENV_DIRECTORY/bin/activate"
18-
echo "Pulling latest changes from main branch..."
19-
git pull origin main
20-
echo "Installing dependencies..."
21-
pip install -r requirements.txt
22-
echo "Collecting static files..."
23-
python manage.py collectstatic --noinput
24-
echo "Applying migrations..."
25-
python manage.py migrate
26-
echo "Touching WSGI file to restart server..."
27-
touch "$WSGI_FILE"
28-
echo "Deployment completed successfully!"
17+
if [ -z "$PASSWORD" ] && [ -z "$SSH_PRIVATE_KEY" ]; then
18+
echo "Error: Debes proporcionar al menos PASSWORD o SSH_PRIVATE_KEY."
19+
exit 1
20+
fi
21+
22+
echo "🌐 Connecting to PythonAnywhere server..."
23+
24+
REMOTE_COMMANDS=$(cat <<EOF
25+
echo "📂 Changing to working directory..."
26+
cd "$WORKING_DIRECTORY"
27+
echo "🐍 Activating virtual environment..."
28+
source "$VENV_DIRECTORY/bin/activate"
29+
echo "⬇️ Pulling latest changes from main branch..."
30+
git pull origin main
31+
echo "📦 nstalling dependencies..."
32+
pip install -r requirements.txt
33+
echo "🗂️ Collecting static files..."
34+
python manage.py collectstatic --noinput
35+
echo "🛠️ Applying migrations..."
36+
python manage.py migrate
37+
echo "🔄 Touching WSGI file to restart server..."
38+
touch "$WSGI_FILE"
39+
echo "✅ Deployment completed successfully!"
2940
EOF
41+
)
42+
43+
if [ -n "$SSH_PRIVATE_KEY" ]; then
44+
echo "$SSH_PRIVATE_KEY" > /tmp/deploy_key
45+
chmod 600 /tmp/deploy_key
46+
ssh -i /tmp/deploy_key -o StrictHostKeyChecking=no "$USERNAME@$SSH_HOST" bash -s <<< "$REMOTE_COMMANDS"
47+
rm /tmp/deploy_key
48+
else
49+
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no "$USERNAME@$SSH_HOST" bash -s <<< "$REMOTE_COMMANDS"
50+
fi

0 commit comments

Comments
 (0)