Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 44 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Ignore environment files with secrets
.env
**/.env
*.env
node/.env
base/.env

# Ignore git repository
.git
.gitignore
.gitattributes

# Ignore node modules (if any)
**/node_modules

# Ignore security-sensitive files
**/*.pem
**/*.key
**/*.crt
**/*.p12
**/*.pfx
**/*.jks

# Ignore backup and temporary files
**/*.bak
**/*.swp
**/*.swo
**/*~
**/.DS_Store

# Ignore IDE and editor files
.vscode/
.idea/
*.code-workspace

# Ignore CI/CD artifacts (already in repo, but exclude from build context)
.github/

# Ignore documentation
*.md
!node/README.md

# Keep .env.example (it's a template, not secrets)
!**/.env.example
182 changes: 182 additions & 0 deletions .github/workflows/build-container.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
name: Build and Test Orcanode Container

on:
push:
branches:
- main
- container-release
tags:
- 'v*.*.*' # Trigger on version tags.
pull_request:
paths:
- 'node/**'
- 'base/**'
- '.github/workflows/build-container.yml'
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Build container image
run: docker build -t orcanode:test -f node/Dockerfile .

- name: Verify Python is available
run: docker run --rm orcanode:test python3 --version

- name: Verify FFmpeg is installed
run: docker run --rm orcanode:test ffmpeg -version

- name: Verify Jack audio is installed
run: docker run --rm orcanode:test which jackd

- name: Verify Python dependencies
run: docker run --rm orcanode:test python3 -c "import boto3; import inotify; print('Python deps OK')"

- name: Verify application scripts are present
run: docker run --rm orcanode:test ls -la /app/stream.sh

- name: Test container startup with dev-virt-s3
run: |
# Copy .env.example to test.env and customize for CI.
cp node/.env.example test.env

# Replace FILLTHISIN placeholders with test values.
sed -i 's/AWS_ACCESS_KEY_ID=FILLTHISIN/AWS_ACCESS_KEY_ID=test-key-id/g' test.env
sed -i 's/AWS_SECRET_ACCESS_KEY=FILLTHISIN/AWS_SECRET_ACCESS_KEY=test-secret/g' test.env
sed -i 's/key="FILLTHISIN"/key="test-logdna-key"/g' test.env
sed -i 's/NODE_NAME=rpi_location/NODE_NAME=rpi_github_ci/g' test.env

# Use dev-virt-s3 mode for CI (no audio hardware required).
sed -i 's/NODE_TYPE=hls-only/NODE_TYPE=dev-virt-s3/g' test.env
sed -i 's/NODE_LOOPBACK=true/NODE_LOOPBACK=false/g' test.env

# Start container with env file.
docker run -d --name orcanode-test-virt \
--env-file test.env \
orcanode:test

sleep 10
echo "=== Container logs ==="
docker logs orcanode-test-virt

# Check container health: accept running or cleanly exited (exit code 0).
STATUS=$(docker inspect --format='{{.State.Status}}' orcanode-test-virt)
EXIT_CODE=$(docker inspect --format='{{.State.ExitCode}}' orcanode-test-virt)

echo "Container status: $STATUS, exit code: $EXIT_CODE"

if [ "$STATUS" = "running" ]; then
echo "Container is running"

# Verify HLS segments are being generated.
docker exec orcanode-test-virt ls -la /tmp/rpi_github_ci/hls/

echo "Container is generating HLS stream"
elif [ "$STATUS" = "exited" ] && [ "$EXIT_CODE" = "0" ]; then
echo "Container exited cleanly (exit code 0)"
else
echo "Container failed with status: $STATUS, exit code: $EXIT_CODE"
exit 1
fi

docker stop orcanode-test-virt || true
docker rm orcanode-test-virt
rm test.env

- name: Test container startup with hls-only (hardware path)
run: |
# Copy .env.example to test.env and customize for CI.
cp node/.env.example test.env

# Replace FILLTHISIN placeholders with test values.
sed -i 's/AWS_ACCESS_KEY_ID=FILLTHISIN/AWS_ACCESS_KEY_ID=test-key-id/g' test.env
sed -i 's/AWS_SECRET_ACCESS_KEY=FILLTHISIN/AWS_SECRET_ACCESS_KEY=test-secret/g' test.env
sed -i 's/key="FILLTHISIN"/key="test-logdna-key"/g' test.env
sed -i 's/NODE_NAME=rpi_location/NODE_NAME=rpi_github_ci_hw/g' test.env

