Load Flutter runtime config from Vault
Build and deploy Flutter Web / build (push) Successful in 6m25s
Build and deploy Flutter Web / build (push) Successful in 6m25s
This commit is contained in:
@@ -16,33 +16,17 @@ jobs:
|
|||||||
SERVICE_NAME: flutter
|
SERVICE_NAME: flutter
|
||||||
IMAGE_SHA: gitea.dsrptlab.com/mapflow/flutter:${{ github.sha }}
|
IMAGE_SHA: gitea.dsrptlab.com/mapflow/flutter:${{ github.sha }}
|
||||||
IMAGE_LATEST: gitea.dsrptlab.com/mapflow/flutter:latest
|
IMAGE_LATEST: gitea.dsrptlab.com/mapflow/flutter:latest
|
||||||
VAULT_HOST: mars
|
|
||||||
VAULT_SERVICE: mapflow-vault-zy5kwf
|
|
||||||
VAULT_PROJECT_PATH: secret/projects/mapflow/prod
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
mapbox_token_file="$(mktemp)"
|
|
||||||
trap 'rm -f "$mapbox_token_file"' EXIT
|
|
||||||
ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes "root@$VAULT_HOST" \
|
|
||||||
"set -euo pipefail
|
|
||||||
vault_service='$VAULT_SERVICE'
|
|
||||||
vault_project_path='$VAULT_PROJECT_PATH'
|
|
||||||
c=\$(docker ps --format '{{.Names}}' | grep \"^\$vault_service\" | head -1)
|
|
||||||
token=\$(docker service inspect \"\$vault_service\" --format '{{json .Spec.TaskTemplate.ContainerSpec.Env}}' | jq -r '.[] | select(startswith(\"VAULT_ROOT_TOKEN=\")) | sub(\"^VAULT_ROOT_TOKEN=\"; \"\")')
|
|
||||||
docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN=\"\$token\" \"\$c\" vault kv get -field=MAPBOX_ACCESS_TOKEN \"\$vault_project_path\"" \
|
|
||||||
> "$mapbox_token_file"
|
|
||||||
[ -s "$mapbox_token_file" ]
|
|
||||||
for attempt in 1 2 3; do
|
for attempt in 1 2 3; do
|
||||||
if docker build \
|
if docker build \
|
||||||
--provenance=false \
|
--provenance=false \
|
||||||
--tag "$IMAGE_SHA" \
|
--tag "$IMAGE_SHA" \
|
||||||
--tag "$IMAGE_LATEST" \
|
--tag "$IMAGE_LATEST" \
|
||||||
--secret id=mapbox_access_token,src="$mapbox_token_file" \
|
|
||||||
--build-arg MAPBOX_STYLE="mapbox/standard" \
|
|
||||||
--build-arg TELEGRAM_BOT_USERNAME="carfteebot" \
|
--build-arg TELEGRAM_BOT_USERNAME="carfteebot" \
|
||||||
--build-arg BUILD_VERSION="${GITHUB_SHA}" \
|
--build-arg BUILD_VERSION="${GITHUB_SHA}" \
|
||||||
. &&
|
. &&
|
||||||
|
|||||||
+4
-7
@@ -1,18 +1,12 @@
|
|||||||
# syntax=docker/dockerfile:1.7
|
|
||||||
FROM ghcr.io/cirruslabs/flutter:stable AS build
|
FROM ghcr.io/cirruslabs/flutter:stable AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG MAPBOX_STYLE="mapbox/standard"
|
|
||||||
ARG TELEGRAM_BOT_USERNAME="carfteebot"
|
ARG TELEGRAM_BOT_USERNAME="carfteebot"
|
||||||
ARG BUILD_VERSION="local"
|
ARG BUILD_VERSION="local"
|
||||||
COPY pubspec.* ./
|
COPY pubspec.* ./
|
||||||
RUN flutter pub get
|
RUN flutter pub get
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN --mount=type=secret,id=mapbox_access_token,required=true \
|
RUN rm -rf build/web && \
|
||||||
MAPBOX_ACCESS_TOKEN="$(cat /run/secrets/mapbox_access_token)" && \
|
|
||||||
rm -rf build/web && \
|
|
||||||
flutter build web --release --base-href=/ -o build/web \
|
flutter build web --release --base-href=/ -o build/web \
|
||||||
--dart-define=MAPBOX_ACCESS_TOKEN="$MAPBOX_ACCESS_TOKEN" \
|
|
||||||
--dart-define=MAPBOX_STYLE="$MAPBOX_STYLE" \
|
|
||||||
--dart-define=TELEGRAM_BOT_USERNAME="$TELEGRAM_BOT_USERNAME" && \
|
--dart-define=TELEGRAM_BOT_USERNAME="$TELEGRAM_BOT_USERNAME" && \
|
||||||
cp web/index.prod.html build/web/index.html && \
|
cp web/index.prod.html build/web/index.html && \
|
||||||
rm -f build/web/index.prod.html && \
|
rm -f build/web/index.prod.html && \
|
||||||
@@ -22,6 +16,9 @@ RUN --mount=type=secret,id=mapbox_access_token,required=true \
|
|||||||
--version="$BUILD_VERSION"
|
--version="$BUILD_VERSION"
|
||||||
|
|
||||||
FROM nginx:1.27-alpine
|
FROM nginx:1.27-alpine
|
||||||
|
RUN apk add --no-cache curl jq
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY scripts/40-mapflow-runtime-config.sh /docker-entrypoint.d/40-mapflow-runtime-config.sh
|
||||||
COPY --from=build /app/build/web /usr/share/nginx/html
|
COPY --from=build /app/build/web /usr/share/nginx/html
|
||||||
|
RUN chmod +x /docker-entrypoint.d/40-mapflow-runtime-config.sh
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|||||||
@@ -15,16 +15,17 @@ import '../../../app/router/app_router.dart';
|
|||||||
import '../../../app/theme/mapflow_theme.dart';
|
import '../../../app/theme/mapflow_theme.dart';
|
||||||
import '../../../shared/auth/telegram_login_button.dart';
|
import '../../../shared/auth/telegram_login_button.dart';
|
||||||
import '../../../shared/auth/telegram_session.dart' as telegram_session;
|
import '../../../shared/auth/telegram_session.dart' as telegram_session;
|
||||||
|
import '../../../shared/runtime_config/runtime_config.dart' as runtime_config;
|
||||||
import '../application/place_cubit.dart';
|
import '../application/place_cubit.dart';
|
||||||
import '../data/auth_repository.dart';
|
import '../data/auth_repository.dart';
|
||||||
import '../data/places_repository.dart';
|
import '../data/places_repository.dart';
|
||||||
import '../domain/place_models.dart';
|
import '../domain/place_models.dart';
|
||||||
import 'widgets/place_photo_card.dart';
|
import 'widgets/place_photo_card.dart';
|
||||||
|
|
||||||
const _mapboxStyle = String.fromEnvironment(
|
String get _mapboxStyle {
|
||||||
'MAPBOX_STYLE',
|
final style = runtime_config.mapboxStyle();
|
||||||
defaultValue: 'mapbox/standard',
|
return style.isEmpty ? 'mapbox/standard' : style;
|
||||||
);
|
}
|
||||||
|
|
||||||
class MapflowShell extends StatelessWidget {
|
class MapflowShell extends StatelessWidget {
|
||||||
const MapflowShell({super.key});
|
const MapflowShell({super.key});
|
||||||
|
|||||||
+4
-4
@@ -4,13 +4,13 @@ import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart' as mbx;
|
|||||||
|
|
||||||
import 'app/app.dart';
|
import 'app/app.dart';
|
||||||
import 'shared/auth/telegram_session.dart' as telegram_session;
|
import 'shared/auth/telegram_session.dart' as telegram_session;
|
||||||
|
import 'shared/runtime_config/runtime_config.dart' as runtime_config;
|
||||||
const _mapboxAccessToken = String.fromEnvironment('MAPBOX_ACCESS_TOKEN');
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
if (_mapboxAccessToken.isNotEmpty) {
|
final mapboxAccessToken = runtime_config.mapboxAccessToken();
|
||||||
mbx.MapboxOptions.setAccessToken(_mapboxAccessToken);
|
if (mapboxAccessToken.isNotEmpty) {
|
||||||
|
mbx.MapboxOptions.setAccessToken(mapboxAccessToken);
|
||||||
}
|
}
|
||||||
usePathUrlStrategy();
|
usePathUrlStrategy();
|
||||||
telegram_session.configureTelegramWebApp();
|
telegram_session.configureTelegramWebApp();
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export 'runtime_config_stub.dart'
|
||||||
|
if (dart.library.js_interop) 'runtime_config_web.dart';
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
String mapboxAccessToken() => '';
|
||||||
|
|
||||||
|
String mapboxStyle() => '';
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import 'dart:js_interop';
|
||||||
|
|
||||||
|
@JS('__MAPFLOW_CONFIG__')
|
||||||
|
external _MapflowConfig? get _config;
|
||||||
|
|
||||||
|
extension type _MapflowConfig(JSObject _) implements JSObject {
|
||||||
|
external JSString? get mapboxAccessToken;
|
||||||
|
external JSString? get mapboxStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
String mapboxAccessToken() => _config?.mapboxAccessToken?.toDart ?? '';
|
||||||
|
|
||||||
|
String mapboxStyle() => _config?.mapboxStyle?.toDart ?? '';
|
||||||
+1
-1
@@ -28,7 +28,7 @@ server {
|
|||||||
try_files /index.html =404;
|
try_files /index.html =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~* ^/(bootstrap\.js|sw\.js)$ {
|
location ~* ^/(bootstrap\.js|sw\.js|config\.js)$ {
|
||||||
add_header Cache-Control "no-cache" always;
|
add_header Cache-Control "no-cache" always;
|
||||||
add_header Pragma "no-cache" always;
|
add_header Pragma "no-cache" always;
|
||||||
add_header Expires "0" always;
|
add_header Expires "0" always;
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
config_path="/usr/share/nginx/html/config.js"
|
||||||
|
|
||||||
|
require_env() {
|
||||||
|
name="$1"
|
||||||
|
eval "value=\${$name:-}"
|
||||||
|
if [ -z "$value" ]; then
|
||||||
|
echo "$name is required when VAULT_ENABLED=true." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
read_vault_path() {
|
||||||
|
path="$1"
|
||||||
|
curl -fsS \
|
||||||
|
-H "X-Vault-Token: $VAULT_TOKEN" \
|
||||||
|
"$VAULT_ADDR/v1/$VAULT_KV_MOUNT/data/$path" |
|
||||||
|
jq -e '.data.data'
|
||||||
|
}
|
||||||
|
|
||||||
|
json_string() {
|
||||||
|
jq -Rn --arg value "$1" '$value'
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "${VAULT_ENABLED:-}" = "true" ]; then
|
||||||
|
require_env VAULT_ADDR
|
||||||
|
require_env VAULT_TOKEN
|
||||||
|
require_env VAULT_KV_MOUNT
|
||||||
|
require_env VAULT_SHARED_PATH
|
||||||
|
require_env VAULT_PROJECT_PATH
|
||||||
|
|
||||||
|
shared_env="$(read_vault_path "$VAULT_SHARED_PATH")"
|
||||||
|
project_env="$(read_vault_path "$VAULT_PROJECT_PATH")"
|
||||||
|
runtime_env="$(jq -n --argjson shared "$shared_env" --argjson project "$project_env" '$shared * $project')"
|
||||||
|
|
||||||
|
MAPBOX_ACCESS_TOKEN="$(printf '%s' "$runtime_env" | jq -er '.MAPBOX_ACCESS_TOKEN')"
|
||||||
|
MAPBOX_STYLE="$(printf '%s' "$runtime_env" | jq -r '.MAPBOX_STYLE // "mapbox/standard"')"
|
||||||
|
else
|
||||||
|
MAPBOX_ACCESS_TOKEN="${MAPBOX_ACCESS_TOKEN:-}"
|
||||||
|
MAPBOX_STYLE="${MAPBOX_STYLE:-mapbox/standard}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat >"$config_path" <<EOF
|
||||||
|
window.__MAPFLOW_CONFIG__ = {
|
||||||
|
mapboxAccessToken: $(json_string "$MAPBOX_ACCESS_TOKEN"),
|
||||||
|
mapboxStyle: $(json_string "$MAPBOX_STYLE")
|
||||||
|
};
|
||||||
|
EOF
|
||||||
@@ -87,6 +87,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script src="config.js"></script>
|
||||||
<script
|
<script
|
||||||
defer
|
defer
|
||||||
data-sw-bootstrap
|
data-sw-bootstrap
|
||||||
|
|||||||
Reference in New Issue
Block a user