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
29 changes: 27 additions & 2 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,34 @@ jobs:
- name: Test
run: dotnet test "Resgrid Audio.sln" --configuration Release --no-build --verbosity normal

check-approval:
name: Verify PR was approved
needs: build-and-test
if: github.event.action == 'closed' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master'
runs-on: ubuntu-latest
environment: BuildEnv

permissions:
pull-requests: read

steps:
- name: Verify at least one approved review
uses: actions/github-script@v7
with:
script: |
const { data: reviews } = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
});
const approved = reviews.some(r => r.state === 'APPROVED');
if (!approved) {
core.setFailed('PR must have at least one approved review before publishing.');
}

publish-apps:
name: Publish app assets
needs: build-and-test
needs: check-approval
if: github.event.action == 'closed' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master'
runs-on: windows-latest
environment: BuildEnv
Expand Down Expand Up @@ -122,7 +147,7 @@ jobs:

docker-build-and-push:
name: Publish Docker image
needs: build-and-test
needs: check-approval
if: github.event.action == 'closed' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master'
runs-on: ubuntu-latest
environment: BuildEnv
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,4 @@ Web/Resgrid.Services/App_Data/Resgrid.Web.Services.XML
#Common/AssemblyInfo.cs
/node_modules
.vs/
/.idea
43 changes: 42 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,55 @@ COPY . .
RUN dotnet restore "Resgrid.Audio.Relay.Console/Resgrid.Audio.Relay.Console.csproj" -p:TargetFramework=net10.0
RUN dotnet publish "Resgrid.Audio.Relay.Console/Resgrid.Audio.Relay.Console.csproj" -c Release -f net10.0 -o /app/publish /p:UseAppHost=false

