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
108 changes: 81 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# OS2IoT-docker

This repository contains the docker-compose file and configuration needed to run the OS2IoT project.
This repository contains the Docker Compose file and configuration needed to run the OS2IoT project.

Documentation is available at: https://os2iot.readthedocs.io/en/latest/

Expand All @@ -18,17 +18,85 @@ OS2IoT
From the `OS2IoT-docker` folder in a suitable terminal use:

```
docker-compose up
docker compose up --detach
```

### Quick Start with Task Runner (Recommended)

This project includes a [Taskfile](https://taskfile.dev/) to simplify common operations. Install go-task first:

```bash
# macOS
brew install go-task

# Linux (snap)
sudo snap install task --classic

# Other methods: https://taskfile.dev/installation/
```

**Full setup from scratch:**

```bash
# 1. Clone repos, fix line endings, and generate certificates
task setup

# 2. Build and start all services
docker compose up --build --detach

# 3. Wait for services to be healthy (check with: docker compose ps)
# The backend may take a minute to initialize the database

# 4. Create default organization (required for frontend to work)
task setup:org

# 5. Open the frontend in browser
task open
```

**Default login credentials:**
- Email: `global-admin@os2iot.dk`
- Password: `hunter2`

**Available tasks:**

```bash
task setup # Clone sibling repos, fix line endings, generate certs
task setup:check # Verify setup status
task setup:org # Create default organization (requires services running)
task setup:chirpstack # Configure ChirpStack API key (for LoRaWAN integration)
task status # Show status of all containers
task build # Build all Docker images
task clean # Remove containers, volumes, and images
task open # Open frontend in browser
```

### ChirpStack Integration (Optional)

If you're using LoRaWAN features, you need to configure the ChirpStack API key:

```bash
# Run the setup task - it will guide you through the process
task setup:chirpstack
```

Or manually:
1. Open ChirpStack UI: `task open:chirpstack`
2. Login with `admin` / `admin`
3. Go to **API Keys** → Create a new API key
4. Add to `.env` file: `CHIRPSTACK_API_KEY=your-key-here`
5. Restart backend: `docker compose up -d os2iot-backend`

Without this configuration, you'll see `InvalidToken` errors in the backend logs - these can be ignored if you're not using LoRaWAN features.

## Configuration

Edit the files in the configuration folder to adjust settings for each requirement.

## Contents

- Postgres from the official image.
- Chirpstack using their docker-compose
- Chirpstack using their Docker Compose

## Troubleshooting FAQ

Expand All @@ -43,7 +111,7 @@ ERROR: Encountered errors while bringing up the project.
```

Cause:
Docker doesn't have acceess to mount the volumes.
Docker doesn't have access to mount the volumes.

Solution:
On Windows: Go to Docker Desktop (tray icon) -> Settings -> Resources -> File Sharing -> Add the directory which is the parent directory of "OS2IoT-docker" or a parent of that. -> Apply & Restart
Expand All @@ -61,24 +129,24 @@ Cause:
Database has not been setup correctly on local machine.

Solution:
docker-compose down --volumes
dos2unix configuration/os2iot-postgresql/initdb/\* <-- Skal køres fra git bash
docker-compose up
docker compose down --volumes
dos2unix configuration/os2iot-postgresql/initdb/* # Run from git bash on Windows
docker compose up

### error: Error: connect ETIMEDOUT xxx.xxx.xxx.xxx:xxxx at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1141:16)

Cause:
Docker is trying to connect to the wrong ip.
Docker is trying to connect to the wrong IP.

Solution:
1. Navigate to hosts file: C:\Windows\System32\drivers\etc
2. Open hosts file as administrator
3. Change related ip of host.docker.internal and gateway.docker.internal to your new ip (found in terminal using the ipconfig command: e.g. 192.168.0.1)
4. save
5. restart the application.
3. Change related IP of host.docker.internal and gateway.docker.internal to your new IP (found in terminal using the ipconfig command: e.g. 192.168.0.1)
4. Save
5. Restart the application.

## Adding an ADR Algorithm
When the ADR Algorithm has been tested, and is ready for deployment, the ADR Algorithm has to be added to chirpstack. It is mandatory that the custom adr module is writtin in js.
When the ADR Algorithm has been tested, and is ready for deployment, the ADR Algorithm has to be added to chirpstack. It is mandatory that the custom ADR module is written in JavaScript.

## Adding the Plugin to Chirpstack

Expand All @@ -98,18 +166,4 @@ You should now be able to restart the chirpstack server and the new adr algorith

### Helm

When hosting via helm the steps are slightly different.

1. Make sure that the persistent volume claim belonging to the chirpstack exists in your hosted setup.
2. Find the actual name of the network-server pod. This can be done in a few ways. If you're have a connection via a GUI like `Lens` it can be found under the `Pods` list. If you're hosting on an Azure Kubernetes service, it can be found under the side menu `Workloads -> Pods`
3. Use `kubectl` to copy the module into the pod
```bash
kubectl cp ./path/to/module/adr-module chirpstack-xxxxxxxxx-xxxxx:/etc/chirpstack/adr-modules
```
4. Update `configmap.yaml` located under `/helm/charts/chirpstack/templates` with the path to the plugin under `[network]`, like this:
```toml
[network]
adr_plugins=["/etc/chirpstack/adr-modules/example-file.js"]
```
The first line already exists
5. Once the helm chart has redeployed restart the network server to enable the new module.
See <https://github.com/OS2iot/OS2IoT-helm> for an example Helm setup.
216 changes: 216 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
version: '3'

vars:
PARENT_DIR: '{{.ROOT_DIR}}/..'
BACKEND_DIR: '{{.PARENT_DIR}}/OS2IoT-backend'
FRONTEND_DIR: '{{.PARENT_DIR}}/OS2IoT-frontend'

tasks:
default:
desc: Show available tasks
cmds:
- task --list
silent: true

# Setup tasks
setup:
desc: Run all setup steps (clone repos, fix line endings, generate certs)
cmds:
- task: setup:clone-repos
- task: setup:fix-line-endings
- task: setup:certs
- task: setup:check

setup:clone-repos:
desc: Clone OS2IoT-backend and OS2IoT-frontend to sibling directories
silent: true
cmds:
- |
if [ ! -d "{{.BACKEND_DIR}}" ]; then
echo "Cloning OS2IoT-backend..."
git clone https://github.com/OS2iot/OS2IoT-backend.git "{{.BACKEND_DIR}}"
else
echo "OS2IoT-backend already exists"
fi
- |
if [ ! -d "{{.FRONTEND_DIR}}" ]; then
echo "Cloning OS2IoT-frontend..."
git clone https://github.com/OS2iot/OS2IoT-frontend.git "{{.FRONTEND_DIR}}"
else
echo "OS2IoT-frontend already exists"
fi

setup:fix-line-endings:
desc: Fix line endings on database init scripts (for Windows/cross-platform)
silent: true
cmds:
- |
if command -v dos2unix &> /dev/null; then
dos2unix configuration/os2iot-postgresql/initdb/*.sh 2>/dev/null || true
dos2unix configuration/postgres/initdb/*.sh 2>/dev/null || true
dos2unix configuration/postgresql/initdb/*.sh 2>/dev/null || true
echo "Line endings fixed"
else
echo "dos2unix not installed - skipping (install with: apt install dos2unix)"
fi

setup:certs:
desc: Generate MQTT broker certificates (CA and server)
silent: true
dir: configuration/mosquitto-broker-os2iot
cmds:
- |
if [ -f "ca.crt" ] && [ -f "ca.key" ] && [ -f "server.crt" ] && [ -f "server.key" ]; then
echo "Certificates already exist"
else
# Remove directories if they exist (created by Docker when files were missing)
rm -rf ca.crt ca.key server.crt server.key ca.srl server.csr 2>/dev/null || true
echo "Generating CA certificate..."
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/CN=OS2IoT-CA"
echo "Generating server certificate..."
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -subj "/CN=mosquitto-os2iot"
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650
rm -f server.csr
chmod 644 ca.key server.key
echo "Certificates generated successfully"
fi

setup:check:
desc: Verify sibling repos exist and show status
silent: true
cmds:
- |
echo "=== Setup Status ==="
if [ -d "{{.BACKEND_DIR}}" ]; then
echo "✓ OS2IoT-backend: found"
else
echo "✗ OS2IoT-backend: NOT FOUND (run: task setup:clone-repos)"
fi
if [ -d "{{.FRONTEND_DIR}}" ]; then
echo "✓ OS2IoT-frontend: found"
else
echo "✗ OS2IoT-frontend: NOT FOUND (run: task setup:clone-repos)"
fi
if [ -f "configuration/mosquitto-broker-os2iot/ca.crt" ] && [ -f "configuration/mosquitto-broker-os2iot/ca.key" ]; then
echo "✓ MQTT certificates: found"
else
echo "✗ MQTT certificates: NOT FOUND (run: task setup:certs)"
fi

setup:org:
desc: Create default organization (requires services running)
silent: true
cmds:
- |
NGINX_PORT=$(docker compose port nginx 80 | cut -d: -f2)
if [ -z "$NGINX_PORT" ]; then
echo "✗ Services not running (run: docker compose up --detach)"
exit 1
fi
TOKEN=$(curl -s -X POST "http://localhost:${NGINX_PORT}/api/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"global-admin@os2iot.dk","password":"hunter2"}' | jq -r '.accessToken')
if [ "$TOKEN" = "null" ] || [ -z "$TOKEN" ]; then
echo "✗ Failed to login - ensure services are running"
exit 1
fi
RESULT=$(curl -s -X POST "http://localhost:${NGINX_PORT}/api/v1/organization" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Default Organization"}')
if echo "$RESULT" | grep -q '"id"'; then
echo "✓ Default Organization created"
else
echo "Organization may already exist or error: $RESULT"
fi

setup:chirpstack:
desc: Configure ChirpStack API key for LoRaWAN integration
silent: true
cmds:
- |
CHIRPSTACK_PORT=$(docker compose port chirpstack 8080 2>/dev/null | cut -d: -f2)
if [ -z "$CHIRPSTACK_PORT" ]; then
echo "✗ ChirpStack not running (run: docker compose up --detach)"
exit 1
fi
echo "=== ChirpStack API Key Setup ==="
echo ""
echo "1. Opening ChirpStack UI at http://localhost:${CHIRPSTACK_PORT}"
echo "2. Login with: admin / admin"
echo "3. Go to: API Keys (in the left menu)"
echo "4. Click 'Add API key'"
echo "5. Give it a name and click 'Submit'"
echo "6. Copy the generated token"
echo ""
# Try to open in browser
if command -v xdg-open &> /dev/null; then
xdg-open "http://localhost:${CHIRPSTACK_PORT}" 2>/dev/null &
elif command -v open &> /dev/null; then
open "http://localhost:${CHIRPSTACK_PORT}" 2>/dev/null &
fi
echo "Paste your API key here (or press Ctrl+C to cancel):"
read -r API_KEY
if [ -n "$API_KEY" ]; then
if [ -f ".env" ]; then
if grep -q "^CHIRPSTACK_API_KEY=" .env; then
sed -i.bak "s/^CHIRPSTACK_API_KEY=.*/CHIRPSTACK_API_KEY=${API_KEY}/" .env && rm -f .env.bak
echo "✓ Updated CHIRPSTACK_API_KEY in .env"
else
echo "CHIRPSTACK_API_KEY=${API_KEY}" >> .env
echo "✓ Added CHIRPSTACK_API_KEY to .env"
fi
else
echo "CHIRPSTACK_API_KEY=${API_KEY}" > .env
echo "✓ Created .env with CHIRPSTACK_API_KEY"
fi
echo ""
echo "Restarting backend to apply changes..."
docker compose up -d os2iot-backend
echo "✓ Done! Backend restarted with new API key"
else
echo "✗ No API key provided"
fi

open:chirpstack:
desc: Open ChirpStack UI in browser
cmds:
- |
URL="http://$(docker compose port chirpstack 8080)"
if command -v xdg-open &> /dev/null; then
xdg-open "$URL"
elif command -v open &> /dev/null; then
open "$URL"
else
echo "Open in browser: $URL"
fi

status:
desc: Show status of all containers
cmds:
- docker compose ps --format 'table {{"{{"}}.Name{{"}}"}} {{"{{"}}.Status{{"}}"}}'

build:
desc: Build all images without starting
cmds:
- docker compose build

clean:
desc: Remove all containers, volumes, and images for this project
cmds:
- docker compose down --volumes --rmi local

open:
desc: Open the frontend in browser
cmds:
- |
URL="http://$(docker compose port nginx 80)"
if command -v xdg-open &> /dev/null; then
xdg-open "$URL"
elif command -v open &> /dev/null; then
open "$URL"
else
echo "Open in browser: $URL"
fi

This file was deleted.

Loading