Skip to content

Commit 13b06ee

Browse files
committed
Update CI/CD
1 parent 3deb2e0 commit 13b06ee

5 files changed

Lines changed: 394 additions & 35 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,28 @@ jobs:
3232
REMOTE_APP_DIR: ${{ env.APP_DIR }}
3333
run: |
3434
set -euo pipefail
35-
APP_DIR="${REMOTE_APP_DIR:-/home/ubuntu/nasa-sky-explorer}"
36-
ssh "${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}" "mkdir -p ${APP_DIR}"
35+
APP_DIR="${REMOTE_APP_DIR:-/opt/nasa-sky-explorer}"
36+
ssh "${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}" "sudo mkdir -p ${APP_DIR}"
3737
3838
- name: Sync repository to EC2
3939
env:
4040
REMOTE_APP_DIR: ${{ env.APP_DIR }}
4141
run: |
4242
set -euo pipefail
43-
APP_DIR="${REMOTE_APP_DIR:-/home/ubuntu/nasa-sky-explorer}"
43+
APP_DIR="${REMOTE_APP_DIR:-/opt/nasa-sky-explorer}"
44+
TEMP_DIR="/tmp/nasa-sky-explorer-deploy-$$"
45+
46+
# Sync to temp directory first
47+
ssh "${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}" "mkdir -p ${TEMP_DIR}"
4448
rsync -az --delete \
4549
--exclude ".git" \
4650
--exclude ".github/" \
4751
--exclude ".venv/" \
4852
--exclude "logs/" \
49-
./ "${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:${APP_DIR}/"
53+
./ "${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:${TEMP_DIR}/"
54+
55+
# Move from temp to final location with sudo
56+
ssh "${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}" "sudo rsync -a --delete ${TEMP_DIR}/ ${APP_DIR}/ && rm -rf ${TEMP_DIR}"
5057
5158
- name: Install Python dependencies and restart the service
5259
env:
@@ -56,10 +63,10 @@ jobs:
5663
REMOTE_PORT: ${{ env.UVICORN_PORT }}
5764
run: |
5865
set -euo pipefail
59-
APP_DIR="${REMOTE_APP_DIR:-/home/ubuntu/nasa-sky-explorer}"
66+
APP_DIR="${REMOTE_APP_DIR:-/opt/nasa-sky-explorer}"
6067
PYTHON_BIN="${REMOTE_PYTHON_BIN:-/usr/bin/python3}"
6168
SERVICE_NAME="${REMOTE_SERVICE:-nasaspaceapps}"
62-
UVICORN_PORT="${REMOTE_PORT:-8000}"
69+
UVICORN_PORT="${REMOTE_PORT:-80}"
6370
6471
ssh "${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}" \
65-
"cd '${APP_DIR}' && APP_DIR='${APP_DIR}' PYTHON_BIN='${PYTHON_BIN}' SERVICE_NAME='${SERVICE_NAME}' UVICORN_PORT='${UVICORN_PORT}' bash deploy/remote_deploy.sh"
72+
"cd '${APP_DIR}' && sudo APP_DIR='${APP_DIR}' PYTHON_BIN='${PYTHON_BIN}' SERVICE_NAME='${SERVICE_NAME}' UVICORN_PORT='${UVICORN_PORT}' bash deploy/remote_deploy.sh"

README.md

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,24 @@ ruff check .
4242

4343
```
4444
NASASpaceAppsChallenge2025/
45-
├── requirements.txt # FastAPI and Uvicorn dependencies
45+
├── requirements.txt # FastAPI and Uvicorn dependencies
4646
├── src/
4747
│ ├── __init__.py
48-
│ └── server.py # FastAPI application serving the HTML page
48+
│ └── server.py # FastAPI application serving the HTML page
4949
├── web/
50-
│ ├── index.html # Landing page served at the root route
51-
│ └── aladin.html # Secondary page under /aladin
50+
│ ├── index.html # Landing page served at the root route
51+
│ ├── aladin.html # FITS explorer with Aladin Lite integration
52+
│ └── styles.css # Styling for the viewer interface
5253
├── deploy/
53-
│ └── remote_deploy.sh # Remote helper script invoked by the CI workflow
54-
└── .github/
55-
└── workflows/
56-
└── deploy.yml # Continuous deployment pipeline for EC2
54+
│ ├── remote_deploy.sh # Remote deployment script (run on EC2)
55+
│ └── nasaspaceapps.service.template # SystemD service unit template
56+
├── .github/
57+
│ └── workflows/
58+
│ └── deploy.yml # Continuous deployment pipeline for EC2
59+
├── README.md # Main documentation
60+
├── MIGRATION.md # Migration guide for existing deployments
61+
├── DEPLOYMENT_CHANGES.md # Detailed deployment architecture documentation
62+
└── OPS_REFERENCE.md # Quick reference for operations team
5763
```
5864