# Download the LocalXpose CLI binary.
# LocalXpose distributes only a rolling "latest" build from S3 — there are no
# versioned release URLs. SHA256 values below are sourced from the AUR PKGBUILD
# (https://aur.archlinux.org/packages/localxpose-cli, last updated 2025-02-04)
# and must be updated whenever loclx publishes a new binary.
# Supported Docker architectures: linux/amd64, linux/arm64, linux/386, linux/arm
FROM debian:bookworm-slim AS loclx-download
ARG TARGETARCH=amd64
ARG LOCLX_SHA256_AMD64=03c6d1d35dfd0acb673473314c1384156ed2bfcb96e581b3e0bb398fef45fb88
ARG LOCLX_SHA256_ARM64=a423e0ce90fcab7044b4f0244fb8483fe12acf30361fff0e37d5abc2dae2da91
ARG LOCLX_SHA256_386=2534e0056ba5c1e4d55322b2b975a4945104604be15bef9df01c933ad4352804
ARG LOCLX_SHA256_ARM=83e5484169ea28f05fe221056374bbdaac129850b607bdc243326a06c22575e4
RUN apt-get update && apt-get install -y --no-install-recommends wget zstd ca-certificates \
&& case "${TARGETARCH}" in \
amd64) sha="${LOCLX_SHA256_AMD64}" ;; \
arm64) sha="${LOCLX_SHA256_ARM64}" ;; \
386) sha="${LOCLX_SHA256_386}" ;; \
arm) sha="${LOCLX_SHA256_ARM}" ;; \
*) echo "Unsupported arch: ${TARGETARCH}" >&2 ; exit 1 ;; \
esac \
&& wget -q -O /tmp/loclx.pkg.tar.zst \
"https://loclx-client.s3.amazonaws.com/loclx-linux-${TARGETARCH}.pkg.tar.zst" \
&& echo "${sha} /tmp/loclx.pkg.tar.zst" | sha256sum -c - \
&& mkdir -p /tmp/loclx-extract \
&& tar --zstd -xf /tmp/loclx.pkg.tar.zst -C /tmp/loclx-extract \
&& find /tmp/loclx-extract -type f -name 'loclx' \
-exec install -m 755 {} /usr/local/bin/loclx \; \
&& [ -x /usr/local/bin/loclx ] \
&& rm -rf /tmp/loclx.pkg.tar.zst /tmp/loclx-extract /var/lib/apt/lists/*

FROM mcr.microsoft.com/dotnet/runtime:10.0 AS final
WORKDIR /app

COPY --from=build /app/publish .
COPY --from=loclx-download /usr/local/bin/loclx /usr/local/bin/loclx
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh

# Relay configuration
ENV RELAY_Mode=smtp
ENV RELAY_Smtp__Port=2525

# LocalXpose tunnel configuration (all optional — tunnel is disabled by default).
# Set LOCLX_ENABLED=true to activate the tunnel.
# Provide LOCLX_TOKEN with your LocalXpose access token.
# Optionally set LOCLX_RESERVED_ENDPOINT=<host:port> to use a reserved endpoint.
# Alternatively, mount a tunnels YAML file at /etc/resgrid/loclx-tunnels.yaml.
ENV LOCLX_ENABLED=false

EXPOSE 2525

ENTRYPOINT ["dotnet", "Resgrid.Audio.Relay.Console.dll"]
ENTRYPOINT ["/docker-entrypoint.sh"]
54 changes: 54 additions & 0 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/bin/bash
# Entrypoint for the Resgrid Relay container.
#
# Optionally starts a LocalXpose TCP tunnel before launching the relay,
# allowing the SMTP port to be reached from outside a firewall.
#
# Configuration — choose one approach:
#
# APPROACH 1: environment variables
# LOCLX_ENABLED=true enable the tunnel (default: false)
# LOCLX_TOKEN=<token> LocalXpose access token (required)
# LOCLX_RESERVED_ENDPOINT=<h:p> optional reserved endpoint, e.g. smtp.loclx.io:25
# RELAY_Smtp__Port=2525 SMTP port the relay listens on (default: 2525)
#
# APPROACH 2: config file (takes precedence over env vars when present)
# Mount a tunnels YAML file into the container:
# -v /host/loclx-tunnels.yaml:/etc/resgrid/loclx-tunnels.yaml
# and set LOCLX_ENABLED=true and LOCLX_TOKEN=<token>.
#
# Example loclx-tunnels.yaml:
# tunnels:
# - name: smtp
# type: tcp
# to: localhost:2525
# reserved: smtp.loclx.io:25
#
set -e

if [ "${LOCLX_ENABLED:-false}" = "true" ]; then
SMTP_PORT="${RELAY_Smtp__Port:-2525}"

if [ -n "${LOCLX_TOKEN}" ]; then
echo "[localxpose] Authenticating..."
export LX_ACCESS_TOKEN="${LOCLX_TOKEN}"
loclx auth login
else
echo "[localxpose] WARNING: LOCLX_TOKEN is not set; tunnel may fail to authenticate."
fi

if [ -f "/etc/resgrid/loclx-tunnels.yaml" ]; then
echo "[localxpose] Starting tunnel from /etc/resgrid/loclx-tunnels.yaml..."
loclx tunnel -c /etc/resgrid/loclx-tunnels.yaml &
elif [ -n "${LOCLX_RESERVED_ENDPOINT}" ]; then
echo "[localxpose] Starting reserved TCP tunnel to localhost:${SMTP_PORT} via ${LOCLX_RESERVED_ENDPOINT}..."
loclx tunnel tcp --to "localhost:${SMTP_PORT}" --reserved-endpoint "${LOCLX_RESERVED_ENDPOINT}" &
else
echo "[localxpose] Starting ephemeral TCP tunnel to localhost:${SMTP_PORT}..."
loclx tunnel tcp --to "localhost:${SMTP_PORT}" &
fi

echo "[localxpose] Tunnel started (PID: $!)."
fi

exec dotnet /app/Resgrid.Audio.Relay.Console.dll
Loading