octo-agent 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.clacky/skills/commit/SKILL.md +423 -0
- data/.clacky/skills/gem-release/SKILL.md +199 -0
- data/.clacky/skills/gem-release/scripts/release.sh +304 -0
- data/.clacky/skills/oss-upload/SKILL.md +47 -0
- data/.octorules +106 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +76 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CONTRIBUTING.md +92 -0
- data/Dockerfile +28 -0
- data/LICENSE.txt +22 -0
- data/POSITIONING.md +46 -0
- data/README.md +134 -0
- data/README_CN.md +134 -0
- data/Rakefile +34 -0
- data/benchmark/fixtures/sample_project/Gemfile +3 -0
- data/benchmark/fixtures/sample_project/lib/api_handler.rb +32 -0
- data/benchmark/fixtures/sample_project/lib/order_calculator.rb +23 -0
- data/benchmark/fixtures/sample_project/lib/user_renderer.rb +20 -0
- data/benchmark/fixtures/sample_project/spec/order_calculator_spec.rb +20 -0
- data/benchmark/results/EVALUATION_REPORT.md +165 -0
- data/benchmark/results/baseline_20260511_174424.json +128 -0
- data/benchmark/results/report_20260511_175256.json +271 -0
- data/benchmark/results/report_20260511_175444.json +271 -0
- data/benchmark/results/treatment_20260511_175103.json +130 -0
- data/benchmark/runner.rb +441 -0
- data/bin/octo +7 -0
- data/docs/agent-first-ui-design.md +77 -0
- data/docs/billing-system.md +318 -0
- data/docs/channel-architecture.md +235 -0
- data/docs/engineering-article.md +343 -0
- data/docs/session-skill-invocation.md +69 -0
- data/docs/time_machine_design.md +247 -0
- data/docs/ui2-architecture.md +124 -0
- data/homebrew/README.md +96 -0
- data/homebrew/openocto.rb +24 -0
- data/lib/octo/agent/hook_manager.rb +61 -0
- data/lib/octo/agent/llm_caller.rb +800 -0
- data/lib/octo/agent/memory_updater.rb +246 -0
- data/lib/octo/agent/message_compressor.rb +225 -0
- data/lib/octo/agent/message_compressor_helper.rb +869 -0
- data/lib/octo/agent/next_message_suggester.rb +215 -0
- data/lib/octo/agent/session_serializer.rb +685 -0
- data/lib/octo/agent/skill_auto_creator.rb +114 -0
- data/lib/octo/agent/skill_evolution.rb +61 -0
- data/lib/octo/agent/skill_manager.rb +466 -0
- data/lib/octo/agent/skill_reflector.rb +89 -0
- data/lib/octo/agent/system_prompt_builder.rb +101 -0
- data/lib/octo/agent/time_machine.rb +214 -0
- data/lib/octo/agent/tool_executor.rb +454 -0
- data/lib/octo/agent/tool_registry.rb +150 -0
- data/lib/octo/agent.rb +2180 -0
- data/lib/octo/agent_config.rb +989 -0
- data/lib/octo/agent_profile.rb +112 -0
- data/lib/octo/anthropic_stream_aggregator.rb +137 -0
- data/lib/octo/background_task_registry.rb +324 -0
- data/lib/octo/banner.rb +34 -0
- data/lib/octo/bedrock_stream_aggregator.rb +137 -0
- data/lib/octo/block_font.rb +331 -0
- data/lib/octo/cli.rb +968 -0
- data/lib/octo/client.rb +623 -0
- data/lib/octo/default_agents/SOUL.md +3 -0
- data/lib/octo/default_agents/USER.md +1 -0
- data/lib/octo/default_agents/base_prompt.md +66 -0
- data/lib/octo/default_agents/coding/profile.yml +2 -0
- data/lib/octo/default_agents/coding/system_prompt.md +67 -0
- data/lib/octo/default_agents/general/profile.yml +2 -0
- data/lib/octo/default_agents/general/system_prompt.md +16 -0
- data/lib/octo/default_parsers/doc_parser.rb +69 -0
- data/lib/octo/default_parsers/docx_parser.rb +188 -0
- data/lib/octo/default_parsers/pdf_parser.rb +120 -0
- data/lib/octo/default_parsers/pdf_parser_ocr.py +103 -0
- data/lib/octo/default_parsers/pdf_parser_plumber.py +62 -0
- data/lib/octo/default_parsers/pptx_parser.rb +140 -0
- data/lib/octo/default_parsers/xlsx_parser.rb +121 -0
- data/lib/octo/default_skills/browser-setup/SKILL.md +426 -0
- data/lib/octo/default_skills/channel-manager/SKILL.md +623 -0
- data/lib/octo/default_skills/channel-manager/dingtalk_setup.rb +191 -0
- data/lib/octo/default_skills/channel-manager/discord_setup.rb +199 -0
- data/lib/octo/default_skills/channel-manager/feishu_setup.rb +574 -0
- data/lib/octo/default_skills/channel-manager/import_lark_skills.rb +97 -0
- data/lib/octo/default_skills/channel-manager/install_feishu_skills.rb +105 -0
- data/lib/octo/default_skills/channel-manager/weixin_setup.rb +274 -0
- data/lib/octo/default_skills/code-explorer/SKILL.md +36 -0
- data/lib/octo/default_skills/cron-task-creator/SKILL.md +257 -0
- data/lib/octo/default_skills/cron-task-creator/evals/evals.json +38 -0
- data/lib/octo/default_skills/onboard/SKILL.md +578 -0
- data/lib/octo/default_skills/onboard/scripts/import_external_skills.rb +413 -0
- data/lib/octo/default_skills/onboard/scripts/install_builtin_skills.rb +97 -0
- data/lib/octo/default_skills/persist-memory/SKILL.md +59 -0
- data/lib/octo/default_skills/personal-website/SKILL.md +113 -0
- data/lib/octo/default_skills/personal-website/publish.rb +235 -0
- data/lib/octo/default_skills/product-help/SKILL.md +123 -0
- data/lib/octo/default_skills/product-help/docs/agent-config.md +74 -0
- data/lib/octo/default_skills/product-help/docs/best-practices.md +49 -0
- data/lib/octo/default_skills/product-help/docs/browser-tool.md +53 -0
- data/lib/octo/default_skills/product-help/docs/built-in-skills.md +43 -0
- data/lib/octo/default_skills/product-help/docs/cli-reference.md +82 -0
- data/lib/octo/default_skills/product-help/docs/create-your-first-skill.md +47 -0
- data/lib/octo/default_skills/product-help/docs/faq.md +98 -0
- data/lib/octo/default_skills/product-help/docs/how-to-use-a-skill.md +58 -0
- data/lib/octo/default_skills/product-help/docs/installation.md +59 -0
- data/lib/octo/default_skills/product-help/docs/memory-system.md +61 -0
- data/lib/octo/default_skills/product-help/docs/octorules.md +62 -0
- data/lib/octo/default_skills/product-help/docs/session-management.md +63 -0
- data/lib/octo/default_skills/product-help/docs/skill-basics.md +55 -0
- data/lib/octo/default_skills/product-help/docs/skill-frontmatter.md +61 -0
- data/lib/octo/default_skills/product-help/docs/web-server.md +49 -0
- data/lib/octo/default_skills/product-help/docs/what-is-octo.md +37 -0
- data/lib/octo/default_skills/product-help/docs/windows-installation.md +36 -0
- data/lib/octo/default_skills/product-help/docs/writing-tips.md +53 -0
- data/lib/octo/default_skills/recall-memory/SKILL.md +65 -0
- data/lib/octo/default_skills/skill-add/SKILL.md +59 -0
- data/lib/octo/default_skills/skill-add/scripts/install_from_zip.rb +295 -0
- data/lib/octo/default_skills/skill-creator/SKILL.md +602 -0
- data/lib/octo/default_skills/skill-creator/agents/analyzer.md +274 -0
- data/lib/octo/default_skills/skill-creator/agents/comparator.md +202 -0
- data/lib/octo/default_skills/skill-creator/agents/grader.md +223 -0
- data/lib/octo/default_skills/skill-creator/eval-viewer/generate_review.py +471 -0
- data/lib/octo/default_skills/skill-creator/eval-viewer/viewer.html +1325 -0
- data/lib/octo/default_skills/skill-creator/references/schemas.md +430 -0
- data/lib/octo/default_skills/skill-creator/scripts/__init__.py +0 -0
- data/lib/octo/default_skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- data/lib/octo/default_skills/skill-creator/scripts/generate_report.py +326 -0
- data/lib/octo/default_skills/skill-creator/scripts/improve_description.py +310 -0
- data/lib/octo/default_skills/skill-creator/scripts/quick_validate.py +103 -0
- data/lib/octo/default_skills/skill-creator/scripts/run_eval.py +317 -0
- data/lib/octo/default_skills/skill-creator/scripts/run_loop.py +331 -0
- data/lib/octo/default_skills/skill-creator/scripts/utils.py +47 -0
- data/lib/octo/default_skills/skill-creator/scripts/validate_skill_frontmatter.rb +143 -0
- data/lib/octo/idle_compression_timer.rb +115 -0
- data/lib/octo/json_ui_controller.rb +204 -0
- data/lib/octo/message_format/anthropic.rb +409 -0
- data/lib/octo/message_format/bedrock.rb +361 -0
- data/lib/octo/message_format/open_ai.rb +222 -0
- data/lib/octo/message_history.rb +373 -0
- data/lib/octo/openai_stream_aggregator.rb +130 -0
- data/lib/octo/plain_ui_controller.rb +166 -0
- data/lib/octo/providers.rb +534 -0
- data/lib/octo/server/browser_manager.rb +397 -0
- data/lib/octo/server/channel/adapters/base.rb +82 -0
- data/lib/octo/server/channel/adapters/dingtalk/adapter.rb +314 -0
- data/lib/octo/server/channel/adapters/dingtalk/api_client.rb +391 -0
- data/lib/octo/server/channel/adapters/dingtalk/stream_client.rb +203 -0
- data/lib/octo/server/channel/adapters/discord/adapter.rb +229 -0
- data/lib/octo/server/channel/adapters/discord/api_client.rb +107 -0
- data/lib/octo/server/channel/adapters/discord/gateway_client.rb +270 -0
- data/lib/octo/server/channel/adapters/feishu/adapter.rb +320 -0
- data/lib/octo/server/channel/adapters/feishu/bot.rb +478 -0
- data/lib/octo/server/channel/adapters/feishu/file_processor.rb +36 -0
- data/lib/octo/server/channel/adapters/feishu/message_parser.rb +129 -0
- data/lib/octo/server/channel/adapters/feishu/ws_client.rb +423 -0
- data/lib/octo/server/channel/adapters/telegram/adapter.rb +375 -0
- data/lib/octo/server/channel/adapters/telegram/api_client.rb +205 -0
- data/lib/octo/server/channel/adapters/wecom/adapter.rb +148 -0
- data/lib/octo/server/channel/adapters/wecom/media_downloader.rb +115 -0
- data/lib/octo/server/channel/adapters/wecom/ws_client.rb +395 -0
- data/lib/octo/server/channel/adapters/weixin/adapter.rb +692 -0
- data/lib/octo/server/channel/adapters/weixin/api_client.rb +402 -0
- data/lib/octo/server/channel/channel_config.rb +178 -0
- data/lib/octo/server/channel/channel_manager.rb +468 -0
- data/lib/octo/server/channel/channel_ui_controller.rb +224 -0
- data/lib/octo/server/channel.rb +33 -0
- data/lib/octo/server/discover.rb +77 -0
- data/lib/octo/server/epipe_safe_io.rb +105 -0
- data/lib/octo/server/http_server.rb +3554 -0
- data/lib/octo/server/scheduler.rb +317 -0
- data/lib/octo/server/server_master.rb +325 -0
- data/lib/octo/server/session_registry.rb +431 -0
- data/lib/octo/server/web_ui_controller.rb +487 -0
- data/lib/octo/session_manager.rb +385 -0
- data/lib/octo/skill.rb +466 -0
- data/lib/octo/skill_loader.rb +328 -0
- data/lib/octo/tools/base.rb +118 -0
- data/lib/octo/tools/browser.rb +625 -0
- data/lib/octo/tools/edit.rb +165 -0
- data/lib/octo/tools/file_reader.rb +549 -0
- data/lib/octo/tools/glob.rb +162 -0
- data/lib/octo/tools/grep.rb +356 -0
- data/lib/octo/tools/invoke_skill.rb +96 -0
- data/lib/octo/tools/list_tasks.rb +54 -0
- data/lib/octo/tools/redo_task.rb +41 -0
- data/lib/octo/tools/request_user_feedback.rb +84 -0
- data/lib/octo/tools/security.rb +333 -0
- data/lib/octo/tools/terminal/output_cleaner.rb +63 -0
- data/lib/octo/tools/terminal/persistent_session.rb +268 -0
- data/lib/octo/tools/terminal/safe_rm.sh +106 -0
- data/lib/octo/tools/terminal/session_manager.rb +213 -0
- data/lib/octo/tools/terminal.rb +1828 -0
- data/lib/octo/tools/todo_manager.rb +374 -0
- data/lib/octo/tools/trash_manager.rb +388 -0
- data/lib/octo/tools/undo_task.rb +35 -0
- data/lib/octo/tools/web_fetch.rb +242 -0
- data/lib/octo/tools/web_search.rb +260 -0
- data/lib/octo/tools/write.rb +77 -0
- data/lib/octo/ui2/block_font.rb +10 -0
- data/lib/octo/ui2/components/base_component.rb +163 -0
- data/lib/octo/ui2/components/command_suggestions.rb +290 -0
- data/lib/octo/ui2/components/common_component.rb +96 -0
- data/lib/octo/ui2/components/inline_input.rb +226 -0
- data/lib/octo/ui2/components/input_area.rb +1338 -0
- data/lib/octo/ui2/components/message_component.rb +99 -0
- data/lib/octo/ui2/components/modal_component.rb +419 -0
- data/lib/octo/ui2/components/todo_area.rb +149 -0
- data/lib/octo/ui2/components/tool_component.rb +107 -0
- data/lib/octo/ui2/components/welcome_banner.rb +139 -0
- data/lib/octo/ui2/layout_manager.rb +807 -0
- data/lib/octo/ui2/line_editor.rb +363 -0
- data/lib/octo/ui2/markdown_renderer.rb +100 -0
- data/lib/octo/ui2/output_buffer.rb +370 -0
- data/lib/octo/ui2/progress_handle.rb +362 -0
- data/lib/octo/ui2/progress_indicator.rb +55 -0
- data/lib/octo/ui2/screen_buffer.rb +273 -0
- data/lib/octo/ui2/terminal_detector.rb +119 -0
- data/lib/octo/ui2/theme_manager.rb +85 -0
- data/lib/octo/ui2/themes/base_theme.rb +105 -0
- data/lib/octo/ui2/themes/hacker_theme.rb +62 -0
- data/lib/octo/ui2/themes/minimal_theme.rb +56 -0
- data/lib/octo/ui2/thinking_verbs.rb +26 -0
- data/lib/octo/ui2/ui_controller.rb +1625 -0
- data/lib/octo/ui2/view_renderer.rb +177 -0
- data/lib/octo/ui2.rb +40 -0
- data/lib/octo/ui_interface.rb +154 -0
- data/lib/octo/utils/arguments_parser.rb +191 -0
- data/lib/octo/utils/browser_detector.rb +195 -0
- data/lib/octo/utils/encoding.rb +92 -0
- data/lib/octo/utils/environment_detector.rb +140 -0
- data/lib/octo/utils/file_ignore_helper.rb +170 -0
- data/lib/octo/utils/file_processor.rb +601 -0
- data/lib/octo/utils/gitignore_parser.rb +154 -0
- data/lib/octo/utils/limit_stack.rb +152 -0
- data/lib/octo/utils/logger.rb +124 -0
- data/lib/octo/utils/login_shell.rb +72 -0
- data/lib/octo/utils/model_pricing.rb +646 -0
- data/lib/octo/utils/parser_manager.rb +165 -0
- data/lib/octo/utils/path_helper.rb +15 -0
- data/lib/octo/utils/scripts_manager.rb +59 -0
- data/lib/octo/utils/string_matcher.rb +158 -0
- data/lib/octo/utils/trash_directory.rb +112 -0
- data/lib/octo/utils/workspace_rules.rb +46 -0
- data/lib/octo/version.rb +5 -0
- data/lib/octo/web/app.css +7141 -0
- data/lib/octo/web/app.js +543 -0
- data/lib/octo/web/apple-touch-icon.png +0 -0
- data/lib/octo/web/auth.js +150 -0
- data/lib/octo/web/channels.js +276 -0
- data/lib/octo/web/datepicker.js +205 -0
- data/lib/octo/web/favicon.png +0 -0
- data/lib/octo/web/i18n.js +1073 -0
- data/lib/octo/web/icon-512.png +0 -0
- data/lib/octo/web/icon-dark.svg +25 -0
- data/lib/octo/web/icon.svg +29 -0
- data/lib/octo/web/index.html +871 -0
- data/lib/octo/web/marked.min.js +69 -0
- data/lib/octo/web/onboard.js +491 -0
- data/lib/octo/web/profile.js +442 -0
- data/lib/octo/web/sessions.js +4421 -0
- data/lib/octo/web/settings.js +913 -0
- data/lib/octo/web/sidebar.js +32 -0
- data/lib/octo/web/skills.js +885 -0
- data/lib/octo/web/tasks.js +297 -0
- data/lib/octo/web/theme.js +105 -0
- data/lib/octo/web/trash.js +343 -0
- data/lib/octo/web/vendor/hljs/highlight.min.js +1244 -0
- data/lib/octo/web/vendor/hljs/hljs-theme.css +95 -0
- data/lib/octo/web/vendor/katex/auto-render.min.js +1 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/katex.min.css +1 -0
- data/lib/octo/web/vendor/katex/katex.min.js +1 -0
- data/lib/octo/web/version.js +449 -0
- data/lib/octo/web/weixin-qr.html +209 -0
- data/lib/octo/web/ws-dispatcher.js +357 -0
- data/lib/octo/web/ws.js +128 -0
- data/lib/octo.rb +145 -0
- data/scripts/build/build.sh +329 -0
- data/scripts/build/lib/apt.sh +56 -0
- data/scripts/build/lib/brew.sh +89 -0
- data/scripts/build/lib/colors.sh +17 -0
- data/scripts/build/lib/gem.sh +95 -0
- data/scripts/build/lib/mise.sh +125 -0
- data/scripts/build/lib/network.sh +157 -0
- data/scripts/build/lib/os.sh +57 -0
- data/scripts/build/lib/shell.sh +37 -0
- data/scripts/build/src/install.sh.cc +174 -0
- data/scripts/build/src/install_browser.sh.cc +101 -0
- data/scripts/build/src/install_full.sh.cc +290 -0
- data/scripts/build/src/install_rails_deps.sh.cc +145 -0
- data/scripts/build/src/install_system_deps.sh.cc +123 -0
- data/scripts/build/src/uninstall.sh.cc +101 -0
- data/scripts/install.ps1 +532 -0
- data/scripts/install.sh +567 -0
- data/scripts/install_browser.sh +479 -0
- data/scripts/install_full.sh +838 -0
- data/scripts/install_rails_deps.sh +746 -0
- data/scripts/install_system_deps.sh +518 -0
- data/scripts/uninstall.sh +287 -0
- data/sig/octo.rbs +4 -0
- metadata +614 -0
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# install_rails_deps.sh — install Ruby 3.3+ and Node.js 22+ via mise, and PostgreSQL for Rails development
|
|
3
|
+
# Generated from scripts/build/src/install_rails_deps.sh.cc — DO NOT EDIT DIRECTLY
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# bash install_rails_deps.sh # install ruby + node + postgres
|
|
7
|
+
# bash install_rails_deps.sh ruby # ruby only
|
|
8
|
+
# bash install_rails_deps.sh node # node only
|
|
9
|
+
# bash install_rails_deps.sh postgres # postgres only
|
|
10
|
+
|
|
11
|
+
set -e
|
|
12
|
+
|
|
13
|
+
INSTALL_TARGET="${1:-all}" # all | ruby | node | postgres
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ---[ @include lib/colors.sh ]---
|
|
17
|
+
|
|
18
|
+
RED='\033[0;31m'
|
|
19
|
+
GREEN='\033[0;32m'
|
|
20
|
+
YELLOW='\033[1;33m'
|
|
21
|
+
BLUE='\033[0;34m'
|
|
22
|
+
NC='\033[0m'
|
|
23
|
+
|
|
24
|
+
print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
|
|
25
|
+
print_success() { echo -e "${GREEN}✓${NC} $1"; }
|
|
26
|
+
print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
|
|
27
|
+
print_error() { echo -e "${RED}✗${NC} $1"; }
|
|
28
|
+
print_step() { echo -e "\n${BLUE}==>${NC} $1"; }
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---[ @include lib/os.sh ]---
|
|
32
|
+
|
|
33
|
+
# Sets OS (macOS | Linux | Windows | Unknown) and DISTRO (ubuntu | debian | …)
|
|
34
|
+
detect_os() {
|
|
35
|
+
case "$(uname -s)" in
|
|
36
|
+
Linux*) OS=Linux ;;
|
|
37
|
+
Darwin*) OS=macOS ;;
|
|
38
|
+
CYGWIN*) OS=Windows ;;
|
|
39
|
+
MINGW*) OS=Windows ;;
|
|
40
|
+
*) OS=Unknown ;;
|
|
41
|
+
esac
|
|
42
|
+
print_info "Detected OS: $OS"
|
|
43
|
+
|
|
44
|
+
if [ "$OS" = "Linux" ] && [ -f /etc/os-release ]; then
|
|
45
|
+
# shellcheck source=/dev/null
|
|
46
|
+
. /etc/os-release
|
|
47
|
+
DISTRO=$ID
|
|
48
|
+
print_info "Detected Linux distribution: $DISTRO"
|
|
49
|
+
else
|
|
50
|
+
DISTRO=unknown
|
|
51
|
+
fi
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Returns 0 if the given command is on PATH
|
|
55
|
+
command_exists() { command -v "$1" >/dev/null 2>&1; }
|
|
56
|
+
|
|
57
|
+
# Boolean helpers — use these in business logic instead of inline string comparisons
|
|
58
|
+
is_macos() { [ "$OS" = "macOS" ]; }
|
|
59
|
+
is_linux_apt() { [ "$OS" = "Linux" ] && { [ "$DISTRO" = "ubuntu" ] || [ "$DISTRO" = "debian" ]; }; }
|
|
60
|
+
|
|
61
|
+
# Returns 0 (true) if $1 >= $2 (semantic version comparison)
|
|
62
|
+
version_ge() { printf '%s\n%s\n' "$2" "$1" | sort -V -C; }
|
|
63
|
+
|
|
64
|
+
# Assert that the current OS/distro is supported (macOS or Ubuntu/Debian).
|
|
65
|
+
# Optional $1: hint message printed on failure (e.g. manual install instructions).
|
|
66
|
+
# Exits with code 1 on unsupported OS or distro.
|
|
67
|
+
assert_supported_os() {
|
|
68
|
+
local hint="${1:-}"
|
|
69
|
+
if [ "$OS" = "Linux" ]; then
|
|
70
|
+
if [ "$DISTRO" = "ubuntu" ] || [ "$DISTRO" = "debian" ]; then
|
|
71
|
+
return 0
|
|
72
|
+
fi
|
|
73
|
+
print_error "Unsupported Linux distribution: $DISTRO"
|
|
74
|
+
[ -n "$hint" ] && print_info "$hint"
|
|
75
|
+
exit 1
|
|
76
|
+
elif [ "$OS" = "macOS" ]; then
|
|
77
|
+
return 0
|
|
78
|
+
else
|
|
79
|
+
print_error "Unsupported OS: $OS"
|
|
80
|
+
[ -n "$hint" ] && print_info "$hint"
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# ---[ @include lib/shell.sh ]---
|
|
87
|
+
|
|
88
|
+
# Sets CURRENT_SHELL (zsh | bash | fish) and SHELL_RC (path to rc file)
|
|
89
|
+
detect_shell() {
|
|
90
|
+
local shell_name
|
|
91
|
+
shell_name=$(basename "$SHELL")
|
|
92
|
+
|
|
93
|
+
case "$shell_name" in
|
|
94
|
+
zsh)
|
|
95
|
+
CURRENT_SHELL="zsh"
|
|
96
|
+
SHELL_RC="$HOME/.zshrc"
|
|
97
|
+
;;
|
|
98
|
+
fish)
|
|
99
|
+
CURRENT_SHELL="fish"
|
|
100
|
+
SHELL_RC="$HOME/.config/fish/config.fish"
|
|
101
|
+
;;
|
|
102
|
+
bash)
|
|
103
|
+
CURRENT_SHELL="bash"
|
|
104
|
+
# macOS uses ~/.bash_profile; Linux uses ~/.bashrc
|
|
105
|
+
if is_macos; then
|
|
106
|
+
SHELL_RC="$HOME/.bash_profile"
|
|
107
|
+
else
|
|
108
|
+
SHELL_RC="$HOME/.bashrc"
|
|
109
|
+
fi
|
|
110
|
+
;;
|
|
111
|
+
*)
|
|
112
|
+
CURRENT_SHELL="bash"
|
|
113
|
+
SHELL_RC="$HOME/.bashrc"
|
|
114
|
+
;;
|
|
115
|
+
esac
|
|
116
|
+
|
|
117
|
+
print_info "Detected shell: $CURRENT_SHELL (rc file: $SHELL_RC)"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ---[ @include lib/network.sh ]---
|
|
122
|
+
|
|
123
|
+
# --------------------------------------------------------------------------
|
|
124
|
+
# Mirror variables — overridden by detect_network_region()
|
|
125
|
+
# --------------------------------------------------------------------------
|
|
126
|
+
SLOW_THRESHOLD_MS=5000
|
|
127
|
+
NETWORK_REGION="global" # china | global | unknown
|
|
128
|
+
USE_CN_MIRRORS=false
|
|
129
|
+
|
|
130
|
+
GITHUB_RAW_BASE_URL="https://raw.githubusercontent.com"
|
|
131
|
+
DEFAULT_RUBYGEMS_URL="https://rubygems.org"
|
|
132
|
+
DEFAULT_NPM_REGISTRY="https://registry.npmjs.org"
|
|
133
|
+
DEFAULT_MISE_INSTALL_URL="https://mise.run"
|
|
134
|
+
|
|
135
|
+
CN_CDN_BASE_URL="https://oss.1024code.com"
|
|
136
|
+
CN_ALIYUN_MIRROR="https://mirrors.aliyun.com"
|
|
137
|
+
CN_MISE_INSTALL_URL="${CN_CDN_BASE_URL}/mise.sh"
|
|
138
|
+
CN_RUBY_PRECOMPILED_URL="${CN_CDN_BASE_URL}/ruby/ruby-{version}.{platform}.tar.gz"
|
|
139
|
+
CN_RUBYGEMS_URL="${CN_ALIYUN_MIRROR}/rubygems/"
|
|
140
|
+
CN_NPM_REGISTRY="https://registry.npmmirror.com"
|
|
141
|
+
CN_NODE_MIRROR_URL="https://cdn.npmmirror.com/binaries/node/"
|
|
142
|
+
CN_GEM_BASE_URL="${CN_CDN_BASE_URL}/octo"
|
|
143
|
+
CN_GEM_LATEST_URL="${CN_GEM_BASE_URL}/latest.txt"
|
|
144
|
+
|
|
145
|
+
# Active values (set by detect_network_region)
|
|
146
|
+
MISE_INSTALL_URL="$DEFAULT_MISE_INSTALL_URL"
|
|
147
|
+
RUBYGEMS_INSTALL_URL="$DEFAULT_RUBYGEMS_URL"
|
|
148
|
+
NPM_REGISTRY_URL="$DEFAULT_NPM_REGISTRY"
|
|
149
|
+
NODE_MIRROR_URL="" # empty = mise default (nodejs.org)
|
|
150
|
+
RUBY_VERSION_SPEC="ruby@3" # CN mode pins to a specific precompiled build
|
|
151
|
+
|
|
152
|
+
# --------------------------------------------------------------------------
|
|
153
|
+
# Internal probe helpers
|
|
154
|
+
# --------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
# Probe a single URL; echoes round-trip time in ms, or "timeout"
|
|
157
|
+
_probe_url() {
|
|
158
|
+
local url="$1"
|
|
159
|
+
local out http_code total_time
|
|
160
|
+
out=$(curl -s -o /dev/null -w "%{http_code} %{time_total}" \
|
|
161
|
+
--connect-timeout 5 --max-time 5 "$url" 2>/dev/null) || true
|
|
162
|
+
http_code="${out%% *}"
|
|
163
|
+
total_time="${out#* }"
|
|
164
|
+
if [ -z "$http_code" ] || [ "$http_code" = "000" ] || [ "$http_code" = "$out" ]; then
|
|
165
|
+
echo "timeout"; return
|
|
166
|
+
fi
|
|
167
|
+
awk -v s="$total_time" 'BEGIN { printf "%d", s * 1000 }'
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# Returns 0 (true) if result is slow or unreachable
|
|
171
|
+
_is_slow_or_unreachable() {
|
|
172
|
+
local r="$1"
|
|
173
|
+
[ "$r" = "timeout" ] && return 0
|
|
174
|
+
[ "${r:-9999}" -ge "$SLOW_THRESHOLD_MS" ] 2>/dev/null
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
_format_probe_time() {
|
|
178
|
+
local r="$1"
|
|
179
|
+
[ "$r" = "timeout" ] && echo "timeout" && return
|
|
180
|
+
awk -v ms="$r" 'BEGIN { printf "%.1fs", ms / 1000 }'
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
_print_probe_result() {
|
|
184
|
+
local label="$1" result="$2"
|
|
185
|
+
if [ "$result" = "timeout" ]; then
|
|
186
|
+
print_warning "UNREACHABLE ${label}"
|
|
187
|
+
elif _is_slow_or_unreachable "$result"; then
|
|
188
|
+
print_warning "SLOW ($(_format_probe_time "$result")) ${label}"
|
|
189
|
+
else
|
|
190
|
+
print_success "OK ($(_format_probe_time "$result")) ${label}"
|
|
191
|
+
fi
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# Probe URL up to max_retries times; returns first fast result or last result
|
|
195
|
+
_probe_url_with_retry() {
|
|
196
|
+
local url="$1" max="${2:-2}" result
|
|
197
|
+
for _ in $(seq 1 "$max"); do
|
|
198
|
+
result=$(_probe_url "$url")
|
|
199
|
+
! _is_slow_or_unreachable "$result" && { echo "$result"; return 0; }
|
|
200
|
+
done
|
|
201
|
+
echo "$result"
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# --------------------------------------------------------------------------
|
|
205
|
+
# detect_network_region — sets USE_CN_MIRRORS and active mirror variables
|
|
206
|
+
# --------------------------------------------------------------------------
|
|
207
|
+
detect_network_region() {
|
|
208
|
+
print_step "Network pre-flight check..."
|
|
209
|
+
echo ""
|
|
210
|
+
|
|
211
|
+
local google_result baidu_result
|
|
212
|
+
google_result=$(_probe_url "https://www.google.com")
|
|
213
|
+
baidu_result=$(_probe_url "https://www.baidu.com")
|
|
214
|
+
|
|
215
|
+
_print_probe_result "google.com" "$google_result"
|
|
216
|
+
_print_probe_result "baidu.com" "$baidu_result"
|
|
217
|
+
|
|
218
|
+
local google_ok=false baidu_ok=false
|
|
219
|
+
! _is_slow_or_unreachable "$google_result" && google_ok=true
|
|
220
|
+
! _is_slow_or_unreachable "$baidu_result" && baidu_ok=true
|
|
221
|
+
|
|
222
|
+
if [ "$google_ok" = true ]; then
|
|
223
|
+
NETWORK_REGION="global"
|
|
224
|
+
print_success "Region: global"
|
|
225
|
+
elif [ "$baidu_ok" = true ]; then
|
|
226
|
+
NETWORK_REGION="china"
|
|
227
|
+
print_success "Region: china"
|
|
228
|
+
else
|
|
229
|
+
NETWORK_REGION="unknown"
|
|
230
|
+
print_warning "Region: unknown (both unreachable)"
|
|
231
|
+
fi
|
|
232
|
+
echo ""
|
|
233
|
+
|
|
234
|
+
if [ "$NETWORK_REGION" = "china" ]; then
|
|
235
|
+
local cdn_result mirror_result
|
|
236
|
+
cdn_result=$(_probe_url_with_retry "$CN_MISE_INSTALL_URL")
|
|
237
|
+
mirror_result=$(_probe_url_with_retry "$CN_RUBYGEMS_URL")
|
|
238
|
+
|
|
239
|
+
_print_probe_result "CN CDN (mise/Ruby)" "$cdn_result"
|
|
240
|
+
_print_probe_result "Aliyun (gem)" "$mirror_result"
|
|
241
|
+
|
|
242
|
+
local cdn_ok=false mirror_ok=false
|
|
243
|
+
! _is_slow_or_unreachable "$cdn_result" && cdn_ok=true
|
|
244
|
+
! _is_slow_or_unreachable "$mirror_result" && mirror_ok=true
|
|
245
|
+
|
|
246
|
+
if [ "$cdn_ok" = true ] || [ "$mirror_ok" = true ]; then
|
|
247
|
+
USE_CN_MIRRORS=true
|
|
248
|
+
MISE_INSTALL_URL="$CN_MISE_INSTALL_URL"
|
|
249
|
+
RUBYGEMS_INSTALL_URL="$CN_RUBYGEMS_URL"
|
|
250
|
+
NPM_REGISTRY_URL="$CN_NPM_REGISTRY"
|
|
251
|
+
NODE_MIRROR_URL="$CN_NODE_MIRROR_URL"
|
|
252
|
+
RUBY_VERSION_SPEC="ruby@3.4.8"
|
|
253
|
+
print_info "CN mirrors applied"
|
|
254
|
+
else
|
|
255
|
+
print_warning "CN mirrors unreachable — falling back to global sources"
|
|
256
|
+
fi
|
|
257
|
+
else
|
|
258
|
+
local rubygems_result mise_result
|
|
259
|
+
rubygems_result=$(_probe_url_with_retry "$DEFAULT_RUBYGEMS_URL")
|
|
260
|
+
mise_result=$(_probe_url_with_retry "$DEFAULT_MISE_INSTALL_URL")
|
|
261
|
+
|
|
262
|
+
_print_probe_result "RubyGems" "$rubygems_result"
|
|
263
|
+
_print_probe_result "mise.run" "$mise_result"
|
|
264
|
+
|
|
265
|
+
_is_slow_or_unreachable "$rubygems_result" && print_warning "RubyGems is slow/unreachable."
|
|
266
|
+
_is_slow_or_unreachable "$mise_result" && print_warning "mise.run is slow/unreachable."
|
|
267
|
+
|
|
268
|
+
USE_CN_MIRRORS=false
|
|
269
|
+
RUBY_VERSION_SPEC="ruby@3"
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
echo ""
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
# ---[ @include lib/apt.sh ]---
|
|
277
|
+
|
|
278
|
+
# Configure apt mirror for CN region and run apt-get update.
|
|
279
|
+
# Guards: only runs on ubuntu/debian ($DISTRO).
|
|
280
|
+
# Relies on $USE_CN_MIRRORS set by detect_network_region (network.sh).
|
|
281
|
+
setup_apt_mirror() {
|
|
282
|
+
[ "$DISTRO" = "ubuntu" ] || [ "$DISTRO" = "debian" ] || return 0
|
|
283
|
+
|
|
284
|
+
if [ "$USE_CN_MIRRORS" = true ]; then
|
|
285
|
+
print_info "Region: China — configuring Aliyun apt mirror"
|
|
286
|
+
|
|
287
|
+
if [ -f /etc/apt/sources.list ]; then
|
|
288
|
+
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
|
|
289
|
+
print_info "Backed up /etc/apt/sources.list to sources.list.bak"
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
if [ "$DISTRO" = "debian" ]; then
|
|
293
|
+
local codename="${VERSION_CODENAME:-bookworm}"
|
|
294
|
+
local components="main contrib non-free non-free-firmware"
|
|
295
|
+
local mirror="${CN_ALIYUN_MIRROR}/debian/"
|
|
296
|
+
local security_mirror="${CN_ALIYUN_MIRROR}/debian-security/"
|
|
297
|
+
sudo tee /etc/apt/sources.list > /dev/null <<EOF
|
|
298
|
+
deb ${mirror} ${codename} ${components}
|
|
299
|
+
deb ${mirror} ${codename}-updates ${components}
|
|
300
|
+
deb ${mirror} ${codename}-backports ${components}
|
|
301
|
+
deb ${security_mirror} ${codename}-security ${components}
|
|
302
|
+
EOF
|
|
303
|
+
else
|
|
304
|
+
local codename="${VERSION_CODENAME:-jammy}"
|
|
305
|
+
local components="main restricted universe multiverse"
|
|
306
|
+
local arch; arch=$(dpkg --print-architecture 2>/dev/null || uname -m)
|
|
307
|
+
if [ "$arch" = "arm64" ] || [ "$arch" = "aarch64" ]; then
|
|
308
|
+
local mirror="${CN_ALIYUN_MIRROR}/ubuntu-ports/"
|
|
309
|
+
else
|
|
310
|
+
local mirror="${CN_ALIYUN_MIRROR}/ubuntu/"
|
|
311
|
+
fi
|
|
312
|
+
sudo tee /etc/apt/sources.list > /dev/null <<EOF
|
|
313
|
+
deb ${mirror} ${codename} ${components}
|
|
314
|
+
deb ${mirror} ${codename}-updates ${components}
|
|
315
|
+
deb ${mirror} ${codename}-backports ${components}
|
|
316
|
+
deb ${mirror} ${codename}-security ${components}
|
|
317
|
+
EOF
|
|
318
|
+
fi
|
|
319
|
+
|
|
320
|
+
print_success "apt mirror set to Aliyun"
|
|
321
|
+
else
|
|
322
|
+
print_info "Region: global — using default apt sources"
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
sudo apt-get update -qq
|
|
326
|
+
print_success "apt updated"
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
# ---[ @include lib/brew.sh ]---
|
|
331
|
+
|
|
332
|
+
# --------------------------------------------------------------------------
|
|
333
|
+
# Homebrew CN mirror URLs (Aliyun)
|
|
334
|
+
# --------------------------------------------------------------------------
|
|
335
|
+
CN_HOMEBREW_INSTALL_SCRIPT_URL="${CN_CDN_BASE_URL}/Homebrew/install/HEAD/install.sh"
|
|
336
|
+
CN_HOMEBREW_BREW_GIT_REMOTE="https://mirrors.aliyun.com/homebrew/brew.git"
|
|
337
|
+
CN_HOMEBREW_CORE_GIT_REMOTE="https://mirrors.aliyun.com/homebrew/homebrew-core.git"
|
|
338
|
+
CN_HOMEBREW_BOTTLE_DOMAIN="https://mirrors.aliyun.com/homebrew/homebrew-bottles"
|
|
339
|
+
CN_HOMEBREW_API_DOMAIN="https://mirrors.aliyun.com/homebrew-bottles/api"
|
|
340
|
+
|
|
341
|
+
HOMEBREW_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh"
|
|
342
|
+
|
|
343
|
+
# --------------------------------------------------------------------------
|
|
344
|
+
# configure_homebrew_cn_mirrors — export env vars and persist to SHELL_RC
|
|
345
|
+
# Only runs when USE_CN_MIRRORS=true
|
|
346
|
+
# --------------------------------------------------------------------------
|
|
347
|
+
configure_homebrew_cn_mirrors() {
|
|
348
|
+
[ "$USE_CN_MIRRORS" = true ] || return 0
|
|
349
|
+
|
|
350
|
+
print_info "Configuring Homebrew CN mirrors..."
|
|
351
|
+
export HOMEBREW_INSTALL_FROM_API=1
|
|
352
|
+
export HOMEBREW_API_DOMAIN="$CN_HOMEBREW_API_DOMAIN"
|
|
353
|
+
export HOMEBREW_BREW_GIT_REMOTE="$CN_HOMEBREW_BREW_GIT_REMOTE"
|
|
354
|
+
export HOMEBREW_CORE_GIT_REMOTE="$CN_HOMEBREW_CORE_GIT_REMOTE"
|
|
355
|
+
export HOMEBREW_BOTTLE_DOMAIN="$CN_HOMEBREW_BOTTLE_DOMAIN"
|
|
356
|
+
|
|
357
|
+
if ! grep -q "HOMEBREW_BOTTLE_DOMAIN" "$SHELL_RC" 2>/dev/null; then
|
|
358
|
+
{
|
|
359
|
+
echo ""
|
|
360
|
+
echo "# Homebrew CN mirrors (added by octo installer)"
|
|
361
|
+
echo "export HOMEBREW_INSTALL_FROM_API=1"
|
|
362
|
+
echo "export HOMEBREW_API_DOMAIN=\"${CN_HOMEBREW_API_DOMAIN}\""
|
|
363
|
+
echo "export HOMEBREW_BREW_GIT_REMOTE=\"${CN_HOMEBREW_BREW_GIT_REMOTE}\""
|
|
364
|
+
echo "export HOMEBREW_CORE_GIT_REMOTE=\"${CN_HOMEBREW_CORE_GIT_REMOTE}\""
|
|
365
|
+
echo "export HOMEBREW_BOTTLE_DOMAIN=\"${CN_HOMEBREW_BOTTLE_DOMAIN}\""
|
|
366
|
+
} >> "$SHELL_RC"
|
|
367
|
+
print_success "Homebrew CN mirrors written to $SHELL_RC"
|
|
368
|
+
else
|
|
369
|
+
print_success "Homebrew CN mirrors already configured in $SHELL_RC"
|
|
370
|
+
fi
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
# --------------------------------------------------------------------------
|
|
374
|
+
# restore_homebrew_cn_mirrors — remove CN mirror lines from SHELL_RC
|
|
375
|
+
# --------------------------------------------------------------------------
|
|
376
|
+
restore_homebrew_cn_mirrors() {
|
|
377
|
+
if [ -n "$SHELL_RC" ] && [ -f "$SHELL_RC" ] && grep -q "HOMEBREW_BOTTLE_DOMAIN" "$SHELL_RC" 2>/dev/null; then
|
|
378
|
+
sed -i.bak '/# Homebrew CN mirrors (added by octo installer)/d' "$SHELL_RC"
|
|
379
|
+
sed -i.bak '/HOMEBREW_INSTALL_FROM_API/d' "$SHELL_RC"
|
|
380
|
+
sed -i.bak '/HOMEBREW_API_DOMAIN/d' "$SHELL_RC"
|
|
381
|
+
sed -i.bak '/HOMEBREW_BREW_GIT_REMOTE/d' "$SHELL_RC"
|
|
382
|
+
sed -i.bak '/HOMEBREW_CORE_GIT_REMOTE/d' "$SHELL_RC"
|
|
383
|
+
sed -i.bak '/HOMEBREW_BOTTLE_DOMAIN/d' "$SHELL_RC"
|
|
384
|
+
rm -f "${SHELL_RC}.bak"
|
|
385
|
+
unset HOMEBREW_BREW_GIT_REMOTE HOMEBREW_CORE_GIT_REMOTE HOMEBREW_BOTTLE_DOMAIN
|
|
386
|
+
print_success "Homebrew CN mirrors removed from $SHELL_RC"
|
|
387
|
+
else
|
|
388
|
+
print_info "Homebrew CN mirrors — nothing to restore"
|
|
389
|
+
fi
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
# --------------------------------------------------------------------------
|
|
393
|
+
# ensure_homebrew — install Homebrew if missing, add to PATH
|
|
394
|
+
# --------------------------------------------------------------------------
|
|
395
|
+
ensure_homebrew() {
|
|
396
|
+
if command_exists brew; then
|
|
397
|
+
print_success "Homebrew already installed"
|
|
398
|
+
return 0
|
|
399
|
+
fi
|
|
400
|
+
|
|
401
|
+
print_info "Installing Homebrew..."
|
|
402
|
+
local brew_url="$HOMEBREW_INSTALL_SCRIPT_URL"
|
|
403
|
+
[ "$USE_CN_MIRRORS" = true ] && brew_url="$CN_HOMEBREW_INSTALL_SCRIPT_URL"
|
|
404
|
+
|
|
405
|
+
if /bin/bash -c "$(curl -fsSL "$brew_url")"; then
|
|
406
|
+
# Add Homebrew to PATH (Apple Silicon default path)
|
|
407
|
+
echo 'export PATH="/opt/homebrew/bin:$PATH"' >> "$SHELL_RC"
|
|
408
|
+
export PATH="/opt/homebrew/bin:$PATH"
|
|
409
|
+
print_success "Homebrew installed"
|
|
410
|
+
else
|
|
411
|
+
print_error "Failed to install Homebrew"
|
|
412
|
+
return 1
|
|
413
|
+
fi
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
# ---[ @include lib/mise.sh ]---
|
|
418
|
+
|
|
419
|
+
# --------------------------------------------------------------------------
|
|
420
|
+
# Internal helper — locate the mise binary
|
|
421
|
+
# --------------------------------------------------------------------------
|
|
422
|
+
_mise_bin() {
|
|
423
|
+
if [ -x "$HOME/.local/bin/mise" ]; then
|
|
424
|
+
echo "$HOME/.local/bin/mise"
|
|
425
|
+
elif command_exists mise; then
|
|
426
|
+
command -v mise
|
|
427
|
+
else
|
|
428
|
+
echo ""
|
|
429
|
+
fi
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
# --------------------------------------------------------------------------
|
|
433
|
+
# ensure_mise — install mise if missing, activate it in the current session
|
|
434
|
+
# --------------------------------------------------------------------------
|
|
435
|
+
ensure_mise() {
|
|
436
|
+
local mise
|
|
437
|
+
mise=$(_mise_bin)
|
|
438
|
+
|
|
439
|
+
if [ -n "$mise" ]; then
|
|
440
|
+
print_success "mise already installed: $($mise --version 2>/dev/null || echo 'n/a')"
|
|
441
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
442
|
+
eval "$($mise activate bash 2>/dev/null)" 2>/dev/null || true
|
|
443
|
+
MISE_BIN="$mise"
|
|
444
|
+
return 0
|
|
445
|
+
fi
|
|
446
|
+
|
|
447
|
+
print_step "Installing mise..."
|
|
448
|
+
if curl -fsSL "$MISE_INSTALL_URL" | sh; then
|
|
449
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
450
|
+
eval "$(~/.local/bin/mise activate bash 2>/dev/null)" 2>/dev/null || true
|
|
451
|
+
|
|
452
|
+
# Persist mise activation to shell rc
|
|
453
|
+
local init_line='eval "$(~/.local/bin/mise activate '"$CURRENT_SHELL"')"'
|
|
454
|
+
if ! grep -q "mise activate" "$SHELL_RC" 2>/dev/null; then
|
|
455
|
+
echo "$init_line" >> "$SHELL_RC"
|
|
456
|
+
print_info "Added mise activation to $SHELL_RC"
|
|
457
|
+
fi
|
|
458
|
+
|
|
459
|
+
MISE_BIN="$HOME/.local/bin/mise"
|
|
460
|
+
print_success "mise installed"
|
|
461
|
+
else
|
|
462
|
+
print_error "Failed to install mise"
|
|
463
|
+
return 1
|
|
464
|
+
fi
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
# --------------------------------------------------------------------------
|
|
468
|
+
# install_ruby_via_mise — install Ruby via mise
|
|
469
|
+
# CN mode: precompiled binary from oss.1024code.com
|
|
470
|
+
# Global: mise default (precompiled where available)
|
|
471
|
+
# --------------------------------------------------------------------------
|
|
472
|
+
install_ruby_via_mise() {
|
|
473
|
+
local mise="${MISE_BIN:-$(_mise_bin)}"
|
|
474
|
+
if [ -z "$mise" ]; then
|
|
475
|
+
print_error "mise not found — call ensure_mise first"
|
|
476
|
+
return 1
|
|
477
|
+
fi
|
|
478
|
+
|
|
479
|
+
print_info "Installing Ruby via mise ($RUBY_VERSION_SPEC)..."
|
|
480
|
+
|
|
481
|
+
if [ "$USE_CN_MIRRORS" = true ]; then
|
|
482
|
+
"$mise" settings ruby.compile=false 2>/dev/null || true
|
|
483
|
+
"$mise" settings ruby.precompiled_url="$CN_RUBY_PRECOMPILED_URL" 2>/dev/null || true
|
|
484
|
+
print_info "Using precompiled Ruby from CN CDN"
|
|
485
|
+
else
|
|
486
|
+
# Enable precompiled binaries globally (mise supports this on common platforms)
|
|
487
|
+
"$mise" settings ruby.compile=false 2>/dev/null || true
|
|
488
|
+
"$mise" settings unset ruby.precompiled_url 2>/dev/null || true
|
|
489
|
+
fi
|
|
490
|
+
|
|
491
|
+
if "$mise" use -g "$RUBY_VERSION_SPEC"; then
|
|
492
|
+
eval "$($mise activate bash 2>/dev/null)" 2>/dev/null || true
|
|
493
|
+
local installed
|
|
494
|
+
installed=$(ruby -e 'puts RUBY_VERSION' 2>/dev/null || echo "unknown")
|
|
495
|
+
print_success "Ruby $installed installed"
|
|
496
|
+
else
|
|
497
|
+
print_error "Failed to install Ruby via mise"
|
|
498
|
+
return 1
|
|
499
|
+
fi
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
# --------------------------------------------------------------------------
|
|
503
|
+
# install_node_via_mise — install Node.js 22 via mise
|
|
504
|
+
# --------------------------------------------------------------------------
|
|
505
|
+
install_node_via_mise() {
|
|
506
|
+
local mise="${MISE_BIN:-$(_mise_bin)}"
|
|
507
|
+
if [ -z "$mise" ]; then
|
|
508
|
+
print_error "mise not found — call ensure_mise first"
|
|
509
|
+
return 1
|
|
510
|
+
fi
|
|
511
|
+
|
|
512
|
+
# Skip if compatible Node is already active
|
|
513
|
+
if command_exists node; then
|
|
514
|
+
local ver major
|
|
515
|
+
ver=$(node --version 2>/dev/null | sed 's/v//')
|
|
516
|
+
major="${ver%%.*}"
|
|
517
|
+
if [ "${major:-0}" -ge 22 ] 2>/dev/null; then
|
|
518
|
+
print_success "Node.js v${ver} already satisfies >= 22 — skipping"
|
|
519
|
+
return 0
|
|
520
|
+
fi
|
|
521
|
+
fi
|
|
522
|
+
|
|
523
|
+
# Apply CN Node mirror if needed
|
|
524
|
+
if [ "$USE_CN_MIRRORS" = true ] && [ -n "$NODE_MIRROR_URL" ]; then
|
|
525
|
+
"$mise" settings node.mirror_url="$NODE_MIRROR_URL" 2>/dev/null || true
|
|
526
|
+
print_info "Node mirror → ${NODE_MIRROR_URL}"
|
|
527
|
+
fi
|
|
528
|
+
|
|
529
|
+
print_info "Installing Node.js 22 via mise..."
|
|
530
|
+
if "$mise" use -g node@22; then
|
|
531
|
+
eval "$($mise activate bash 2>/dev/null)" 2>/dev/null || true
|
|
532
|
+
print_success "Node.js $(node --version 2>/dev/null) installed"
|
|
533
|
+
else
|
|
534
|
+
print_error "Failed to install Node.js via mise"
|
|
535
|
+
return 1
|
|
536
|
+
fi
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
# ---[ @include lib/gem.sh ]---
|
|
541
|
+
|
|
542
|
+
configure_gem_source() {
|
|
543
|
+
local gemrc="$HOME/.gemrc"
|
|
544
|
+
|
|
545
|
+
if [ "$USE_CN_MIRRORS" = true ]; then
|
|
546
|
+
if grep -q "${CN_RUBYGEMS_URL}" "$gemrc" 2>/dev/null; then
|
|
547
|
+
print_success "gem source already → ${CN_RUBYGEMS_URL}"
|
|
548
|
+
else
|
|
549
|
+
[ -f "$gemrc" ] && mv "$gemrc" "$HOME/.gemrc_octobak"
|
|
550
|
+
cat > "$gemrc" <<GEMRC
|
|
551
|
+
:sources:
|
|
552
|
+
- ${CN_RUBYGEMS_URL}
|
|
553
|
+
GEMRC
|
|
554
|
+
print_success "gem source → ${CN_RUBYGEMS_URL}"
|
|
555
|
+
fi
|
|
556
|
+
else
|
|
557
|
+
if [ -f "$gemrc" ] && grep -q "${CN_RUBYGEMS_URL}" "$gemrc" 2>/dev/null; then
|
|
558
|
+
if [ -f "$HOME/.gemrc_octobak" ]; then
|
|
559
|
+
mv "$HOME/.gemrc_octobak" "$gemrc"
|
|
560
|
+
print_info "gem source restored from backup"
|
|
561
|
+
else
|
|
562
|
+
rm "$gemrc"
|
|
563
|
+
print_info "gem source restored to default"
|
|
564
|
+
fi
|
|
565
|
+
fi
|
|
566
|
+
fi
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
restore_gemrc() {
|
|
570
|
+
local gemrc="$HOME/.gemrc"
|
|
571
|
+
local gemrc_bak="$HOME/.gemrc_octobak"
|
|
572
|
+
if [ -f "$gemrc_bak" ]; then
|
|
573
|
+
mv "$gemrc_bak" "$gemrc"
|
|
574
|
+
print_success "~/.gemrc restored from backup"
|
|
575
|
+
elif [ -f "$gemrc" ]; then
|
|
576
|
+
rm "$gemrc"
|
|
577
|
+
print_success "~/.gemrc removed"
|
|
578
|
+
else
|
|
579
|
+
print_info "~/.gemrc — nothing to restore"
|
|
580
|
+
fi
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
# --------------------------------------------------------------------------
|
|
584
|
+
# Redirect GEM_HOME to user dir when system Ruby gem dir is not writable
|
|
585
|
+
# --------------------------------------------------------------------------
|
|
586
|
+
setup_gem_home() {
|
|
587
|
+
local gem_dir
|
|
588
|
+
gem_dir=$(gem environment gemdir 2>/dev/null || true)
|
|
589
|
+
[ -w "$gem_dir" ] && return 0
|
|
590
|
+
|
|
591
|
+
local ruby_api
|
|
592
|
+
ruby_api=$(ruby -e 'puts RbConfig::CONFIG["ruby_version"]' 2>/dev/null)
|
|
593
|
+
export GEM_HOME="$HOME/.gem/ruby/${ruby_api}"
|
|
594
|
+
export GEM_PATH="$HOME/.gem/ruby/${ruby_api}"
|
|
595
|
+
export PATH="$HOME/.gem/ruby/${ruby_api}/bin:$PATH"
|
|
596
|
+
print_info "System Ruby detected — gems will install to ~/.gem/ruby/${ruby_api}"
|
|
597
|
+
|
|
598
|
+
if [ -n "$SHELL_RC" ] && ! grep -q "GEM_HOME" "$SHELL_RC" 2>/dev/null; then
|
|
599
|
+
{
|
|
600
|
+
echo ""
|
|
601
|
+
echo "# Ruby user gem dir (added by octo installer)"
|
|
602
|
+
echo "export GEM_HOME=\"\$HOME/.gem/ruby/${ruby_api}\""
|
|
603
|
+
echo "export GEM_PATH=\"\$HOME/.gem/ruby/${ruby_api}\""
|
|
604
|
+
echo "export PATH=\"\$HOME/.gem/ruby/${ruby_api}/bin:\$PATH\""
|
|
605
|
+
} >> "$SHELL_RC"
|
|
606
|
+
print_info "GEM_HOME written to $SHELL_RC"
|
|
607
|
+
fi
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
restore_gem_home() {
|
|
611
|
+
[ -n "$SHELL_RC" ] && [ -f "$SHELL_RC" ] || return 0
|
|
612
|
+
grep -q "GEM_HOME" "$SHELL_RC" 2>/dev/null || return 0
|
|
613
|
+
# Remove the block written by setup_gem_home (comment + 3 export lines)
|
|
614
|
+
local tmp
|
|
615
|
+
tmp=$(mktemp)
|
|
616
|
+
grep -v "# Ruby user gem dir (added by octo installer)" "$SHELL_RC" \
|
|
617
|
+
| grep -v "export GEM_HOME=" \
|
|
618
|
+
| grep -v "export GEM_PATH=" \
|
|
619
|
+
| grep -v "/.gem/ruby/" \
|
|
620
|
+
> "$tmp" && mv "$tmp" "$SHELL_RC"
|
|
621
|
+
print_success "GEM_HOME removed from $SHELL_RC"
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
# --------------------------------------------------------------------------
|
|
626
|
+
# Ruby: install via mise and configure gem source
|
|
627
|
+
# --------------------------------------------------------------------------
|
|
628
|
+
install_ruby() {
|
|
629
|
+
print_step "Installing Ruby via mise..."
|
|
630
|
+
|
|
631
|
+
# Install Ruby compile-time dependencies first
|
|
632
|
+
if is_macos; then
|
|
633
|
+
brew install openssl@3 libyaml gmp
|
|
634
|
+
elif is_linux_apt; then
|
|
635
|
+
sudo apt-get install -y \
|
|
636
|
+
rustc libssl-dev libyaml-dev zlib1g-dev libgmp-dev
|
|
637
|
+
fi
|
|
638
|
+
|
|
639
|
+
ensure_mise || return 1
|
|
640
|
+
install_ruby_via_mise || return 1
|
|
641
|
+
|
|
642
|
+
# Configure gem source for CN users
|
|
643
|
+
configure_gem_source
|
|
644
|
+
|
|
645
|
+
# Reinstall octo in the new Ruby environment
|
|
646
|
+
"${MISE_BIN:-mise}" exec -- gem install octo --no-document \
|
|
647
|
+
&& print_success "octo reinstalled" \
|
|
648
|
+
|| print_warning "Could not reinstall octo — run manually: gem install octo --no-document"
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
# --------------------------------------------------------------------------
|
|
652
|
+
# Node: install via mise and configure npm registry
|
|
653
|
+
# --------------------------------------------------------------------------
|
|
654
|
+
install_node() {
|
|
655
|
+
print_step "Installing Node.js via mise..."
|
|
656
|
+
|
|
657
|
+
ensure_mise || return 1
|
|
658
|
+
install_node_via_mise || return 1
|
|
659
|
+
|
|
660
|
+
if [ "$USE_CN_MIRRORS" = true ] && command_exists npm; then
|
|
661
|
+
npm config set registry "$NPM_REGISTRY_URL" 2>/dev/null || true
|
|
662
|
+
print_info "npm registry → ${NPM_REGISTRY_URL}"
|
|
663
|
+
fi
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
# --------------------------------------------------------------------------
|
|
667
|
+
# PostgreSQL: install server + client via brew (macOS) or apt (Linux/WSL)
|
|
668
|
+
# --------------------------------------------------------------------------
|
|
669
|
+
install_postgres() {
|
|
670
|
+
print_step "Installing PostgreSQL..."
|
|
671
|
+
|
|
672
|
+
if is_macos; then
|
|
673
|
+
ensure_homebrew || return 1
|
|
674
|
+
brew install postgresql
|
|
675
|
+
brew services start postgresql
|
|
676
|
+
|
|
677
|
+
elif is_linux_apt; then
|
|
678
|
+
setup_apt_mirror
|
|
679
|
+
sudo apt-get install -y postgresql libpq-dev \
|
|
680
|
+
libssl-dev libreadline-dev zlib1g-dev libyaml-dev libffi-dev
|
|
681
|
+
sudo systemctl enable --now postgresql || true
|
|
682
|
+
fi
|
|
683
|
+
|
|
684
|
+
print_success "PostgreSQL installed"
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
# --------------------------------------------------------------------------
|
|
688
|
+
# Main
|
|
689
|
+
# --------------------------------------------------------------------------
|
|
690
|
+
main() {
|
|
691
|
+
echo ""
|
|
692
|
+
echo "╔═══════════════════════════════════════════════════════════╗"
|
|
693
|
+
echo "║ ║"
|
|
694
|
+
echo "║ 🔧 Rails Dependencies Installer ║"
|
|
695
|
+
echo "║ ║"
|
|
696
|
+
echo "╚═══════════════════════════════════════════════════════════╝"
|
|
697
|
+
echo ""
|
|
698
|
+
|
|
699
|
+
detect_shell
|
|
700
|
+
detect_network_region
|
|
701
|
+
|
|
702
|
+
# On macOS, Homebrew is required — if missing, guide the user to install_full.sh
|
|
703
|
+
if is_macos && ! command_exists brew; then
|
|
704
|
+
local install_cmd
|
|
705
|
+
if [ "$USE_CN_MIRRORS" = true ]; then
|
|
706
|
+
install_cmd='/bin/bash -c "$(curl -sSL https://oss.1024code.com/scripts/install_full.sh)"'
|
|
707
|
+
else
|
|
708
|
+
install_cmd='/bin/bash -c "$(curl -sSL https://raw.githubusercontent.com/octo-ai/octo/main/scripts/install_full.sh)"'
|
|
709
|
+
fi
|
|
710
|
+
echo ""
|
|
711
|
+
print_error "Homebrew is not installed — it is required to continue."
|
|
712
|
+
echo ""
|
|
713
|
+
echo " Homebrew installation requires your sudo password and interactive confirmation"
|
|
714
|
+
echo " — it cannot be run automatically by the AI agent."
|
|
715
|
+
echo ""
|
|
716
|
+
echo " Please open a new terminal window and run the full installer:"
|
|
717
|
+
echo ""
|
|
718
|
+
echo " ${install_cmd}"
|
|
719
|
+
echo ""
|
|
720
|
+
echo " This will install Homebrew, Ruby, Node.js, and all required dependencies."
|
|
721
|
+
echo " Once done, come back and try again."
|
|
722
|
+
echo ""
|
|
723
|
+
exit 1
|
|
724
|
+
fi
|
|
725
|
+
|
|
726
|
+
# Run system deps script if available
|
|
727
|
+
local sys_deps="$HOME/.octo/scripts/install_system_deps.sh"
|
|
728
|
+
[ -f "$sys_deps" ] && { bash "$sys_deps" || print_warning "System deps install had warnings — continuing"; }
|
|
729
|
+
|
|
730
|
+
case "$INSTALL_TARGET" in
|
|
731
|
+
ruby) install_ruby || exit 1 ;;
|
|
732
|
+
node) install_node || exit 1 ;;
|
|
733
|
+
postgres) install_postgres || exit 1 ;;
|
|
734
|
+
*)
|
|
735
|
+
install_ruby || exit 1
|
|
736
|
+
install_node || exit 1
|
|
737
|
+
install_postgres || exit 1
|
|
738
|
+
;;
|
|
739
|
+
esac
|
|
740
|
+
|
|
741
|
+
echo ""
|
|
742
|
+
print_success "Done. Please re-source your shell or open a new terminal if paths changed."
|
|
743
|
+
echo ""
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
main "$@"
|