# Keep default NODE_TYPE=hls-only (tests hardware audio path)

# Start container with env file.
docker run -d --name orcanode-test-hw \
--env-file test.env \
orcanode:test

sleep 5
echo "=== Container logs (hls-only mode) ==="
docker logs orcanode-test-hw

# Check container health
STATUS=$(docker inspect --format='{{.State.Status}}' orcanode-test-hw)
EXIT_CODE=$(docker inspect --format='{{.State.ExitCode}}' orcanode-test-hw)

echo "Container status: $STATUS, exit code: $EXIT_CODE"

if [ "$STATUS" = "running" ]; then
echo "✓ Container is running (hls-only mode, audio hardware errors expected)"
elif [ "$STATUS" = "exited" ] && [ "$EXIT_CODE" = "0" ]; then
echo "✓ Container exited cleanly (exit code 0)"
else
echo "✗ Container failed with status: $STATUS, exit code: $EXIT_CODE"
exit 1
fi

docker stop orcanode-test-hw || true
docker rm orcanode-test-hw
rm test.env

build-and-push:
needs: test
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/container-release' || github.ref_type == 'tag' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
actions: write # Required for cache-to: type=gha

steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up QEMU
uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1

- name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
with:
images: ghcr.io/${{ github.repository }}/orcanode
tags: |
# Semver tags on release
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=ref,event=tag
# Branch-based tags
type=ref,event=branch
type=sha
# Latest tag for main branch
type=raw,value=latest,enable={{is_default_branch}}
Comment on lines +155 to +170

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the repository name to confirm the resulting image path
echo "Repository: orcasound/orcanode"
echo "Resulting image path: ghcr.io/orcasound/orcanode/orcanode"
echo ""
echo "If this is unintended, the image path should be: ghcr.io/orcasound/orcanode"

Repository: orcasound/orcanode

Length of output: 227


🏁 Script executed:

# Check if there are other container images or references in the workflow
rg "images:" .github/workflows/ -A 2

Repository: orcasound/orcanode

Length of output: 290


🏁 Script executed:

# Look for any documentation about container image naming in the repo
fd -e md -e txt | xargs rg -l "container|image|ghcr" 2>/dev/null | head -20

Repository: orcasound/orcanode

Length of output: 87


🏁 Script executed:

# Check for other docker/metadata-action uses in the repository
rg "docker/metadata-action" .github/workflows/

Repository: orcasound/orcanode

Length of output: 186


🏁 Script executed:

# Look at the broader workflow context to see if there are multiple services/images
cat -n .github/workflows/build-container.yml | head -50

Repository: orcasound/orcanode

Length of output: 2092


🏁 Script executed:

# Check the README files for any documentation about container image naming
cat README.md | grep -i "container\|image\|ghcr" -A 2 -B 2

Repository: orcasound/orcanode

Length of output: 1719


🏁 Script executed:

# Also check the full build-container.yml workflow to understand if there's any reason for the /orcanode suffix
cat -n .github/workflows/build-container.yml | sed -n '130,182p'

Repository: orcasound/orcanode

Length of output: 2446


Simplify the image path by removing the redundant /orcanode suffix.

The image path ghcr.io/${{ github.repository }}/orcanode resolves to ghcr.io/orcasound/orcanode/orcanode, which is redundant since only a single container image is built in this workflow. Use:

images: ghcr.io/${{ github.repository }}

This produces the simpler and more conventional image path ghcr.io/orcasound/orcanode.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-container.yml around lines 155 - 170, Update the
docker/metadata-action step (id: meta) to use a simplified image name under the
images key: change images: ghcr.io/${{ github.repository }}/orcanode to images:
ghcr.io/${{ github.repository }} so the resulting image path becomes
ghcr.io/orcasound/orcanode instead of the redundant
ghcr.io/orcasound/orcanode/orcanode; keep the rest of the metadata-action
configuration (tags block and type/raw latest enable) unchanged.


