Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ba4c706
Initial refactor
albireox May 9, 2026
48fb0e3
Add workflow_dispatch to actions
albireox May 9, 2026
3b01c96
Do not build the wrapper in debug mode
albireox May 9, 2026
08f3aae
Some fixes
albireox May 9, 2026
f4cc5af
Add docker workflow and update Dockerfile
albireox May 9, 2026
f5e3327
Build docker image on all pushes for now
albireox May 9, 2026
774328d
Run docker build after copying the Andor SDK to the runner
albireox May 9, 2026
827f4e8
Add && \
albireox May 9, 2026
b05c2ae
Try to fix the docker build
albireox May 9, 2026
e37e1b8
Add an ls for debugging
albireox May 9, 2026
0aa6632
Put curl command in its own step
albireox May 9, 2026
562c203
Move SDK copy to the docker job
albireox May 9, 2026
efae8c5
List SDK files
albireox May 9, 2026
15edbf2
Do not use checkout
albireox May 9, 2026
91d0617
Use current context
albireox May 9, 2026
c01762e
Add focus control API and router
albireox May 11, 2026
a33164f
Add timeout to focus API client
albireox May 11, 2026
7629147
Reduce extra delay
albireox May 11, 2026
445e3c6
Add /expose/abort endpoint
albireox May 11, 2026
218afe8
Add TESTEXP header keyword to FITS files created
albireox May 11, 2026
1529150
Add last exposure path
albireox May 11, 2026
a4293bc
Add /expose/last endpoint to return the path of the last exposure taken
albireox May 11, 2026
f5bae7e
Change config to be a dataclass
albireox May 11, 2026
8e1be6b
Also make EvoraState a dataclass
albireox May 11, 2026
b03bda7
Set filter in /expose if filter is provided
albireox May 11, 2026
4b6ad03
Add TCS info to headers and refactor tools into separate files
albireox May 11, 2026
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
17 changes: 0 additions & 17 deletions .flake8

This file was deleted.

86 changes: 86 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: Docker

on:
push:
paths-ignore:
- 'docs/**'
pull_request:
branches:
- main
paths-ignore:
- 'docs/**'

jobs:

docker:
name: Docker

runs-on: ubuntu-24.04

env:
USER: albireox
APP: evora-server

steps:
- uses: actions/checkout@v6

- name: Download SDK zip file
run: |
curl -L -o andor.zip ${{ secrets.ANDOR_SDK_LINK }}

- name: Extract SDK
run: |
rm -Rf andor
unzip andor.zip -d andor_tmp
mv andor_tmp/andor ./
rm -Rf andor.zip andor_tmp

- name: List files
run: |
ls

- name: List Andor SDK files
run: |
ls andor