5965
Feel free to extend the UI with annotations, multi-layer comparisons, or temporal sliders to address the broader Space Apps challenge goals.
@@ -64,33 +70,46 @@ Every push to `main` triggers the GitHub Actions workflow in `.github/workflows/
6470
pipeline performs the following steps:
6571

6672
1. Checks out the latest code.
67-
2. Copies the repository to your EC2 instance via `rsync` (preserving any existing `.venv` or
68-
`logs` folders).
69-
3. Runs `deploy/remote_deploy.sh` on the instance to create/refresh a virtual environment, install
70-
dependencies, and restart the application via `systemd` when available. If a systemd service is
71-
not present, it falls back to launching Uvicorn in the background with `nohup`.
73+
2. Copies the repository to `/opt/nasa-sky-explorer` on your EC2 instance via `rsync`.
74+
3. Creates a dedicated service user (`nasaapp`) if it doesn't exist.
75+
4. Sets proper ownership and permissions for the application directory.
76+
5. Installs Python dependencies in a virtual environment owned by the service user.
77+
6. Applies necessary capabilities to bind to port 80 (if running on a privileged port).
78+
7. Restarts the systemd service or launches Uvicorn as a background process.
7279

7380
### Required GitHub secrets
7481

7582
Create the following secrets at **Settings → Secrets and variables → Actions**:
7683

7784
- `EC2_HOST` – Public DNS name or IP address of the instance.
78-
- `EC2_USER` – SSH user (for example, `ubuntu`).
85+
- `EC2_USER` – SSH user with sudo privileges (for example, `ubuntu`).
7986
- `EC2_SSH_KEY` – Private SSH key allowed to log in as `EC2_USER`.
8087

8188
### Optional overrides
8289

8390
You can customise the deployment without editing the workflow by providing additional (optional)
8491
secrets:
8592

86-
- `EC2_APP_DIR` – Absolute path where the repo should live (defaults to `/home/ubuntu/nasa-sky-explorer`).
93+
- `EC2_APP_DIR` – Absolute path where the repo should live (defaults to `/opt/nasa-sky-explorer`).
8794
- `EC2_PYTHON_BIN` – Python interpreter used to build the virtual environment (defaults to
8895
`/usr/bin/python3`).
8996
- `EC2_SERVICE_NAME` – Name of the `systemd` service to restart (defaults to `nasaspaceapps`).
90-
- `EC2_UVICORN_PORT` – Port exposed by Uvicorn when no `systemd` unit is available (defaults to
91-
`8000`).
97+
- `EC2_UVICORN_PORT` – Port exposed by Uvicorn (defaults to `80`).
9298

93-
Ensure the EC2 machine has `git`, `rsync`, `python3`, and `pip` installed. If you prefer a managed
94-
service, create a `systemd` unit named after `EC2_SERVICE_NAME` that executes
95-
`/home/ubuntu/nasa-sky-explorer/.venv/bin/uvicorn src.server:app --host 0.0.0.0 --port 8000`
96-
and the workflow will restart it after each deployment.
99+
### Setting up the systemd service
100+
101+
For production use, create a systemd service to manage the application lifecycle:
102+
103+
```bash
104+
# On your EC2 instance:
105+
sudo cp /opt/nasa-sky-explorer/deploy/nasaspaceapps.service.template /etc/systemd/system/nasaspaceapps.service
106+
sudo systemctl daemon-reload
107+
sudo systemctl enable nasaspaceapps.service
108+
sudo systemctl start nasaspaceapps.service
109+
```
110+
111+
The service runs as the `nasaapp` user and automatically restarts on failure. Logs are written to
112+
`/opt/nasa-sky-explorer/logs/uvicorn.log`.
113+
114+
**Note:** The application directory `/opt/nasa-sky-explorer` is accessible to all sudo users, and the
115+
service runs under a dedicated unprivileged user (`nasaapp`) for security.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[Unit]
2+
Description=NASA Sky Explorer Web Application
3+
After=network.target
4+
5+
[Service]
6+
Type=simple
7+
User=nasaapp
8+
Group=nasaapp
9+
WorkingDirectory=/opt/nasa-sky-explorer
10+
Environment="PATH=/opt/nasa-sky-explorer/.venv/bin:/usr/local/bin:/usr/bin:/bin"
11+
ExecStart=/opt/nasa-sky-explorer/.venv/bin/uvicorn src.server:app --host 0.0.0.0 --port 80
12+
Restart=always
13+
RestartSec=10
14+
15+
# Security hardening
16+
NoNewPrivileges=true
17+
PrivateTmp=true
18+
ProtectSystem=strict
19+
ProtectHome=true
20+
ReadWritePaths=/opt/nasa-sky-explorer/logs
21+
22+
# Logging
23+
StandardOutput=append:/opt/nasa-sky-explorer/logs/uvicorn.log
24+
StandardError=append:/opt/nasa-sky-explorer/logs/uvicorn.log
25+
26+
[Install]
27+
WantedBy=multi-user.target

deploy/remote_deploy.sh

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,57 @@ set -euo pipefail
44
APP_DIR="${APP_DIR:-$(pwd)}"
55
PYTHON_BIN="${PYTHON_BIN:-/usr/bin/python3}"
66
SERVICE_NAME="${SERVICE_NAME:-nasaspaceapps}"
7-
UVICORN_PORT="${UVICORN_PORT:-8000}"
7+
UVICORN_PORT="${UVICORN_PORT:-80}"
8+
APP_USER="${APP_USER:-nasaapp}"
89

10+
# Create dedicated service user if it doesn't exist
11+
if ! id "${APP_USER}" >/dev/null 2>&1; then
12+
echo "Creating service user ${APP_USER}..."
13+
useradd --system --no-create-home --shell /usr/sbin/nologin "${APP_USER}"
14+
fi
15+
16+
# Set ownership and permissions for app directory
17+
chown -R "${APP_USER}:${APP_USER}" "${APP_DIR}"
18+
chmod -R 755 "${APP_DIR}"
19+
20+
# Create and setup virtual environment as the service user
921
if [ ! -d ".venv" ]; then
10-
"${PYTHON_BIN}" -m venv .venv
22+
sudo -u "${APP_USER}" "${PYTHON_BIN}" -m venv .venv
1123
fi
1224

25+
# Install dependencies as the service user
1326
# shellcheck source=/dev/null
27+
sudo -u "${APP_USER}" bash << 'EOSCRIPT'
1428
source .venv/bin/activate
1529
pip install --upgrade pip wheel
1630
pip install -r requirements.txt
17-
1831
deactivate || true
32+
EOSCRIPT
33+
34+
# Apply capability to allow binding to port 80 (if port < 1024)
35+
if [ "${UVICORN_PORT}" -lt 1024 ]; then
36+
REAL_PYTHON=$(readlink -f "${APP_DIR}/.venv/bin/python")
37+
if [ -f "${REAL_PYTHON}" ]; then
38+
echo "Applying cap_net_bind_service to ${REAL_PYTHON}..."
39+
setcap 'cap_net_bind_service=+ep' "${REAL_PYTHON}" || echo "Warning: Failed to set capability"
40+
fi
41+
fi
1942

2043
LOG_DIR="${APP_DIR}/logs"
2144
mkdir -p "${LOG_DIR}"
45+
chown "${APP_USER}:${APP_USER}" "${LOG_DIR}"
46+
chmod 755 "${LOG_DIR}"
2247

2348
if command -v systemctl >/dev/null 2>&1; then
24-
sudo systemctl daemon-reload || true
25-
if sudo systemctl list-unit-files | grep -q "^${SERVICE_NAME}\.service"; then
26-
sudo systemctl restart "${SERVICE_NAME}.service"
49+
systemctl daemon-reload || true
50+
if systemctl list-unit-files | grep -q "^${SERVICE_NAME}\.service"; then
51+
systemctl restart "${SERVICE_NAME}.service"
2752
exit 0
2853
fi
2954
fi
3055

3156
echo "systemd unit ${SERVICE_NAME}.service not found or unavailable. Relaunching Uvicorn with nohup."
3257

3358
pkill -f "uvicorn src.server:app" || true
34-
nohup "${APP_DIR}/.venv/bin/uvicorn" src.server:app --host 0.0.0.0 --port "${UVICORN_PORT}" \
59+
sudo -u "${APP_USER}" nohup "${APP_DIR}/.venv/bin/uvicorn" src.server:app --host 0.0.0.0 --port "${UVICORN_PORT}" \
3560
>"${LOG_DIR}/uvicorn.log" 2>&1 &

0 commit comments

Comments
 (0)