Repository: Zackriya-Solutions/meetily Branch: main Commit: 91b0c0985932 Files: 441 Total size: 3.7 MB Directory structure: gitextract_vm4cqimc/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── documentation.md │ │ └── feature_request.md │ ├── issue_template.md │ ├── pull_request_template.md │ └── workflows/ │ ├── ACCELERATION_GUIDE.md │ ├── README_DEVTEST.md │ ├── WORKFLOWS_OVERVIEW.md │ ├── build-devtest.yml │ ├── build-linux.yml │ ├── build-macos.yml │ ├── build-test.yml │ ├── build-windows.yml │ ├── build.yml │ ├── pr-main-check.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── BLUETOOTH_PLAYBACK_NOTICE.md ├── CLAUDE.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE.md ├── PRIVACY_POLICY.md ├── README.md ├── backend/ │ ├── .gitignore │ ├── API_DOCUMENTATION.md │ ├── Dockerfile.app │ ├── Dockerfile.server-cpu │ ├── Dockerfile.server-gpu │ ├── Dockerfile.server-macos │ ├── README.md │ ├── SCRIPTS_DOCUMENTATION.md │ ├── app/ │ │ ├── db.py │ │ ├── main.py │ │ ├── schema_validator.py │ │ └── transcript_processor.py │ ├── build-docker.ps1 │ ├── build-docker.sh │ ├── build_whisper.cmd │ ├── build_whisper.sh │ ├── clean_start_backend.cmd │ ├── clean_start_backend.sh │ ├── debug_cors.py │ ├── docker/ │ │ └── entrypoint.sh │ ├── docker-compose.yml │ ├── download-ggml-model.cmd │ ├── download-ggml-model.sh │ ├── examples/ │ │ └── run_summary_workflow.py │ ├── install_dependancies_for_windows.ps1 │ ├── requirements.txt │ ├── run-docker.ps1 │ ├── run-docker.sh │ ├── set_env.sh │ ├── setup-db.ps1 │ ├── setup-db.sh │ ├── start_python_backend.cmd │ ├── start_whisper_server.cmd │ ├── start_with_output.ps1 │ ├── temp.env │ └── whisper-custom/ │ └── server/ │ ├── CMakeLists.txt │ ├── README.md │ ├── httplib.h │ ├── public/ │ │ └── index.html │ └── server.cpp ├── docs/ │ ├── BUILDING.md │ ├── GPU_ACCELERATION.md │ ├── architecture.md │ └── building_in_linux.md ├── frontend/ │ ├── .gitignore │ ├── API.md │ ├── README.md │ ├── build-gpu.bat │ ├── build-gpu.ps1 │ ├── build-gpu.sh │ ├── build.bat │ ├── build.ps1 │ ├── build_backup.bat │ ├── clean_build.sh │ ├── clean_build_windows.bat │ ├── clean_run.sh │ ├── clean_run_windows.bat │ ├── components.json │ ├── dev-gpu.bat │ ├── dev-gpu.ps1 │ ├── dev-gpu.sh │ ├── eslint.config.mjs │ ├── next.config.js │ ├── package-app.sh │ ├── package.json │ ├── postcss.config.js │ ├── postcss.config.mjs │ ├── scripts/ │ │ ├── auto-detect-gpu.js │ │ ├── load-env.ps1 │ │ └── tauri-auto.js │ ├── src/ │ │ ├── app/ │ │ │ ├── _components/ │ │ │ │ ├── SettingsModal.tsx │ │ │ │ ├── StatusOverlays.tsx │ │ │ │ └── TranscriptPanel.tsx │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ ├── meeting-details/ │ │ │ │ ├── page-content.tsx │ │ │ │ └── page.tsx │ │ │ ├── metadata.ts │ │ │ ├── metadata.tsx │ │ │ ├── notes/ │ │ │ │ └── [id]/ │ │ │ │ └── page.tsx │ │ │ ├── page.tsx │ │ │ └── settings/ │ │ │ └── page.tsx │ │ ├── components/ │ │ │ ├── AISummary/ │ │ │ │ ├── Block.tsx │ │ │ │ ├── BlockNoteSummaryView.tsx │ │ │ │ ├── Section.tsx │ │ │ │ └── index.tsx │ │ │ ├── About.tsx │ │ │ ├── AnalyticsConsentSwitch.tsx │ │ │ ├── AnalyticsDataModal.tsx │ │ │ ├── AnalyticsProvider.tsx │ │ │ ├── AudioBackendSelector.tsx │ │ │ ├── AudioLevelMeter.tsx │ │ │ ├── AudioPlayer.tsx │ │ │ ├── BetaSettings.tsx │ │ │ ├── BlockNoteEditor/ │ │ │ │ ├── BasicBlockNoteTest.tsx │ │ │ │ └── Editor.tsx │ │ │ ├── BluetoothPlaybackWarning.tsx │ │ │ ├── BuiltInModelManager.tsx │ │ │ ├── ChunkProgressDisplay.tsx │ │ │ ├── ComplianceNotification.tsx │ │ │ ├── ConfidenceIndicator.tsx │ │ │ ├── ConfirmationModel/ │ │ │ │ └── confirmation-modal.tsx │ │ │ ├── ConsoleToggle.tsx │ │ │ ├── CustomDialog.tsx │ │ │ ├── DatabaseImport/ │ │ │ │ ├── HomebrewDatabaseDetector.tsx │ │ │ │ └── LegacyDatabaseImport.tsx │ │ │ ├── DeviceSelection.tsx │ │ │ ├── EditableTitle.tsx │ │ │ ├── EmptyStateSummary.tsx │ │ │ ├── ImportAudio/ │ │ │ │ ├── ImportAudioDialog.tsx │ │ │ │ ├── ImportDropOverlay.tsx │ │ │ │ └── index.ts │ │ │ ├── Info.tsx │ │ │ ├── LanguageSelection.tsx │ │ │ ├── Logo.tsx │ │ │ ├── MainContent/ │ │ │ │ └── index.tsx │ │ │ ├── MainNav/ │ │ │ │ └── index.tsx │ │ │ ├── MeetingDetails/ │ │ │ │ ├── RetranscribeDialog.tsx │ │ │ │ ├── SummaryGeneratorButtonGroup.tsx │ │ │ │ ├── SummaryPanel.tsx │ │ │ │ ├── SummaryUpdaterButtonGroup.tsx │ │ │ │ ├── TranscriptButtonGroup.tsx │ │ │ │ └── TranscriptPanel.tsx │ │ │ ├── MessageToast.tsx │ │ │ ├── ModelDownloadProgress.tsx │ │ │ ├── ModelSettingsModal.tsx │ │ │ ├── ParakeetModelManager.tsx │ │ │ ├── PermissionWarning.tsx │ │ │ ├── PreferenceSettings.tsx │ │ │ ├── RecordingControls.tsx │ │ │ ├── RecordingSettings.tsx │ │ │ ├── RecordingStatusBar.tsx │ │ │ ├── SettingTabs.tsx │ │ │ ├── Sidebar/ │ │ │ │ ├── SidebarProvider.tsx │ │ │ │ └── index.tsx │ │ │ ├── SummaryModelSettings.tsx │ │ │ ├── TranscriptRecovery/ │ │ │ │ ├── TranscriptRecovery.tsx │ │ │ │ └── index.ts │ │ │ ├── TranscriptSettings.tsx │ │ │ ├── TranscriptView.tsx │ │ │ ├── UpdateCheckProvider.tsx │ │ │ ├── UpdateDialog.tsx │ │ │ ├── UpdateNotification.tsx │ │ │ ├── VirtualizedTranscriptView.tsx │ │ │ ├── WhisperModelManager.tsx │ │ │ ├── molecules/ │ │ │ │ └── form-components/ │ │ │ │ ├── form-input-item.tsx │ │ │ │ ├── form-input-switch.tsx │ │ │ │ └── form-select-item.tsx │ │ │ ├── onboarding/ │ │ │ │ ├── OnboardingContainer.tsx │ │ │ │ ├── OnboardingFlow.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── shared/ │ │ │ │ │ ├── PermissionRow.tsx │ │ │ │ │ ├── ProgressIndicator.tsx │ │ │ │ │ ├── StatusIndicator.tsx │ │ │ │ │ └── index.ts │ │ │ │ └── steps/ │ │ │ │ ├── DownloadProgressStep.tsx │ │ │ │ ├── PermissionsStep.tsx │ │ │ │ ├── SetupOverviewStep.tsx │ │ │ │ ├── WelcomeStep.tsx │ │ │ │ └── index.ts │ │ │ ├── shared/ │ │ │ │ └── DownloadProgressToast.tsx │ │ │ └── ui/ │ │ │ ├── accordion.tsx │ │ │ ├── alert.tsx │ │ │ ├── button-group.tsx │ │ │ ├── button.tsx │ │ │ ├── command.tsx │ │ │ ├── dialog.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── form.tsx │ │ │ ├── input-group.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── popover.tsx │ │ │ ├── progress.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── switch.tsx │ │ │ ├── tabs.tsx │ │ │ ├── textarea.tsx │ │ │ ├── tooltip.tsx │ │ │ └── visually-hidden.tsx │ │ ├── config/ │ │ │ └── api.ts │ │ ├── constants/ │ │ │ ├── audioFormats.ts │ │ │ ├── languages.ts │ │ │ └── modelDefaults.ts │ │ ├── contexts/ │ │ │ ├── ConfigContext.tsx │ │ │ ├── ImportDialogContext.tsx │ │ │ ├── OllamaDownloadContext.tsx │ │ │ ├── OnboardingContext.tsx │ │ │ ├── RecordingPostProcessingProvider.tsx │ │ │ ├── RecordingStateContext.tsx │ │ │ └── TranscriptContext.tsx │ │ ├── hooks/ │ │ │ ├── meeting-details/ │ │ │ │ ├── useCopyOperations.ts │ │ │ │ ├── useMeetingData.ts │ │ │ │ ├── useMeetingOperations.ts │ │ │ │ ├── useModelConfiguration.ts │ │ │ │ ├── useSummaryGeneration.ts │ │ │ │ └── useTemplates.ts │ │ │ ├── useAudioPlayer.ts │ │ │ ├── useAutoScroll.ts │ │ │ ├── useImportAudio.ts │ │ │ ├── useModalState.ts │ │ │ ├── useNavigation.ts │ │ │ ├── usePaginatedTranscripts.ts │ │ │ ├── usePermissionCheck.ts │ │ │ ├── usePlatform.ts │ │ │ ├── useProcessingProgress.ts │ │ │ ├── useRecordingStart.ts │ │ │ ├── useRecordingStateSync.ts │ │ │ ├── useRecordingStop.ts │ │ │ ├── useTranscriptRecovery.ts │ │ │ ├── useTranscriptStreaming.ts │ │ │ ├── useTranscriptionModels.ts │ │ │ └── useUpdateCheck.ts │ │ ├── lib/ │ │ │ ├── analytics.ts │ │ │ ├── builtin-ai.ts │ │ │ ├── parakeet.ts │ │ │ ├── recordingNotification.tsx │ │ │ ├── utils.ts │ │ │ └── whisper.ts │ │ ├── services/ │ │ │ ├── configService.ts │ │ │ ├── indexedDBService.ts │ │ │ ├── recordingService.ts │ │ │ ├── storageService.ts │ │ │ ├── transcriptService.ts │ │ │ └── updateService.ts │ │ └── types/ │ │ ├── betaFeatures.ts │ │ ├── index.ts │ │ ├── onboarding.ts │ │ └── summary.ts │ ├── src-tauri/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── .gitignore │ │ ├── CLEANUP_PLAN.md │ │ ├── Cargo.toml │ │ ├── Info.plist │ │ ├── LOGGING_OPTIMIZATIONS.md │ │ ├── NOTIFICATION_TESTING.md │ │ ├── build/ │ │ │ └── ffmpeg.rs │ │ ├── build.rs │ │ ├── check_screen_permission.swift │ │ ├── config/ │ │ │ └── backend_config.json │ │ ├── entitlements.plist │ │ ├── icons/ │ │ │ ├── app_icon.icns │ │ │ └── icon.icns │ │ ├── migrations/ │ │ │ ├── 20250916100000_initial_schema.sql │ │ │ ├── 20250920155811_add_openrouter_api_key.sql │ │ │ ├── 20251006000000_add_audio_sync_fields.sql │ │ │ ├── 20251010153942_add_ollama_endpoint.sql │ │ │ ├── 20251101000000_add_summary_backup.sql │ │ │ ├── 20251105120000_add_pro_license_custom_openai.sql │ │ │ ├── 20251110000000_add_grace_period_to_licensing.sql │ │ │ ├── 20251110000001_add_speaker_field.sql │ │ │ ├── 20251223000000_add_meeting_notes.sql │ │ │ └── 20251229000000_add_gemini_api_key.sql │ │ ├── scripts/ │ │ │ └── sign-windows.ps1 │ │ ├── src/ │ │ │ ├── analytics/ │ │ │ │ ├── analytics.rs │ │ │ │ ├── commands.rs │ │ │ │ └── mod.rs │ │ │ ├── anthropic/ │ │ │ │ ├── anthropic.rs │ │ │ │ └── mod.rs │ │ │ ├── api/ │ │ │ │ ├── api.rs │ │ │ │ ├── commands.rs │ │ │ │ └── mod.rs │ │ │ ├── audio/ │ │ │ │ ├── async_logger.rs │ │ │ │ ├── audio_processing.rs │ │ │ │ ├── batch_processor.rs │ │ │ │ ├── buffer_pool.rs │ │ │ │ ├── capture/ │ │ │ │ │ ├── backend_config.rs │ │ │ │ │ ├── core_audio.rs │ │ │ │ │ ├── microphone.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── system.rs │ │ │ │ ├── common.rs │ │ │ │ ├── constants.rs │ │ │ │ ├── core-old.rs │ │ │ │ ├── decoder.rs │ │ │ │ ├── device_detection.rs │ │ │ │ ├── device_monitor.rs │ │ │ │ ├── devices/ │ │ │ │ │ ├── configuration.rs │ │ │ │ │ ├── discovery.rs │ │ │ │ │ ├── fallback.rs │ │ │ │ │ ├── microphone.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── platform/ │ │ │ │ │ │ ├── linux.rs │ │ │ │ │ │ ├── macos.rs │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── windows.rs │ │ │ │ │ └── speakers.rs │ │ │ │ ├── diagnostics.rs │ │ │ │ ├── encode.rs │ │ │ │ ├── ffmpeg.rs │ │ │ │ ├── ffmpeg_mixer.rs │ │ │ │ ├── hardware_detector.rs │ │ │ │ ├── import.rs │ │ │ │ ├── incremental_saver.rs │ │ │ │ ├── level_monitor.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── permissions.rs │ │ │ │ ├── pipeline.rs │ │ │ │ ├── playback_monitor.rs │ │ │ │ ├── post_processor.rs │ │ │ │ ├── recording_commands.rs │ │ │ │ ├── recording_commands.rs.backup │ │ │ │ ├── recording_manager.rs │ │ │ │ ├── recording_preferences.rs │ │ │ │ ├── recording_saver.rs │ │ │ │ ├── recording_saver_old.rs │ │ │ │ ├── recording_state.rs │ │ │ │ ├── retranscription.rs │ │ │ │ ├── simple_level_monitor.rs │ │ │ │ ├── stream.rs │ │ │ │ ├── stt.rs │ │ │ │ ├── system_audio_commands.rs │ │ │ │ ├── system_audio_stream.rs │ │ │ │ ├── system_audio_types.ts │ │ │ │ ├── system_detector.rs │ │ │ │ ├── transcription/ │ │ │ │ │ ├── engine.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── parakeet_provider.rs │ │ │ │ │ ├── provider.rs │ │ │ │ │ ├── whisper_provider.rs │ │ │ │ │ └── worker.rs │ │ │ │ └── vad.rs │ │ │ ├── audio_v2/ │ │ │ │ ├── compatibility.rs │ │ │ │ ├── lib.rs │ │ │ │ ├── limiter.rs │ │ │ │ ├── mixer.rs │ │ │ │ ├── normalizer.rs │ │ │ │ ├── recorder.rs │ │ │ │ ├── resampler.rs │ │ │ │ ├── stream.rs │ │ │ │ └── sync.rs │ │ │ ├── config.rs │ │ │ ├── console_utils/ │ │ │ │ ├── commands.rs │ │ │ │ ├── console_utils.rs │ │ │ │ └── mod.rs │ │ │ ├── database/ │ │ │ │ ├── commands.rs │ │ │ │ ├── manager.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── models.rs │ │ │ │ ├── repositories/ │ │ │ │ │ ├── meeting.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── setting.rs │ │ │ │ │ ├── summary.rs │ │ │ │ │ ├── transcript.rs │ │ │ │ │ └── transcript_chunk.rs │ │ │ │ └── setup.rs │ │ │ ├── groq/ │ │ │ │ ├── groq.rs │ │ │ │ └── mod.rs │ │ │ ├── lib.rs │ │ │ ├── lib_old_complex.rs │ │ │ ├── main.rs │ │ │ ├── notifications/ │ │ │ │ ├── commands.rs │ │ │ │ ├── manager.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── settings.rs │ │ │ │ ├── system.rs │ │ │ │ └── types.rs │ │ │ ├── ollama/ │ │ │ │ ├── commands.rs │ │ │ │ ├── metadata.rs │ │ │ │ ├── mod.rs │ │ │ │ └── ollama.rs │ │ │ ├── onboarding.rs │ │ │ ├── openai/ │ │ │ │ ├── mod.rs │ │ │ │ └── openai.rs │ │ │ ├── openrouter/ │ │ │ │ ├── commands.rs │ │ │ │ ├── mod.rs │ │ │ │ └── openrouter.rs │ │ │ ├── parakeet_engine/ │ │ │ │ ├── commands.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── model.rs │ │ │ │ └── parakeet_engine.rs │ │ │ ├── state.rs │ │ │ ├── summary/ │ │ │ │ ├── commands.rs │ │ │ │ ├── llm_client.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── processor.rs │ │ │ │ ├── service.rs │ │ │ │ ├── summary_engine/ │ │ │ │ │ ├── client.rs │ │ │ │ │ ├── commands.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── model_manager.rs │ │ │ │ │ ├── models.rs │ │ │ │ │ └── sidecar.rs │ │ │ │ ├── template_commands.rs │ │ │ │ └── templates/ │ │ │ │ ├── defaults.rs │ │ │ │ ├── loader.rs │ │ │ │ ├── mod.rs │ │ │ │ └── types.rs │ │ │ ├── tray.rs │ │ │ ├── utils.rs │ │ │ └── whisper_engine/ │ │ │ ├── _stderr_suppressor.rs │ │ │ ├── commands.rs │ │ │ ├── mod.rs │ │ │ ├── parallel_commands.rs │ │ │ ├── parallel_processor.rs │ │ │ ├── system_monitor.rs │ │ │ └── whisper_engine.rs │ │ ├── tauri.conf.json │ │ └── templates/ │ │ ├── README.md │ │ ├── daily_standup.json │ │ ├── project_sync.json │ │ ├── psychatric_session.json │ │ ├── retrospective.json │ │ ├── sales_marketing_client_call.json │ │ └── standard_meeting.json │ ├── tailwind.config.js │ ├── tailwind.config.ts │ └── tsconfig.json ├── llama-helper/ │ ├── Cargo.toml │ └── src/ │ └── main.rs └── scripts/ ├── generate-update-manifest-github.js ├── inject_transcript.py └── test-update-locally.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug Report about: Create a report to help us improve title: '[BUG] ' labels: bug assignees: '' --- ## Bug Description [Provide a clear and concise description of the bug] ## Current Behavior [Describe what is currently happening] ## Expected Behavior [Describe what you expected to happen] ## Steps to Reproduce 1. [First step] 2. [Second step] 3. [And so on...] ## Environment - OS: [e.g., macOS, Windows, Linux] - Browser: [e.g., Chrome, Firefox, Safari] - Version: [e.g., 1.0.0] - Node Version: [e.g., 18.0.0] - npm/pnpm Version: [e.g., 8.0.0] ## Screenshots/Videos [If applicable, add screenshots or videos to help explain your problem] ## Error Messages [If applicable, paste any error messages you're seeing] ## Additional Context [Add any other context about the problem here] ## Possible Solution [If you have suggestions on how to fix the issue, please describe them here] ## Checklist - [ ] I have searched for similar issues - [ ] I have provided all required information - [ ] I have included screenshots/videos if applicable - [ ] I have included error messages if applicable ================================================ FILE: .github/ISSUE_TEMPLATE/documentation.md ================================================ --- name: Documentation about: Report documentation issues or suggest improvements title: '[DOCS] ' labels: documentation assignees: '' --- ## Documentation Issue [Describe the documentation issue or improvement needed] ## Current Documentation [Provide links or quotes from the current documentation that needs to be updated] ## Proposed Changes [Describe the changes you'd like to see in the documentation] ## Reason for Change [Explain why this documentation change is needed] ## Additional Context [Add any other context about the documentation issue here] ## Checklist - [ ] I have searched for similar documentation issues - [ ] I have provided links to the current documentation - [ ] I have clearly described the proposed changes - [ ] I have explained the reason for the change] ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature Request about: Suggest an idea for this project title: '[FEATURE] ' labels: enhancement assignees: '' --- ## Feature Description [Provide a clear and concise description of the feature you'd like to see] ## Problem Statement [Describe the problem this feature would solve] ## Proposed Solution [Describe how you envision this feature working] ## User Story As a [type of user], I want [goal], So that [benefit] ## Acceptance Criteria - [ ] Criteria 1 - [ ] Criteria 2 - [ ] Criteria 3 ## Technical Considerations [Any technical details or considerations that should be taken into account] ## Alternatives Considered [Describe any alternative solutions or features you've considered] ## Additional Context [Add any other context, screenshots, or mockups about the feature request here] ## Checklist - [ ] I have searched for similar feature requests - [ ] I have provided all required information - [ ] I have included any relevant screenshots/mockups - [ ] I have described the problem and proposed solution clearly ================================================ FILE: .github/issue_template.md ================================================ ## Description [Provide a clear and concise description of the issue] ## Expected Behavior [Describe what you expected to happen] ## Actual Behavior [Describe what actually happened] ## Steps to Reproduce 1. [First step] 2. [Second step] 3. [And so on...] ## Environment - OS: [e.g., macOS, Windows, Linux] - Browser: [e.g., Chrome, Firefox, Safari] - Version: [e.g., 1.0.0] ## Screenshots [If applicable, add screenshots to help explain your problem] ## Additional Context [Add any other context about the problem here] ## Possible Solution [If you have suggestions on how to fix the issue, please describe them here] ================================================ FILE: .github/pull_request_template.md ================================================ ## Description [Provide a detailed description of your changes] ## Related Issue [Link to the issue this PR addresses (e.g., "Fixes #123")] ## Type of Change - [ ] Bug fix - [ ] New feature - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring - [ ] Other (please describe) ## Testing - [ ] Unit tests added/updated - [ ] Manual testing performed - [ ] All tests pass ## Documentation - [ ] Documentation updated - [ ] No documentation needed ## Checklist - [ ] Code follows project style - [ ] Self-reviewed the code - [ ] Added comments for complex code - [ ] Updated README if needed - [ ] Branch is up to date with devtest - [ ] No merge conflicts ## Screenshots (if applicable) [Add screenshots here if your changes affect the UI] ## Additional Notes [Add any additional information that might be helpful for reviewers] ================================================ FILE: .github/workflows/ACCELERATION_GUIDE.md ================================================ # CI/CD Hardware Acceleration Guide This document explains the hardware acceleration configuration for all CI/CD workflows. ## Overview All workflows now build with optimal hardware acceleration based on the platform: | Platform | Acceleration | Technology | Performance Boost | |----------|-------------|------------|------------------| | **macOS** | GPU | Metal (default) | ~10-15x faster than CPU | | **Windows** | GPU | Vulkan | ~5-10x faster than CPU | | **Linux** | CPU Optimized | OpenBLAS | ~2-3x faster than vanilla CPU | ## Previous Configuration (REMOVED) ### ❌ What Was Wrong **Linux/Ubuntu builds:** ```yaml env: WHISPER_NO_AVX: ON # Disabled AVX CPU instructions WHISPER_NO_AVX2: ON # Disabled AVX2 CPU instructions ``` This configuration **explicitly disabled CPU optimizations**, resulting in very slow transcription performance. Even though Vulkan SDK and OpenBLAS were installed, they were not being used because the build didn't enable the required features. **Windows builds:** ```yaml # Vulkan SDK installed but not used # No --features flag specified ``` The Vulkan SDK was installed but the build didn't include `--features vulkan`, so it fell back to unoptimized CPU mode. ## New Configuration (ENABLED) ### ✅ What's Fixed **All workflows now include:** #### 1. Windows Builds (Vulkan GPU) ```yaml args: --target x86_64-pc-windows-msvc --features vulkan ``` **Benefits:** - Uses Vulkan API for GPU acceleration - Works with AMD, Intel, and NVIDIA GPUs - 5-10x faster transcription than CPU - Compatible with GitHub Actions Windows runners **How it works:** - Vulkan SDK installed via `humbletim/install-vulkan-sdk@v1.2` - Whisper.cpp compiled with Vulkan backend - GPU automatically used for inference #### 2. Linux Builds (OpenBLAS CPU) ```yaml args: --target x86_64-unknown-linux-gnu --features openblas ``` **Benefits:** - Optimized BLAS (Basic Linear Algebra Subprograms) - Hardware-optimized CPU operations - 2-3x faster than vanilla CPU - No GPU required (works on GitHub Actions runners) **Why not Vulkan on Linux?** - GitHub Actions runners don't have GPUs - OpenBLAS provides best performance for CPU-only - More reliable than trying to use virtual GPU **How it works:** - OpenBLAS libraries installed (`libopenblas-dev`) - Whisper.cpp linked against OpenBLAS - Optimized matrix operations for transcription #### 3. macOS Builds (Metal GPU) ```yaml # Metal enabled by default, no flags needed # Automatically uses Apple Silicon GPU ``` **Benefits:** - Native Apple Metal GPU acceleration - 10-15x faster than CPU - CoreML acceleration also available - Built-in on macOS runners **How it works:** - Metal support is default on macOS - Automatically uses M1/M2/M3 GPU - No additional configuration needed ## Updated Workflows ### 1. `build.yml` (Reusable Workflow) **New step added:** ```yaml - name: Determine build features id: build-features shell: bash run: | FEATURES="" # Windows: Use Vulkan for GPU acceleration if [[ "${{ inputs.platform }}" == *"windows"* ]]; then FEATURES="--features vulkan" echo "Windows build with Vulkan GPU acceleration" fi # Linux: Use OpenBLAS for optimized CPU performance if [[ "${{ inputs.platform }}" == *"ubuntu"* ]]; then FEATURES="--features openblas" echo "Linux build with OpenBLAS CPU optimization" fi # macOS: Uses Metal by default if [[ "${{ inputs.platform }}" == *"macos"* ]]; then echo "macOS build with Metal GPU acceleration (default)" fi echo "features=$FEATURES" >> "$GITHUB_OUTPUT" ``` **Build command updated:** ```yaml args: ${{ inputs.build-args }} ${{ steps.build-features.outputs.features }} ``` **Removed:** ```yaml # REMOVED: These were disabling CPU optimizations WHISPER_NO_AVX: ${{ contains(inputs.platform, 'ubuntu') && 'ON' || '' }} WHISPER_NO_AVX2: ${{ contains(inputs.platform, 'ubuntu') && 'ON' || '' }} ``` ### 2. `build-devtest.yml` (DevTest Workflow) Same changes as `build.yml`: - ✅ Added feature detection step - ✅ Removed `WHISPER_NO_AVX` and `WHISPER_NO_AVX2` - ✅ Appends features to build args ### 3. `build-windows.yml` (Windows Standalone) **Build command updated:** ```yaml args: --target x86_64-pc-windows-msvc --features vulkan ${{ steps.build-profile.outputs.args }} ``` Now explicitly enables Vulkan acceleration. ### 4. `build-linux.yml` (Linux Standalone) **Build command updated:** ```yaml args: --target x86_64-unknown-linux-gnu --features openblas ${{ steps.build-profile.outputs.args }} ``` Now explicitly enables OpenBLAS optimization. ### 5. `build-macos.yml` (macOS Standalone) **New info step added:** ```yaml - name: Configure build acceleration run: | echo "✓ macOS build will use Metal GPU acceleration (enabled by default)" echo "✓ CoreML acceleration available for Apple Silicon" ``` Documents that Metal is enabled by default. ## Performance Impact ### Transcription Speed Comparison For a **10-minute meeting recording** (Whisper `base` model): | Configuration | Time to Transcribe | Real-time Factor | |--------------|-------------------|------------------| | **Old Linux (no AVX)** | ~15 minutes | 1.5x slower than real-time ⚠️ | | **New Linux (OpenBLAS)** | ~5 minutes | 2x faster than real-time ✅ | | **Old Windows (CPU)** | ~10 minutes | Same as real-time ⚠️ | | **New Windows (Vulkan)** | ~2 minutes | 5x faster than real-time ✅ | | **macOS (Metal)** | ~1 minute | 10x faster than real-time ✅ | ### Build Time Impact The acceleration changes **do not significantly increase build time**: - Vulkan SDK: Already being installed - OpenBLAS: Lightweight library - Compilation time: ~same (30-45 minutes total) ## Verification ### How to Verify Acceleration is Working **1. Check Build Logs** Look for these messages in the workflow output: ``` Windows build with Vulkan GPU acceleration ✓ Windows build with Vulkan GPU acceleration ``` ``` Linux build with OpenBLAS CPU optimization ✓ Linux build with OpenBLAS CPU optimization ``` ``` macOS build with Metal GPU acceleration (default) ✓ macOS build will use Metal GPU acceleration (enabled by default) ``` **2. Check Build Command** In the "Build with Tauri" step, verify the command includes: ```bash # Windows tauri build --target x86_64-pc-windows-msvc --features vulkan # Linux tauri build --target x86_64-unknown-linux-gnu --features openblas # macOS (features implicit) tauri build --target aarch64-apple-darwin ``` **3. Runtime Verification** When using the built application: - Transcription should feel snappy - Real-time transcription should keep up with speech - No noticeable lag when processing audio ### Checking Locally You can verify the features locally: ```bash # Windows (from frontend directory) pnpm run tauri build -- --features vulkan # Linux pnpm run tauri build -- --features openblas # macOS (Metal is default) pnpm run tauri build ``` ## Technical Details ### Whisper.cpp Features The `whisper-rs` crate (which wraps whisper.cpp) supports these features: ```toml [features] metal = ["whisper-rs/metal"] # macOS Metal cuda = ["whisper-rs/cuda"] # NVIDIA CUDA vulkan = ["whisper-rs/vulkan"] # Cross-platform Vulkan hipblas = ["whisper-rs/hipblas"] # AMD ROCm openblas = ["whisper-rs/openblas"] # Optimized CPU BLAS ``` ### Why Not CUDA? **CUDA requires:** - NVIDIA GPU hardware - CUDA toolkit installation - NVIDIA drivers **GitHub Actions runners:** - Don't have NVIDIA GPUs - Can't use CUDA **Vulkan is better for CI/CD because:** - Software-based fallback available - Works without dedicated GPU hardware - Broader compatibility ### OpenBLAS vs Vulkan on Linux We chose **OpenBLAS** over Vulkan for Linux because: - ✅ More reliable on CI runners - ✅ Better CPU optimization - ✅ No GPU hardware needed - ✅ Consistent performance - ⚠️ Vulkan without GPU gives minimal benefit For **local Linux development with GPU**, users can manually build with: ```bash pnpm run tauri build -- --features vulkan ``` ## Troubleshooting ### Build Fails with Vulkan Error (Windows) **Error:** ``` error: failed to compile whisper-rs with Vulkan support ``` **Solution:** - Ensure Vulkan SDK step runs successfully - Check `humbletim/install-vulkan-sdk@v1.2` output - Verify Vulkan version matches (1.4.309.0) ### Build Fails with OpenBLAS Error (Linux) **Error:** ``` error: could not find OpenBLAS library ``` **Solution:** - Ensure `libopenblas-dev` is in apt install list - Check dependency installation step completed - Verify OpenBLAS package is available for Ubuntu version ### Performance Still Slow **Check:** 1. ✅ Build logs show correct features enabled 2. ✅ Build command includes `--features` flag 3. ✅ No error messages during Whisper compilation 4. ✅ Application binary is from new build (not cached old version) **If still slow:** - May be Whisper model size (try smaller model) - May be audio file issues (check format) - May be system resource constraints ## Future Improvements ### Potential Enhancements 1. **Add CUDA support** for users with NVIDIA GPUs - Detect if NVIDIA GPU available - Optionally enable CUDA feature - Fallback to Vulkan if CUDA fails 2. **Add CoreML support** for macOS - Enable explicit CoreML acceleration - Test performance vs Metal alone - Document benefits 3. **Dynamic feature detection** - Detect available hardware at runtime - Automatically select best backend - Provide user override options 4. **Performance metrics** - Log transcription performance in CI - Compare across builds - Alert if performance degrades ## Related Documentation - [CLAUDE.md](../../CLAUDE.md) - Project overview with build commands - [WORKFLOWS_OVERVIEW.md](WORKFLOWS_OVERVIEW.md) - All workflows comparison - [README_DEVTEST.md](README_DEVTEST.md) - DevTest workflow guide - [Whisper.cpp GitHub](https://github.com/ggerganov/whisper.cpp) - Upstream project ## Summary ✅ **All CI/CD workflows now use hardware acceleration** - Windows: Vulkan GPU - Linux: OpenBLAS CPU optimization - macOS: Metal GPU (default) ✅ **Performance improvements** - 2-10x faster transcription - Better real-time factor - Improved user experience ✅ **No build time increase** - Same overall build duration - Dependencies already installed - Just enabling features ❌ **Removed slow configurations** - No more `WHISPER_NO_AVX` - No more `WHISPER_NO_AVX2` - No more unoptimized CPU-only --- **Last Updated:** 2025-01-15 **Version:** 1.0 **Impact:** All workflows ================================================ FILE: .github/workflows/README_DEVTEST.md ================================================ # DevTest Build Workflow This document explains how to use the `build-devtest.yml` workflow for building and testing. ## Overview The DevTest workflow is specifically designed for development and testing purposes. It: - Builds for all platforms (macOS, Windows, Linux) - Has **code signing disabled by default** to speed up builds - Allows **optional signing** via workflow dispatch input - Uploads artifacts for testing ## Triggering the Workflow The workflow runs via **manual dispatch only**: 1. Go to **Actions** tab in your GitHub repository 2. Select **Build and Test - DevTest** from the left sidebar 3. Click **Run workflow** button 4. Configure options: - **Branch**: Select the branch to build - **Sign the build**: Check to enable code signing (default: unchecked) - **Upload build artifacts**: Check to upload artifacts (default: checked) 5. Click **Run workflow** to start ## Workflow Options ### Sign the build - **Unchecked (default)**: Fast builds without code signing (~25-30 minutes) - **Checked**: Full code signing for all platforms (~35-45 minutes) ### Upload build artifacts - **Checked (default)**: Artifacts are uploaded and available for download - **Unchecked**: Build runs but no artifacts are saved ## Build Matrix The workflow builds for all platforms in parallel: | Platform | Target | Output | |----------|--------|--------| | macOS (Apple Silicon) | aarch64-apple-darwin | DMG + App | | Windows (x64) | x86_64-pc-windows-msvc | MSI + NSIS | | Linux (Ubuntu 22.04) | x86_64-unknown-linux-gnu | DEB | | Linux (Ubuntu 24.04) | x86_64-unknown-linux-gnu | AppImage + RPM | ## Code Signing Details When signing is enabled: ### macOS - Uses **Apple Developer Certificate** from secrets - Performs **notarization** with Apple ID - Signs both DMG and .app bundle - Verifies signatures with `codesign` and `spctl` ### Windows - Uses **DigiCert KeyLocker** (cloud HSM) - Signs both MSI and NSIS installers - Verifies signatures with PowerShell ### Linux - Uses **Tauri updater signing** (Ed25519) - Signs update manifests for auto-updater ## Build Artifacts Artifacts are automatically uploaded and retained for **14 days**: - **macOS**: `*.dmg`, `*.app`, `*.app.tar.gz`, `*.app.tar.gz.sig` - **Windows**: `*.msi`, `*.msi.sig`, `*.exe`, `*.exe.sig` - **Linux**: `*.deb`, `*.AppImage`, `*.rpm` ### Downloading Artifacts 1. Go to **Actions** tab 2. Select the completed workflow run 3. Scroll down to **Artifacts** section 4. Click on the artifact name to download ## Examples ### Example 1: Unsigned Build (Default, Fast) 1. Go to Actions > Build and Test - DevTest 2. Click "Run workflow" 3. Leave all options at defaults 4. Click "Run workflow" **Result:** Builds without signing in ~25-30 minutes --- ### Example 2: Signed Build 1. Go to Actions > Build and Test - DevTest 2. Click "Run workflow" 3. Check "Sign the build" 4. Click "Run workflow" **Result:** Builds with full code signing in ~35-45 minutes ## Hardware Acceleration Each platform uses optimal hardware acceleration: | Platform | Acceleration | Performance | |----------|-------------|-------------| | macOS | Metal GPU | 10-15x faster than CPU | | Windows | Vulkan GPU | 5-10x faster than CPU | | Linux | OpenBLAS CPU | 2-3x faster than vanilla CPU | ## Troubleshooting ### Signing Not Working **Problem:** Signing enabled but builds are still unsigned **Solutions:** 1. Verify all required secrets are configured in repository settings 2. Check the workflow logs for specific error messages 3. Ensure secrets haven't expired ### Build Failures **Problem:** Build fails during signing phase **Solutions:** 1. Check that all required secrets are configured: - `APPLE_CERTIFICATE`, `APPLE_ID`, `APPLE_PASSWORD`, `APPLE_TEAM_ID` - `SM_HOST`, `SM_API_KEY`, `SM_CODE_SIGNING_CERT_SHA1_HASH` - `TAURI_SIGNING_PRIVATE_KEY`, `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` 2. Review workflow logs for specific error messages 3. Try running without signing first to isolate the issue ### Artifacts Not Available **Problem:** Can't download build artifacts **Solutions:** 1. Check workflow status - artifacts only available after successful build 2. Artifacts expire after 14 days 3. Ensure "Upload build artifacts" was checked when running ## Performance Comparison | Build Type | Duration | When to Use | |------------|----------|-------------| | **Unsigned** (default) | ~25-30 min | Regular development, quick testing | | **Signed** | ~35-45 min | Pre-release testing, production-like testing | ## Best Practices 1. **Use unsigned builds** for routine development and testing 2. **Enable signing** only when: - Testing production-like scenarios - Preparing for release - Testing installer behavior - Verifying code signing infrastructure 3. **Always test** locally before triggering workflow to save CI time 4. **Review** the workflow summary to confirm build status ## Workflow Configuration Located at: `.github/workflows/build-devtest.yml` Key configuration: - **Default signing:** OFF - **Artifact retention:** 14 days - **Parallel builds:** All platforms simultaneously - **Trigger:** Manual dispatch only ## Related Workflows - `build-macos.yml` - macOS-specific builds with signing - `build-windows.yml` - Windows-specific builds with signing - `build-linux.yml` - Linux-specific builds with signing - `build-test.yml` - All platforms with signing (pre-release) - `release.yml` - Production release workflow ================================================ FILE: .github/workflows/WORKFLOWS_OVERVIEW.md ================================================ # GitHub Actions Workflows Overview This document provides a quick overview of all available CI/CD workflows in this repository. **Note:** All workflows in this repository use **manual triggers only** (`workflow_dispatch`). There are no automatic triggers from push or pull request events. ## Workflow Files ### 1. **build-devtest.yml** - DevTest Builds **Purpose:** Fast builds for development and testing **Key Features:** - Signing OFF by default (faster builds) - Optional signing via workflow dispatch input - All platforms in parallel - 14-day artifact retention **Triggers:** - Manual dispatch only **Use When:** - Regular development work - Testing features - Need fast feedback --- ### 2. **build-macos.yml** - macOS Standalone Builds **Purpose:** Build and test specifically for Apple Silicon (M1/M2/M3) **Key Features:** - Apple Developer Certificate signing (optional) - Notarization with Apple ID - Signature verification - macOS-focused optimizations **Triggers:** - Manual dispatch only **Use When:** - macOS-specific development - Testing Metal GPU acceleration - Verifying macOS-specific features **Outputs:** - `.dmg` installer - `.app` bundle --- ### 3. **build-windows.yml** - Windows Standalone Builds **Purpose:** Build and test specifically for Windows x64 **Key Features:** - DigiCert KeyLocker signing (cloud HSM) - Signs both MSI and NSIS installers - Signature verification with PowerShell - MSI installer validation **Triggers:** - Manual dispatch only **Use When:** - Windows-specific development - Testing CUDA/Vulkan GPU acceleration - Verifying Windows-specific features **Outputs:** - `.msi` installer - `.exe` NSIS installer --- ### 4. **build-linux.yml** - Linux Standalone Builds **Purpose:** Build and test for Linux distributions **Key Features:** - Support for Ubuntu 22.04 and 24.04 - Multiple bundle formats (DEB, AppImage, RPM) - Tauri updater signing - AppImage compatibility fixes - Package verification **Triggers:** - Manual dispatch only **Use When:** - Linux-specific development - Testing Vulkan GPU acceleration - Verifying package formats **Outputs:** - `.deb` package (Ubuntu/Debian) - `.AppImage` portable - `.rpm` package (Fedora/RHEL) --- ### 5. **build-test.yml** - Multi-Platform Test Builds **Purpose:** Test builds across all platforms with signing **Key Features:** - Signing ON by default - All platforms in parallel - Uses reusable `build.yml` workflow - 30-day artifact retention - Artifacts prefixed with `meetily-test-` **Triggers:** - Manual dispatch only **Use When:** - Pre-release testing - Verifying signing infrastructure - Testing across all platforms simultaneously --- ### 6. **build.yml** - Reusable Build Workflow **Purpose:** Shared workflow used by other workflows **Key Features:** - Reusable workflow (called by others) - Highly configurable inputs - Used by `build-test.yml` and `release.yml` **Not directly triggered** - used as a building block --- ### 7. **release.yml** - Production Release **Purpose:** Create official releases with signed binaries **Key Features:** - Signing REQUIRED - Creates GitHub Release (draft) - Version tags from `tauri.conf.json` - Uploads release assets - **macOS and Windows only** (Linux excluded from production releases) - Auto-generates `latest.json` for Tauri updater - **Auto-increment versioning**: If tag exists, auto-increments (e.g., `0.1.1` -> `0.1.1.1` -> `0.1.1.2`, up to `.100`) **Triggers:** - Manual dispatch only **Use When:** - Ready to publish a new version - Creating official release artifacts **Outputs:** - GitHub Release (draft) - macOS: DMG installer, app.tar.gz (updater), .sig - Windows: MSI installer (signed), NSIS installer (signed), .sig files - Updater manifest: latest.json - Release notes auto-generated **Version Behavior:** - If `v0.1.1` tag doesn't exist: creates `v0.1.1` - If `v0.1.1` exists: creates `v0.1.1.1` - If `v0.1.1.1` exists: creates `v0.1.1.2` - Maximum: `v0.1.1.100` (then update `tauri.conf.json`) **Note:** Linux builds are not included in releases. Use `build-linux.yml` for Linux testing. --- ### 8. **pr-main-check.yml** - Validation Check **Purpose:** Quick validation of version and configuration **Key Features:** - No builds triggered - Validates version format - Shows current branch info - Provides next steps guidance **Triggers:** - Manual dispatch only **Use When:** - Quick configuration check - Before running full builds --- ## How to Run Workflows 1. **Go to Actions tab** in GitHub repository 2. **Select workflow** from left sidebar 3. **Click "Run workflow"** button 4. **Select branch** to run against 5. **Configure options** (build type, signing, etc.) 6. **Click "Run workflow"** to start 7. **Monitor progress** in the Actions tab --- ## Quick Decision Guide ### "I'm developing a new feature..." - **Use `build-devtest.yml`** (manual dispatch) - Fast builds, no signing by default - Enable signing checkbox if needed ### "I need to test macOS-specific code..." - **Use `build-macos.yml`** (manual dispatch) - Focus on macOS - Optional signing ### "I need to test Windows-specific code..." - **Use `build-windows.yml`** (manual dispatch) - Focus on Windows - Optional signing ### "I need to test Linux packages..." - **Use `build-linux.yml`** (manual dispatch) - Choose Ubuntu version - Choose bundle types ### "I need signed builds for all platforms..." - **Use `build-test.yml`** (manual dispatch) - All platforms - Signing enabled - Full verification ### "I'm ready to release..." - **Use `release.yml`** (manual dispatch) - Creates GitHub Release - All platforms, fully signed - Production-ready artifacts --- ## Workflow Dependencies ``` build.yml (reusable) |-- build-test.yml (calls build.yml) |-- release.yml (calls build.yml) Standalone (don't use build.yml): |-- build-macos.yml |-- build-windows.yml |-- build-linux.yml |-- build-devtest.yml |-- pr-main-check.yml (validation only) ``` --- ## Comparison Matrix | Workflow | Platforms | Default Signing | Speed | Retention | Use Case | |----------|-----------|----------------|-------|-----------|----------| | `build-devtest.yml` | All | OFF | Fast | 14 days | Development | | `build-macos.yml` | macOS | Optional | Medium | 30 days | macOS dev | | `build-windows.yml` | Windows | Optional | Medium | 30 days | Windows dev | | `build-linux.yml` | Linux | Optional | Medium | 30 days | Linux dev | | `build-test.yml` | All | ON | Slow | 30 days | Pre-release | | `release.yml` | macOS + Windows | REQUIRED | Slow | Permanent | Release | --- ## Artifact Naming Convention ``` meetily-{workflow}-{platform}-{target}-{version} ``` **Examples:** - `meetily-devtest-macOS-aarch64-apple-darwin-0.1.3` - `meetily-test-windows-x86_64-pc-windows-msvc-0.1.3` - `meetily-macos-aarch64-release-0.1.3` --- ## Required Secrets All workflows require these secrets to be configured: ### macOS Signing - `APPLE_CERTIFICATE` - Developer ID certificate (base64) - `APPLE_CERTIFICATE_PASSWORD` - Certificate password - `APPLE_ID` - Apple ID email - `APPLE_PASSWORD` - App-specific password - `APPLE_TEAM_ID` - Team ID - `KEYCHAIN_PASSWORD` - Temporary keychain password ### Windows Signing (DigiCert) - `SM_HOST` - DigiCert host URL - `SM_API_KEY` - API key - `SM_CLIENT_CERT_FILE_B64` - Client cert (base64) - `SM_CLIENT_CERT_PASSWORD` - Client cert password - `SM_CODE_SIGNING_CERT_SHA1_HASH` - Certificate hash ### Tauri Updater (All Platforms) - `TAURI_SIGNING_PRIVATE_KEY` - Ed25519 private key - `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` - Key password ### Application Configuration - `MEETILY_RSA_PUBLIC_KEY` - License validation public key - `SUPABASE_URL` - Online license verification - `SUPABASE_ANON_KEY` - Supabase anonymous key --- ## Performance Tips 1. **Use devtest workflow** for routine development (fastest) 2. **Enable signing** only when necessary (adds 10-15 minutes) 3. **Test specific platforms** when working on platform-specific code 4. **Run full builds** (`build-test.yml`) before releases 5. **Cache is enabled** - subsequent builds are faster --- ## Troubleshooting ### Build fails with version error (Windows MSI) - Ensure version in `tauri.conf.json` doesn't contain non-numeric pre-release identifiers - Use `0.1.3` not `0.1.2-pro-trial` ### Signing fails - Verify all required secrets are configured - Check secret expiration dates - Review workflow logs for specific errors ### Artifacts not available - Check build succeeded completely - Artifacts expire based on retention period - Ensure `upload-artifacts` is enabled ### Workflow not appearing in Actions - Verify YAML syntax is valid - Check file is in `.github/workflows/` directory - Ensure file extension is `.yml` or `.yaml` --- ## Support For issues with workflows: 1. Check workflow logs in Actions tab 2. Review this documentation 3. Check `README_DEVTEST.md` for devtest-specific help 4. Check `ACCELERATION_GUIDE.md` for GPU/performance info ================================================ FILE: .github/workflows/build-devtest.yml ================================================ name: "Build and Test - DevTest" on: workflow_dispatch: inputs: sign-build: description: 'Sign the build' required: false type: boolean default: false upload-artifacts: description: 'Upload build artifacts' required: false type: boolean default: true # Cancel duplicate workflow runs concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: RUST_BACKTRACE: 1 CARGO_TERM_COLOR: always jobs: build-devtest: name: Build ${{ matrix.platform-name }} runs-on: ${{ matrix.platform }} permissions: contents: write strategy: fail-fast: false matrix: include: - platform: macos-latest platform-name: macOS target: aarch64-apple-darwin args: --target aarch64-apple-darwin - platform: windows-latest platform-name: Windows target: x86_64-pc-windows-msvc args: --target x86_64-pc-windows-msvc - platform: ubuntu-22.04 platform-name: Linux (Ubuntu 22.04) target: x86_64-unknown-linux-gnu args: --target x86_64-unknown-linux-gnu --bundles deb - platform: ubuntu-24.04 platform-name: Linux (Ubuntu 24.04) target: x86_64-unknown-linux-gnu args: --target x86_64-unknown-linux-gnu --bundles appimage,rpm steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Get version from tauri.conf.json id: get-version shell: bash run: | VERSION=$(grep -o '"version": "[^"]*"' frontend/src-tauri/tauri.conf.json | cut -d'"' -f4) echo "Application version: $VERSION" echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 8 run_install: false - name: Setup Node uses: actions/setup-node@v4 with: node-version: '20' - name: Get pnpm store directory shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - name: Setup pnpm cache uses: actions/cache@v4 with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install Rust stable uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - name: Rust cache uses: swatinem/rust-cache@v2 with: workspaces: ". -> target" key: ${{ matrix.platform }}-${{ matrix.target }} # Platform-specific dependencies - name: Install dependencies (Ubuntu 24.04) if: matrix.platform == 'ubuntu-24.04' run: | sudo apt-get update sudo apt-get install -y libappindicator3-dev librsvg2-dev patchelf libasound2-dev libopenblas-dev libx11-dev libxtst-dev libxrandr-dev \ libwebkit2gtk-4.1-0=2.44.0-2 \ libwebkit2gtk-4.1-dev=2.44.0-2 \ libjavascriptcoregtk-4.1-0=2.44.0-2 \ libjavascriptcoregtk-4.1-dev=2.44.0-2 \ gir1.2-javascriptcoregtk-4.1=2.44.0-2 \ gir1.2-webkit2-4.1=2.44.0-2 \ fuse libfuse2 - name: Install dependencies (Ubuntu 22.04) if: matrix.platform == 'ubuntu-22.04' run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libasound2-dev libopenblas-dev libx11-dev libxtst-dev libxrandr-dev fuse libfuse2 - name: Prepare Vulkan SDK (Ubuntu 24.04) if: matrix.platform == 'ubuntu-24.04' run: | wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-1.3.290-noble.list https://packages.lunarg.com/vulkan/1.3.290/lunarg-vulkan-1.3.290-noble.list sudo apt update sudo apt install vulkan-sdk -y sudo apt-get install -y mesa-vulkan-drivers - name: Prepare Vulkan SDK (Ubuntu 22.04) if: matrix.platform == 'ubuntu-22.04' run: | wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-1.3.290-jammy.list https://packages.lunarg.com/vulkan/1.3.290/lunarg-vulkan-1.3.290-jammy.list sudo apt update sudo apt install vulkan-sdk -y sudo apt-get install -y mesa-vulkan-drivers - name: Install Vulkan SDK (Windows) if: contains(matrix.platform, 'windows') uses: humbletim/install-vulkan-sdk@v1.2 with: version: 1.4.309.0 cache: true - name: Install frontend dependencies run: | cd frontend pnpm install # macOS signing setup - name: Import Apple Developer Certificate if: contains(matrix.platform, 'macos') && github.event.inputs.sign-build == 'true' env: APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} run: | echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security default-keychain -s build.keychain security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security set-keychain-settings -t 3600 -u build.keychain security import certificate.p12 -k build.keychain -P "$APPLE_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 -v -p codesigning build.keychain - name: Verify Apple certificate if: contains(matrix.platform, 'macos') && github.event.inputs.sign-build == 'true' run: | CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application") CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV echo "Certificate imported: $CERT_ID" # Windows signing setup - name: Setup DigiCert KeyLocker if: contains(matrix.platform, 'windows') && github.event.inputs.sign-build == 'true' uses: digicert/ssm-code-signing@v1.1.1 - name: Setup DigiCert Environment if: contains(matrix.platform, 'windows') && github.event.inputs.sign-build == 'true' shell: pwsh run: | # Set environment variables "SM_HOST=${{ secrets.SM_HOST }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "SM_API_KEY=${{ secrets.SM_API_KEY }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "SM_CODE_SIGNING_CERT_SHA1_HASH=${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append # Decode and save client certificate using PowerShell $certPath = "D:\Certificate_pkcs12.p12" $base64String = "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}" $certBytes = [System.Convert]::FromBase64String($base64String) [System.IO.File]::WriteAllBytes($certPath, $certBytes) # Verify the certificate file was created and has content if (Test-Path $certPath) { $certFile = Get-Item $certPath Write-Host "Certificate file created: $certPath" Write-Host " Size: $($certFile.Length) bytes" Write-Host " Last Modified: $($certFile.LastWriteTime)" # Verify it's a valid PKCS12 file by checking if we can load it try { $testCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certPath, "${{ secrets.SM_CLIENT_CERT_PASSWORD }}") Write-Host " Certificate file is valid PKCS12 format" Write-Host " Password is correct" Write-Host " Certificate Subject: $($testCert.Subject)" Write-Host " Certificate Thumbprint: $($testCert.Thumbprint)" } catch { Write-Warning "Certificate validation failed" Write-Warning "Error: $($_.Exception.Message)" Write-Warning "This may cause issues with signing, but we'll continue..." } } else { Write-Error "Certificate file was not created at $certPath" exit 1 } # Set the environment variable with Windows-style path "SM_CLIENT_CERT_FILE=D:\Certificate_pkcs12.p12" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: Verify DigiCert Setup if: contains(matrix.platform, 'windows') && github.event.inputs.sign-build == 'true' shell: pwsh run: | smctl --version smctl keypair ls smctl healthcheck - name: Sync Certificate to Windows Certificate Store if: contains(matrix.platform, 'windows') && github.event.inputs.sign-build == 'true' shell: pwsh run: | Write-Host "============================================================" Write-Host "=== SYNCING CERTIFICATE TO WINDOWS CERTIFICATE STORE ===" Write-Host "============================================================" Write-Host "This step syncs the DigiCert certificate from KeyLocker HSM" Write-Host "to the Windows Certificate Store (required for signing)" Write-Host "" # First, get the keypair alias from smctl keypair ls Write-Host "Retrieving keypair alias from DigiCert KeyLocker..." $keypairOutput = smctl keypair ls 2>&1 | Out-String Write-Host $keypairOutput # Extract the alias (looking for pattern like "key_XXXXXXXXXX") $aliasMatch = [regex]::Match($keypairOutput, 'key_\d+') if ($aliasMatch.Success) { $keypairAlias = $aliasMatch.Value Write-Host "Found keypair alias: $keypairAlias" } else { Write-Error "Could not find keypair alias in smctl output" Write-Error "Output was: $keypairOutput" exit 1 } Write-Host "" Write-Host "Syncing certificate using alias: $keypairAlias" $certsyncOutput = smctl windows certsync --keypair-alias $keypairAlias 2>&1 Write-Host $certsyncOutput if ($LASTEXITCODE -ne 0) { Write-Host "" Write-Error "Certificate sync FAILED" Write-Error "Exit code: $LASTEXITCODE" Write-Error "Output: $certsyncOutput" Write-Error "" Write-Error "Possible causes:" Write-Error " 1. Keypair alias '$keypairAlias' not found in KeyLocker" Write-Error " 2. Certificate is revoked or expired" Write-Error " 3. API credentials are invalid" exit 1 } Write-Host "" Write-Host "Certificate synced successfully" Write-Host "" # Verify certificate is now in Windows store Write-Host "Verifying certificate in Windows Certificate Store..." $cert = Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq $env:SM_CODE_SIGNING_CERT_SHA1_HASH } | Select-Object -First 1 if (-not $cert) { # Try LocalMachine store $cert = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq $env:SM_CODE_SIGNING_CERT_SHA1_HASH } | Select-Object -First 1 } if ($cert) { Write-Host "Certificate found in Windows Certificate Store" Write-Host " Subject: $($cert.Subject)" Write-Host " Issuer: $($cert.Issuer)" Write-Host " Thumbprint: $($cert.Thumbprint)" Write-Host " Valid From: $($cert.NotBefore)" Write-Host " Valid Until: $($cert.NotAfter)" } else { Write-Warning "Certificate not found in Windows Certificate Store after sync" Write-Warning "Signing may fail. Continuing anyway..." } # Export keypair alias for Tauri signCommand Write-Host "" Write-Host "Exporting DIGICERT_KEYPAIR_ALIAS for Tauri signCommand..." "DIGICERT_KEYPAIR_ALIAS=$keypairAlias" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append Write-Host "DIGICERT_KEYPAIR_ALIAS=$keypairAlias" Write-Host "" Write-Host "============================================================" Write-Host "" # Determine build features for acceleration - name: Determine build features id: build-features shell: bash run: | FEATURES="" # Windows: Use Vulkan for GPU acceleration if [[ "${{ matrix.platform }}" == *"windows"* ]]; then FEATURES="--features vulkan" echo "Windows build with Vulkan GPU acceleration" fi # Linux: Use OpenBLAS for optimized CPU performance if [[ "${{ matrix.platform }}" == *"ubuntu"* ]]; then FEATURES="--features openblas" echo "Linux build with OpenBLAS CPU optimization" fi # macOS: Uses Metal by default, no additional features needed if [[ "${{ matrix.platform }}" == *"macos"* ]]; then echo "macOS build with Metal GPU acceleration (default)" fi echo "features=$FEATURES" >> "$GITHUB_OUTPUT" - name: Build llama-helper sidecar shell: bash run: | echo "Building llama-helper sidecar..." # Determine llama-helper features based on platform LLAMA_FEATURES="" if [[ "${{ matrix.platform }}" == *"macos"* ]]; then LLAMA_FEATURES="--features metal" echo "Using Metal GPU acceleration for macOS" elif [[ "${{ matrix.platform }}" == *"windows"* ]]; then LLAMA_FEATURES="--features vulkan" echo "Using Vulkan GPU acceleration for Windows" else echo "Using CPU-only mode for Linux" fi # Build llama-helper from workspace root cargo build --release -p llama-helper $LLAMA_FEATURES # Determine binary extension EXT="" if [[ "${{ matrix.platform }}" == *"windows"* ]]; then EXT=".exe" fi # Copy binary to binaries directory mkdir -p frontend/src-tauri/binaries cp target/release/llama-helper${EXT} frontend/src-tauri/binaries/llama-helper-${{ matrix.target }}${EXT} echo "Copied llama-helper to frontend/src-tauri/binaries/llama-helper-${{ matrix.target }}${EXT}" ls -la frontend/src-tauri/binaries/ # Build step (with code signing) - name: Build Tauri app (with code signing) if: github.event.inputs.sign-build == 'true' uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # macOS platform code signing APPLE_ID: ${{ contains(matrix.platform, 'macos') && secrets.APPLE_ID || '' }} APPLE_ID_PASSWORD: ${{ contains(matrix.platform, 'macos') && secrets.APPLE_ID_PASSWORD || '' }} APPLE_PASSWORD: ${{ contains(matrix.platform, 'macos') && secrets.APPLE_PASSWORD || '' }} APPLE_TEAM_ID: ${{ contains(matrix.platform, 'macos') && secrets.APPLE_TEAM_ID || '' }} APPLE_CERTIFICATE: ${{ contains(matrix.platform, 'macos') && secrets.APPLE_CERTIFICATE || '' }} APPLE_CERTIFICATE_PASSWORD: ${{ contains(matrix.platform, 'macos') && secrets.APPLE_CERTIFICATE_PASSWORD || '' }} APPLE_SIGNING_IDENTITY: ${{ contains(matrix.platform, 'macos') && env.CERT_ID || '' }} # Tauri updater signing (ALWAYS enabled for .sig files) TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} # License validation RSA public key (embedded at build time) MEETILY_RSA_PUBLIC_KEY: ${{ secrets.MEETILY_RSA_PUBLIC_KEY }} # Supabase configuration (for online license verification) SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} with: projectPath: frontend args: ${{ matrix.args }} ${{ steps.build-features.outputs.features }} # Build step (without code signing - updater signing only) - name: Build Tauri app (unsigned) if: github.event.inputs.sign-build != 'true' uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Tauri updater signing (ALWAYS enabled for .sig files) TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} # License validation RSA public key (embedded at build time) MEETILY_RSA_PUBLIC_KEY: ${{ secrets.MEETILY_RSA_PUBLIC_KEY }} # Supabase configuration (for online license verification) SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} with: projectPath: frontend args: ${{ matrix.args }} ${{ steps.build-features.outputs.features }} # Linux post-build processing - name: Remove libwayland-client.so from AppImage if: contains(matrix.platform, 'ubuntu-24.04') run: | APPIMAGE_PATH=$(find target/x86_64-unknown-linux-gnu/release/bundle/appimage -name "*.AppImage" | head -1) if [ -n "$APPIMAGE_PATH" ]; then echo "Processing AppImage: $APPIMAGE_PATH" chmod +x "$APPIMAGE_PATH" cd "$(dirname "$APPIMAGE_PATH")" APPIMAGE_NAME=$(basename "$APPIMAGE_PATH") "./$APPIMAGE_NAME" --appimage-extract find squashfs-root -name "libwayland-client.so*" -type f -delete wget -q https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage chmod +x appimagetool-x86_64.AppImage ARCH=x86_64 ./appimagetool-x86_64.AppImage --no-appstream squashfs-root "$APPIMAGE_NAME" rm -rf squashfs-root appimagetool-x86_64.AppImage echo "AppImage processed successfully" fi # Verification steps - name: Verify build artifacts shell: bash run: | echo "Build artifacts:" find target -name "*.dmg" -o -name "*.app" -o -name "*.msi" -o -name "*.exe" -o -name "*.deb" -o -name "*.AppImage" -o -name "*.rpm" 2>/dev/null || echo "Finding artifacts..." - name: Verify code signing (macOS App) if: contains(matrix.platform, 'macos') && github.event.inputs.sign-build == 'true' run: | APP_PATH=$(find target/aarch64-apple-darwin/release/bundle/macos -name "*.app" | head -1) if [ -n "$APP_PATH" ]; then echo "=== Verifying App Bundle: $APP_PATH ===" codesign -dv --verbose=4 "$APP_PATH" 2>&1 | grep -i "signature\|authority" codesign --verify --deep --strict "$APP_PATH" && echo "Code signature is valid" spctl -a -vvv "$APP_PATH" && echo "App is notarized and accepted by Gatekeeper" fi DMG_PATH=$(find target/aarch64-apple-darwin/release/bundle/dmg -name "*.dmg" | head -1) if [ -n "$DMG_PATH" ]; then echo "=== Verifying DMG: $DMG_PATH ===" codesign -dv --verbose=4 "$DMG_PATH" 2>&1 | grep -i "signature\|authority" echo "DMG is signed (contains notarized .app)" fi - name: Verify code signing (Windows) if: contains(matrix.platform, 'windows') && github.event.inputs.sign-build == 'true' shell: pwsh run: | $msiPath = Get-ChildItem -Path "target/x86_64-pc-windows-msvc/release/bundle/msi" -Filter "*.msi" | Select-Object -First 1 -ExpandProperty FullName if ($msiPath) { Write-Host "Verifying MSI signature: $msiPath" Get-AuthenticodeSignature -FilePath "$msiPath" | Format-List } # Upload artifacts - name: Upload artifacts if: github.event.inputs.upload-artifacts == 'true' uses: actions/upload-artifact@v4 with: name: meetily-devtest-${{ matrix.platform-name }}-${{ matrix.target }}-${{ steps.get-version.outputs.version }} path: | target/aarch64-apple-darwin/release/bundle/dmg/*.dmg target/aarch64-apple-darwin/release/bundle/macos/*.app target/aarch64-apple-darwin/release/bundle/macos/*.app.tar.gz target/aarch64-apple-darwin/release/bundle/macos/*.app.tar.gz.sig target/x86_64-pc-windows-msvc/release/bundle/msi/*.msi target/x86_64-pc-windows-msvc/release/bundle/msi/*.msi.sig target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe.sig target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage target/x86_64-unknown-linux-gnu/release/bundle/rpm/*.rpm retention-days: 14 # Generate summary - name: Generate build summary shell: bash run: | echo "## DevTest Build Summary - ${{ matrix.platform-name }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- **Version:** ${{ steps.get-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY echo "- **Platform:** ${{ matrix.platform-name }}" >> $GITHUB_STEP_SUMMARY echo "- **Target:** ${{ matrix.target }}" >> $GITHUB_STEP_SUMMARY echo "- **Signed:** ${{ github.event.inputs.sign-build == 'true' && 'Yes' || 'No (default)' }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY ================================================ FILE: .github/workflows/build-linux.yml ================================================ name: "Build and Test - Linux" on: workflow_dispatch: inputs: build-type: description: 'Build type' required: true type: choice options: - debug - release default: release ubuntu-version: description: 'Ubuntu version' required: true type: choice options: - ubuntu-22.04 - ubuntu-24.04 default: ubuntu-22.04 bundle-type: description: 'Bundle type' required: true type: choice options: - deb - appimage - rpm - all default: all sign-build: description: 'Sign the build' required: true type: boolean default: true upload-artifacts: description: 'Upload build artifacts' required: true type: boolean default: true # Cancel duplicate workflow runs concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: RUST_BACKTRACE: 1 CARGO_TERM_COLOR: always WHISPER_NO_AVX: ON WHISPER_NO_AVX2: ON jobs: build-linux: name: Build Linux (x64) runs-on: ${{ github.event.inputs.ubuntu-version || 'ubuntu-22.04' }} permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Get version from tauri.conf.json id: get-version shell: bash run: | VERSION=$(grep -o '"version": "[^"]*"' frontend/src-tauri/tauri.conf.json | cut -d'"' -f4) echo "Application version: $VERSION" echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Determine build profile and bundle args id: build-profile shell: bash run: | # Determine build profile if [[ "${{ github.event.inputs.build-type }}" == "debug" ]]; then echo "profile=debug" >> "$GITHUB_OUTPUT" echo "Build profile: debug" else echo "profile=release" >> "$GITHUB_OUTPUT" echo "Build profile: release" fi # Determine bundle arguments BUNDLE_TYPE="${{ github.event.inputs.bundle-type }}" if [[ "$BUNDLE_TYPE" == "all" ]] || [[ -z "$BUNDLE_TYPE" ]]; then # Default based on Ubuntu version if [[ "${{ github.event.inputs.ubuntu-version }}" == "ubuntu-24.04" ]]; then BUNDLES="appimage,rpm" else BUNDLES="deb" fi else BUNDLES="$BUNDLE_TYPE" fi if [[ "${{ github.event.inputs.build-type }}" == "debug" ]]; then echo "args=--debug --bundles $BUNDLES" >> "$GITHUB_OUTPUT" else echo "args=--bundles $BUNDLES" >> "$GITHUB_OUTPUT" fi echo "Bundle types: $BUNDLES" - name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 8 run_install: false - name: Setup Node uses: actions/setup-node@v4 with: node-version: '20' - name: Get pnpm store directory shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - name: Setup pnpm cache uses: actions/cache@v4 with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install Rust stable uses: dtolnay/rust-toolchain@stable with: targets: x86_64-unknown-linux-gnu - name: Rust cache uses: swatinem/rust-cache@v2 with: workspaces: ". -> target" key: ${{ github.event.inputs.ubuntu-version || 'ubuntu-22.04' }}-x86_64-unknown-linux-gnu - name: Install dependencies (Ubuntu 24.04) if: github.event.inputs.ubuntu-version == 'ubuntu-24.04' run: | sudo apt-get update sudo apt-get install -y libappindicator3-dev librsvg2-dev patchelf libasound2-dev libopenblas-dev libx11-dev libxtst-dev libxrandr-dev \ libwebkit2gtk-4.1-0=2.44.0-2 \ libwebkit2gtk-4.1-dev=2.44.0-2 \ libjavascriptcoregtk-4.1-0=2.44.0-2 \ libjavascriptcoregtk-4.1-dev=2.44.0-2 \ gir1.2-javascriptcoregtk-4.1=2.44.0-2 \ gir1.2-webkit2-4.1=2.44.0-2 - name: Install dependencies (Ubuntu 22.04) if: github.event.inputs.ubuntu-version == 'ubuntu-22.04' || github.event.inputs.ubuntu-version == '' run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libasound2-dev libopenblas-dev libx11-dev libxtst-dev libxrandr-dev - name: Prepare Vulkan SDK (Ubuntu 24.04) if: github.event.inputs.ubuntu-version == 'ubuntu-24.04' run: | wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-1.3.290-noble.list https://packages.lunarg.com/vulkan/1.3.290/lunarg-vulkan-1.3.290-noble.list sudo apt update sudo apt install vulkan-sdk -y sudo apt-get install -y mesa-vulkan-drivers - name: Prepare Vulkan SDK (Ubuntu 22.04) if: github.event.inputs.ubuntu-version == 'ubuntu-22.04' || github.event.inputs.ubuntu-version == '' run: | wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-1.3.290-jammy.list https://packages.lunarg.com/vulkan/1.3.290/lunarg-vulkan-1.3.290-jammy.list sudo apt update sudo apt install vulkan-sdk -y sudo apt-get install -y mesa-vulkan-drivers - name: Install FUSE for AppImage run: | sudo apt-get update sudo apt-get install -y fuse libfuse2 - name: Install frontend dependencies run: | cd frontend pnpm install - name: Build llama-helper sidecar run: | echo "Building llama-helper sidecar (CPU mode)..." cargo build --release -p llama-helper # Copy binary to binaries directory mkdir -p frontend/src-tauri/binaries cp target/release/llama-helper frontend/src-tauri/binaries/llama-helper-x86_64-unknown-linux-gnu echo "Copied llama-helper to frontend/src-tauri/binaries/" ls -la frontend/src-tauri/binaries/ - name: Build Tauri app uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Tauri updater signing (ALWAYS enabled for .sig files) TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} # License validation RSA public key (embedded at build time) MEETILY_RSA_PUBLIC_KEY: ${{ secrets.MEETILY_RSA_PUBLIC_KEY }} # Supabase configuration (for online license verification) SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} with: projectPath: frontend args: --target x86_64-unknown-linux-gnu --features openblas ${{ steps.build-profile.outputs.args }} - name: Verify build artifacts run: | echo "Build artifacts:" find target/x86_64-unknown-linux-gnu/${{ steps.build-profile.outputs.profile }}/bundle -type f - name: Remove libwayland-client.so from AppImage if: contains(steps.build-profile.outputs.args, 'appimage') run: | # Find the AppImage file APPIMAGE_PATH=$(find target/x86_64-unknown-linux-gnu/${{ steps.build-profile.outputs.profile }}/bundle/appimage -name "*.AppImage" | head -1) if [ -n "$APPIMAGE_PATH" ]; then echo "Processing AppImage: $APPIMAGE_PATH" # Make AppImage executable chmod +x "$APPIMAGE_PATH" # Extract AppImage cd "$(dirname "$APPIMAGE_PATH")" APPIMAGE_NAME=$(basename "$APPIMAGE_PATH") # Extract using the AppImage itself "./$APPIMAGE_NAME" --appimage-extract # Remove libwayland-client.so files echo "Removing libwayland-client.so files..." find squashfs-root -name "libwayland-client.so*" -type f -delete # List what was removed for verification echo "Files remaining in lib directories:" find squashfs-root -name "lib*" -type d | head -5 | while read dir; do echo "Contents of $dir:" ls "$dir" | grep -E "(wayland|fuse)" || echo " No wayland/fuse libraries found" done # Get appimagetool wget -q https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage chmod +x appimagetool-x86_64.AppImage # Repackage AppImage with no-appstream to avoid warnings ARCH=x86_64 ./appimagetool-x86_64.AppImage --no-appstream squashfs-root "$APPIMAGE_NAME" # Clean up rm -rf squashfs-root appimagetool-x86_64.AppImage echo "libwayland-client.so removed from AppImage successfully" else echo "No AppImage found to process" fi - name: Verify DEB package if: contains(steps.build-profile.outputs.args, 'deb') run: | DEB_PATH=$(find target/x86_64-unknown-linux-gnu/${{ steps.build-profile.outputs.profile }}/bundle/deb -name "*.deb" | head -1) if [ -n "$DEB_PATH" ]; then echo "Verifying DEB package: $DEB_PATH" dpkg-deb --info "$DEB_PATH" dpkg-deb --contents "$DEB_PATH" | head -20 echo "DEB package is valid" else echo "No DEB package found to verify" fi - name: Verify RPM package if: contains(steps.build-profile.outputs.args, 'rpm') run: | RPM_PATH=$(find target/x86_64-unknown-linux-gnu/${{ steps.build-profile.outputs.profile }}/bundle/rpm -name "*.rpm" | head -1) if [ -n "$RPM_PATH" ]; then echo "Verifying RPM package: $RPM_PATH" sudo apt-get install -y rpm rpm -qip "$RPM_PATH" echo "RPM package is valid" else echo "No RPM package found to verify" fi - name: Verify AppImage if: contains(steps.build-profile.outputs.args, 'appimage') run: | APPIMAGE_PATH=$(find target/x86_64-unknown-linux-gnu/${{ steps.build-profile.outputs.profile }}/bundle/appimage -name "*.AppImage" | head -1) if [ -n "$APPIMAGE_PATH" ]; then echo "Verifying AppImage: $APPIMAGE_PATH" chmod +x "$APPIMAGE_PATH" "$APPIMAGE_PATH" --appimage-help echo "AppImage is valid" else echo "No AppImage found to verify" fi - name: Upload artifacts if: ${{ github.event.inputs.upload-artifacts == 'true' }} uses: actions/upload-artifact@v4 with: name: meetily-linux-${{ github.event.inputs.ubuntu-version || 'ubuntu-22.04' }}-x64-${{ steps.build-profile.outputs.profile }}-${{ steps.get-version.outputs.version }} path: | target/x86_64-unknown-linux-gnu/${{ steps.build-profile.outputs.profile }}/bundle/deb/*.deb target/x86_64-unknown-linux-gnu/${{ steps.build-profile.outputs.profile }}/bundle/appimage/*.AppImage target/x86_64-unknown-linux-gnu/${{ steps.build-profile.outputs.profile }}/bundle/rpm/*.rpm retention-days: 30 - name: Generate build summary run: | echo "## 🐧 Linux Build Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY echo "| **Version** | ${{ steps.get-version.outputs.version }} |" >> $GITHUB_STEP_SUMMARY echo "| **Build Profile** | ${{ steps.build-profile.outputs.profile }} |" >> $GITHUB_STEP_SUMMARY echo "| **Target** | x86_64-unknown-linux-gnu |" >> $GITHUB_STEP_SUMMARY echo "| **Ubuntu Version** | ${{ github.event.inputs.ubuntu-version || 'ubuntu-22.04' }} |" >> $GITHUB_STEP_SUMMARY echo "| **Bundle Types** | ${{ steps.build-profile.outputs.args }} |" >> $GITHUB_STEP_SUMMARY echo "| **Signed** | ${{ github.event.inputs.sign-build == 'true' && '✅ Yes' || '❌ No' }} |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### 📦 Build Artifacts" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| File Type | Count |" >> $GITHUB_STEP_SUMMARY echo "|-----------|-------|" >> $GITHUB_STEP_SUMMARY echo "| DEB Packages | $(find target/x86_64-unknown-linux-gnu/${{ steps.build-profile.outputs.profile }}/bundle/deb -name "*.deb" 2>/dev/null | wc -l) |" >> $GITHUB_STEP_SUMMARY echo "| AppImages | $(find target/x86_64-unknown-linux-gnu/${{ steps.build-profile.outputs.profile }}/bundle/appimage -name "*.AppImage" 2>/dev/null | wc -l) |" >> $GITHUB_STEP_SUMMARY echo "| RPM Packages | $(find target/x86_64-unknown-linux-gnu/${{ steps.build-profile.outputs.profile }}/bundle/rpm -name "*.rpm" 2>/dev/null | wc -l) |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "
" >> $GITHUB_STEP_SUMMARY echo "📋 File List" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY find target/x86_64-unknown-linux-gnu/${{ steps.build-profile.outputs.profile }}/bundle -type f \( -name "*.deb" -o -name "*.AppImage" -o -name "*.rpm" \) 2>/dev/null >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "
" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY ================================================ FILE: .github/workflows/build-macos.yml ================================================ name: "Build and Test - macOS" on: workflow_dispatch: inputs: build-type: description: 'Build type' required: true type: choice options: - debug - release default: release sign-build: description: 'Sign the build' required: true type: boolean default: true upload-artifacts: description: 'Upload build artifacts' required: true type: boolean default: true # Cancel duplicate workflow runs concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: RUST_BACKTRACE: 1 CARGO_TERM_COLOR: always jobs: build-macos: name: Build macOS (Apple Silicon) runs-on: macos-latest permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Get version from tauri.conf.json id: get-version shell: bash run: | VERSION=$(grep -o '"version": "[^"]*"' frontend/src-tauri/tauri.conf.json | cut -d'"' -f4) echo "Application version: $VERSION" echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Determine build profile id: build-profile shell: bash run: | if [[ "${{ github.event.inputs.build-type }}" == "debug" ]]; then echo "profile=debug" >> "$GITHUB_OUTPUT" echo "args=--debug" >> "$GITHUB_OUTPUT" echo "Build profile: debug" else echo "profile=release" >> "$GITHUB_OUTPUT" echo "args=" >> "$GITHUB_OUTPUT" echo "Build profile: release" fi - name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 8 run_install: false - name: Setup Node uses: actions/setup-node@v4 with: node-version: '20' - name: Get pnpm store directory shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - name: Setup pnpm cache uses: actions/cache@v4 with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install Rust stable uses: dtolnay/rust-toolchain@stable with: targets: aarch64-apple-darwin - name: Rust cache uses: swatinem/rust-cache@v2 with: workspaces: ". -> target" key: macos-latest-aarch64-apple-darwin - name: Install frontend dependencies run: | cd frontend pnpm install - name: Import Apple Developer Certificate if: ${{ github.event.inputs.sign-build == 'true' }} env: APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} run: | echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security default-keychain -s build.keychain security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security set-keychain-settings -t 3600 -u build.keychain security import certificate.p12 -k build.keychain -P "$APPLE_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 -v -p codesigning build.keychain - name: Verify certificate if: ${{ github.event.inputs.sign-build == 'true' }} run: | CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application") CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV echo "Certificate imported: $CERT_ID" - name: Configure build acceleration run: | echo "✓ macOS build will use Metal GPU acceleration (enabled by default)" echo "✓ CoreML acceleration available for Apple Silicon" - name: Build llama-helper sidecar run: | echo "Building llama-helper sidecar with Metal support..." cargo build --release -p llama-helper --features metal # Copy binary to binaries directory mkdir -p frontend/src-tauri/binaries cp target/release/llama-helper frontend/src-tauri/binaries/llama-helper-aarch64-apple-darwin echo "Copied llama-helper to frontend/src-tauri/binaries/" ls -la frontend/src-tauri/binaries/ - name: Cache FFmpeg binary uses: actions/cache@v4 with: path: frontend/src-tauri/binaries/ffmpeg-* key: ${{ runner.os }}-ffmpeg-${{ hashFiles('frontend/src-tauri/build.rs', 'frontend/src-tauri/build/ffmpeg.rs') }} - name: Build Tauri app (with code signing) if: ${{ github.event.inputs.sign-build == 'true' }} uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # macOS code signing APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }} # Tauri updater signing (ALWAYS enabled for .sig files) TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} # License validation RSA public key (embedded at build time) MEETILY_RSA_PUBLIC_KEY: ${{ secrets.MEETILY_RSA_PUBLIC_KEY }} # Supabase configuration (for online license verification) SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} with: projectPath: frontend args: --target aarch64-apple-darwin ${{ steps.build-profile.outputs.args }} - name: Build Tauri app (unsigned) if: ${{ github.event.inputs.sign-build != 'true' }} uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Tauri updater signing (ALWAYS enabled for .sig files) TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} # License validation RSA public key (embedded at build time) MEETILY_RSA_PUBLIC_KEY: ${{ secrets.MEETILY_RSA_PUBLIC_KEY }} # Supabase configuration (for online license verification) SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} with: projectPath: frontend args: --target aarch64-apple-darwin ${{ steps.build-profile.outputs.args }} - name: Verify build artifacts run: | echo "Build artifacts:" find target/aarch64-apple-darwin/${{ steps.build-profile.outputs.profile }}/bundle -type f - name: Verify code signing (App Bundle) if: ${{ github.event.inputs.sign-build == 'true' }} run: | APP_PATH=$(find target/aarch64-apple-darwin/${{ steps.build-profile.outputs.profile }}/bundle/macos -name "*.app" | head -1) if [ -n "$APP_PATH" ]; then echo "=== Verifying App Bundle: $APP_PATH ===" echo "" echo "Code signature details:" codesign -dv --verbose=4 "$APP_PATH" 2>&1 | grep -i "signature\|authority" echo "" echo "Verifying signature..." codesign --verify --deep --strict "$APP_PATH" && echo "✓ Code signature is valid" echo "" echo "Checking notarization..." spctl -a -vvv "$APP_PATH" && echo "✓ App is notarized and will be accepted by Gatekeeper" else echo "No App bundle found to verify" fi - name: Verify code signing (DMG) if: ${{ github.event.inputs.sign-build == 'true' }} run: | DMG_PATH=$(find target/aarch64-apple-darwin/${{ steps.build-profile.outputs.profile }}/bundle/dmg -name "*.dmg" | head -1) if [ -n "$DMG_PATH" ]; then echo "=== Verifying DMG: $DMG_PATH ===" echo "" echo "Code signature details:" codesign -dv --verbose=4 "$DMG_PATH" 2>&1 | grep -i "signature\|authority" echo "" echo "Verifying signature..." codesign --verify --deep --strict "$DMG_PATH" && echo "✓ DMG signature is valid" echo "" echo "Note: DMG contains notarized .app bundle but DMG itself may not be notarized" echo "This is acceptable - users will mount the DMG and run the notarized .app inside" else echo "No DMG found to verify" fi - name: Upload artifacts if: ${{ github.event.inputs.upload-artifacts == 'true' }} uses: actions/upload-artifact@v4 with: name: meetily-macos-aarch64-${{ steps.build-profile.outputs.profile }}-${{ steps.get-version.outputs.version }} path: | target/aarch64-apple-darwin/${{ steps.build-profile.outputs.profile }}/bundle/dmg/*.dmg target/aarch64-apple-darwin/${{ steps.build-profile.outputs.profile }}/bundle/macos/*.app target/aarch64-apple-darwin/${{ steps.build-profile.outputs.profile }}/bundle/macos/*.app.tar.gz target/aarch64-apple-darwin/${{ steps.build-profile.outputs.profile }}/bundle/macos/*.app.tar.gz.sig retention-days: 30 - name: Generate build summary run: | echo "## 🍎 macOS Build Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY echo "| **Version** | ${{ steps.get-version.outputs.version }} |" >> $GITHUB_STEP_SUMMARY echo "| **Build Profile** | ${{ steps.build-profile.outputs.profile }} |" >> $GITHUB_STEP_SUMMARY echo "| **Target** | aarch64-apple-darwin (Apple Silicon) |" >> $GITHUB_STEP_SUMMARY echo "| **Signed** | ${{ github.event.inputs.sign-build == 'true' && '✅ Yes' || '❌ No' }} |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### 📦 Build Artifacts" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| File Type | Count |" >> $GITHUB_STEP_SUMMARY echo "|-----------|-------|" >> $GITHUB_STEP_SUMMARY echo "| DMG Installers | $(find target/aarch64-apple-darwin/${{ steps.build-profile.outputs.profile }}/bundle/dmg -name "*.dmg" 2>/dev/null | wc -l) |" >> $GITHUB_STEP_SUMMARY echo "| APP Bundles | $(find target/aarch64-apple-darwin/${{ steps.build-profile.outputs.profile }}/bundle/macos -name "*.app" -type d 2>/dev/null | wc -l) |" >> $GITHUB_STEP_SUMMARY echo "| APP Archives | $(find target/aarch64-apple-darwin/${{ steps.build-profile.outputs.profile }}/bundle/macos -name "*.app.tar.gz" 2>/dev/null | wc -l) |" >> $GITHUB_STEP_SUMMARY echo "| APP Signatures | $(find target/aarch64-apple-darwin/${{ steps.build-profile.outputs.profile }}/bundle/macos -name "*.app.tar.gz.sig" 2>/dev/null | wc -l) |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "
" >> $GITHUB_STEP_SUMMARY echo "📋 File List" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY find target/aarch64-apple-darwin/${{ steps.build-profile.outputs.profile }}/bundle -type f \( -name "*.dmg" -o -name "*.tar.gz" -o -name "*.sig" \) 2>/dev/null >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "
" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY ================================================ FILE: .github/workflows/build-test.yml ================================================ name: "Build Test" on: workflow_dispatch jobs: build-test: permissions: contents: write strategy: fail-fast: false matrix: include: - platform: "macos-latest" # for Apple Silicon only (M1 and above) args: "--target aarch64-apple-darwin" target: "aarch64-apple-darwin" - platform: "ubuntu-22.04" # Build .deb on 22.04 args: "--bundles deb" target: "x86_64-unknown-linux-gnu" - platform: "ubuntu-24.04" # Build AppImage and RPM on 24.04 args: "--bundles appimage,rpm" target: "x86_64-unknown-linux-gnu" - platform: "windows-latest" args: "" target: "x86_64-pc-windows-msvc" uses: ./.github/workflows/build.yml with: platform: ${{ matrix.platform }} target: ${{ matrix.target }} build-args: ${{ matrix.args }} sign-binaries: true asset-prefix: "meetily-test" upload-artifacts: true is-debug-build: ${{ contains(matrix.args, '--debug') }} secrets: inherit ================================================ FILE: .github/workflows/build-windows.yml ================================================ name: "Build and Test - Windows" on: workflow_dispatch: inputs: build-type: description: 'Build type' required: true type: choice options: - debug - release default: release sign-build: description: 'Sign the build' required: true type: boolean default: true test-signing: description: 'Run pre-build signing test (optional)' required: false type: boolean default: false upload-artifacts: description: 'Upload build artifacts' required: true type: boolean default: true # Cancel duplicate workflow runs concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: RUST_BACKTRACE: 1 CARGO_TERM_COLOR: always jobs: build-windows: name: Build Windows (x64) runs-on: windows-latest permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Get version from tauri.conf.json id: get-version shell: bash run: | VERSION=$(grep -o '"version": "[^"]*"' frontend/src-tauri/tauri.conf.json | cut -d'"' -f4) echo "Application version: $VERSION" echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Determine build profile id: build-profile shell: bash run: | if [[ "${{ github.event.inputs.build-type }}" == "debug" ]]; then echo "profile=debug" >> "$GITHUB_OUTPUT" echo "args=--debug" >> "$GITHUB_OUTPUT" echo "Build profile: debug" else echo "profile=release" >> "$GITHUB_OUTPUT" echo "args=" >> "$GITHUB_OUTPUT" echo "Build profile: release" fi - name: Setup DigiCert Environment if: ${{ github.event.inputs.sign-build == 'true' }} shell: pwsh run: | # Set environment variables "SM_HOST=${{ secrets.SM_HOST }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "SM_API_KEY=${{ secrets.SM_API_KEY }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "SM_CODE_SIGNING_CERT_SHA1_HASH=${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append # Decode and save client certificate using PowerShell $certPath = "D:\Certificate_pkcs12.p12" $base64String = "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}" $certBytes = [System.Convert]::FromBase64String($base64String) [System.IO.File]::WriteAllBytes($certPath, $certBytes) # Verify the certificate file was created and has content if (Test-Path $certPath) { $certFile = Get-Item $certPath Write-Host "Certificate file created: $certPath" Write-Host " Size: $($certFile.Length) bytes" Write-Host " Last Modified: $($certFile.LastWriteTime)" # Verify it's a valid PKCS12 file by checking if we can load it try { $testCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certPath, "${{ secrets.SM_CLIENT_CERT_PASSWORD }}") Write-Host " ✓ Certificate file is valid PKCS12 format" Write-Host " ✓ Password is correct" Write-Host " Certificate Subject: $($testCert.Subject)" Write-Host " Certificate Thumbprint: $($testCert.Thumbprint)" } catch { Write-Warning "⚠ Certificate validation failed" Write-Warning "Error: $($_.Exception.Message)" Write-Warning "This may cause issues with signing, but we'll continue..." } } else { Write-Error "Certificate file was not created at $certPath" exit 1 } # Set the environment variable with Windows-style path "SM_CLIENT_CERT_FILE=D:\Certificate_pkcs12.p12" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: Setup DigiCert KeyLocker if: ${{ github.event.inputs.sign-build == 'true' }} uses: digicert/ssm-code-signing@v1.1.1 - name: Sync Certificate to Windows Certificate Store if: ${{ github.event.inputs.sign-build == 'true' }} shell: pwsh run: | Write-Host "============================================================" Write-Host "=== SYNCING CERTIFICATE TO WINDOWS CERTIFICATE STORE ===" Write-Host "============================================================" Write-Host "This step syncs the DigiCert certificate from KeyLocker HSM" Write-Host "to the Windows Certificate Store (required for signing)" Write-Host "" # First, get the keypair alias from smctl keypair ls Write-Host "Retrieving keypair alias from DigiCert KeyLocker..." $keypairOutput = smctl keypair ls 2>&1 | Out-String Write-Host $keypairOutput # Extract the alias (looking for pattern like "key_XXXXXXXXXX") $aliasMatch = [regex]::Match($keypairOutput, 'key_\d+') if ($aliasMatch.Success) { $keypairAlias = $aliasMatch.Value Write-Host "✓ Found keypair alias: $keypairAlias" } else { Write-Error "✗ Could not find keypair alias in smctl output" Write-Error "Output was: $keypairOutput" exit 1 } Write-Host "" Write-Host "Syncing certificate using alias: $keypairAlias" $certsyncOutput = smctl windows certsync --keypair-alias $keypairAlias 2>&1 Write-Host $certsyncOutput if ($LASTEXITCODE -ne 0) { Write-Host "" Write-Error "✗ Certificate sync FAILED" Write-Error "Exit code: $LASTEXITCODE" Write-Error "Output: $certsyncOutput" Write-Error "" Write-Error "Possible causes:" Write-Error " 1. Keypair alias '$keypairAlias' not found in KeyLocker" Write-Error " 2. Certificate is revoked or expired" Write-Error " 3. API credentials are invalid" exit 1 } Write-Host "" Write-Host "✓ Certificate synced successfully" Write-Host "" # Verify certificate is now in Windows store Write-Host "Verifying certificate in Windows Certificate Store..." $cert = Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq $env:SM_CODE_SIGNING_CERT_SHA1_HASH } | Select-Object -First 1 if (-not $cert) { # Try LocalMachine store $cert = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq $env:SM_CODE_SIGNING_CERT_SHA1_HASH } | Select-Object -First 1 } if ($cert) { Write-Host "✓ Certificate found in Windows Certificate Store" Write-Host " Subject: $($cert.Subject)" Write-Host " Issuer: $($cert.Issuer)" Write-Host " Thumbprint: $($cert.Thumbprint)" Write-Host " Valid From: $($cert.NotBefore)" Write-Host " Valid Until: $($cert.NotAfter)" } else { Write-Warning "Certificate not found in Windows Certificate Store after sync" Write-Warning "Signing may fail. Continuing anyway..." } # Export keypair alias for Tauri signCommand Write-Host "" Write-Host "Exporting DIGICERT_KEYPAIR_ALIAS for Tauri signCommand..." "DIGICERT_KEYPAIR_ALIAS=$keypairAlias" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append Write-Host "✓ DIGICERT_KEYPAIR_ALIAS=$keypairAlias" Write-Host "" Write-Host "============================================================" Write-Host "" - name: Verify DigiCert Setup if: ${{ github.event.inputs.sign-build == 'true' }} shell: pwsh run: | Write-Host "=== DigiCert Setup Verification ===" Write-Host "" # Verify SMCTL is available Write-Host "SMCTL Version:" smctl --version Write-Host "" # Check environment variables Write-Host "Environment Variables:" Write-Host " SM_HOST: $env:SM_HOST" Write-Host " SM_API_KEY: $($env:SM_API_KEY.Substring(0, [Math]::Min(20, $env:SM_API_KEY.Length)))..." Write-Host " SM_CLIENT_CERT_FILE: $env:SM_CLIENT_CERT_FILE" Write-Host " SM_CLIENT_CERT_PASSWORD: $(if ($env:SM_CLIENT_CERT_PASSWORD) { '***SET***' } else { 'NOT SET' })" Write-Host "" # Verify client certificate file exists and check size if (Test-Path $env:SM_CLIENT_CERT_FILE) { $certFile = Get-Item $env:SM_CLIENT_CERT_FILE Write-Host "Client Certificate File:" Write-Host " Path: $($certFile.FullName)" Write-Host " Size: $($certFile.Length) bytes" Write-Host " Last Modified: $($certFile.LastWriteTime)" } else { Write-Error "Client certificate file NOT FOUND: $env:SM_CLIENT_CERT_FILE" } Write-Host "" # List available keypairs (verifies API authentication works) Write-Host "Available Keypairs:" smctl keypair ls Write-Host "" # Verify certificate (healthcheck) Write-Host "DigiCert Healthcheck:" smctl healthcheck Write-Host "" # Note: Healthcheck may show cert path/password warning but if keypair ls works, # the signing should still work. We'll test in the pre-build signing test. - name: Pre-Build Signing Test if: ${{ github.event.inputs.sign-build == 'true' && github.event.inputs.test-signing == 'true' }} shell: pwsh run: | Write-Host "============================================================" Write-Host "=== PRE-BUILD SIGNING TEST ===" Write-Host "============================================================" Write-Host "Testing signing before Tauri build to fail fast if signing is broken" Write-Host "" Write-Host "Environment Configuration:" Write-Host " SM_HOST: $($env:SM_HOST ?? 'NOT SET')" Write-Host " SM_API_KEY: $(if ($env:SM_API_KEY) { $env:SM_API_KEY.Substring(0, [Math]::Min(20, $env:SM_API_KEY.Length)) + '...' } else { 'NOT SET' })" Write-Host " SM_CLIENT_CERT_FILE: $($env:SM_CLIENT_CERT_FILE ?? 'NOT SET')" Write-Host " SM_CODE_SIGNING_CERT_SHA1_HASH: $($env:SM_CODE_SIGNING_CERT_SHA1_HASH ?? 'NOT SET')" # Verify environment is set up if (-not $env:SM_HOST -or -not $env:SM_API_KEY) { Write-Error "DigiCert environment not configured. Make sure 'Sign the build' is enabled." exit 1 } Write-Host "" # Verify client certificate exists if (Test-Path $env:SM_CLIENT_CERT_FILE) { Write-Host "✓ Client certificate file exists" $certInfo = Get-Item $env:SM_CLIENT_CERT_FILE Write-Host " File size: $($certInfo.Length) bytes" } else { Write-Error "✗ Client certificate file not found: $env:SM_CLIENT_CERT_FILE" exit 1 } Write-Host "" Write-Host "--- Available Keypairs in DigiCert KeyLocker ---" $keypairOutput = smctl keypair ls 2>&1 Write-Host $keypairOutput Write-Host "" Write-Host "Note: Certificate was already synced to Windows Certificate Store in previous step" Write-Host "" # Create a minimal test executable using dotnet Write-Host "--- Creating Test Executable ---" Write-Host "Creating minimal C# console app..." # Create simple C# file $testCode = @" using System; class Program { static void Main() { Console.WriteLine("Test signing executable"); } } "@ Set-Content -Path "TestSigning.cs" -Value $testCode # Create minimal project file $projectFile = @" Exe net8.0 "@ Set-Content -Path "TestSigning.csproj" -Value $projectFile # Build executable in one command Write-Host "Building with dotnet publish..." dotnet publish TestSigning.csproj -c Release -o . --self-contained false 2>&1 | Out-Null if ($LASTEXITCODE -ne 0) { Write-Error "Failed to build test executable" Write-Host "Attempting to list dotnet info for debugging:" dotnet --version exit 1 } if (-not (Test-Path "TestSigning.exe")) { Write-Error "TestSigning.exe was not created" Get-ChildItem -Filter "*.exe" | Format-Table exit 1 } $exeInfo = Get-Item "TestSigning.exe" Write-Host "✓ Test executable compiled successfully" Write-Host " File: $($exeInfo.FullName)" Write-Host " Size: $($exeInfo.Length) bytes" # Verify unsigned state Write-Host "" Write-Host "--- Checking Unsigned State ---" $unsignedSig = Get-AuthenticodeSignature -FilePath "TestSigning.exe" Write-Host "Pre-signing status: $($unsignedSig.Status) (expected: NotSigned)" # Sign the test executable with signtool (certificate already in Windows store) Write-Host "" Write-Host "--- Signing Test Executable with SignTool ---" Write-Host "Certificate is already synced to Windows Certificate Store" Write-Host "Detecting certificate store location..." Write-Host "" # Extract keypair alias (needed for /k flag with /csp) $aliasMatch = [regex]::Match($keypairOutput, 'key_\d+') if ($aliasMatch.Success) { $keypairAlias = $aliasMatch.Value Write-Host "✓ Found keypair alias: $keypairAlias" } else { Write-Error "✗ Could not find keypair alias in smctl output" exit 1 } Write-Host "" # Detect which store the certificate is in $certCurrentUser = Get-ChildItem -Path Cert:\CurrentUser\My -ErrorAction SilentlyContinue | Where-Object { $_.Thumbprint -eq $env:SM_CODE_SIGNING_CERT_SHA1_HASH } | Select-Object -First 1 $certLocalMachine = Get-ChildItem -Path Cert:\LocalMachine\My -ErrorAction SilentlyContinue | Where-Object { $_.Thumbprint -eq $env:SM_CODE_SIGNING_CERT_SHA1_HASH } | Select-Object -First 1 $storeFlag = "" $storeName = "" if ($certCurrentUser) { Write-Host "✓ Certificate found in CurrentUser\My store" $storeFlag = "/s My" $storeName = "CurrentUser\My" } elseif ($certLocalMachine) { Write-Host "✓ Certificate found in LocalMachine\My store" $storeFlag = "/sm /s My" $storeName = "LocalMachine\My" } else { Write-Error "✗ Certificate not found in CurrentUser\My or LocalMachine\My stores" Write-Error "SHA1 hash: $env:SM_CODE_SIGNING_CERT_SHA1_HASH" exit 1 } Write-Host "Using store: $storeName" Write-Host "SHA1 hash: $env:SM_CODE_SIGNING_CERT_SHA1_HASH" Write-Host "Keypair alias: $keypairAlias" Write-Host "" # Test using the exact same command as tauri.conf.json signCommand Write-Host "Testing sign-windows.ps1 script (same as Tauri signCommand)..." Write-Host "Command: powershell -ExecutionPolicy Bypass -File frontend/src-tauri/scripts/sign-windows.ps1 -FilePath TestSigning.exe" Write-Host "" # Set DIGICERT_KEYPAIR_ALIAS for the script (already exported to GITHUB_ENV, but set locally too) $env:DIGICERT_KEYPAIR_ALIAS = $keypairAlias $signOutput = powershell -ExecutionPolicy Bypass -File "frontend/src-tauri/scripts/sign-windows.ps1" -FilePath "TestSigning.exe" 2>&1 Write-Host $signOutput if ($LASTEXITCODE -ne 0) { Write-Host "" Write-Host "============================================================" Write-Error "✗✗✗ SIGNING FAILED ✗✗✗" Write-Host "============================================================" Write-Error "Exit code: $LASTEXITCODE" Write-Error "Script output: $signOutput" Write-Error "" Write-Error "Certificate was found in: $storeName" Write-Error "DIGICERT_KEYPAIR_ALIAS: $keypairAlias" Write-Error "" exit 1 } Write-Host "" Write-Host "✓ Test executable signed successfully using sign-windows.ps1" # Verify the signature Write-Host "" Write-Host "--- Verifying Signature ---" $signature = Get-AuthenticodeSignature -FilePath "TestSigning.exe" Write-Host "Signature Details:" Write-Host " Status: $($signature.Status)" Write-Host " Status Message: $($signature.StatusMessage)" Write-Host " Signer Certificate:" Write-Host " Subject: $($signature.SignerCertificate.Subject)" Write-Host " Issuer: $($signature.SignerCertificate.Issuer)" Write-Host " Thumbprint: $($signature.SignerCertificate.Thumbprint)" Write-Host " Valid From: $($signature.SignerCertificate.NotBefore)" Write-Host " Valid To: $($signature.SignerCertificate.NotAfter)" if ($signature.TimeStamperCertificate) { Write-Host " Timestamp Certificate:" Write-Host " Subject: $($signature.TimeStamperCertificate.Subject)" Write-Host " Issuer: $($signature.TimeStamperCertificate.Issuer)" } # Validate signature if ($signature.Status -ne 'Valid') { Write-Host "" Write-Host "============================================================" Write-Error "✗✗✗ SIGNATURE VERIFICATION FAILED ✗✗✗" Write-Host "============================================================" Write-Error "Expected Status: Valid" Write-Error "Actual Status: $($signature.Status)" Write-Error "Status Message: $($signature.StatusMessage)" exit 1 } if (-not $signature.TimeStamperCertificate) { Write-Host "" Write-Host "============================================================" Write-Error "✗✗✗ MISSING TIMESTAMP ✗✗✗" Write-Host "============================================================" Write-Error "Timestamp certificate is missing!" Write-Error "Without timestamp, signature will become invalid when cert expires" exit 1 } Write-Host "" Write-Host "✓ Signature verified successfully" Write-Host "✓ Timestamp present" Write-Host "" Write-Host "--- Cleanup ---" Remove-Item "TestSigning.exe" -ErrorAction SilentlyContinue Remove-Item "TestSigning.cs" -ErrorAction SilentlyContinue Remove-Item "TestSigning.csproj" -ErrorAction SilentlyContinue Remove-Item "TestSigning.dll" -ErrorAction SilentlyContinue Remove-Item "TestSigning.pdb" -ErrorAction SilentlyContinue Remove-Item "TestSigning.deps.json" -ErrorAction SilentlyContinue Remove-Item "TestSigning.runtimeconfig.json" -ErrorAction SilentlyContinue Write-Host "✓ Test files cleaned up" Write-Host "" Write-Host "============================================================" Write-Host "=== PRE-BUILD SIGNING TEST PASSED ===" Write-Host "============================================================" Write-Host "Signing infrastructure is working correctly" Write-Host "Proceeding with Tauri build..." Write-Host "" - name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 8 run_install: false - name: Setup Node uses: actions/setup-node@v4 with: node-version: '20' - name: Get pnpm store directory shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - name: Setup pnpm cache uses: actions/cache@v4 with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install Rust stable uses: dtolnay/rust-toolchain@stable with: targets: x86_64-pc-windows-msvc - name: Rust cache uses: swatinem/rust-cache@v2 with: workspaces: ". -> target" key: windows-x64-vulkan-v2 - name: Install Vulkan SDK uses: humbletim/install-vulkan-sdk@v1.2 with: version: 1.4.309.0 cache: true - name: Verify and Configure Vulkan SDK shell: pwsh run: | Write-Host "=== Vulkan SDK Configuration ===" Write-Host "" # Get VULKAN_SDK from environment $vulkanSdk = $env:VULKAN_SDK Write-Host "VULKAN_SDK: $vulkanSdk" if (-not $vulkanSdk -or -not (Test-Path $vulkanSdk)) { Write-Error "VULKAN_SDK not set or path does not exist" exit 1 } # Verify critical directories exist $binDir = Join-Path $vulkanSdk "Bin" $libDir = Join-Path $vulkanSdk "Lib" $includeDir = Join-Path $vulkanSdk "Include" Write-Host "" Write-Host "Directory structure:" Write-Host " Bin: $(if (Test-Path $binDir) { 'EXISTS' } else { 'MISSING' })" Write-Host " Lib: $(if (Test-Path $libDir) { 'EXISTS' } else { 'MISSING' })" Write-Host " Include: $(if (Test-Path $includeDir) { 'EXISTS' } else { 'MISSING' })" # Check for glslc and glslangValidator $glslc = Join-Path $binDir "glslc.exe" $glslangValidator = Join-Path $binDir "glslangValidator.exe" Write-Host "" Write-Host "Shader compilers:" Write-Host " glslc: $(if (Test-Path $glslc) { 'FOUND' } else { 'MISSING' })" Write-Host " glslangValidator: $(if (Test-Path $glslangValidator) { 'FOUND' } else { 'MISSING' })" # Check for vulkan-1.lib (required for linking) $vulkanLib = Join-Path $libDir "vulkan-1.lib" Write-Host "" Write-Host "Libraries:" Write-Host " vulkan-1.lib: $(if (Test-Path $vulkanLib) { 'FOUND' } else { 'MISSING' })" # Ensure Bin is in PATH for shader compilers $currentPath = $env:PATH if (-not $currentPath.Contains($binDir)) { Write-Host "" Write-Host "Adding Vulkan SDK Bin to PATH..." "PATH=$binDir;$currentPath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append } # Set additional environment variables for CMake Write-Host "" Write-Host "Setting CMake-related environment variables..." "Vulkan_LIBRARY=$libDir\vulkan-1.lib" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "Vulkan_INCLUDE_DIR=$includeDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "VK_SDK_PATH=$vulkanSdk" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append # List SDK contents for debugging Write-Host "" Write-Host "Vulkan SDK Bin contents:" Get-ChildItem $binDir -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "*.exe" } | Select-Object -First 10 | ForEach-Object { Write-Host " $($_.Name)" } Write-Host "" Write-Host "Vulkan SDK Lib contents:" Get-ChildItem $libDir -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "*.lib" } | Select-Object -First 10 | ForEach-Object { Write-Host " $($_.Name)" } Write-Host "" Write-Host "=== Vulkan SDK Configuration Complete ===" - name: Copy Vulkan runtime for bundling shell: pwsh run: | # Create runtime directory $runtimeDir = "frontend/src-tauri/vulkan-runtime" if (!(Test-Path $runtimeDir)) { New-Item -ItemType Directory -Force -Path $runtimeDir | Out-Null } # Find Vulkan SDK - check multiple possible locations $vulkanSdk = $env:VULKAN_SDK Write-Host "VULKAN_SDK env: $vulkanSdk" # Common installation paths $possiblePaths = @( "$vulkanSdk\Bin\vulkan-1.dll", "$vulkanSdk\runtime\x64\vulkan-1.dll", "C:\VulkanSDK\1.4.309.0\Bin\vulkan-1.dll", "C:\VulkanSDK\1.4.309.0\runtime\x64\vulkan-1.dll", "$env:ProgramFiles\VulkanSDK\Bin\vulkan-1.dll", "$env:SystemRoot\System32\vulkan-1.dll" ) $vulkanDll = $null foreach ($path in $possiblePaths) { Write-Host "Checking: $path" if (Test-Path $path) { $vulkanDll = $path Write-Host "Found vulkan-1.dll at: $path" break } } if ($vulkanDll) { Copy-Item $vulkanDll -Destination $runtimeDir Write-Host "Copied vulkan-1.dll to $runtimeDir" } else { Write-Host "Searching for vulkan-1.dll on system..." $found = Get-ChildItem -Path "C:\" -Filter "vulkan-1.dll" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 if ($found) { Write-Host "Found at: $($found.FullName)" Copy-Item $found.FullName -Destination $runtimeDir } else { Write-Warning "vulkan-1.dll not found - Vulkan apps may not work without it" Write-Host "System will use the user's installed Vulkan runtime" } } # Verify Write-Host "Contents of vulkan-runtime directory:" Get-ChildItem $runtimeDir -ErrorAction SilentlyContinue - name: Install frontend dependencies run: | cd frontend pnpm install - name: Build llama-helper sidecar shell: pwsh run: | # Build llama-helper without GPU features (CPU-only) # Note: Vulkan build has CMake race condition issues with llama-cpp-sys-2 # The Tauri app itself uses whisper-rs with Vulkan for transcription # llama-helper is for LLM inference and works fine on CPU Write-Host "Building llama-helper sidecar (CPU-only)..." cargo build --release -p llama-helper if ($LASTEXITCODE -ne 0) { Write-Error "Failed to build llama-helper" exit $LASTEXITCODE } # Copy binary to binaries directory New-Item -ItemType Directory -Force -Path "frontend/src-tauri/binaries" | Out-Null Copy-Item "target/release/llama-helper.exe" -Destination "frontend/src-tauri/binaries/llama-helper-x86_64-pc-windows-msvc.exe" Write-Host "Copied llama-helper to frontend/src-tauri/binaries/" Get-ChildItem "frontend/src-tauri/binaries/" - name: Cache FFmpeg binary uses: actions/cache@v4 with: path: frontend/src-tauri/binaries/ffmpeg-*.exe key: ${{ runner.os }}-ffmpeg-${{ hashFiles('frontend/src-tauri/build.rs', 'frontend/src-tauri/build/ffmpeg.rs') }} - name: Build Tauri app uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Tauri updater signing (ALWAYS enabled for .sig files) TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} # License validation RSA public key (embedded at build time) MEETILY_RSA_PUBLIC_KEY: ${{ secrets.MEETILY_RSA_PUBLIC_KEY }} # Supabase configuration (for online license verification) SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} with: projectPath: frontend args: --target x86_64-pc-windows-msvc --features vulkan ${{ steps.build-profile.outputs.args }} - name: Verify build artifacts shell: bash run: | echo "## Build Artifacts" find target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle -type f \( -name "*.msi" -o -name "*.exe" \) -exec ls -lh {} \; - name: Upload artifacts if: ${{ github.event.inputs.upload-artifacts == 'true' }} uses: actions/upload-artifact@v4 with: name: meetily-windows-x64-${{ steps.build-profile.outputs.profile }}-${{ steps.get-version.outputs.version }} path: | target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/msi/*.msi target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/msi/*.msi.sig target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/nsis/*.exe target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/nsis/*.exe.sig retention-days: 30 - name: Generate build summary shell: bash run: | echo "## 🪟 Windows Build Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY echo "| **Version** | ${{ steps.get-version.outputs.version }} |" >> $GITHUB_STEP_SUMMARY echo "| **Build Profile** | ${{ steps.build-profile.outputs.profile }} |" >> $GITHUB_STEP_SUMMARY echo "| **Target** | x86_64-pc-windows-msvc |" >> $GITHUB_STEP_SUMMARY echo "| **Signed** | ${{ (github.event.inputs.sign-build == 'true') && '✅ Yes' || '❌ No' }} |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### 📦 Build Artifacts" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| File Type | Count |" >> $GITHUB_STEP_SUMMARY echo "|-----------|-------|" >> $GITHUB_STEP_SUMMARY echo "| MSI Installers | $(find target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/msi -name "*.msi" 2>/dev/null | wc -l) |" >> $GITHUB_STEP_SUMMARY echo "| MSI Signatures | $(find target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/msi -name "*.msi.sig" 2>/dev/null | wc -l) |" >> $GITHUB_STEP_SUMMARY echo "| NSIS Installers | $(find target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/nsis -name "*.exe" 2>/dev/null | wc -l) |" >> $GITHUB_STEP_SUMMARY echo "| NSIS Signatures | $(find target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/nsis -name "*.exe.sig" 2>/dev/null | wc -l) |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "
" >> $GITHUB_STEP_SUMMARY echo "📋 File List" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY find target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle -type f \( -name "*.msi" -o -name "*.exe" -o -name "*.sig" \) 2>/dev/null >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "
" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY ================================================ FILE: .github/workflows/build.yml ================================================ name: "Build" on: workflow_call: inputs: platform: required: true type: string target: required: true type: string build-args: required: false type: string default: "" release-id: required: false type: string asset-prefix: required: false type: string default: "meetily" asset-name-pattern: required: false type: string default: "" upload-artifacts: required: false type: boolean default: false sign-binaries: required: false type: boolean default: false repository: required: false type: string ref: required: false type: string default: ${{ github.ref }} is-debug-build: required: false type: boolean default: false jobs: build: permissions: contents: write runs-on: ${{ inputs.platform }} steps: - name: Checkout repository uses: actions/checkout@v4 with: repository: ${{ inputs.repository }} ref: ${{ inputs.ref }} fetch-depth: 0 - name: Get version from tauri.conf.json. id: get-version shell: bash run: | VERSION=$(grep -o '"version": "[^"]*"' frontend/src-tauri/tauri.conf.json | cut -d'"' -f4) echo "Application version from tauri.conf.json: $VERSION" echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Determine build profile id: build-profile shell: bash run: | if [[ "${{ inputs.is-debug-build }}" == "true" ]]; then echo "profile=debug" >> "$GITHUB_OUTPUT" echo "Build profile: debug" else echo "profile=release" >> "$GITHUB_OUTPUT" echo "Build profile: release" fi - name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 8 run_install: false - name: Setup Node uses: actions/setup-node@v4 with: node-version: '20' - name: Get pnpm store directory shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - name: Setup pnpm cache uses: actions/cache@v4 with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: # Only aarch64-apple-darwin for Apple Silicon (no Intel support) targets: ${{ contains(inputs.platform, 'macos') && 'aarch64-apple-darwin' || '' }} - name: Rust cache uses: swatinem/rust-cache@v2 with: workspaces: ". -> target" key: ${{ inputs.platform }}-${{ inputs.target }}-vulkan-v2 - name: install dependencies (ubuntu 24.04) if: contains(inputs.platform, 'ubuntu-24.04') run: | sudo apt-get update sudo apt-get install -y libappindicator3-dev librsvg2-dev patchelf libasound2-dev libopenblas-dev libx11-dev libxtst-dev libxrandr-dev \ libwebkit2gtk-4.1-0=2.44.0-2 \ libwebkit2gtk-4.1-dev=2.44.0-2 \ libjavascriptcoregtk-4.1-0=2.44.0-2 \ libjavascriptcoregtk-4.1-dev=2.44.0-2 \ gir1.2-javascriptcoregtk-4.1=2.44.0-2 \ gir1.2-webkit2-4.1=2.44.0-2 - name: install dependencies (ubuntu 22.04) if: contains(inputs.platform, 'ubuntu-22.04') run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libasound2-dev libopenblas-dev libx11-dev libxtst-dev libxrandr-dev - name: Install Vulkan SDK (Windows) if: contains(inputs.platform, 'windows') uses: humbletim/install-vulkan-sdk@v1.2 with: version: 1.4.309.0 cache: true - name: Verify and Configure Vulkan SDK (Windows) if: contains(inputs.platform, 'windows') shell: pwsh run: | Write-Host "=== Vulkan SDK Configuration ===" $vulkanSdk = $env:VULKAN_SDK Write-Host "VULKAN_SDK: $vulkanSdk" if (-not $vulkanSdk -or -not (Test-Path $vulkanSdk)) { Write-Error "VULKAN_SDK not set or path does not exist" exit 1 } # Verify critical directories exist $binDir = Join-Path $vulkanSdk "Bin" $libDir = Join-Path $vulkanSdk "Lib" $includeDir = Join-Path $vulkanSdk "Include" Write-Host "Directory structure:" Write-Host " Bin: $(if (Test-Path $binDir) { 'EXISTS' } else { 'MISSING' })" Write-Host " Lib: $(if (Test-Path $libDir) { 'EXISTS' } else { 'MISSING' })" Write-Host " Include: $(if (Test-Path $includeDir) { 'EXISTS' } else { 'MISSING' })" # Check for glslc and glslangValidator $glslc = Join-Path $binDir "glslc.exe" $glslangValidator = Join-Path $binDir "glslangValidator.exe" Write-Host "Shader compilers:" Write-Host " glslc: $(if (Test-Path $glslc) { 'FOUND' } else { 'MISSING' })" Write-Host " glslangValidator: $(if (Test-Path $glslangValidator) { 'FOUND' } else { 'MISSING' })" # Ensure Bin is in PATH for shader compilers $currentPath = $env:PATH if (-not $currentPath.Contains($binDir)) { Write-Host "Adding Vulkan SDK Bin to PATH..." "PATH=$binDir;$currentPath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append } # Set additional environment variables for CMake Write-Host "Setting CMake-related environment variables..." "Vulkan_LIBRARY=$libDir\vulkan-1.lib" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "Vulkan_INCLUDE_DIR=$includeDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "VK_SDK_PATH=$vulkanSdk" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append Write-Host "=== Vulkan SDK Configuration Complete ===" - name: Copy Vulkan runtime for bundling (Windows) if: contains(inputs.platform, 'windows') shell: pwsh run: | # Create runtime directory $runtimeDir = "frontend/src-tauri/vulkan-runtime" if (!(Test-Path $runtimeDir)) { New-Item -ItemType Directory -Force -Path $runtimeDir | Out-Null } # Find Vulkan SDK - check multiple possible locations $vulkanSdk = $env:VULKAN_SDK Write-Host "VULKAN_SDK env: $vulkanSdk" # Common installation paths $possiblePaths = @( "$vulkanSdk\Bin\vulkan-1.dll", "$vulkanSdk\runtime\x64\vulkan-1.dll", "C:\VulkanSDK\1.4.309.0\Bin\vulkan-1.dll", "C:\VulkanSDK\1.4.309.0\runtime\x64\vulkan-1.dll", "$env:ProgramFiles\VulkanSDK\Bin\vulkan-1.dll", "$env:SystemRoot\System32\vulkan-1.dll" ) $vulkanDll = $null foreach ($path in $possiblePaths) { Write-Host "Checking: $path" if (Test-Path $path) { $vulkanDll = $path Write-Host "Found vulkan-1.dll at: $path" break } } if ($vulkanDll) { Copy-Item $vulkanDll -Destination $runtimeDir Write-Host "Copied vulkan-1.dll to $runtimeDir" } else { Write-Host "Searching for vulkan-1.dll on system..." $found = Get-ChildItem -Path "C:\" -Filter "vulkan-1.dll" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 if ($found) { Write-Host "Found at: $($found.FullName)" Copy-Item $found.FullName -Destination $runtimeDir } else { Write-Warning "vulkan-1.dll not found - Vulkan apps may not work without it" Write-Host "System will use the user's installed Vulkan runtime" } } # Verify Write-Host "Contents of vulkan-runtime directory:" Get-ChildItem $runtimeDir -ErrorAction SilentlyContinue - name: Setup DigiCert Environment if: contains(inputs.platform, 'windows') && inputs.sign-binaries shell: pwsh run: | # Set environment variables "SM_HOST=${{ secrets.SM_HOST }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "SM_API_KEY=${{ secrets.SM_API_KEY }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "SM_CODE_SIGNING_CERT_SHA1_HASH=${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append # Decode and save client certificate using PowerShell $certPath = "D:\Certificate_pkcs12.p12" $base64String = "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}" $certBytes = [System.Convert]::FromBase64String($base64String) [System.IO.File]::WriteAllBytes($certPath, $certBytes) # Verify the certificate file was created and has content if (Test-Path $certPath) { $certFile = Get-Item $certPath Write-Host "Certificate file created: $certPath" Write-Host " Size: $($certFile.Length) bytes" Write-Host " Last Modified: $($certFile.LastWriteTime)" # Verify it's a valid PKCS12 file by checking if we can load it try { $testCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certPath, "${{ secrets.SM_CLIENT_CERT_PASSWORD }}") Write-Host " ✓ Certificate file is valid PKCS12 format" Write-Host " ✓ Password is correct" Write-Host " Certificate Subject: $($testCert.Subject)" Write-Host " Certificate Thumbprint: $($testCert.Thumbprint)" } catch { Write-Warning "⚠ Certificate validation failed" Write-Warning "Error: $($_.Exception.Message)" Write-Warning "This may cause issues with signing, but we'll continue..." } } else { Write-Error "Certificate file was not created at $certPath" exit 1 } # Set the environment variable with Windows-style path "SM_CLIENT_CERT_FILE=D:\Certificate_pkcs12.p12" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: Setup DigiCert KeyLocker if: contains(inputs.platform, 'windows') && inputs.sign-binaries uses: digicert/ssm-code-signing@v1.1.1 - name: Sync Certificate to Windows Certificate Store if: contains(inputs.platform, 'windows') && inputs.sign-binaries shell: pwsh run: | Write-Host "============================================================" Write-Host "=== SYNCING CERTIFICATE TO WINDOWS CERTIFICATE STORE ===" Write-Host "============================================================" Write-Host "This step syncs the DigiCert certificate from KeyLocker HSM" Write-Host "to the Windows Certificate Store (required for signing)" Write-Host "" # First, get the keypair alias from smctl keypair ls Write-Host "Retrieving keypair alias from DigiCert KeyLocker..." $keypairOutput = smctl keypair ls 2>&1 | Out-String Write-Host $keypairOutput # Extract the alias (looking for pattern like "key_XXXXXXXXXX") $aliasMatch = [regex]::Match($keypairOutput, 'key_\d+') if ($aliasMatch.Success) { $keypairAlias = $aliasMatch.Value Write-Host "✓ Found keypair alias: $keypairAlias" } else { Write-Error "✗ Could not find keypair alias in smctl output" Write-Error "Output was: $keypairOutput" exit 1 } Write-Host "" Write-Host "Syncing certificate using alias: $keypairAlias" $certsyncOutput = smctl windows certsync --keypair-alias $keypairAlias 2>&1 Write-Host $certsyncOutput if ($LASTEXITCODE -ne 0) { Write-Host "" Write-Error "✗ Certificate sync FAILED" Write-Error "Exit code: $LASTEXITCODE" Write-Error "Output: $certsyncOutput" Write-Error "" Write-Error "Possible causes:" Write-Error " 1. Keypair alias '$keypairAlias' not found in KeyLocker" Write-Error " 2. Certificate is revoked or expired" Write-Error " 3. API credentials are invalid" exit 1 } Write-Host "" Write-Host "✓ Certificate synced successfully" Write-Host "" # Verify certificate is now in Windows store Write-Host "Verifying certificate in Windows Certificate Store..." $cert = Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq $env:SM_CODE_SIGNING_CERT_SHA1_HASH } | Select-Object -First 1 if (-not $cert) { # Try LocalMachine store $cert = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq $env:SM_CODE_SIGNING_CERT_SHA1_HASH } | Select-Object -First 1 } if ($cert) { Write-Host "✓ Certificate found in Windows Certificate Store" Write-Host " Subject: $($cert.Subject)" Write-Host " Issuer: $($cert.Issuer)" Write-Host " Thumbprint: $($cert.Thumbprint)" Write-Host " Valid From: $($cert.NotBefore)" Write-Host " Valid Until: $($cert.NotAfter)" } else { Write-Warning "Certificate not found in Windows Certificate Store after sync" Write-Warning "Signing may fail. Continuing anyway..." } # Export keypair alias for Tauri signCommand Write-Host "" Write-Host "Exporting DIGICERT_KEYPAIR_ALIAS for Tauri signCommand..." "DIGICERT_KEYPAIR_ALIAS=$keypairAlias" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append Write-Host "✓ DIGICERT_KEYPAIR_ALIAS=$keypairAlias" Write-Host "" Write-Host "============================================================" Write-Host "" - name: Verify DigiCert Setup if: contains(inputs.platform, 'windows') && inputs.sign-binaries shell: pwsh run: | Write-Host "=== DigiCert Setup Verification ===" Write-Host "" # Verify SMCTL is available Write-Host "SMCTL Version:" smctl --version Write-Host "" # Check environment variables Write-Host "Environment Variables:" Write-Host " SM_HOST: $env:SM_HOST" Write-Host " SM_API_KEY: $($env:SM_API_KEY.Substring(0, [Math]::Min(20, $env:SM_API_KEY.Length)))..." Write-Host " SM_CLIENT_CERT_FILE: $env:SM_CLIENT_CERT_FILE" Write-Host " SM_CLIENT_CERT_PASSWORD: $(if ($env:SM_CLIENT_CERT_PASSWORD) { '***SET***' } else { 'NOT SET' })" Write-Host "" # Verify client certificate file exists and check size if (Test-Path $env:SM_CLIENT_CERT_FILE) { $certFile = Get-Item $env:SM_CLIENT_CERT_FILE Write-Host "Client Certificate File:" Write-Host " Path: $($certFile.FullName)" Write-Host " Size: $($certFile.Length) bytes" Write-Host " Last Modified: $($certFile.LastWriteTime)" } else { Write-Error "Client certificate file NOT FOUND: $env:SM_CLIENT_CERT_FILE" } Write-Host "" # List available keypairs (verifies API authentication works) Write-Host "Available Keypairs:" smctl keypair ls Write-Host "" # Verify certificate (healthcheck) Write-Host "DigiCert Healthcheck:" smctl healthcheck Write-Host "" # Note: Healthcheck may show cert path/password warning but if keypair ls works, # the signing should still work. We'll test in the pre-build signing test. - name: Prepare Vulkan SDK for Ubuntu 24.04 if: contains(inputs.platform, 'ubuntu-24.04') run: | wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-1.3.290-noble.list https://packages.lunarg.com/vulkan/1.3.290/lunarg-vulkan-1.3.290-noble.list sudo apt update sudo apt install vulkan-sdk -y sudo apt-get install -y mesa-vulkan-drivers - name: Prepare Vulkan SDK for Ubuntu 22.04 if: contains(inputs.platform, 'ubuntu-22.04') run: | wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-1.3.290-jammy.list https://packages.lunarg.com/vulkan/1.3.290/lunarg-vulkan-1.3.290-jammy.list sudo apt update sudo apt install vulkan-sdk -y sudo apt-get install -y mesa-vulkan-drivers - name: Install frontend dependencies run: | cd frontend pnpm install - name: rustup install target if: ${{ inputs.target != '' && !contains(inputs.target, 'unknown-linux-gnu') && !contains(inputs.target, 'pc-windows-msvc') }} run: rustup target add ${{ inputs.target }} - name: Import Apple Developer Certificate if: contains(inputs.platform, 'macos') && inputs.sign-binaries env: APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} run: | echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security default-keychain -s build.keychain security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security set-keychain-settings -t 3600 -u build.keychain security import certificate.p12 -k build.keychain -P "$APPLE_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 -v -p codesigning build.keychain - name: Verify certificate if: contains(inputs.platform, 'macos') && inputs.sign-binaries run: | CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application") CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV echo "Certificate imported: $CERT_ID" - name: Configure build acceleration if: contains(inputs.platform, 'macos') run: | echo "✓ macOS build will use Metal GPU acceleration (enabled by default)" echo "✓ CoreML acceleration available for Apple Silicon" - name: Patch asset name pattern id: patch-release-name shell: bash if: ${{ inputs.release-id != '' && inputs.asset-name-pattern != '' }} run: | platform="${{ inputs.platform }}" replacement="$(echo ${platform} | sed -E 's/-latest//')" patched_platform=$(echo '${{ inputs.asset-name-pattern }}' | sed -E "s/\[platform\]/${replacement}/") if [[ -n "${{ inputs.asset-prefix }}" ]]; then patched_platform="${{ inputs.asset-prefix }}_${patched_platform}" fi echo "platform=${patched_platform}" >> $GITHUB_OUTPUT - name: Determine build features id: build-features shell: bash run: | FEATURES="" # Windows: Use Vulkan for GPU acceleration if [[ "${{ inputs.platform }}" == *"windows"* ]]; then FEATURES="--features vulkan" echo "Windows build with Vulkan GPU acceleration" fi # Linux: Use OpenBLAS for optimized CPU performance if [[ "${{ inputs.platform }}" == *"ubuntu"* ]]; then FEATURES="--features openblas" echo "Linux build with OpenBLAS CPU optimization" fi # macOS: Uses Metal by default, no additional features needed if [[ "${{ inputs.platform }}" == *"macos"* ]]; then echo "macOS build with Metal GPU acceleration (default)" fi echo "features=$FEATURES" >> "$GITHUB_OUTPUT" - name: Build llama-helper sidecar (Windows) if: contains(inputs.platform, 'windows') shell: pwsh run: | # Build llama-helper without GPU features (CPU-only) # Note: Vulkan build has CMake race condition issues with llama-cpp-sys-2 # The Tauri app itself uses whisper-rs with Vulkan for transcription # llama-helper is for LLM inference and works fine on CPU Write-Host "Building llama-helper sidecar (CPU-only)..." cargo build --release -p llama-helper if ($LASTEXITCODE -ne 0) { Write-Error "Failed to build llama-helper" exit $LASTEXITCODE } # Copy binary to binaries directory New-Item -ItemType Directory -Force -Path "frontend/src-tauri/binaries" | Out-Null Copy-Item "target/release/llama-helper.exe" -Destination "frontend/src-tauri/binaries/llama-helper-x86_64-pc-windows-msvc.exe" Write-Host "Copied llama-helper to frontend/src-tauri/binaries/" Get-ChildItem "frontend/src-tauri/binaries/" - name: Build llama-helper sidecar (macOS/Linux) if: "!contains(inputs.platform, 'windows')" shell: bash run: | echo "Building llama-helper sidecar..." # Determine llama-helper features based on platform LLAMA_FEATURES="" if [[ "${{ inputs.platform }}" == *"macos"* ]]; then LLAMA_FEATURES="--features metal" echo "Using Metal GPU acceleration for macOS" else echo "Using CPU-only mode for Linux" fi # Build llama-helper from workspace root cargo build --release -p llama-helper $LLAMA_FEATURES # Determine target triple and binary extension TARGET="${{ inputs.target }}" if [[ -z "$TARGET" ]]; then TARGET=$(rustc -vV | grep "host:" | awk '{print $2}') fi # Copy binary to binaries directory mkdir -p frontend/src-tauri/binaries cp target/release/llama-helper frontend/src-tauri/binaries/llama-helper-${TARGET} echo "Copied llama-helper to frontend/src-tauri/binaries/llama-helper-${TARGET}" ls -la frontend/src-tauri/binaries/ - name: Cache FFmpeg binary uses: actions/cache@v4 with: path: frontend/src-tauri/binaries/ffmpeg-* key: ${{ runner.os }}-ffmpeg-${{ hashFiles('frontend/src-tauri/build.rs', 'frontend/src-tauri/build/ffmpeg.rs') }} - name: Build with Tauri id: tauri-build uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} APPLE_ID: ${{ inputs.sign-binaries && secrets.APPLE_ID || '' }} APPLE_ID_PASSWORD: ${{ inputs.sign-binaries && secrets.APPLE_ID_PASSWORD || '' }} APPLE_PASSWORD: ${{ inputs.sign-binaries && secrets.APPLE_PASSWORD || '' }} APPLE_TEAM_ID: ${{ inputs.sign-binaries && secrets.APPLE_TEAM_ID || '' }} APPLE_CERTIFICATE: ${{ inputs.sign-binaries && secrets.APPLE_CERTIFICATE || '' }} APPLE_CERTIFICATE_PASSWORD: ${{ inputs.sign-binaries && secrets.APPLE_CERTIFICATE_PASSWORD || '' }} APPLE_SIGNING_IDENTITY: ${{ inputs.sign-binaries && env.CERT_ID || '' }} # Tauri updater signing (ALWAYS enabled for .sig files) TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} # License validation RSA public key (embedded at build time) MEETILY_RSA_PUBLIC_KEY: ${{ secrets.MEETILY_RSA_PUBLIC_KEY }} # Supabase configuration (for online license verification) SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} with: projectPath: frontend tagName: ${{ inputs.release-id && format('v{0}', steps.get-version.outputs.version) || '' }} releaseName: ${{ inputs.release-id && format('v{0}', steps.get-version.outputs.version) || '' }} releaseId: ${{ inputs.release-id }} args: ${{ inputs.build-args }} ${{ steps.build-features.outputs.features }} - name: Upload artifacts (macOS) if: inputs.upload-artifacts && contains(inputs.platform, 'macos') uses: actions/upload-artifact@v4 with: name: ${{ inputs.asset-prefix }}-${{ inputs.target }} path: | target/${{ inputs.target }}/${{ steps.build-profile.outputs.profile }}/bundle/dmg/*.dmg target/${{ inputs.target }}/${{ steps.build-profile.outputs.profile }}/bundle/macos/*.app target/${{ inputs.target }}/${{ steps.build-profile.outputs.profile }}/bundle/macos/*.app.tar.gz target/${{ inputs.target }}/${{ steps.build-profile.outputs.profile }}/bundle/macos/*.app.tar.gz.sig retention-days: 30 - name: Install FUSE for AppImage processing if: contains(inputs.platform, 'ubuntu') run: | sudo apt-get update sudo apt-get install -y fuse libfuse2 - name: Remove libwayland-client.so from AppImage if: contains(inputs.platform, 'ubuntu') run: | # Find the AppImage file APPIMAGE_PATH=$(find target/${{ steps.build-profile.outputs.profile }}/bundle/appimage -name "*.AppImage" | head -1) if [ -n "$APPIMAGE_PATH" ]; then echo "Processing AppImage: $APPIMAGE_PATH" # Make AppImage executable chmod +x "$APPIMAGE_PATH" # Extract AppImage cd "$(dirname "$APPIMAGE_PATH")" APPIMAGE_NAME=$(basename "$APPIMAGE_PATH") # Extract using the AppImage itself "./$APPIMAGE_NAME" --appimage-extract # Remove libwayland-client.so files echo "Removing libwayland-client.so files..." find squashfs-root -name "libwayland-client.so*" -type f -delete # List what was removed for verification echo "Files remaining in lib directories:" find squashfs-root -name "lib*" -type d | head -5 | while read dir; do echo "Contents of $dir:" ls "$dir" | grep -E "(wayland|fuse)" || echo " No wayland/fuse libraries found" done # Get appimagetool wget -q https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage chmod +x appimagetool-x86_64.AppImage # Repackage AppImage with no-appstream to avoid warnings ARCH=x86_64 ./appimagetool-x86_64.AppImage --no-appstream squashfs-root "$APPIMAGE_NAME" # Clean up rm -rf squashfs-root appimagetool-x86_64.AppImage echo "libwayland-client.so removed from AppImage successfully" else echo "No AppImage found to process" fi - name: Upload artifacts (Linux) if: inputs.upload-artifacts && contains(inputs.platform, 'ubuntu') uses: actions/upload-artifact@v4 with: name: ${{ inputs.asset-prefix }}-${{ inputs.platform }}-${{ inputs.target }} path: | target/${{ steps.build-profile.outputs.profile }}/bundle/deb/*.deb target/${{ steps.build-profile.outputs.profile }}/bundle/appimage/*.AppImage target/${{ steps.build-profile.outputs.profile }}/bundle/rpm/*.rpm retention-days: 30 - name: Upload artifacts (Windows) if: inputs.upload-artifacts && contains(inputs.platform, 'windows') uses: actions/upload-artifact@v4 with: name: ${{ inputs.asset-prefix }}-${{ inputs.target }} path: | target/${{ steps.build-profile.outputs.profile }}/bundle/msi/*.msi target/${{ steps.build-profile.outputs.profile }}/bundle/msi/*.msi.sig target/${{ steps.build-profile.outputs.profile }}/bundle/nsis/*.exe target/${{ steps.build-profile.outputs.profile }}/bundle/nsis/*.exe.sig retention-days: 30 ================================================ FILE: .github/workflows/pr-main-check.yml ================================================ name: "Validation Check" # This workflow runs basic validation checks without building # Use this to verify version and configuration before a release on: workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: validation-check: name: Validation Check runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Get version from tauri.conf.json id: get-version run: | VERSION=$(grep -o '"version": "[^"]*"' frontend/src-tauri/tauri.conf.json | cut -d'"' -f4) echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "Version: $VERSION" - name: Check current branch run: | CURRENT_BRANCH="${{ github.ref_name }}" echo "Current branch: $CURRENT_BRANCH" if [[ "$CURRENT_BRANCH" == "main" ]]; then echo "On main branch - ready for release" elif [[ "$CURRENT_BRANCH" == "devtest" ]]; then echo "On devtest branch - ready for testing" else echo "On feature branch: $CURRENT_BRANCH" fi - name: Validate version format run: | VERSION="${{ steps.get-version.outputs.version }}" # Check if version matches semantic versioning pattern if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Version format is valid: $VERSION" else echo "Warning: Version '$VERSION' may not be in standard semver format" echo "Expected format: X.Y.Z (e.g., 1.0.0)" fi - name: Generate validation summary run: | echo "## Validation Check Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Version:** ${{ steps.get-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY echo "**Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY echo "**Commit:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Next Steps" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "1. Run **Build and Test - DevTest** for unsigned builds" >> $GITHUB_STEP_SUMMARY echo "2. Run **Build and Test** for signed builds on all platforms" >> $GITHUB_STEP_SUMMARY echo "3. Run **Release** to create a production release" >> $GITHUB_STEP_SUMMARY ================================================ FILE: .github/workflows/release.yml ================================================ name: "Release" on: workflow_dispatch: # Prevent duplicate releases concurrency: group: release-${{ github.ref }} cancel-in-progress: false # Don't cancel releases, fail instead jobs: # STEP 1: Create draft release first create-release: permissions: contents: write runs-on: ubuntu-latest outputs: release-id: ${{ steps.create-release.outputs.result }} version: ${{ steps.get-version.outputs.version }} steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch all tags - name: Get version from tauri.conf.json and check for existing tags id: get-version shell: bash run: | BASE_VERSION=$(grep -o '"version": "[^"]*"' frontend/src-tauri/tauri.conf.json | cut -d'"' -f4) echo "Base version from tauri.conf.json: $BASE_VERSION" # Fetch all tags git fetch --tags # Check if base version tag exists if git tag -l "v${BASE_VERSION}" | grep -q .; then echo "Tag v${BASE_VERSION} already exists, looking for minor updates..." # Find all existing minor versions (e.g., 0.1.1.1, 0.1.1.2, etc.) LATEST_MINOR=0 for i in $(seq 1 100); do if git tag -l "v${BASE_VERSION}.${i}" | grep -q .; then LATEST_MINOR=$i else break fi done # Increment to next minor version NEXT_MINOR=$((LATEST_MINOR + 1)) if [ $NEXT_MINOR -gt 100 ]; then echo "ERROR: Maximum minor version (100) reached for ${BASE_VERSION}" echo "Please update the version in tauri.conf.json" exit 1 fi VERSION="${BASE_VERSION}.${NEXT_MINOR}" echo "Using minor update version: $VERSION" else VERSION="${BASE_VERSION}" echo "Using base version: $VERSION" fi echo "Final version: $VERSION" echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Create Draft Release id: create-release uses: actions/github-script@v7 with: script: | const { data } = await github.rest.repos.createRelease({ owner: context.repo.owner, repo: context.repo.repo, tag_name: `v${{ steps.get-version.outputs.version }}`, name: `Meetily v${{ steps.get-version.outputs.version }}`, draft: true, prerelease: false, generate_release_notes: true }) console.log(`Created draft release with ID: ${data.id}`) return data.id # STEP 2: Build all platforms and upload directly to release # Note: Linux builds are excluded from release (only macOS and Windows) build-all-platforms: needs: create-release permissions: contents: write strategy: fail-fast: false matrix: include: - platform: "macos-latest" # for Apple Silicon only (M1 and above) args: "--target aarch64-apple-darwin" target: "aarch64-apple-darwin" - platform: "windows-latest" args: "" target: "x86_64-pc-windows-msvc" uses: ./.github/workflows/build.yml with: platform: ${{ matrix.platform }} target: ${{ matrix.target }} build-args: ${{ matrix.args }} sign-binaries: true asset-prefix: "meetily" upload-artifacts: false # tauri-action uploads directly to release release-id: ${{ needs.create-release.outputs.release-id }} secrets: inherit # STEP 3: Show release summary release-summary: needs: [create-release, build-all-platforms] runs-on: ubuntu-latest permissions: contents: write steps: - name: Get release assets and latest.json id: release-info run: | VERSION="${{ needs.create-release.outputs.version }}" RELEASE_ID="${{ needs.create-release.outputs.release-id }}" echo "## Release Assets" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY # List all release assets echo "### Files in Release" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY gh api repos/${{ github.repository }}/releases/${RELEASE_ID}/assets --jq '.[] | "\(.name) (\(.size / 1048576 | floor)MB)"' >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY # Download and show latest.json echo "### latest.json content" >> $GITHUB_STEP_SUMMARY gh release download "v${VERSION}" --pattern "latest.json" --dir . 2>/dev/null || echo "latest.json not yet available" if [ -f latest.json ]; then echo "\`\`\`json" >> $GITHUB_STEP_SUMMARY cat latest.json >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY fi echo "" >> $GITHUB_STEP_SUMMARY # Download links echo "### Download Links" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Release page:** https://github.com/${{ github.repository }}/releases/tag/v${VERSION}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Download all as ZIP:** https://github.com/${{ github.repository }}/archive/refs/tags/v${VERSION}.zip" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Manual S3 upload:** Download latest.json from release and upload to \`s3://meetily-updates/latest.json\`" >> $GITHUB_STEP_SUMMARY env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Release Summary run: | echo "============================================" echo "=== Release Created Successfully ===" echo "============================================" echo "Version: v${{ needs.create-release.outputs.version }}" echo "Release URL: https://github.com/${{ github.repository }}/releases/tag/v${{ needs.create-release.outputs.version }}" echo "" echo "Uploaded assets (via tauri-action):" echo " - macOS: DMG installer + app.tar.gz (for updater) + .sig" echo " - Windows: MSI + NSIS installers (SIGNED via signCommand) + .sig files" echo " - Updater manifest: latest.json (auto-generated by tauri-action)" echo "" echo "Next steps:" echo " 1. Review the draft release" echo " 2. Edit release notes if needed" echo " 3. Publish the release when ready" echo "============================================" # Add to GitHub Step Summary echo "## Release Created Successfully" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Version:** v${{ needs.create-release.outputs.version }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Release URL:** [View Release](https://github.com/${{ github.repository }}/releases/tag/v${{ needs.create-release.outputs.version }})" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Uploaded Assets" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Platform | Assets |" >> $GITHUB_STEP_SUMMARY echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY echo "| **macOS** | DMG installer, app.tar.gz (updater), .sig |" >> $GITHUB_STEP_SUMMARY echo "| **Windows** | MSI installer (SIGNED), NSIS installer (SIGNED), .sig files |" >> $GITHUB_STEP_SUMMARY echo "| **Updater** | latest.json manifest (auto-generated by tauri-action) |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Next Steps" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "1. [Review the draft release](https://github.com/${{ github.repository }}/releases/tag/v${{ needs.create-release.outputs.version }})" >> $GITHUB_STEP_SUMMARY echo "2. Edit release notes if needed" >> $GITHUB_STEP_SUMMARY echo "3. Publish the release when ready" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. /experiments # dependencies /node_modules **/models /.pnp .pnp.* .yarn/* !.yarn/patches !.yarn/plugins !.yarn/releases !.yarn/versions *.mp4 # testing /coverage /dist # next.js /.next/ /out/ # production /build # misc .DS_Store *.pem # debug npm-debug.log* yarn-debug.log* yarn-error.log* .pnpm-debug.log* # env files (can opt-in for committing if needed) .env # vercel .vercel # typescript *.tsbuildinfo next-env.d.ts # Audio files *.wav # Large media files docs/demo.mov docs/demo.gif meeting_minutes.db # Added by Task Master AI # Logs logs *.log dev-debug.log # Dependency directories node_modules/ # Environment variables .env # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw? experiments/ .cursor/ target/ frontend/src-tauri/binaries/ Cargo.lock ================================================ FILE: .gitmodules ================================================ [submodule "backend/whisper.cpp"] path = backend/whisper.cpp url = https://github.com/Zackriya-Solutions/whisper.cpp branch = develop ================================================ FILE: BLUETOOTH_PLAYBACK_NOTICE.md ================================================ # Bluetooth Headphone Playback Notice ## Important Information for Recording Review When **reviewing recordings** in Meetily, we recommend using **computer speakers** or **wired headphones** rather than Bluetooth headphones for accurate playback. --- ## The Issue Recordings may sound **distorted, sped up, or have clarity issues** when played through Bluetooth headphones, even though the recording file itself is perfectly fine. ### Symptoms - Audio plays too fast or too slow - Voice sounds higher/lower pitched than normal - Quality seems degraded or "chipmunk-like" - **Different Bluetooth devices cause different playback speeds** ### What's Actually Happening **Your recording is fine!** The issue occurs during **playback**, not recording. --- ## Technical Explanation ### Why This Happens 1. **Meetily records at 48kHz** (professional audio standard) 2. **Bluetooth headphones use various sample rates**: 8kHz, 16kHz, 24kHz, 44.1kHz, or 48kHz 3. **macOS resamples audio** when sending 48kHz content to Bluetooth devices 4. **Resampling can fail** if macOS: - Negotiates the wrong Bluetooth codec (SBC vs AAC vs LDAC) - Misidentifies the device's playback capability - Uses low-quality resampling for power efficiency ### Device-Specific Behavior Different Bluetooth headphones report different capabilities: | Device Type | Typical Playback Rate | Result When Playing 48kHz | |------------|----------------------|---------------------------| | Sony WH-1000XM4 | 16-44.1kHz (varies) | May sound 1.5-3x faster | | AirPods Pro | 24kHz or 48kHz | Usually OK, but can vary | | Cheap BT Headset | 8-16kHz | Often sounds very fast | | High-end BT (LDAC) | 44.1-48kHz | Usually works correctly | The rate depends on: - **Bluetooth profile** (A2DP for music vs HFP for calls) - **Active codec** (SBC, AAC, aptX, LDAC) - **Battery mode** (power-saving modes may reduce quality) - **macOS version** and audio driver quirks --- ## Solution: Use Computer Speakers ### For Accurate Review ✅ **Computer speakers** (built-in or external) ✅ **Wired headphones** (3.5mm jack or USB) ✅ **High-quality DAC** (digital audio converter) ❌ **Bluetooth headphones** (for reviewing recordings) ❌ **Bluetooth speakers** (same resampling issues) ### Bluetooth Headphones Are Fine For - ✅ **Recording** (microphone input) - We handle sample rate conversion correctly - ✅ **Live monitoring** during recording - macOS handles real-time audio - ✅ **General computer use** - Normal audio playback - ❌ **Reviewing Meetily recordings** - Use wired/speakers instead --- ## Verification Steps To confirm your recording is actually fine: 1. **Play recording through computer speakers** - If it sounds normal → Recording is good, BT playback is the issue ✅ - If it still sounds wrong → May be a different issue ❌ 2. **Check file properties** ```bash # In terminal: ffprobe path/to/recording/audio.mp4 ``` Should show: - `sample_rate=48000` ✅ - `channels=1` ✅ - `codec_name=aac` ✅ 3. **Try different playback devices** - Computer speakers: Should sound normal - Wired headphones: Should sound normal - Bluetooth device A: Might sound wrong - Bluetooth device B: Might sound differently wrong --- ## Why We Don't "Fix" This ### This is Not a Meetily Bug The issue is in **macOS's Bluetooth audio stack**, not in Meetily's recording engine. **Evidence:** - Recordings play perfectly on computer speakers - File metadata shows correct 48kHz encoding - Other professional audio apps have the same limitation - Issue varies by Bluetooth device (different devices = different problems) ### Industry Standard Practice Professional audio software **always** recommends: - Monitor through studio monitors (speakers) or wired headphones - Avoid Bluetooth for critical listening - Use wired connections for audio work Examples: - **Logic Pro X**: Warns against BT monitoring - **Audacity**: Recommends wired headphones - **GarageBand**: Disables BT for recording/monitoring --- ## Workarounds ### Option 1: Use Computer Speakers (Recommended) **Best**: Most accurate, no resampling issues ### Option 2: Export at Different Sample Rate If you **must** use Bluetooth for playback: 1. **Export recording** at lower sample rate (future feature) 2. **Transcode manually** using ffmpeg: ```bash ffmpeg -i audio.mp4 -ar 44100 audio_44k.mp4 ``` 3. **Try 44.1kHz** (better BT compatibility than 48kHz) ### Option 3: Use High-Quality Bluetooth Devices with **LDAC** or **aptX HD** codecs: - Sony WH-1000XM5 (LDAC mode) - Sennheiser Momentum 4 - Some high-end Bose models These handle 48kHz better (but still not perfect). --- ## Technical Details for Developers ### Sample Rate Chain ``` Recording Pipeline: Microphone (16kHz) → Resample to 48kHz → Pipeline (48kHz) System Audio (48kHz) → No resampling → Pipeline (48kHz) Mixed Audio (48kHz) → Encode → File (48kHz AAC) Playback (Computer Speakers): File (48kHz) → macOS CoreAudio → Speakers (48kHz) ✅ Playback (Bluetooth): File (48kHz) → macOS CoreAudio → Bluetooth Stack → Resample → BT Device (16-48kHz) ⚠️ ↑ This step can fail! ``` ### Why macOS Resampling Fails 1. **Codec negotiation**: BT device claims 48kHz support but actually uses 16kHz 2. **Profile switching**: Device switches from A2DP (music) to HFP (call) mid-playback 3. **Power management**: macOS downsamples to save battery 4. **Driver bugs**: CoreAudio → Bluetooth handoff has known issues ### Apple's Documentation From [Apple Technical Note TN2321](https://developer.apple.com/library/archive/technotes/tn2321/): > "Bluetooth audio devices may report supported sample rates that differ from > their actual playback rates. Applications should not rely on Bluetooth > devices for accurate audio monitoring." --- ## FAQ ### Q: Will this be fixed in a future update? **A**: This is a macOS/Bluetooth limitation, not a Meetily bug. We've correctly recorded at 48kHz. ### Q: Why not record at 16kHz if that's what Bluetooth uses? **A**: Because: 1. System audio is 48kHz (can't be changed) 2. 48kHz is professional quality (16kHz is phone-call quality) 3. Most users play back on computer speakers 4. Recording at 16kHz would degrade quality for 95% of users ### Q: Can you detect my Bluetooth device and warn me? **A**: Yes! Meetily now shows a warning when Bluetooth headphones are active during playback. ### Q: Does this affect recording quality? **A**: **No**. Recording quality is perfect. Only **playback** through Bluetooth has issues. ### Q: What about AirPods? They're supposed to be high quality. **A**: AirPods handle 48kHz better than most BT devices, but can still have issues depending on: - Codec negotiation (AAC vs SBC) - Battery level (power-saving mode) - Connection quality (Bluetooth interference) - macOS audio driver quirks --- ## Summary ✅ **Recordings are perfect** - 48kHz, high quality ✅ **Computer playback works** - Use speakers or wired headphones ⚠️ **Bluetooth playback may sound wrong** - macOS resampling issue ✅ **Recording through BT mic works** - We handle resampling correctly **Bottom line**: Review your recordings through computer speakers, not Bluetooth headphones. --- ## Related Documentation - [AIRPODS_BLUETOOTH_FIX.md](AIRPODS_BLUETOOTH_FIX.md) - Bluetooth device reconnection handling - [BLUETOOTH_SAMPLE_RATE_FIX.md](BLUETOOTH_SAMPLE_RATE_FIX.md) - Microphone sample rate resampling - [Apple Technical Note TN2321](https://developer.apple.com/library/archive/technotes/tn2321/) - Bluetooth Audio Best Practices --- **Last Updated**: October 10, 2025 **Applies To**: Meetily v0.0.5+ on macOS ================================================ FILE: CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview **Meetily** is a privacy-first AI meeting assistant that captures, transcribes, and summarizes meetings entirely on local infrastructure. The project consists of two main components: 1. **Frontend**: Tauri-based desktop application (Rust + Next.js + TypeScript) 2. **Backend**: FastAPI server for meeting storage and LLM-based summarization (Python) ### Key Technology Stack - **Desktop App**: Tauri 2.x (Rust) + Next.js 14 + React 18 - **Audio Processing**: Rust (cpal, whisper-rs, professional audio mixing) - **Transcription**: Whisper.cpp (local, GPU-accelerated) - **Backend API**: FastAPI + SQLite (aiosqlite) - **LLM Integration**: Ollama (local), Claude, Groq, OpenRouter ## Essential Development Commands ### Frontend Development (Tauri Desktop App) **Location**: `/frontend` ```bash # macOS Development ./clean_run.sh # Clean build and run with info logging ./clean_run.sh debug # Run with debug logging ./clean_build.sh # Production build # Windows Development clean_run_windows.bat # Clean build and run clean_build_windows.bat # Production build # Manual Commands pnpm install # Install dependencies pnpm run dev # Next.js dev server (port 3118) pnpm run tauri:dev # Full Tauri development mode pnpm run tauri:build # Production build # GPU-Specific Builds (for testing acceleration) pnpm run tauri:dev:metal # macOS Metal GPU pnpm run tauri:dev:cuda # NVIDIA CUDA pnpm run tauri:dev:vulkan # AMD/Intel Vulkan pnpm run tauri:dev:cpu # CPU-only (no GPU) ``` ### Backend Development (FastAPI Server) **Location**: `/backend` ```bash # macOS ./build_whisper.sh small # Build Whisper with 'small' model ./clean_start_backend.sh # Start FastAPI server (port 5167) # Windows build_whisper.cmd small # Build Whisper with model start_with_output.ps1 # Interactive setup and start clean_start_backend.cmd # Start server # Docker (Cross-Platform) ./run-docker.sh start --interactive # Interactive setup (macOS/Linux) .\run-docker.ps1 start -Interactive # Interactive setup (Windows) ./run-docker.sh logs --service app # View logs ``` **Available Whisper Models**: `tiny`, `tiny.en`, `base`, `base.en`, `small`, `small.en`, `medium`, `medium.en`, `large-v1`, `large-v2`, `large-v3`, `large-v3-turbo` ### Service Endpoints - **Whisper Server**: http://localhost:8178 - **Backend API**: http://localhost:5167 - **Backend Docs**: http://localhost:5167/docs - **Frontend Dev**: http://localhost:3118 ## High-Level Architecture ### Three-Tier System Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ │ Frontend (Tauri Desktop App) │ │ ┌──────────────────┐ ┌─────────────────┐ ┌────────────────┐ │ │ │ Next.js UI │ │ Rust Backend │ │ Whisper Engine │ │ │ │ (React/TS) │←→│ (Audio + IPC) │←→│ (Local STT) │ │ │ └──────────────────┘ └─────────────────┘ └────────────────┘ │ │ ↑ Tauri Events ↑ Audio Pipeline │ └─────────┼────────────────────────┼─────────────────────────────┘ │ HTTP/WebSocket │ ↓ │ ┌─────────────────────────────────┼─────────────────────────────┐ │ Backend (FastAPI) │ │ │ ┌────────────┐ ┌─────────────┴──────┐ ┌────────────────┐ │ │ │ SQLite │←→│ Meeting Manager │←→│ LLM Provider │ │ │ │ (Meetings) │ │ (CRUD + Summary) │ │ (Ollama/etc.) │ │ │ └────────────┘ └────────────────────┘ └────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` ### Audio Processing Pipeline (Critical Understanding) The audio system has **two parallel paths** with different purposes: ``` Raw Audio (Mic + System) ↓ ┌────────────────────────────────────────────────────────────┐ │ Audio Pipeline Manager │ │ (frontend/src-tauri/src/audio/pipeline.rs) │ └─────────────┬──────────────────────────┬───────────────────┘ ↓ ↓ ┌─────────────────┐ ┌─────────────────────┐ │ Recording Path │ │ Transcription Path │ │ (Pre-mixed) │ │ (VAD-filtered) │ └─────────────────┘ └─────────────────────┘ ↓ ↓ RecordingSaver.save() WhisperEngine.transcribe() ``` **Key Insight**: The pipeline performs **professional audio mixing** (RMS-based ducking, clipping prevention) for recording, while simultaneously applying **Voice Activity Detection (VAD)** to send only speech segments to Whisper for transcription. ### Audio Device Modularization (Recently Completed) **Context**: The audio system was refactored from a monolithic 1028-line `core.rs` file into focused modules. See [AUDIO_MODULARIZATION_PLAN.md](AUDIO_MODULARIZATION_PLAN.md) for details. ``` audio/ ├── devices/ # Device discovery and configuration │ ├── discovery.rs # list_audio_devices, trigger_audio_permission │ ├── microphone.rs # default_input_device │ ├── speakers.rs # default_output_device │ ├── configuration.rs # AudioDevice types, parsing │ └── platform/ # Platform-specific implementations │ ├── windows.rs # WASAPI logic (~200 lines) │ ├── macos.rs # ScreenCaptureKit logic │ └── linux.rs # ALSA/PulseAudio logic ├── capture/ # Audio stream capture │ ├── microphone.rs # Microphone capture stream │ ├── system.rs # System audio capture stream │ └── core_audio.rs # macOS ScreenCaptureKit integration ├── pipeline.rs # Audio mixing and VAD processing ├── recording_manager.rs # High-level recording coordination ├── recording_commands.rs # Tauri command interface └── recording_saver.rs # Audio file writing ``` **When working on audio features**: - Device detection issues → `devices/discovery.rs` or `devices/platform/{windows,macos,linux}.rs` - Microphone/speaker problems → `devices/microphone.rs` or `devices/speakers.rs` - Audio capture issues → `capture/microphone.rs` or `capture/system.rs` - Mixing/processing problems → `pipeline.rs` - Recording workflow → `recording_manager.rs` ### Rust ↔ Frontend Communication (Tauri Architecture) **Command Pattern** (Frontend → Rust): ```typescript // Frontend: src/app/page.tsx await invoke('start_recording', { mic_device_name: "Built-in Microphone", system_device_name: "BlackHole 2ch", meeting_name: "Team Standup" }); ``` ```rust // Rust: src/lib.rs #[tauri::command] async fn start_recording( app: AppHandle, mic_device_name: Option, system_device_name: Option, meeting_name: Option ) -> Result<(), String> { // Implementation delegates to audio::recording_commands } ``` **Event Pattern** (Rust → Frontend): ```rust // Rust: Emit transcript updates app.emit("transcript-update", TranscriptUpdate { text: "Hello world".to_string(), timestamp: chrono::Utc::now(), // ... })?; ``` ```typescript // Frontend: Listen for events await listen('transcript-update', (event) => { setTranscripts(prev => [...prev, event.payload]); }); ``` ### Whisper Model Management **Model Storage Locations**: - **Development**: `frontend/models/` or `backend/whisper-server-package/models/` - **Production (macOS)**: `~/Library/Application Support/Meetily/models/` - **Production (Windows)**: `%APPDATA%\Meetily\models\` **Model Loading** (frontend/src-tauri/src/whisper_engine/whisper_engine.rs): ```rust pub async fn load_model(&self, model_name: &str) -> Result<()> { // Automatically detects GPU capabilities (Metal/CUDA/Vulkan) // Falls back to CPU if GPU unavailable } ``` **GPU Acceleration**: - **macOS**: Metal + CoreML (automatically enabled) - **Windows/Linux**: CUDA (NVIDIA), Vulkan (AMD/Intel), or CPU - Configure via Cargo features: `--features cuda`, `--features vulkan` ## Critical Development Patterns ### 1. Audio Buffer Management **Ring Buffer Mixing** (pipeline.rs): - Mic and system audio arrive asynchronously at different rates - Ring buffer accumulates samples until both streams have aligned windows (50ms) - Professional mixing applies RMS-based ducking to prevent system audio from drowning out microphone - Uses `VecDeque` for efficient windowed processing ### 2. Thread Safety and Async Boundaries **Recording State** (recording_state.rs): ```rust pub struct RecordingState { is_recording: Arc, audio_sender: Arc>>>, // ... } ``` **Key Pattern**: Use `Arc>` for shared state across async tasks, `Arc` for simple flags. ### 3. Error Handling and Logging **Performance-Aware Logging** (lib.rs): ```rust #[cfg(debug_assertions)] macro_rules! perf_debug { ($($arg:tt)*) => { log::debug!($($arg)*) }; } #[cfg(not(debug_assertions))] macro_rules! perf_debug { ($($arg:tt)*) => {}; // Zero overhead in release builds } ``` **Usage**: Use `perf_debug!()` and `perf_trace!()` for hot-path logging that should be eliminated in production. ### 4. Frontend State Management **Sidebar Context** (components/Sidebar/SidebarProvider.tsx): - Global state for meetings list, current meeting, recording status - Communicates with backend API (http://localhost:5167) - Manages WebSocket connections for real-time updates **Pattern**: Tauri commands update Rust state → Emit events → Frontend listeners update React state → Context propagates to components ## Common Development Tasks ### Adding a New Audio Device Platform 1. Create platform file: `audio/devices/platform/{platform_name}.rs` 2. Implement device enumeration for the platform 3. Add platform-specific configuration in `audio/devices/configuration.rs` 4. Update `audio/devices/platform/mod.rs` to export new platform functions 5. Test with `cargo check` and platform-specific device tests ### Adding a New Tauri Command 1. Define command in `src/lib.rs`: ```rust #[tauri::command] async fn my_command(arg: String) -> Result { /* ... */ } ``` 2. Register in `tauri::Builder`: ```rust .invoke_handler(tauri::generate_handler![ start_recording, my_command, // Add here ]) ``` 3. Call from frontend: ```typescript const result = await invoke('my_command', { arg: 'value' }); ``` ### Modifying Audio Pipeline Behavior **Location**: `frontend/src-tauri/src/audio/pipeline.rs` Key components: - `AudioMixerRingBuffer`: Manages mic + system audio synchronization - `ProfessionalAudioMixer`: RMS-based ducking and mixing - `AudioPipelineManager`: Orchestrates VAD, mixing, and distribution **Testing Audio Changes**: ```bash # Enable verbose audio logging RUST_LOG=app_lib::audio=debug ./clean_run.sh # Monitor audio metrics in real-time # Check Developer Console in the app (Cmd+Shift+I on macOS) ``` ### Backend API Development **Adding New Endpoints** (backend/app/main.py): ```python @app.post("/api/my-endpoint") async def my_endpoint(request: MyRequest) -> MyResponse: # Use DatabaseManager for persistence db = DatabaseManager() result = await db.some_operation() return result ``` **Database Operations** (backend/app/db.py): - All meeting data stored in SQLite - Use `DatabaseManager` class for all DB operations - Async operations with `aiosqlite` ## Testing and Debugging ### Frontend Debugging **Enable Rust Logging**: ```bash # macOS RUST_LOG=debug ./clean_run.sh # Windows (PowerShell) $env:RUST_LOG="debug"; ./clean_run_windows.bat ``` **Developer Tools**: - Open DevTools: `Cmd+Shift+I` (macOS) or `Ctrl+Shift+I` (Windows) - Console Toggle: Built into app UI (console icon) - View Rust logs: Check terminal output ### Backend Debugging **View API Logs**: ```bash # Backend logs show in terminal with detailed formatting: # 2025-01-03 12:34:56 - INFO - [main.py:123 - endpoint_name()] - Message ``` **Test API Directly**: - Swagger UI: http://localhost:5167/docs - ReDoc: http://localhost:5167/redoc ### Audio Pipeline Debugging **Key Metrics** (emitted by pipeline): - Buffer sizes (mic/system) - Mixing window count - VAD detection rate - Dropped chunk warnings **Monitor via Developer Console**: The app includes real-time metrics display when recording. ## Platform-Specific Notes ### macOS - **Audio Capture**: Uses ScreenCaptureKit for system audio (macOS 13+) - **GPU**: Metal + CoreML automatically enabled - **Permissions**: Requires microphone + screen recording permissions - **System Audio**: Requires virtual audio device (BlackHole) for system capture ### Windows - **Audio Capture**: Uses WASAPI (Windows Audio Session API) - **GPU**: CUDA (NVIDIA) or Vulkan (AMD/Intel) via Cargo features - **Build Tools**: Requires Visual Studio Build Tools with C++ workload - **System Audio**: Uses WASAPI loopback for system capture ### Linux - **Audio Capture**: ALSA/PulseAudio - **GPU**: CUDA (NVIDIA) or Vulkan via Cargo features - **Dependencies**: Requires cmake, llvm, libomp ## Performance Optimization Guidelines ### Audio Processing - Use `perf_debug!()` / `perf_trace!()` for hot-path logging (zero cost in release) - Batch audio metrics using `AudioMetricsBatcher` (pipeline.rs) - Pre-allocate buffers with `AudioBufferPool` (buffer_pool.rs) - VAD filtering reduces Whisper load by ~70% (only processes speech) ### Whisper Transcription - **Model Selection**: Balance accuracy vs speed - Development: `base` or `small` (fast iteration) - Production: `medium` or `large-v3` (best quality) - **GPU Acceleration**: 5-10x faster than CPU - **Parallel Processing**: Available in `whisper_engine/parallel_processor.rs` for batch workloads ### Frontend Performance - React state updates batched via Sidebar context - Transcript rendering virtualized for large meetings - Audio level monitoring throttled to 60fps ## Important Constraints and Gotchas 1. **Audio Chunk Size**: Pipeline expects consistent 48kHz sample rate. Resampling happens at capture time. 2. **Platform Audio Quirks**: - macOS: ScreenCaptureKit requires macOS 13+, needs screen recording permission - Windows: WASAPI exclusive mode can conflict with other apps - System audio requires virtual device (BlackHole on macOS, WASAPI loopback on Windows) 3. **Whisper Model Loading**: Models are loaded once and cached. Changing models requires app restart or manual unload/reload. 4. **Backend Dependency**: Frontend can run standalone (local Whisper), but meeting persistence and LLM features require backend running. 5. **CORS Configuration**: Backend allows all origins (`"*"`) for development. Restrict for production deployment. 6. **File Paths**: Use Tauri's path APIs (`downloadDir`, etc.) for cross-platform compatibility. Never hardcode paths. 7. **Audio Permissions**: Request permissions early. macOS requires both microphone AND screen recording for system audio. ## Repository-Specific Conventions - **Logging Format**: Backend uses detailed formatting with filename:line:function - **Error Handling**: Rust uses `anyhow::Result`, frontend uses try-catch with user-friendly messages - **Naming**: Audio devices use "microphone" and "system" consistently (not "input"/"output") - **Git Branches**: - `main`: Stable releases - `fix/*`: Bug fixes - `enhance/*`: Feature enhancements - Current: `fix/audio-mixing` (working on audio pipeline improvements) ## Key Files Reference **Core Coordination**: - [frontend/src-tauri/src/lib.rs](frontend/src-tauri/src/lib.rs) - Main Tauri entry point, command registration - [frontend/src-tauri/src/audio/mod.rs](frontend/src-tauri/src/audio/mod.rs) - Audio module exports - [backend/app/main.py](backend/app/main.py) - FastAPI application, API endpoints **Audio System**: - [frontend/src-tauri/src/audio/recording_manager.rs](frontend/src-tauri/src/audio/recording_manager.rs) - Recording orchestration - [frontend/src-tauri/src/audio/pipeline.rs](frontend/src-tauri/src/audio/pipeline.rs) - Audio mixing and VAD - [frontend/src-tauri/src/audio/recording_saver.rs](frontend/src-tauri/src/audio/recording_saver.rs) - Audio file writing **UI Components**: - [frontend/src/app/page.tsx](frontend/src/app/page.tsx) - Main recording interface - [frontend/src/components/Sidebar/SidebarProvider.tsx](frontend/src/components/Sidebar/SidebarProvider.tsx) - Global state management **Whisper Integration**: - [frontend/src-tauri/src/whisper_engine/whisper_engine.rs](frontend/src-tauri/src/whisper_engine/whisper_engine.rs) - Whisper model management and transcription ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Meeting Minutes Updates Thank you for your interest in contributing to Meetily! This document provides guidelines and instructions for contributing to this project. ## Development Workflow ### Branch Strategy - `main` - Production branch - `devtest` - Development and testing branch - Feature branches should be created from `devtest` ### Getting Started 1. Fork the repository 2. Clone your fork: ```bash git clone https://github.com/YOUR_USERNAME/meeting-minutes.git ``` 3. Add the original repository as upstream: ```bash git remote add upstream https://github.com/Zackriya-Solutions/meeting-minutes.git ``` 4. Create a new branch from `devtest`: ```bash git checkout devtest git pull upstream devtest git checkout -b feature/your-feature-name ``` ### Development Process 1. Always start your work from the `devtest` branch 2. Create a new branch for each feature/fix 3. Make your changes 4. Write or update tests as needed 5. Ensure all tests pass 6. Update documentation if necessary ### Issue Creation Before starting work on a new feature or bug fix: 1. Check if an issue already exists 2. If not, create a new issue with: - Clear title - Detailed description - Steps to reproduce (for bugs) - Expected behavior - Screenshots (if applicable) - Labels (bug, enhancement, etc.) ### Pull Request Process 1. Create a PR from your feature branch to `devtest` 2. Link the PR to the related issue using the issue number (e.g., "Fixes #123") 3. Fill out the PR template completely 4. Ensure CI checks pass 5. Request review from at least one maintainer 6. Address any review comments 7. Once approved, the PR will be merged into `devtest` ### PR Template ```markdown ## Description [Describe your changes here] ## Related Issue [Link to the issue this PR addresses] ## Type of Change - [ ] Bug fix - [ ] New feature - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring - [ ] Other (please describe) ## Testing - [ ] Unit tests added/updated - [ ] Manual testing performed - [ ] All tests pass ## Documentation - [ ] Documentation updated - [ ] No documentation needed ## Checklist - [ ] Code follows project style - [ ] Self-reviewed the code - [ ] Added comments for complex code - [ ] Updated README if needed ``` ## Code Style - Follow the existing code style - Use meaningful variable and function names - Add comments for complex logic - Keep functions small and focused - Write clear commit messages ## Commit Message Format ``` ():