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
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# API Configuration
VITE_API_URL=http://localhost:3000
VITE_API_URL=http://localhost:3001

# For local development with MinIO:
VITE_METADATA_URL=http://localhost:9000/xlsforms/metadata.json
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/release_chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: 🚀 Release Chart

on:
release:
types: [published]

permissions:
contents: read
packages: write

jobs:
publish:
uses: hotosm/gh-workflows/.github/workflows/just.yml@3.3.2
with:
environment: "test"
command: "chart publish"
secrets: inherit
12 changes: 10 additions & 2 deletions .github/workflows/release_image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,17 @@ permissions:
packages: write

jobs:
container-img:
backend:
uses: hotosm/gh-workflows/.github/workflows/image_build.yml@2.0.5
needs: prep
with:
image_name: ghcr.io/${{ github.repository }}/backend
context: backend
build_target: release

frontend:
uses: hotosm/gh-workflows/.github/workflows/image_build.yml@2.0.5
needs: prep
with:
image_name: ghcr.io/${{ github.repository }}/frontend
build_target: release
image_name: ghcr.io/${{ github.repository }}/server
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,8 @@ backend/.env
*.sw?

# Backend
backend/server
backend/backend
backend/tmp

# Helm chart
xlsform-builder-*.tgz
4 changes: 2 additions & 2 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: 2

builds:
- binary: server
- binary: backend
main: main.go
env:
- CGO_ENABLED=0
Expand All @@ -18,7 +18,7 @@ builds:
- -s -w -X main.version={{.Tag}} -X main.buildTime={{.Date}}

archives:
- name_template: "server_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
- name_template: "backend_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
wrap_in_directory: false
format: tar.gz
# use zip for windows archives
Expand Down
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ coverage
*.min.js
*.yaml
yarn.lock
package-lock.json
pnpm-lock.yaml
2 changes: 1 addition & 1 deletion CNAME
Original file line number Diff line number Diff line change
@@ -1 +1 @@
xlsform-builder.fmtm.hotosm.org
xlsforms.field.hotosm.org
20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM node:20-alpine AS build
WORKDIR /app

COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm@10.13.1 && pnpm install --frozen-lockfile

COPY . .
ARG VITE_API_URL
ARG VITE_METADATA_URL
ENV VITE_API_URL=$VITE_API_URL \
VITE_METADATA_URL=$VITE_METADATA_URL
RUN pnpm run build



FROM nginx:1.27-alpine AS release
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
121 changes: 121 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
set dotenv-load

mod chart 'tasks/chart'

# List available commands
[private]
default:
just help

# List available commands
help:
just --justfile {{justfile()}} --list

# Install curl if missing
[private]
_install-curl:
#!/usr/bin/env bash
set -e

if ! command -v curl &> /dev/null; then
echo "📦 Installing curl..."
if command -v apt-get &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y curl
elif command -v yum &> /dev/null; then
sudo yum install -y curl
elif command -v apk &> /dev/null; then
sudo apk add --no-cache curl
else
echo "❌ Error: curl is not installed and no package manager found"
echo " Please install curl manually"
exit 1
fi
echo "✓ curl installed"
fi

# Install Helm if missing
[private]
_install-helm:
#!/usr/bin/env bash
set -e

if command -v helm &> /dev/null; then
exit 0
fi

echo "📦 Installing Helm..."

# Only Linux / amd64 automated install for now; otherwise instruct user
UNAME_S="$(uname -s || echo unknown)"
UNAME_M="$(uname -m || echo unknown)"

if [ "$UNAME_S" != "Linux" ] || { [ "$UNAME_M" != "x86_64" ] && [ "$UNAME_M" != "amd64" ]; }; then
echo "❌ Automatic Helm install only supported on Linux amd64."
echo " Please install Helm manually: https://helm.sh/docs/intro/install/"
exit 1
fi

TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT

# Get latest Helm release tag
HELM_TAG="$(curl -sSL https://api.github.com/repos/helm/helm/releases/latest | grep -oE '\"tag_name\":\s*\"v[0-9.]+\"' | head -1 | sed -E 's/\"tag_name\":\s*\"(v[0-9.]+)\"/\1/')"
if [ -z "$HELM_TAG" ]; then
echo "❌ Failed to determine latest Helm version."
exit 1
fi

ARCHIVE="helm-${HELM_TAG}-linux-amd64.tar.gz"
URL="https://get.helm.sh/${ARCHIVE}"

echo "⬇️ Downloading ${URL}..."
curl -sSL "$URL" -o "$TMP_DIR/helm.tar.gz"
tar -xzf "$TMP_DIR/helm.tar.gz" -C "$TMP_DIR"

if sudo mv "$TMP_DIR/linux-amd64/helm" /usr/local/bin/helm 2>/dev/null; then
chmod +x /usr/local/bin/helm
echo "✓ Helm installed to /usr/local/bin/helm"
else
mkdir -p "$HOME/.local/bin"
mv "$TMP_DIR/linux-amd64/helm" "$HOME/.local/bin/helm"
chmod +x "$HOME/.local/bin/helm"
export PATH="$HOME/.local/bin:$PATH"
echo "✓ Helm installed to ~/.local/bin/helm"
fi

if ! command -v helm &> /dev/null; then
echo "❌ Error: Failed to install Helm"
exit 1
fi

# Start compose services (backend, frontend, minio s3)
start:
#!/usr/bin/env bash
set -e
docker compose up -d