- name: Build and push multi-arch images
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0
with:
context: .
file: ./node/Dockerfile
platforms: linux/amd64,linux/arm/v7,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.env
.env
.vs
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ An ARM or X86 device with a sound card (or other audio input devices) connected

### Installing

Create a base docker image for your architecture by running the script in /base/rpi or /base/amd64 as appropriate. You will need to create a .env file as appropriate for your projects. Here is an example of an .env file (tested/working as of June, 2021)...
The container is built from `node/Dockerfile` and is self-contained (no separate base image step required). You will need to create a `.env` file based on `node/.env.example` for your deployment. Here is an example of an `.env` file...

```
AWS_METADATA_SERVICE_TIMEOUT=5
Expand All @@ -43,11 +43,11 @@ LC_ALL=C.UTF-8
... except that the following fields are excised and will need to be added if you are integrating with the audio and logging systems of Orcasound:

```
AWSACCESSKEYID=YourAWSaccessKey
AWSSECRETACCESSKEY=YourAWSsecretAccessKey
SYSLOG_URL=syslog+tls://syslog-a.logdna.com:YourLogDNAPort
SYSLOG_STRUCTURED_DATA='logdna@YourLogDNAnumber key="YourLogDNAKey" tag="docker"
AWS_ACCESS_KEY_ID=YourAWSaccessKeyId
AWS_SECRET_ACCESS_KEY=YourAWSsecretAccessKey

