Skip to content

Commit 30a3f46

Browse files
committed
Add CI/CD again
1 parent 1961d16 commit 30a3f46

3 files changed

Lines changed: 186 additions & 0 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Deploy to EC2
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
9+
jobs:
10+
deploy:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v4
16+
17+
- name: Export deployment settings
18+
run: |
19+
echo "EC2_HOST=${{ secrets.EC2_HOST }}" >> "$GITHUB_ENV"
20+
echo "EC2_USER=${{ secrets.EC2_USER }}" >> "$GITHUB_ENV"
21+
if [ -n "${{ vars.DEPLOY_PORT }}" ]; then
22+
echo "PORT=${{ vars.DEPLOY_PORT }}" >> "$GITHUB_ENV"
23+
else
24+
echo "PORT=80" >> "$GITHUB_ENV"
25+
fi
26+
27+
- name: Set up SSH key
28+
uses: webfactory/ssh-agent@v0.8.0
29+
with:
30+
ssh-private-key: ${{ secrets.EC2_SSH_KEY }}
31+
32+
- name: Add EC2 host to known_hosts
33+
run: |
34+
mkdir -p ~/.ssh
35+
ssh-keyscan -H "$EC2_HOST" >> ~/.ssh/known_hosts
36+
37+
- name: Sync project to EC2
38+
run: |
39+
rsync -az --delete \
40+
--exclude '.git' \
41+
--exclude '.github' \
42+
--exclude '.venv' \
43+
--exclude '__pycache__' \
44+
--exclude '*.pyc' \
45+
--exclude '.ruff_cache' \
46+
./ "$EC2_USER@$EC2_HOST:/home/$EC2_USER/nasa-sky-app"
47+
48+
- name: Execute remote deployment
49+
run: |
50+
ssh "$EC2_USER@$EC2_HOST" '
51+
set -euo pipefail
52+
cd ~/nasa-sky-app
53+
chmod +x deploy/deploy.sh
54+
APP_ROOT=/opt/nasa-sky-app SERVICE_NAME=nasa-sky-app PORT=$PORT ./deploy/deploy.sh
55+
'

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,35 @@ NASASpaceAppsChallenge2025/
4040
│ └── server.py # FastAPI application serving the HTML page
4141
└── web/
4242
└── index.html # Static HTML served at the root route
43+
└── deploy/
44+
└── deploy.sh # Helper script to install and run the service via systemd
4345
```
4446

4547
Feel free to build on this foundation for richer APIs or interfaces.
48+
49+
## Deployment script
50+
51+
The `deploy/deploy.sh` script provisions the application on a Linux host using `systemd`. It:
52+
53+
- Syncs the repository into `/opt/nasa-sky-app/app` (configurable with `APP_ROOT`).
54+
- Creates a virtual environment and installs dependencies.
55+
- Generates a `systemd` unit that runs Uvicorn on port `80` by default.
56+
- Enables and restarts the service.
57+
58+
Run it directly on the target server after cloning or syncing the repository:
59+
60+
```bash
61+
chmod +x deploy/deploy.sh
62+
APP_ROOT=/opt/nasa-sky-app SERVICE_NAME=nasa-sky-app ./deploy/deploy.sh
63+
```
64+
65+
Override `SERVICE_USER`, `SERVICE_GROUP`, `PORT`, or `PYTHON_BIN` to fit your environment. When the
66+
port is below `1024` (the default `80`), the generated unit grants
67+
`CAP_NET_BIND_SERVICE` so the application can bind to the port without running as root.
68+
69+
### GitHub Actions deployment
70+
71+
A workflow at `.github/workflows/deploy.yml` uses the repository secrets `EC2_HOST`, `EC2_USER`,
72+
and `EC2_SSH_KEY` to sync the codebase to an Ubuntu 24.04 EC2 instance and execute the deployment
73+
script remotely. It runs on every push to `main` (and can be triggered manually). Optional
74+
repository variable `DEPLOY_PORT` lets you override the port without editing the workflow.

deploy/deploy.sh

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
APP_ROOT=${APP_ROOT:-/opt/nasa-sky-app}
6+
APP_DIR="${APP_ROOT}/app"
7+
VENV_DIR="${APP_ROOT}/venv"
8+
SERVICE_NAME=${SERVICE_NAME:-nasa-sky-app}
9+
PORT=${PORT:-80}
10+
PYTHON_BIN=${PYTHON_BIN:-python3}
11+
SUDO_BIN=${SUDO_BIN:-sudo}
12+
export DEBIAN_FRONTEND=${DEBIAN_FRONTEND:-noninteractive}
13+
14+
SERVICE_USER=${SERVICE_USER:-$(whoami)}
15+
SERVICE_GROUP=${SERVICE_GROUP:-$(id -gn)}
16+
17+
if [ "${PORT}" -lt 1024 ]; then
18+
echo "Deploying service on privileged port ${PORT}."
19+
fi
20+
21+
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
22+
REPO_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd)
23+
24+
if command -v apt-get >/dev/null 2>&1; then
25+
if ! command -v rsync >/dev/null 2>&1; then
26+
echo "Installing rsync via apt-get"
27+
${SUDO_BIN} apt-get update -y
28+
${SUDO_BIN} apt-get install -y rsync
29+
fi
30+
31+
if ! command -v "${PYTHON_BIN}" >/dev/null 2>&1; then
32+
echo "Installing Python via apt-get"
33+
${SUDO_BIN} apt-get update -y
34+
${SUDO_BIN} apt-get install -y python3 python3-venv python3-pip
35+
else
36+
if ! "${PYTHON_BIN}" -m venv --help >/dev/null 2>&1; then
37+
echo "Installing python3-venv via apt-get"
38+
${SUDO_BIN} apt-get update -y
39+
${SUDO_BIN} apt-get install -y python3-venv
40+
fi
41+
if ! command -v pip3 >/dev/null 2>&1; then
42+
echo "Installing python3-pip via apt-get"
43+
${SUDO_BIN} apt-get update -y
44+
${SUDO_BIN} apt-get install -y python3-pip
45+
fi
46+
fi
47+
fi
48+
49+
if ! command -v rsync >/dev/null 2>&1; then
50+
echo "rsync is required but was not found in PATH" >&2
51+
exit 1
52+
fi
53+
54+
if ! command -v "${PYTHON_BIN}" >/dev/null 2>&1; then
55+
echo "Python interpreter '${PYTHON_BIN}' not found" >&2
56+
exit 1
57+
fi
58+
59+
mkdir -p "${APP_DIR}"
60+
rsync -a --delete \
61+
--exclude ".git" \
62+
--exclude ".venv" \
63+
--exclude "__pycache__" \
64+
--exclude "*.pyc" \
65+
--exclude ".ruff_cache" \
66+
"${REPO_ROOT}/" "${APP_DIR}/"
67+
68+
"${PYTHON_BIN}" -m venv "${VENV_DIR}"
69+
"${VENV_DIR}/bin/pip" install --upgrade pip
70+
"${VENV_DIR}/bin/pip" install -r "${APP_DIR}/requirements.txt"
71+
72+
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
73+
74+
CAPABILITY_LINES=""
75+
if [ "${PORT}" -lt 1024 ]; then
76+
CAPABILITY_LINES=$'AmbientCapabilities=CAP_NET_BIND_SERVICE\nCapabilityBoundingSet=CAP_NET_BIND_SERVICE'
77+
fi
78+
79+
read -r -d '' SERVICE_UNIT <<EOF
80+
[Unit]
81+
Description=NASA Sky Explorer FastAPI Service
82+
After=network.target
83+
84+
[Service]
85+
User=${SERVICE_USER}
86+
Group=${SERVICE_GROUP}
87+
WorkingDirectory=${APP_DIR}
88+
Environment="PATH=${VENV_DIR}/bin"
89+
ExecStart=${VENV_DIR}/bin/uvicorn src.server:app --host 0.0.0.0 --port ${PORT}
90+
Restart=on-failure
91+
${CAPABILITY_LINES}
92+
93+
[Install]
94+
WantedBy=multi-user.target
95+
EOF
96+
97+
${SUDO_BIN} tee "${SERVICE_FILE}" >/dev/null <<<"${SERVICE_UNIT}"
98+
${SUDO_BIN} systemctl daemon-reload
99+
${SUDO_BIN} systemctl enable --now "${SERVICE_NAME}.service"
100+
${SUDO_BIN} systemctl restart "${SERVICE_NAME}.service"
101+
102+
${SUDO_BIN} systemctl status "${SERVICE_NAME}.service" --no-pager

0 commit comments

Comments
 (0)