# Stop compose services
stop:
docker compose down --remove-orphans

# Start all services for development
dev:
just start

# Echo to terminal with blue colour
[no-cd]
_echo-blue text:
#!/usr/bin/env sh
printf "\033[0;34m%s\033[0m\n" "{{ text }}"

# Echo to terminal with yellow colour
[no-cd]
_echo-yellow text:
#!/usr/bin/env sh
printf "\033[0;33m%s\033[0m\n" "{{ text }}"

# Echo to terminal with red colour
[no-cd]
_echo-red text:
#!/usr/bin/env sh
printf "\033[0;41m%s\033[0m\n" "{{ text }}"
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ A repo to house:

Next steps:

- [ ] Improve styling / use web-awesome components.
- [ ] Generate IAM creds for bucket `xlsforms` and set relevant upload policy.
- [ ] Attach small backend for creating pre-signed upload URLs (cross-project).
- [x] Improve styling / use web-awesome components.
- [x] Generate IAM creds for bucket `xlsforms` and set relevant upload policy.
- [x] Attach small backend for creating pre-signed upload URLs (cross-project).
- [ ] Allow users to upload their XLSForm examples, with attached metadata.
- [ ] Start the XLSForm editor web component (build around getodk/webforms).
2 changes: 1 addition & 1 deletion backend/.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*.md
.git
.gitignore
server
backend
tmp

# Let Docker generate go.sum during build
Expand Down
2 changes: 1 addition & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Server Configuration
PORT=3000
PORT=3001

# AWS/S3 Configuration
AWS_REGION=us-east-1
Expand Down
36 changes: 5 additions & 31 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,38 +1,12 @@
FROM golang:1.21 AS base


# Build statically compiled binary
FROM base AS build
FROM golang:1.21 AS build
WORKDIR /code
COPY go.mod ./
RUN go mod download
COPY . .
RUN go mod tidy
RUN CGO_ENABLED=0 GOOS=linux go build -o /code/server .


# Add a non-root user to passwd file
FROM base AS useradd
RUN groupadd -g 1000 nonroot
RUN useradd -u 1000 nonroot -g 1000


# Add ca-certs required for calling remote signed APIs
FROM base AS certs
RUN apt-get update --quiet \
&& DEBIAN_FRONTEND=noninteractive \
apt-get install -y --quiet --no-install-recommends \
"ca-certificates" \
&& rm -rf /var/lib/apt/lists/* \
&& update-ca-certificates

RUN CGO_ENABLED=0 GOOS=linux go build -o /code/backend .

# Deploy the application binary into scratch image
FROM scratch AS release
FROM gcr.io/distroless/static-debian13:nonroot
WORKDIR /code
COPY --from=build /code/server /code/server
COPY --from=useradd /etc/group /etc/group
COPY --from=useradd /etc/passwd /etc/passwd
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
USER nonroot:nonroot
ENTRYPOINT ["/code/server"]
COPY --from=build /code/backend /code/backend
ENTRYPOINT ["/code/backend"]
4 changes: 2 additions & 2 deletions backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func main() {
s3Endpoint = os.Getenv("S3_ENDPOINT")
s3ExternalEndpoint = getEnv("S3_EXTERNAL_ENDPOINT", s3Endpoint)
usePathStyle = getEnv("USE_PATH_STYLE", "false") == "true"
port := getEnv("PORT", "3000")
port := getEnv("PORT", "3001")

if err := initS3Client(); err != nil {
log.Fatalf("Failed to initialize S3 client: %v", err)
Expand Down Expand Up @@ -83,7 +83,7 @@ func main() {
if s3Endpoint != "" {
log.Printf("Using S3-compatible storage at: %s", s3Endpoint)
if s3ExternalEndpoint != s3Endpoint {
log.Printf("External endpoint: %s", s3ExternalEndpoint)
log.Printf("S3 external endpoint: %s", s3ExternalEndpoint)
}
} else {
log.Printf("Using AWS S3")
Expand Down
8 changes: 8 additions & 0 deletions chart/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v2
name: xlsform-builder
description: Share and build XLSForms
type: application
version: 0.1.0
appVersion: "1.0.0"


24 changes: 24 additions & 0 deletions chart/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{{- define "xlsform-builder.name" -}}
{{- default .Chart.Name .Values.nameOverride -}}
{{- end -}}

{{- define "xlsform-builder.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{- define "xlsform-builder.backendFullname" -}}
{{- printf "%s-backend" (include "xlsform-builder.fullname" .) -}}
{{- end -}}

{{- define "xlsform-builder.frontendFullname" -}}
{{- printf "%s-frontend" (include "xlsform-builder.fullname" .) -}}
{{- end -}}
42 changes: 42 additions & 0 deletions chart/templates/backend-deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "xlsform-builder.backendFullname" . }}
labels:
app.kubernetes.io/name: {{ include "xlsform-builder.name" . }}
app.kubernetes.io/component: backend
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount.backend }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "xlsform-builder.name" . }}
app.kubernetes.io/component: backend
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "xlsform-builder.name" . }}
app.kubernetes.io/component: backend
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
containers:
- name: backend
image: "{{ .Values.image.backend.repository }}:{{ .Values.image.backend.tag }}"
imagePullPolicy: {{ .Values.image.backend.pullPolicy }}
env:
- name: PORT
value: "3001"
ports:
- name: http
containerPort: 3001
readinessProbe:
httpGet:
path: /
port: http
livenessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources.backend | nindent 12 }}
Loading