Skip to content
Merged
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
86 changes: 0 additions & 86 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,89 +52,3 @@ jobs:
run: mix credo
- name: Run tests
run: mix test

#
# builds public docker image (multi-arch)
#
docker-publish:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- uses: actions/checkout@v4

# Enable QEMU for cross-building arm64 on amd64 runner
- uses: docker/setup-qemu-action@v3

- uses: docker/setup-buildx-action@v3

- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- uses: docker/build-push-action@v6
with:
context: ./server
push: true
tags: ghcr.io/${{ github.repository }}:latest
platforms: linux/amd64,linux/arm64

#
# deploy stacks.ethui.devices:
#
deploy:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/deploy'
environment: production
defaults:
run:
working-directory: server

env:
MIX_ENV: prod
SSH_URL: ${{ vars.DIGITALOCEAN_USER }}@${{ vars.DIGITALOCEAN_HOST }}

steps:
- uses: actions/checkout@v3
- uses: erlef/setup-beam@v1
with:
elixir-version: "1.18.3"
otp-version: "OTP-27"

- name: Install dependencies
run: mix deps.get
- name: Compile
run: mix compile
- name: Build release
run: mix release

- uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1, hardcoded hash for security
with:
ssh-private-key: ${{ secrets.DIGITALOCEAN_DEPLOY_KEY }}

- run: ssh-keyscan ${{ vars.DIGITALOCEAN_HOST }} >> ~/.ssh/known_hosts

- name: Create deploy dir
run: ssh $SSH_URL 'mkdir -p stacks.ethui.dev'

- name: Upload
run: |
rsync -az --delete _build/prod/rel/ethui/ \
$SSH_URL:${{ vars.DEPLOY_PATH }}/build-new

- name: Upload release script
run: scp ../.github/scripts/deploy.sh $SSH_URL:/tmp/stacks-deploy.sh

- name: Chmod deploy script
run: ssh $SSH_URL "chmod +x /tmp/stacks-deploy.sh"

- name: Run deploy script
run: ssh $SSH_URL "/tmp/stacks-deploy.sh '${{ vars.DEPLOY_PATH }}'"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ test-data/
.DS_Store
Thumbs.db
.env*.local
.claude
43 changes: 43 additions & 0 deletions compose.saas.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
services:
server:
build:
context: ./server
dockerfile: Dockerfile
ports:
- "4000:4000"
environment:
- MIX_ENV=prod
- PHX_HOST=${PHX_HOST}
- DATA_ROOT=/data
- ETHUI_STACKS_SAAS=1
- SECRET_KEY_BASE=${SECRET_KEY_BASE}
- JWT_SECRET=${JWT_SECRET}
- MAILER_SMTP=${MAILER_SMTP}
- MAILER_SMTP_PORT=${MAILER_SMTP_PORT}
- MAILER_SMTP_USERNAME=${MAILER_SMTP_USERNAME}
- MAILER_SMTP_PASSWORD=${MAILER_SMTP_PASSWORD}
volumes:
- data:/data
- /var/run/docker.sock:/var/run/docker.sock
- data:/data
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

There are two potentially conflicting volume mounts for data directories. The environment variable DATA_ROOT is set to '/data' which maps to the named volume 'data:/data', but there's also a bind mount './server/data:/app/data'. This could lead to confusion about where data is actually stored or result in data being split across multiple locations.

Verify which data directory should be used and remove the unnecessary volume mount. If DATA_ROOT=/data is correct, the './server/data:/app/data' mount may not be needed.

Suggested change
- data:/data

Copilot uses AI. Check for mistakes.
labels:
# custom rules for *.stacks.ethui.dev
- "traefik.enable=true"
- "traefik.http.routers.ethui-stacks.rule=HostRegexp(`^[a-z0-9-]+\\.stacks\\.ethui\\.dev$`)"
- "traefik.http.routers.ethui-stacks.entrypoints=web"
- "traefik.http.routers.ethui-stacks.service=ethui-stacks-hkdbss-1-web"

# ssl support for *.stacks.ethui.dev
- 'traefik.http.routers.ethui-stacks-secure.rule=HostRegexp(`^[a-z0-9-]+\.stacks\.ethui\.dev$`)'
Comment on lines +26 to +31
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The HostRegexp pattern uses backticks as delimiters which may not be the correct syntax for Traefik v2/v3. Modern Traefik versions typically use parentheses for HostRegexp patterns like 'HostRegexp({subdomain:[a-z0-9-]+}.stacks.ethui.dev)' or use the Host matcher with regex.

Verify this syntax is correct for your Traefik version. The pattern may need to be adjusted to match Traefik's expected syntax.

Suggested change
- "traefik.http.routers.ethui-stacks.rule=HostRegexp(`^[a-z0-9-]+\\.stacks\\.ethui\\.dev$`)"
- "traefik.http.routers.ethui-stacks.entrypoints=web"
- "traefik.http.routers.ethui-stacks.service=ethui-stacks-hkdbss-1-web"
# ssl support for *.stacks.ethui.dev
- 'traefik.http.routers.ethui-stacks-secure.rule=HostRegexp(`^[a-z0-9-]+\.stacks\.ethui\.dev$`)'
- "traefik.http.routers.ethui-stacks.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.stacks.ethui.dev`)"
- "traefik.http.routers.ethui-stacks.entrypoints=web"
- "traefik.http.routers.ethui-stacks.service=ethui-stacks-hkdbss-1-web"
# ssl support for *.stacks.ethui.dev
- 'traefik.http.routers.ethui-stacks-secure.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.stacks.ethui.dev`)'

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +31
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The HostRegexp pattern uses backticks as delimiters which may not be the correct syntax for Traefik v2/v3. Modern Traefik versions typically use parentheses for HostRegexp patterns like 'HostRegexp({subdomain:[a-z0-9-]+}.stacks.ethui.dev)' or use the Host matcher with regex.

