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
|
||||
IMAGE_SHA: gitea.dsrptlab.com/mapflow/flutter:${{ github.sha }}
|
||||
IMAGE_LATEST: gitea.dsrptlab.com/mapflow/flutter:latest
|
||||
VAULT_HOST: mars
|
||||
VAULT_SERVICE: mapflow-vault-zy5kwf
|
||||
VAULT_PROJECT_PATH: secret/projects/mapflow/prod
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build and push image
|
||||
run: |
|
||||
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
|
||||
if docker build \
|
||||
--provenance=false \
|
||||
--tag "$IMAGE_SHA" \
|
||||
--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 BUILD_VERSION="${GITHUB_SHA}" \
|
||||
. &&
|
||||
|
||||
+4
-7
@@ -1,18 +1,12 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
FROM ghcr.io/cirruslabs/flutter:stable AS build
|
||||
WORKDIR /app
|
||||
ARG MAPBOX_STYLE="mapbox/standard"
|
||||
ARG TELEGRAM_BOT_USERNAME="carfteebot"
|
||||
ARG BUILD_VERSION="local"
|
||||
COPY pubspec.* ./
|
||||
RUN flutter pub get
|
||||
COPY . .
|
||||
RUN --mount=type=secret,id=mapbox_access_token,required=true \
|
||||
MAPBOX_ACCESS_TOKEN="$(cat /run/secrets/mapbox_access_token)" && \
|
||||
rm -rf build/web && \
|
||||
RUN rm -rf 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" && \
|
||||
cp web/index.prod.html build/web/index.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"
|
||||
|
||||
FROM nginx:1.27-alpine
|
||||
RUN apk add --no-cache curl jq
|
||||
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
|
||||
RUN chmod +x /docker-entrypoint.d/40-mapflow-runtime-config.sh
|
||||
EXPOSE 80
|
||||
|
||||
@@ -15,16 +15,17 @@ import '../../../app/router/app_router.dart';
|
||||
import '../../../app/theme/mapflow_theme.dart';
|
||||
import '../../../shared/auth/telegram_login_button.dart';
|
||||
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 '../data/auth_repository.dart';
|
||||
import '../data/places_repository.dart';
|
||||
import '../domain/place_models.dart';
|
||||
import 'widgets/place_photo_card.dart';
|
||||
|
||||
const _mapboxStyle = String.fromEnvironment(
|
||||
'MAPBOX_STYLE',
|
||||
defaultValue: 'mapbox/standard',
|
||||
);
|
||||
String get _mapboxStyle {
|
||||
final style = runtime_config.mapboxStyle();
|
||||
return style.isEmpty ? 'mapbox/standard' : style;
|
||||
}
|
||||
|
||||
class MapflowShell extends StatelessWidget {
|
||||
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 'shared/auth/telegram_session.dart' as telegram_session;
|
||||
|
||||
const _mapboxAccessToken = String.fromEnvironment('MAPBOX_ACCESS_TOKEN');
|
||||
import 'shared/runtime_config/runtime_config.dart' as runtime_config;
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
if (_mapboxAccessToken.isNotEmpty) {
|
||||
mbx.MapboxOptions.setAccessToken(_mapboxAccessToken);
|
||||
final mapboxAccessToken = runtime_config.mapboxAccessToken();
|
||||
if (mapboxAccessToken.isNotEmpty) {
|
||||
mbx.MapboxOptions.setAccessToken(mapboxAccessToken);
|
||||
}
|
||||
usePathUrlStrategy();
|
||||
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;
|
||||
}
|
||||
|
||||
location ~* ^/(bootstrap\.js|sw\.js)$ {
|
||||
location ~* ^/(bootstrap\.js|sw\.js|config\.js)$ {
|
||||
add_header Cache-Control "no-cache" always;
|
||||
add_header Pragma "no-cache" 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 src="config.js"></script>
|
||||
<script
|
||||
defer
|
||||
data-sw-bootstrap
|
||||
|
||||
Reference in New Issue
Block a user