- name: Set docker tags
id: set-tags
run: |
if [[ $GITHUB_REF == refs/heads/main ]]
then
echo TAGS=$USER/$APP:latest >> $GITHUB_OUTPUT
elif [[ $GITHUB_REF == refs/heads/* ]]
then
BRANCH=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's/[\/]/_/g')
echo TAGS=$USER/$APP:$BRANCH >> $GITHUB_OUTPUT
elif [[ $GITHUB_REF == refs/pull/* ]]
then
BRANCH=${{ github.head_ref || github.ref_name }}
echo TAGS=$USER/$APP:$BRANCH >> $GITHUB_OUTPUT
else
echo TAGS=$USER/$APP:${GITHUB_REF#refs/tags/} >> $GITHUB_OUTPUT
fi

- name: Show tags
run: echo ${{ steps.set-tags.outputs.TAGS }}

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin

- name: Build and push
id: docker_build
uses: docker/build-push-action@v6
with:
context: .
push: true
provenance: false
tags: ghcr.io/${{ steps.set-tags.outputs.TAGS }}

- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
35 changes: 35 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Lint

on:
workflow_dispatch:
push:
paths-ignore:
- 'docs/**'
pull_request:
paths-ignore:
- 'docs/**'

jobs:
lint:
name: Lint

runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@v6

- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.13'

- name: Lint with ruff
run: |
uv tool install ruff
ruff check src/ tests/
ruff format --check src/ tests/
46 changes: 46 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Test

on:
workflow_dispatch:
push:
paths-ignore:
- 'docs/**'
pull_request:
paths-ignore:
- 'docs/**'

jobs:
test:
name: Test

runs-on: ubuntu-24.04

strategy:
fail-fast: false
matrix:
python-version: ['3.11', '3.12', '3.13', '3.14']

env:
EVORA_SERVER_DEBUG: '1'

steps:
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true

- name: Install dependencies
run: |
uv sync --no-dev --frozen

- name: Test with pytest
run: |
uv pip install pytest pytest-mock pytest-asyncio pytest-cov
uv run pytest
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,11 @@ node_modules
# Ignore data folder if debugging
data/
evora/debug.py

# Do not commit andor drivers
andor/

# Skip the deprecated code
deprecated/

.vscode/
54 changes: 54 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Use a Python image without uv. We don't need uv in the final image
# so we'll mount it only for syncing.
FROM python:3.14-slim

# Use the system Python across both stages
ENV UV_PYTHON_DOWNLOADS=0

# Install the project into `/app`
WORKDIR /app

# Install libusb-1.0-0 for andor drivers
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get -y update
RUN apt-get -y install libusb-1.0-0 libusb-1.0-0-dev build-essential

# Copy andor drivers
COPY andor /app/andor

# Install andor drivers
RUN cd /app/andor && bash install_andor

# Delete source drivers folder
RUN rm -rf /app/andor

# Enable bytecode compilation
ENV UV_COMPILE_BYTECODE=1

# Copy from the cache instead of linking since it's a mounted volume
ENV UV_LINK_MODE=copy

# Then, add the rest of the project source code and install it
ADD ./pyproject.toml /app
ADD ./uv.lock /app
ADD ./setup.py /app
ADD ./src /app/src

RUN --mount=from=ghcr.io/astral-sh/uv,source=/uv,target=/bin/uv \
--mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev

# Remove build tools and clean up apt cache to save space
RUN apt-get purge -y --auto-remove build-essential libusb-1.0-0-dev && \
rm -rf /var/lib/apt/lists/*

# Place executables in the environment at the front of the path
ENV PATH="/app/.venv/bin:$PATH"

ENV WORKERS=1
ENV PORT=80

# Reset the entrypoint, don't invoke `uv`
ENTRYPOINT []

CMD ["sh", "-c", "fastapi run src/evora_server/app.py --port $PORT --workers $WORKERS"]
144 changes: 1 addition & 143 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,143 +1 @@
# Evora

Andor wrapper for Evora and Flask server.

## Installation

`evora-server` requires the proprietary Andor libraries to be installed in `/usr/local/lib`. The library can be used for debugging without the Andor libraries, but they are necessary to run the actual camera.

To install `evora-server`, clone the repository and run

```console
pip install .
```

or to install in editable mode

```console
pip install -e .
```

### Debug mode

To run the server in debug mode, with the dummy module mocking the camera, edit `evora/debug.py` and set `DEBUGGING = True`. This will create a folder in the `evora-server` root that acts as the `/data` folder.

## Running the server

To run the server, from a terminal at the root of the project, execute

```console
python app.py
```

which is equivalent to

```console
flask --debug run --port 3000
```

## Images

`evora-server` will save camera files to `/data/ecam/DATE` where `DATE` is in the format `20230504` and rotates at midnight UTC.

**Note**: Mac OSx doesn't allow the creation of folders in the root `/` directory, since [OSx makes the root directory read-only by default](https://apple.stackexchange.com/questions/388236/unable-to-create-folder-in-root-of-macintosh-hd).

## Deploying for production

The recommended way to run `evora-server` in production is by running the app with the Flask development server with a single process and threading. This allows for concurrent routes and asyncio to work (which is required for features such as aborting exposures). At this point this is preferred to using a UWSGI layer such as `gunicorn` since the camera has a single connection so we cannot run multiple workers.

First, make sure the `/data/ecam` directory exists with the proper user permissions

```console
sudo mkdir -p /data/ecam && sudo chown -R $USER /data
```

and that the Andor SDK is installed with

```console
ls /usr/local/lib/libandor.so
```

Try to run `standalone-start.sh` in the `evora-server` now. It should start downloading around ~20 GB of data for astrometry. Once this is done, you should see the server spin up (ignore the "This is a development server" warning). Test it with `curl localhost:8000/getStatus`.

To run this command in the background as a user `systemd` service, create a file `/usr/lib/systemd/user/evora-server.service` with the contents

```ini
[Unit]
Description=evora-server

[Service]
WorkingDirectory=/home/mrouser/Github/evora-server
ExecStart=/home/mrouser/Github/evora-server/standalone-start.sh

[Install]
WantedBy=default.target
```

Change `WorkingDirectory` and `ExecStart` to the download location of `evora-server`, then run the following commands to get it to run at system start.

```console
systemctl --user daemon-reload
systemctl --user enable --now enable evora-server
systemctl --user status evora-server
```

### Configuring nginx

In addition to running the server, a reverse proxy is needed to run the Evora client and server in the same HTTP server. In Ubuntu, install `nginx` (alternatively you can use `Apache`) with

```console
sudo apt update
sudo apt install nginx
```

and adjust the firewall to open the desired ports. Then start `nginx` with

```console
sudo systemctl enable --now nginx
```

We'll now add a new site for Evora. Create a new file `/etc/nginx/sites-enabled/evora.conf` with

```console
sudo vim /etc/nginx/sites-enabled/evora.conf
```

and include the configuration

```nginx
server {
listen 80;
listen [::]:80;
server_name localhost;
access_log /usr/local/var/log/nginx/evora.log;

location /api/ {
proxy_pass http://127.0.0.1:8000/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 75s;
proxy_read_timeout 1800s;
}

location /data {
alias /data;
autoindex on;
index index.html index.php;
}
}
```

This configuration creates a server running on port `80` (the default HTTP) and adds a reverse proxy to where the `evora-server` webapp is running. It also creates a route to expose and browse `/data`.

After this, restart `nginx` with

```console
sudo systemctl restart nginx
```

and test that it works by navigating to [http://localhost/api/getTemperature](http://localhost/api/getTemperature).

## References

- [Andor SDK documentation](https://neurophysics.ucsd.edu/Manuals/Andor%20Technology/Andor_Software_Development_Kit.pdf)
# evora-server
Loading