SYSLOG_URL=syslog://syslog-a.logdna.com:YourLogDNAPort
SYSLOG_STRUCTURED_DATA='logdna@YourLogDNAnumber key="YourLogDNAKey" tag="docker"'
```

(You can request keys via the #hydrophone-nodes channel in the Orcasound Slack. As of October, 2021, we are continuing to use AWS S3 for storage and LogDNA for live-logging and troubleshooting.)
Expand All @@ -64,7 +64,7 @@ Here are explanations of some of the .env fields:

## Running local tests

At the root of the repository directory (where you also put your .env file) first copy the compose file you want to `docker-compose.yml`. For example, if you have a Raspberry Pi and you want to use the prebuilt image, then copy `docker-compose.rpi-pull.yml` to `docker-compose.yml`. Then run `docker-compose up -d`. Watch what happens using `htop`. If you want to verify files are being written to /tmp or /mnt directories, get the name of your streaming service using `docker-compose ps` (in this case `orcanode_streaming_1`) and then do `docker exec -it orcanode_streaming_1 /bin/bash` to get a bash shell within the running container.
Go to the `node/` directory (where you also put your `.env` file). Copy the compose file you want to `docker-compose.yml`. For example, if you have a Raspberry Pi and you want to use the prebuilt image, then copy `docker-compose.rpi-pull.yml` to `docker-compose.yml`. Then run `docker-compose up -d`. Watch what happens using `htop`. If you want to verify files are being written to /tmp or /mnt directories, get the name of your streaming service using `docker-compose ps` (in this case `orcanode_streaming_1`) and then do `docker exec -it orcanode_streaming_1 /bin/bash` to get a bash shell within the running container.

### Running an end-to-end test

Expand All @@ -91,9 +91,9 @@ If you would like to add a node to the Orcasound hydrophone network, contact adm

## Built With

* [FFmpeg](https://www.ffmpeg.org/) - Uses ALSA to acquire audio data, then generates lossy streams and/or lossless archive files
* [rsync](https://rsync.samba.org/) - Transfers files locally from /tmp to /mnt directories
* [s3fs](https://github.com/s3fs-fuse/s3fs-fuse) - Used to transfer audio data from local device to S3 bucket(s)
* [FFmpeg](https://www.ffmpeg.org/) - Uses JACK/ALSA to acquire audio data, then generates lossy streams and/or lossless archive files
* [JACK](https://jackaudio.org/) - Low-latency audio server used to route audio from the sound card to FFmpeg
* [boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html) - AWS SDK for Python, used to transfer audio data from the local device to S3 bucket(s)

## Contributing

Expand Down
2 changes: 1 addition & 1 deletion base/upload_flac_s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
print("using dev bucket")
BUCKET = "dev-streaming-orcasound-net"

log.debug("archive bucket set to ", BUCKET)
log.debug("archive bucket set to %s", BUCKET)

def s3_copy_file(path, filename):
log.debug('uploading file '+filename+' from '+path+' to bucket '+BUCKET)
Expand Down
2 changes: 1 addition & 1 deletion base/upload_s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
print("using dev bucket")
BUCKET = "dev-streaming-orcasound-net"

log.debug("hls bucket set to ", BUCKET)
log.debug("hls bucket set to %s", BUCKET)

def s3_copy_file(path, filename):
log.debug('uploading file '+filename+' from '+path+' to bucket '+BUCKET)
Expand Down
91 changes: 91 additions & 0 deletions node/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Orcanode Configuration Template
# Copy this file to .env and customize for your deployment
# See README.md for detailed field descriptions

# === AWS Credentials ===
# Get these from AWS IAM Console or contact Orcasound admin
AWS_ACCESS_KEY_ID=FILLTHISIN
AWS_SECRET_ACCESS_KEY=FILLTHISIN

# === AWS Configuration ===
AWS_METADATA_SERVICE_TIMEOUT=5
AWS_METADATA_SERVICE_NUM_ATTEMPTS=0
REGION=us-west-2

# === S3 Bucket Selection ===
BUCKET_TYPE=dev
#BUCKET_TYPE=prod

Comment thread
dthaler marked this conversation as resolved.
# === Node Configuration ===
# Uncomment the NODE_TYPE you need:
NODE_TYPE=hls-only
#NODE_TYPE=research
#NODE_TYPE=debug
#NODE_TYPE=dev-virt-s3

# Unique identifier for this node (format: rpi_location)
NODE_NAME=rpi_location

# Audio loopback for testing (true/false/hls)
# - true: Route audio input directly to output via Jack
# - false: No loopback
# - hls: Play back the generated HLS stream with ffplay (for testing)
NODE_LOOPBACK=true
#NODE_LOOPBACK=false
#NODE_LOOPBACK=hls

# Audio Settings
SAMPLE_RATE=48000
AUDIO_HW_ID=pisound
CHANNELS=1

# === Logging Configuration ===
# Used by logspout sidecar - get credentials from Orcasound admin
SYSLOG_URL=syslog://syslog-a.logdna.com:37043
SYSLOG_STRUCTURED_DATA='logdna@48950 key="FILLTHISIN" tag="docker"'

# === Stream Configuration ===
FLAC_DURATION=30
SEGMENT_DURATION=10

# === System Configuration ===
LC_ALL=C.UTF-8

# === Field Descriptions ===
#
# AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY:
# AWS credentials for S3 bucket access
#
# BUCKET_TYPE:
# - dev: Development bucket (dev-streaming-orcasound-net)
# - prod: Production bucket (audio-orcasound-net)
# - custom: Use custom buckets (requires BUCKET_STREAMING and BUCKET_ARCHIVE)
#
# NODE_TYPE:
# - hls-only: Stream HLS segments only (most common)
# - research: Stream HLS + archive FLAC files
# - debug: Stream DASH via mpegts for debugging (requires test-engine-live-tools)
# - dev-virt-s3: Development mode using sample WAV file (no audio hardware required)
#
# NODE_NAME:
# Unique identifier in format: rpi_location (e.g., rpi_bush_point, rpi_port_townsend)
#
# AUDIO_HW_ID:
# Sound card identifier. Find with: arecord -l
# Use logical name (pisound, USB) not device numbers (0,0)
#
# SAMPLE_RATE:
# Audio sampling rate in Hz (48000 for standard, 192000 for research)
#
# CHANNELS:
# Number of audio channels (1=mono, 2=stereo)
#
# SEGMENT_DURATION:
# Seconds per HLS segment (typically 10)
#
# FLAC_DURATION:
# Seconds per archived FLAC file (research mode only, typically 30)
#
# SYSLOG_URL / SYSLOG_STRUCTURED_DATA:
# LogDNA/Mezmo credentials for centralized logging via logspout
# Request credentials in #hydrophone-nodes on Orcasound Slack
5 changes: 5 additions & 0 deletions node/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Environment variables with secrets
.env

# But keep the example
!.env.example
Loading
Loading