diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
new file mode 100644
index 0000000..ae2442d
--- /dev/null
+++ b/.github/workflows/testing.yml
@@ -0,0 +1,67 @@
+ name: Continuous Integration
+ 
+ on:
+   push:
+     branches:
+     - '*'
+   pull_request:
+     branches:
+     - '*'
+   schedule:
+     - cron: '59 23 * * *'
+   workflow_dispatch:
+    inputs:
+      version:
+        description: Manual CI Run
+        default: test
+        required: false
+
+ jobs:
+   tests:
+     name: ${{ matrix.os }}, ${{ matrix.environment-file }}
+     runs-on: ${{ matrix.os }}
+     timeout-minutes: 30
+     strategy:
+       matrix:
+         os: [ubuntu-latest]
+         environment-file: [
+            ci/py312-latest.yaml,
+         ]
+       fail-fast: false
+
+     defaults:
+       run:
+         shell: bash -l {0}
+
+     steps:
+       - name: checkout repo
+         uses: actions/checkout@v4
+         with:
+           fetch-depth: 0 # Fetch all history for all branches and tags.
+       
+       - name: setup micromamba
+         uses: mamba-org/setup-micromamba@v1
+         with:
+           environment-file: ${{ matrix.environment-file }}
+           micromamba-version: "latest"
+
+       - name: install package
+         run: "pip install -e ."
+
+       - name: environment info
+         run: "micromamba info && micromamba list"
+       
+       - name: run tests
+         run: |
+           pytest \
+           fastpair/ \
+           --verbose \
+           -r a \
+           --color yes \
+           --cov fastpair \
+           --cov-append \
+           --cov-report term-missing \
+           --cov-report xml .
+       
+       - name: codecov
+         uses: codecov/codecov-action@v4
diff --git a/.gitignore b/.gitignore
index 72364f9..ecb7336 100644
--- a/.gitignore
+++ b/.gitignore
@@ -87,3 +87,6 @@ ENV/
 
 # Rope project settings
 .ropeproject
+
+# macOS
+*.DS_Store
diff --git a/ci/py312-latest.yaml b/ci/py312-latest.yaml
new file mode 100644
index 0000000..0c3972a
--- /dev/null
+++ b/ci/py312-latest.yaml
@@ -0,0 +1,10 @@
+name: py312-latest
+channels:
+  - conda-forge
+  - nodefaults
+dependencies:
+  - python=3.12
+  - scipy=1.11
+  # testing
+  - pytest
+  - pytest-cov
diff --git a/setup.py b/setup.py
index 1e3a56c..02754e2 100644
--- a/setup.py
+++ b/setup.py
@@ -21,35 +21,7 @@
 
 PACKAGE_NAME = "FastPair"
 DESCRIPTION = "FastPair: Data-structure for the dynamic closest-pair problem."
-
-MAJOR = 0
-MINOR = 1
-MICRO = 0
-ISRELEASED = False
-VERSION = '%d.%d.%d' % (MAJOR, MINOR, MICRO)
-QUALIFIER = ''
-FULLVERSION = VERSION
-if not ISRELEASED:
-    FULLVERSION += '.dev'
-    try:
-        import subprocess
-        try:
-            pipe = subprocess.Popen(["git", "rev-parse", "--short", "HEAD"],
-                                    stdout=subprocess.PIPE).stdout
-        except OSError:
-            # msysgit compatibility
-            pipe = subprocess.Popen(
-                ["git.cmd", "describe", "HEAD"],
-                stdout=subprocess.PIPE).stdout
-        rev = pipe.read().strip()
-        # Makes distutils blow up on Python 2.7
-        if sys.version_info[0] >= 3:
-            rev = rev.decode('ascii')
-        FULLVERSION = '%d.%d.%d.dev-%s' % (MAJOR, MINOR, MICRO, rev)
-    except:
-        warnings.warn("Couldn't get git revision")
-else:
-    FULLVERSION += QUALIFIER
+FULLVERSION = "v0.1.0"
 
 setup(name=PACKAGE_NAME, version=FULLVERSION, description=DESCRIPTION,
       license='MIT', author='Carson J. Q. Farmer',