diff --git a/.github/workflows/build-fleetctl-pkg.yml b/.github/workflows/build-fleetctl-pkg.yml new file mode 100644 index 0000000000..32afb440cc --- /dev/null +++ b/.github/workflows/build-fleetctl-pkg.yml @@ -0,0 +1,224 @@ +name: Build fleetctl macOS package + +# This workflow builds a signed and notarized macOS .pkg installer for fleetctl +# for manual testing purposes only. +# +# NOTE: For production releases, the goreleaser workflow (.github/workflows/goreleaser-fleet.yaml) +# handles building and uploading the fleetctl package automatically when a fleet-* tag is pushed. +# This workflow is kept for manual testing only. +# +# TESTING: +# To test the package build process: +# 1. Go to Actions → Build fleetctl macOS package → Run workflow +# 2. Select your branch (e.g., main or feature branch) +# 3. Test mode defaults to true (recommended) - package will be built and uploaded as an artifact +# 4. To test release upload, set test_mode to false (use with caution) + +on: + workflow_dispatch: # Manual trigger for testing only + inputs: + test_mode: + description: "Test mode - will skip release upload if enabled (recommended: true)" + type: boolean + default: true + +# This allows a subsequently queued workflow run to interrupt previous runs +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + # fail-fast using bash -eo pipefail. See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference + shell: bash + +permissions: + contents: write # Needed to upload release assets + id-token: write # Needed for attestations + attestations: write # Needed to create build provenance attestations + +jobs: + build-fleetctl-pkg: + runs-on: macos-latest + timeout-minutes: 120 + steps: + - name: Harden Runner + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + with: + fetch-depth: 0 + + - name: Extract version + id: extract_version + run: | + REF_NAME="${{ github.ref_name }}" + + # Check if running from a tag (for testing tag-based workflows) + if [[ "${{ github.ref }}" == refs/tags/* ]] && [[ "$REF_NAME" == fleet-* ]]; then + # Extract version from tag (e.g., fleet-v4.10.0 -> 4.10.0) + VERSION="${REF_NAME#fleet-v}" + TAG_NAME="$REF_NAME" + else + # For branch-based manual triggers, use a test version + VERSION="test-$(date +%Y%m%d-%H%M%S)" + TAG_NAME="fleet-${VERSION}" + fi + + # Determine test mode based on input (defaults to true for safety) + if [ "${{ github.event.inputs.test_mode }}" = "false" ]; then + IS_TEST_MODE="false" + else + # Default to test mode (true) for safety + IS_TEST_MODE="true" + fi + + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + echo "is_test_mode=$IS_TEST_MODE" >> $GITHUB_OUTPUT + echo "Fleet version: $VERSION" + if [ "$IS_TEST_MODE" = "true" ]; then + echo "⚠️ TEST MODE: Package will be built but NOT uploaded to release" + else + echo "⚠️ PRODUCTION MODE: Package will be uploaded to release $TAG_NAME" + fi + + - name: Set up Node + uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 + with: + node-version-file: package.json + check-latest: true + + - name: Set up Go + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version-file: "go.mod" + + - name: Install dependencies + run: make deps + + - name: Generate code + run: make generate-go + + - name: Build fleetctl binary + run: | + VERSION="${{ steps.extract_version.outputs.version }}" + + # Extract branch or tag name from ref + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + BRANCH_NAME="${{ steps.extract_version.outputs.tag_name }}" + else + BRANCH_NAME="${GITHUB_REF#refs/heads/}" + fi + + # Set up version ldflags + LDFLAGS="-X github.com/fleetdm/fleet/v4/server/version.appName=fleetctl \ + -X github.com/fleetdm/fleet/v4/server/version.version=${VERSION} \ + -X github.com/fleetdm/fleet/v4/server/version.branch=${BRANCH_NAME} \ + -X github.com/fleetdm/fleet/v4/server/version.revision=${GITHUB_SHA} \ + -X github.com/fleetdm/fleet/v4/server/version.buildDate=$(date -u +%Y-%m-%d) \ + -X github.com/fleetdm/fleet/v4/server/version.buildUser=github-actions" + + # Build for amd64 + echo "Building fleetctl for darwin/amd64..." + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build \ + -trimpath \ + -ldflags "$LDFLAGS" \ + -o fleetctl_amd64 \ + ./cmd/fleetctl + + # Build for arm64 + echo "Building fleetctl for darwin/arm64..." + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build \ + -trimpath \ + -ldflags "$LDFLAGS" \ + -o fleetctl_arm64 \ + ./cmd/fleetctl + + # Create universal binary using lipo + echo "Creating universal binary..." + lipo -create fleetctl_amd64 fleetctl_arm64 -output fleetctl + chmod +x fleetctl + + # Clean up architecture-specific binaries + rm -f fleetctl_amd64 fleetctl_arm64 + + # Verify we have the binary + if [ ! -f fleetctl ]; then + echo "Error: fleetctl binary not found after build" + exit 1 + fi + + # Verify binary works and show version + ./fleetctl --version + + - name: Import application signing keys (for binary) + id: import_app_cert + env: + APPLE_APPLICATION_CERTIFICATE: ${{ secrets.APPLE_APPLICATION_CERTIFICATE }} + APPLE_APPLICATION_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_APPLICATION_CERTIFICATE_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + echo "$APPLE_APPLICATION_CERTIFICATE" | base64 --decode > app_certificate.p12 + security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain || true + security default-keychain -s build.keychain + security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain + security import app_certificate.p12 -k build.keychain -P "$APPLE_APPLICATION_CERTIFICATE_PASSWORD" -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain + security find-identity -vv + rm app_certificate.p12 + + # Extract the signing identity SHA1 from the imported certificate + IDENTITY=$(security find-identity -v build.keychain | grep "Developer ID Application" | head -1 | awk '{print $2}') + if [[ -z "$IDENTITY" ]]; then + echo "Error: No Developer ID Application identity found in keychain" + exit 1 + fi + echo "codesign_identity=$IDENTITY" >> "$GITHUB_OUTPUT" + + - name: Sign fleetctl binary + run: | + # Sign the binary with Developer ID Application certificate + codesign --sign "${{ steps.import_app_cert.outputs.codesign_identity }}" \ + --options runtime \ + --timestamp \ + --force \ + --verbose \ + fleetctl + + # Verify the signature + codesign --verify --verbose fleetctl + codesign --display --verbose fleetctl + + - name: Build, sign, and notarize .pkg + env: + APPLE_INSTALLER_CERTIFICATE: ${{ secrets.APPLE_INSTALLER_CERTIFICATE }} + APPLE_INSTALLER_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_INSTALLER_CERTIFICATE_PASSWORD }} + APPLE_USERNAME: ${{ secrets.APPLE_USERNAME }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_REF: ${{ github.ref }} + SKIP_UPLOAD: ${{ steps.extract_version.outputs.is_test_mode }} + run: | + chmod +x tools/build-fleetctl-pkg/main.sh + ./tools/build-fleetctl-pkg/main.sh ./fleetctl "${{ steps.extract_version.outputs.version }}" + + - name: Upload package artifact (test mode only) + if: steps.extract_version.outputs.is_test_mode == 'true' + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: fleetctl-test-package + path: dist/fleetctl_v${{ steps.extract_version.outputs.version }}_mac.pkg + retention-days: 7 + + - name: Attest package + uses: actions/attest-build-provenance@619dbb2e03e0189af0c55118e7d3c5e129e99726 # v2.0 + with: + subject-path: dist/fleetctl_v${{ steps.extract_version.outputs.version }}_mac.pkg + push-to-registry: false diff --git a/.github/workflows/goreleaser-fleet.yaml b/.github/workflows/goreleaser-fleet.yaml index 00c2e36c12..4b624ac892 100644 --- a/.github/workflows/goreleaser-fleet.yaml +++ b/.github/workflows/goreleaser-fleet.yaml @@ -23,6 +23,7 @@ jobs: runs-on: ubuntu-22.04-4-cores environment: Docker Hub outputs: + binary_uploaded: ${{ steps.check_binary.outputs.exists }} windows_msi_arches: ${{ steps.check_windows_binaries.outputs.arches }} permissions: contents: write @@ -159,6 +160,24 @@ jobs: docker push quay.io/fleetdm/fleet:${TAG} done + - name: Check if signed binary exists + id: check_binary + continue-on-error: true + run: | + if [[ -f "dist/fleetctl_darwin_all/fleetctl" ]]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Upload signed fleetctl binary for .pkg creation + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + if: steps.check_binary.outputs.exists == 'true' + with: + name: fleetctl-signed-macos + path: dist/fleetctl_darwin_all/fleetctl + retention-days: 1 + - name: Check if Windows fleetctl binaries exist id: check_windows_binaries continue-on-error: true @@ -193,6 +212,68 @@ jobs: path: ${{ steps.check_windows_binaries.outputs.windows_arm64_path }} retention-days: 1 + build-fleetctl-pkg: + runs-on: macos-latest + needs: goreleaser + if: needs.goreleaser.outputs.binary_uploaded == 'true' + timeout-minutes: 120 + permissions: + contents: write + id-token: write + attestations: write + steps: + - name: Harden Runner + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + with: + fetch-depth: 0 + + - name: Download signed fleetctl binary + uses: actions/download-artifact@5e317d1137c093c30a278992a91e9db4e3c1f039 # v4.1.8 + with: + name: fleetctl-signed-macos + path: dist/fleetctl_darwin_all + + - name: Verify binary exists + run: | + if [[ ! -f "dist/fleetctl_darwin_all/fleetctl" ]]; then + echo "Error: Signed fleetctl binary not found" + exit 1 + fi + # The download-artifact does not preserve the execute bit, + # so set it here so execution doesn't fail. + chmod +x dist/fleetctl_darwin_all/fleetctl + echo "Binary found, verifying signature..." + codesign --verify --verbose dist/fleetctl_darwin_all/fleetctl || exit 1 + ./dist/fleetctl_darwin_all/fleetctl --version + + - name: Build and upload macOS .pkg + env: + APPLE_INSTALLER_CERTIFICATE: ${{ secrets.APPLE_INSTALLER_CERTIFICATE }} + APPLE_INSTALLER_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_INSTALLER_CERTIFICATE_PASSWORD }} + APPLE_USERNAME: ${{ secrets.APPLE_USERNAME }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_REF: ${{ github.ref }} + run: | + # Extract version from tag (e.g., fleet-v4.10.0 -> 4.10.0) + VERSION="${GITHUB_REF#refs/tags/fleet-v}" + chmod +x tools/build-fleetctl-pkg/main.sh + ./tools/build-fleetctl-pkg/main.sh dist/fleetctl_darwin_all/fleetctl "$VERSION" + + - name: Attest package + continue-on-error: true + uses: actions/attest-build-provenance@619dbb2e03e0189af0c55118e7d3c5e129e99726 # v2.0 + with: + subject-path: dist/fleetctl_v*_mac.pkg + build-fleetctl-msi: runs-on: windows-2022 needs: goreleaser @@ -290,14 +371,16 @@ jobs: uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: fleetctl-msi-${{ matrix.arch }} - path: dist/fleetctl_v${{ steps.version.outputs.version }}_windows_${{ matrix.arch }}.msi + path: dist/fleetctl_v${{ steps.version.outputs.version }}_windows_${{ + matrix.arch }}.msi retention-days: 1 - name: Attest MSI continue-on-error: true uses: actions/attest-build-provenance@619dbb2e03e0189af0c55118e7d3c5e129e99726 # v2.0 with: - subject-path: dist/fleetctl_v${{ steps.version.outputs.version }}_windows_${{ matrix.arch }}.msi + subject-path: dist/fleetctl_v${{ steps.version.outputs.version }}_windows_${{ + matrix.arch }}.msi upload-fleetctl-msi: needs: build-fleetctl-msi diff --git a/changes/35089-fleetctl-released-as-pkg b/changes/35089-fleetctl-released-as-pkg new file mode 100644 index 0000000000..d6078144c5 --- /dev/null +++ b/changes/35089-fleetctl-released-as-pkg @@ -0,0 +1 @@ +- `fleetctl` is now also released as a `pkg` for macOS diff --git a/tools/build-fleetctl-pkg/main.sh b/tools/build-fleetctl-pkg/main.sh new file mode 100755 index 0000000000..718bc20acd --- /dev/null +++ b/tools/build-fleetctl-pkg/main.sh @@ -0,0 +1,228 @@ +#!/usr/bin/env bash +set -eo pipefail + +# This script creates a signed and notarized macOS .pkg installer for fleetctl. +# It expects the signed universal binary to already exist (created by goreleaser +# or the test workflow) and will create the .pkg, sign it, notarize it, and +# optionally upload it to the GitHub release. +# +# Usage: ./main.sh +# version: semver like "4.72.0" or "v4.72.0" (v prefix stripped) +# +# Environment variables: +# APPLE_INSTALLER_CERTIFICATE - Base64-encoded installer certificate +# APPLE_INSTALLER_CERTIFICATE_PASSWORD - Password for the installer certificate +# APPLE_USERNAME - Apple ID for notarization +# APPLE_PASSWORD - App-specific password for notarization +# APPLE_TEAM_ID - Apple Developer Team ID +# KEYCHAIN_PASSWORD - Password for the build keychain +# SKIP_UPLOAD - set to "true" to skip GitHub release upload (default: upload) +# GITHUB_TOKEN, GITHUB_REPOSITORY, GITHUB_REF - required if uploading to release + +check_env_var() { + if [[ -z "${!1}" ]]; then + echo "Error: Environment variable $1 not set." + exit 1 + fi +} + +# Parse arguments +FLEETCTL_BINARY="$1" +VERSION="$2" + +if [[ -z "$FLEETCTL_BINARY" ]] || [[ -z "$VERSION" ]]; then + echo "Usage: $0 " + echo " version: semver like 4.72.0 or v4.72.0" + exit 1 +fi + +if [[ ! -f "$FLEETCTL_BINARY" ]]; then + echo "Error: Signed fleetctl binary not found at $FLEETCTL_BINARY" + exit 1 +fi + +# Strip v prefix from version +VERSION="${VERSION#v}" + +# Check required environment variables +check_env_var "APPLE_INSTALLER_CERTIFICATE" +check_env_var "APPLE_INSTALLER_CERTIFICATE_PASSWORD" +check_env_var "APPLE_USERNAME" +check_env_var "APPLE_PASSWORD" +check_env_var "APPLE_TEAM_ID" +check_env_var "KEYCHAIN_PASSWORD" + +# Upload is enabled by default; set SKIP_UPLOAD=true to skip +if [[ "$SKIP_UPLOAD" != "true" ]]; then + check_env_var "GITHUB_TOKEN" + check_env_var "GITHUB_REPOSITORY" + check_env_var "GITHUB_REF" +fi + +echo "Found signed binary at: $FLEETCTL_BINARY" +"$FLEETCTL_BINARY" --version + +cleanup() { + echo "Cleaning up..." + rm -f installer_certificate.p12 + rm -f fleetctl_unsigned.pkg + rm -f fleetctl_notarize.zip + rm -rf pkgroot +} + +# Trap EXIT signal to call cleanup function +trap cleanup EXIT + +# Import package signing keys (for .pkg) +echo "Importing package signing certificate..." +echo "$APPLE_INSTALLER_CERTIFICATE" | base64 --decode > installer_certificate.p12 +security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain || true +security default-keychain -s build.keychain +security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain +security import installer_certificate.p12 -k build.keychain -P "$APPLE_INSTALLER_CERTIFICATE_PASSWORD" -T /usr/bin/productsign +security set-key-partition-list -S apple-tool:,apple:,productsign: -s -k "$KEYCHAIN_PASSWORD" build.keychain +security find-identity -vv +rm installer_certificate.p12 + +# Extract the installer signing identity from the keychain +PACKAGE_SIGNING_IDENTITY=$(security find-identity -v build.keychain | grep "Developer ID Installer" | head -1 | awk '{print $2}') +if [[ -z "$PACKAGE_SIGNING_IDENTITY" ]]; then + echo "Error: No Developer ID Installer identity found in keychain" + exit 1 +fi +echo "Using package signing identity: $PACKAGE_SIGNING_IDENTITY" + +# Create package structure +echo "Creating package structure..." +mkdir -p pkgroot/usr/local/bin +cp "$FLEETCTL_BINARY" pkgroot/usr/local/bin/fleetctl +chmod +x pkgroot/usr/local/bin/fleetctl + +# Build the component package +echo "Building .pkg..." +pkgbuild \ + --root pkgroot \ + --identifier com.fleetdm.fleetctl \ + --version "$VERSION" \ + --install-location / \ + fleetctl_unsigned.pkg + +# Sign the package +echo "Signing .pkg..." +PACKAGE_NAME="fleetctl_v${VERSION}_mac.pkg" +productsign --sign "$PACKAGE_SIGNING_IDENTITY" \ + fleetctl_unsigned.pkg \ + "$PACKAGE_NAME" + +# Verify package signature +echo "Verifying package signature..." +pkgutil --check-signature "$PACKAGE_NAME" + +# Notarize package +echo "Notarizing package..." +zip fleetctl_notarize.zip "$PACKAGE_NAME" + +# Submit for notarization with retry logic +MAX_ATTEMPTS=10 +ATTEMPT=1 +while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do + echo "Notarization attempt $ATTEMPT of $MAX_ATTEMPTS..." + + NOTARIZE_OUTPUT=$(xcrun notarytool submit fleetctl_notarize.zip \ + --apple-id "$APPLE_USERNAME" \ + --password "$APPLE_PASSWORD" \ + --team-id "$APPLE_TEAM_ID" \ + --wait 2>&1) || true + + # Extract submission ID and status from output + SUBMISSION_ID=$(echo "$NOTARIZE_OUTPUT" | grep -i "id:" | head -1 | awk '{print $NF}' || echo "") + STATUS=$(echo "$NOTARIZE_OUTPUT" | grep -i "status:" | tail -1 | awk '{print $NF}' || echo "") + + echo "Notarization output:" + echo "$NOTARIZE_OUTPUT" + + if [ "$STATUS" = "Accepted" ]; then + echo "✓ Notarization completed successfully" + break + elif [ -n "$SUBMISSION_ID" ] && [ -n "$STATUS" ]; then + echo "⚠️ Notarization status: $STATUS" + echo "Retrieving notarization log for details..." + xcrun notarytool log "$SUBMISSION_ID" \ + --apple-id "$APPLE_USERNAME" \ + --password "$APPLE_PASSWORD" \ + --team-id "$APPLE_TEAM_ID" || true + else + echo "⚠️ Could not determine notarization status (got: '$STATUS')" + fi + + if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then + echo "Retrying in 10 seconds..." + sleep 10 + ATTEMPT=$((ATTEMPT + 1)) + continue + else + echo "❌ Notarization failed after $MAX_ATTEMPTS attempts. Exiting." + exit 1 + fi +done + +# Staple the notarization ticket +echo "Stapling notarization ticket..." +xcrun stapler staple "$PACKAGE_NAME" || { + echo "❌ Stapling failed after notarization; failing release packaging." + exit 1 +} + +# Clean up notarization zip +rm -f fleetctl_notarize.zip + +# Move package to dist directory so it can be picked up by goreleaser or uploaded later +mkdir -p dist +mv "$PACKAGE_NAME" "dist/$PACKAGE_NAME" + +echo "✓ Package created successfully: dist/$PACKAGE_NAME" + +# Upload to release unless skipped +if [[ "$SKIP_UPLOAD" == "true" ]]; then + echo "Skipping release upload (SKIP_UPLOAD=true)" +elif [[ -n "$GITHUB_TOKEN" ]] && [[ -n "$GITHUB_REPOSITORY" ]] && command -v gh &> /dev/null; then + if [[ "$GITHUB_REF" != refs/tags/* ]]; then + echo "Error: GITHUB_REF is not a tag ref ($GITHUB_REF), cannot upload to release" + exit 1 + fi + TAG_NAME="${GITHUB_REF#refs/tags/}" + echo "Uploading package to release $TAG_NAME..." + + # Wait for release to exist (goreleaser creates it as draft) + MAX_WAIT=300 # 5 minutes max wait + ELAPSED=0 + INTERVAL=10 # Check every 10 seconds + + while [ $ELAPSED -lt $MAX_WAIT ]; do + if gh release view "$TAG_NAME" --repo "$GITHUB_REPOSITORY" &>/dev/null; then + echo "Release found, uploading package..." + break + fi + echo "Waiting for release to be created... (${ELAPSED}s/${MAX_WAIT}s)" + sleep $INTERVAL + ELAPSED=$((ELAPSED + INTERVAL)) + done + + if [ $ELAPSED -ge $MAX_WAIT ]; then + echo "Warning: Release not found after waiting ${MAX_WAIT}s" + echo "Attempting to upload anyway (release might be created as draft)..." + fi + + echo "Uploading dist/$PACKAGE_NAME to release $TAG_NAME" + gh release upload "$TAG_NAME" "dist/$PACKAGE_NAME" --repo "$GITHUB_REPOSITORY" --clobber || { + echo "Upload failed, retrying once..." + sleep 5 + gh release upload "$TAG_NAME" "dist/$PACKAGE_NAME" --repo "$GITHUB_REPOSITORY" --clobber || { + echo "❌ Failed to upload package after retry" + exit 1 + } + } + + echo "✓ Package uploaded successfully: dist/$PACKAGE_NAME" +fi +