Verify this syntax is correct for your Traefik version. The pattern may need to be adjusted to match Traefik's expected syntax.

Suggested change
- "traefik.http.routers.ethui-stacks.rule=HostRegexp(`^[a-z0-9-]+\\.stacks\\.ethui\\.dev$`)"
- "traefik.http.routers.ethui-stacks.entrypoints=web"
- "traefik.http.routers.ethui-stacks.service=ethui-stacks-hkdbss-1-web"
# ssl support for *.stacks.ethui.dev
- 'traefik.http.routers.ethui-stacks-secure.rule=HostRegexp(`^[a-z0-9-]+\.stacks\.ethui\.dev$`)'
- "traefik.http.routers.ethui-stacks.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.stacks.ethui.dev`)"
- "traefik.http.routers.ethui-stacks.entrypoints=web"
- "traefik.http.routers.ethui-stacks.service=ethui-stacks-hkdbss-1-web"
# ssl support for *.stacks.ethui.dev
- 'traefik.http.routers.ethui-stacks-secure.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.stacks.ethui.dev`)'

Copilot uses AI. Check for mistakes.
- 'traefik.http.routers.ethui-stacks-secure.entrypoints=websecure'
- 'traefik.http.routers.ethui-stacks-secure.service=ethui-stacks-hkdbss-1-web'
Comment on lines +28 to +33
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The hardcoded service name 'ethui-stacks-hkdbss-1-web' in the Traefik labels appears to be specific to a particular Dokploy deployment instance. This identifier is typically auto-generated by Dokploy and may differ across deployments. Using a hardcoded value could cause routing to fail if the actual service name generated by Dokploy doesn't match this pattern.

Consider using Traefik's service discovery or a more generic service reference that will work regardless of the Dokploy-generated identifier.

Suggested change
- "traefik.http.routers.ethui-stacks.service=ethui-stacks-hkdbss-1-web"
# ssl support for *.stacks.ethui.dev
- 'traefik.http.routers.ethui-stacks-secure.rule=HostRegexp(`^[a-z0-9-]+\.stacks\.ethui\.dev$`)'
- 'traefik.http.routers.ethui-stacks-secure.entrypoints=websecure'
- 'traefik.http.routers.ethui-stacks-secure.service=ethui-stacks-hkdbss-1-web'
# ssl support for *.stacks.ethui.dev
- 'traefik.http.routers.ethui-stacks-secure.rule=HostRegexp(`^[a-z0-9-]+\.stacks\.ethui\.dev$`)'
- 'traefik.http.routers.ethui-stacks-secure.entrypoints=websecure'

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +33
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The hardcoded service name 'ethui-stacks-hkdbss-1-web' in the Traefik labels appears to be specific to a particular Dokploy deployment instance. This identifier is typically auto-generated by Dokploy and may differ across deployments. Using a hardcoded value could cause routing to fail if the actual service name generated by Dokploy doesn't match this pattern.

Consider using Traefik's service discovery or a more generic service reference that will work regardless of the Dokploy-generated identifier.

Suggested change
- "traefik.http.routers.ethui-stacks.service=ethui-stacks-hkdbss-1-web"
# ssl support for *.stacks.ethui.dev
- 'traefik.http.routers.ethui-stacks-secure.rule=HostRegexp(`^[a-z0-9-]+\.stacks\.ethui\.dev$`)'
- 'traefik.http.routers.ethui-stacks-secure.entrypoints=websecure'
- 'traefik.http.routers.ethui-stacks-secure.service=ethui-stacks-hkdbss-1-web'
- "traefik.http.routers.ethui-stacks.service=ethui-stacks-web"
- "traefik.http.services.ethui-stacks-web.loadbalancer.server.port=4000"
# ssl support for *.stacks.ethui.dev
- 'traefik.http.routers.ethui-stacks-secure.rule=HostRegexp(`^[a-z0-9-]+\.stacks\.ethui\.dev$`)'
- 'traefik.http.routers.ethui-stacks-secure.entrypoints=websecure'
- 'traefik.http.routers.ethui-stacks-secure.service=ethui-stacks-web'

Copilot uses AI. Check for mistakes.
- 'traefik.http.routers.ethui-stacks-secure.tls.certresolver=letsencrypt-dns'
- 'traefik.http.routers.ethui-stacks-secure.tls.domains[0].main=stacks.ethui.dev'
- 'traefik.http.routers.ethui-stacks-secure.tls.domains[0].sans=*.stacks.ethui.dev'

volumes:
data:

networks:
stacks:
name: ethui-stacks
2 changes: 1 addition & 1 deletion server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ARG DEBIAN_VERSION=bullseye-20250407-slim
ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-ubuntu-noble-20250805"
ARG RUNNER_IMAGE="ubuntu:noble"

FROM ${BUILDER_IMAGE} as builder
FROM ${BUILDER_IMAGE} AS builder

# install build dependencies
RUN apt-get update -y && apt-get install -y build-essential git \
Expand Down
3 changes: 1 addition & 2 deletions server/config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,11 @@ if config_env() == :prod do

host = System.get_env("PHX_HOST") || "stacks.ethui.dev"
port = System.get_env("PHX_PORT") || 4000
listen_ip = if is_saas?, do: {127, 0, 0, 1}, else: {0, 0, 0, 0}

config :ethui, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")

config :ethui, EthuiWeb.Endpoint,
http: [ip: listen_ip, port: port],
http: [ip: {0, 0, 0, 0}, port: port],
url: [host: host, port: 443, scheme: "https"],
force_ssl: [rewrite_on: [:x_forwarded_proto]],
secret_key_base: secret_key_base
Expand Down