├── webui ├── README.md ├── .vscode │ └── extensions.json ├── svelte.config.js ├── src │ ├── init.ts │ ├── main.ts │ ├── components │ │ ├── Skeleton.css │ │ ├── Skeleton.svelte │ │ ├── Toast.svelte │ │ ├── BottomActions.svelte │ │ ├── Toast.css │ │ ├── TopBar.css │ │ ├── NavBar.css │ │ ├── NavBar.svelte │ │ ├── TopBar.svelte │ │ └── ChipInput.svelte │ ├── lib │ │ ├── constants_gen.ts │ │ ├── types.ts │ │ ├── theme.ts │ │ └── api.mock.ts │ ├── layout.css │ ├── routes │ │ ├── WinnowingTab.css │ │ ├── LogsTab.css │ │ ├── InfoTab.css │ │ ├── GranaryTab.css │ │ ├── WinnowingTab.svelte │ │ ├── LogsTab.svelte │ │ ├── ConfigTab.css │ │ └── InfoTab.svelte │ ├── app.css │ ├── App.svelte │ └── locales │ │ ├── zht.json │ │ ├── ja.json │ │ ├── zhs.json │ │ ├── en.json │ │ ├── ru.json │ │ └── es.json ├── vite.config.js ├── .gitignore ├── index.html ├── package.json └── tsconfig.json ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ └── bug_report.yml ├── dependabot.yml └── workflows │ ├── lints.yml │ ├── build.yml │ └── release.yml ├── module ├── .gitignore ├── config.toml ├── uninstall.sh ├── module.prop ├── metauninstall.sh ├── metamount.sh ├── metainstall.sh └── customize.sh ├── src ├── conf │ ├── mod.rs │ ├── cli.rs │ └── config.rs ├── mount │ ├── mod.rs │ └── node.rs ├── defs.rs └── core │ ├── winnow.rs │ ├── state.rs │ ├── inventory.rs │ ├── sync.rs │ ├── modules.rs │ ├── mod.rs │ └── storage.rs ├── .gitignore ├── xtask ├── Cargo.toml └── src │ └── zip_ext.rs ├── update.json ├── .cargo └── config.toml ├── module.json ├── changelog.md ├── icon.svg ├── Cargo.toml ├── README_ZH.md ├── scripts └── tg_notify.py └── README.md /webui/README.md: -------------------------------------------------------------------------------- 1 | # Hybird Mount Webui 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /module/.gitignore: -------------------------------------------------------------------------------- 1 | webroot/ 2 | magic_mount_rs 3 | *.zip 4 | -------------------------------------------------------------------------------- /src/conf/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cli; 2 | pub mod cli_handlers; 3 | pub mod config; 4 | -------------------------------------------------------------------------------- /webui/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["svelte.svelte-vscode"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /output 3 | **/.DS_Store 4 | /.vscode 5 | webui/src/lib/constants_gen.js -------------------------------------------------------------------------------- /module/config.toml: -------------------------------------------------------------------------------- 1 | moduledir = "/data/adb/modules/" 2 | mountsource = "KSU" 3 | verbose = false 4 | partitions = [] 5 | -------------------------------------------------------------------------------- /webui/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 2 | export default { 3 | preprocess: vitePreprocess(), 4 | } -------------------------------------------------------------------------------- /src/mount/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(target_os = "linux", target_os = "android"))] 2 | pub mod hymofs; 3 | pub mod magic; 4 | pub mod node; 5 | pub mod overlay; 6 | -------------------------------------------------------------------------------- /webui/src/init.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | litDisableBundleWarning: boolean; 4 | } 5 | } 6 | 7 | window.litDisableBundleWarning = true; 8 | 9 | export {}; -------------------------------------------------------------------------------- /webui/src/main.ts: -------------------------------------------------------------------------------- 1 | import { mount } from 'svelte' 2 | import App from './App.svelte' 3 | 4 | const app = mount(App, { 5 | target: document.getElementById('app')!, 6 | }) 7 | 8 | export default app -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | [dependencies] 6 | anyhow = "1.0" 7 | clap = { version = "4.5", features = ["derive"] } 8 | fs_extra = "1.3" 9 | zip = "6.0" -------------------------------------------------------------------------------- /module/uninstall.sh: -------------------------------------------------------------------------------- 1 | 2 | BASE_DIR="/data/adb/meta-hybrid" 3 | MNT_DIR="$BASE_DIR/mnt" 4 | if mountpoint -q "$MNT_DIR"; then 5 | umount "$MNT_DIR" 2>/dev/null || umount -l "$MNT_DIR" 6 | fi 7 | rm -rf "$BASE_DIR" 8 | exit 0 -------------------------------------------------------------------------------- /module/module.prop: -------------------------------------------------------------------------------- 1 | id=meta-hybrid 2 | metamodule=1 3 | name=Hybrid Mount 4 | version=v1.8.1 5 | versionCode=83 6 | author=YuzakiKokuban & Tools-cx-app 7 | updateJson=https://raw.githubusercontent.com/YuzakiKokuban/meta-hybrid_mount/master/update.json 8 | description=Waiting for daemon... -------------------------------------------------------------------------------- /update.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v1.8.1", 3 | "versionCode": 83, 4 | "zipUrl": "https://github.com/YuzakiKokuban/meta-hybrid_mount/releases/download/v1.8.1/Meta-Hybrid-v1.8.1.zip", 5 | "changelog": "https://raw.githubusercontent.com/YuzakiKokuban/meta-hybrid_mount/master/changelog.md" 6 | } 7 | -------------------------------------------------------------------------------- /module/metauninstall.sh: -------------------------------------------------------------------------------- 1 | 2 | MNT_DIR="/data/adb/meta-hybrid/mnt" 3 | if [ -z "$MODULE_ID" ]; then 4 | exit 0 5 | fi 6 | if ! mountpoint -q "$MNT_DIR" 2>/dev/null; then 7 | exit 0 8 | fi 9 | MOD_IMG_DIR="$MNT_DIR/$MODULE_ID" 10 | if [ -d "$MOD_IMG_DIR" ]; then 11 | rm -rf "$MOD_IMG_DIR" 12 | fi 13 | exit 0 -------------------------------------------------------------------------------- /webui/src/components/Skeleton.css: -------------------------------------------------------------------------------- 1 | .skeleton { 2 | background-color: var(--md-sys-color-surface-variant); 3 | opacity: 0.3; 4 | animation: pulse 1.5s infinite ease-in-out; 5 | flex-shrink: 0; 6 | } 7 | @keyframes pulse { 8 | 0% { opacity: 0.1; } 9 | 50% { opacity: 0.3; } 10 | 100% { opacity: 0.1; } 11 | } -------------------------------------------------------------------------------- /webui/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { svelte } from '@sveltejs/vite-plugin-svelte' 3 | 4 | export default defineConfig({ 5 | base: './', 6 | build: { 7 | outDir: '../module/webroot', 8 | }, 9 | plugins: [svelte()], 10 | optimizeDeps: { 11 | exclude: ['@material/web'] 12 | } 13 | }) -------------------------------------------------------------------------------- /webui/src/components/Skeleton.svelte: -------------------------------------------------------------------------------- 1 | 5 |
-------------------------------------------------------------------------------- /webui/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | # Dependencies 11 | node_modules 12 | .pnpm-store 13 | 14 | # Build Output 15 | dist 16 | dist-ssr 17 | .svelte-kit 18 | build 19 | 20 | # Editor directories 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | /lib/constants_gen.js -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xbuild = "run --package xbuild" 3 | [target.aarch64-linux-android] 4 | ar = "llvm-ar" 5 | linker = "aarch64-linux-android29-clang" 6 | [target.armv7-linux-androideabi] 7 | ar = "llvm-ar" 8 | linker = "armv7a-linux-androideabi29-clang" 9 | [target.x86_64-linux-android] 10 | ar = "llvm-ar" 11 | linker = "x86_64-linux-android29-clang" 12 | [target.riscv64-linux-android] 13 | ar = "llvm-ar" 14 | linker = "riscv64-linux-android35-clang" -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | open-pull-requests-limit: 10 8 | 9 | - package-ecosystem: "npm" 10 | directory: "/webui" 11 | schedule: 12 | interval: "weekly" 13 | open-pull-requests-limit: 10 14 | 15 | - package-ecosystem: "github-actions" 16 | directory: "/" 17 | schedule: 18 | interval: "monthly" 19 | open-pull-requests-limit: 5 -------------------------------------------------------------------------------- /webui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |{safe_commit_msg}\n\n"
74 | f"🚜 查看日志 (View Log)"
75 | )
76 |
77 | data = {
78 | "chat_id": chat_id,
79 | "caption": caption,
80 | "parse_mode": "HTML"
81 | }
82 |
83 | if topic_id and topic_id.strip() != "" and topic_id != "0":
84 | data["message_thread_id"] = topic_id
85 | print(f"Targeting Topic ID: {topic_id}")
86 |
87 | print(f"Dispatching yield to Granary (Telegram)...")
88 |
89 | max_retries = 2
90 | for attempt in range(max_retries):
91 | try:
92 | with open(file_path, "rb") as f:
93 | if attempt > 0:
94 | f.seek(0)
95 |
96 | files_payload = {"document": f}
97 | response = requests.post(url, data=data, files=files_payload, timeout=120)
98 |
99 | if response.status_code == 200:
100 | print("✅ Yield stored successfully!")
101 | return
102 |
103 | if response.status_code == 400 and "TOPIC_CLOSED" in response.text:
104 | if attempt < max_retries - 1:
105 | if reopen_topic(bot_token, chat_id, topic_id):
106 | print("🔄 Retrying upload in 2 seconds...")
107 | time.sleep(2)
108 | continue
109 | else:
110 | print("❌ Could not reopen topic. Aborting.")
111 | sys.exit(1)
112 | else:
113 | print("❌ Retries exhausted.")
114 |
115 | print(f"❌ Storage failed: {response.status_code} - {response.text}")
116 | sys.exit(1)
117 |
118 | except Exception as e:
119 | print(f"❌ Transport error: {e}")
120 | if attempt < max_retries - 1:
121 | time.sleep(2)
122 | continue
123 | sys.exit(1)
124 |
125 | if __name__ == "__main__":
126 | send_telegram_file()
--------------------------------------------------------------------------------
/webui/src/routes/GranaryTab.css:
--------------------------------------------------------------------------------
1 | .granary-list {
2 | display: flex;
3 | flex-direction: column;
4 | gap: 16px;
5 | padding: 16px 16px 100px 16px;
6 | max-width: 600px;
7 | margin: 0 auto;
8 | }
9 |
10 | /* Card Styles */
11 | .silo-card {
12 | background: var(--md-sys-color-surface-container-low);
13 | border-radius: 24px;
14 | padding: 20px;
15 | display: flex;
16 | flex-direction: column;
17 | gap: 16px;
18 | position: relative;
19 | transition: background-color 0.2s;
20 | border: 1px solid transparent;
21 | }
22 |
23 | .silo-card.variant-manual {
24 | background: var(--md-sys-color-surface-container);
25 | border-color: var(--md-sys-color-outline-variant);
26 | }
27 |
28 | .card-main {
29 | display: flex;
30 | gap: 16px;
31 | align-items: flex-start;
32 | }
33 |
34 | .type-icon-container {
35 | width: 48px;
36 | height: 48px;
37 | border-radius: 16px;
38 | background: var(--md-sys-color-secondary-container);
39 | color: var(--md-sys-color-on-secondary-container);
40 | display: flex;
41 | align-items: center;
42 | justify-content: center;
43 | flex-shrink: 0;
44 | }
45 |
46 | .variant-manual .type-icon-container {
47 | background: var(--md-sys-color-primary-container);
48 | color: var(--md-sys-color-on-primary-container);
49 | }
50 |
51 | .type-icon svg {
52 | width: 24px;
53 | height: 24px;
54 | fill: currentColor;
55 | }
56 |
57 | .info-block {
58 | flex: 1;
59 | display: flex;
60 | flex-direction: column;
61 | justify-content: center;
62 | gap: 4px;
63 | min-width: 0;
64 | }
65 |
66 | .silo-title {
67 | font-size: 18px;
68 | font-weight: 600;
69 | color: var(--md-sys-color-on-surface);
70 | white-space: nowrap;
71 | overflow: hidden;
72 | text-overflow: ellipsis;
73 | line-height: 1.3;
74 | }
75 |
76 | .silo-meta-row {
77 | display: flex;
78 | align-items: center;
79 | gap: 8px;
80 | flex-wrap: wrap;
81 | }
82 |
83 | .reason-badge {
84 | font-size: 11px;
85 | font-weight: 600;
86 | background: var(--md-sys-color-surface-container-high);
87 | color: var(--md-sys-color-on-surface-variant);
88 | padding: 2px 8px;
89 | border-radius: 6px;
90 | text-transform: uppercase;
91 | }
92 |
93 | .variant-manual .reason-badge {
94 | background: var(--md-sys-color-primary-container);
95 | color: var(--md-sys-color-on-primary-container);
96 | }
97 |
98 | .time-text {
99 | font-size: 13px;
100 | color: var(--md-sys-color-on-surface-variant);
101 | font-family: var(--md-ref-typeface-mono);
102 | }
103 |
104 | .top-action {
105 | margin-top: -8px;
106 | margin-right: -8px;
107 | }
108 |
109 | .delete-btn {
110 | --md-icon-button-icon-color: var(--md-sys-color-on-surface-variant);
111 | }
112 |
113 | .delete-btn:hover {
114 | --md-icon-button-icon-color: var(--md-sys-color-error);
115 | --md-icon-button-state-layer-color: var(--md-sys-color-error);
116 | }
117 |
118 | .card-actions {
119 | display: flex;
120 | }
121 |
122 | .restore-btn {
123 | width: 100%;
124 | --md-filled-tonal-button-container-color: var(--md-sys-color-secondary-container);
125 | --md-filled-tonal-button-label-text-color: var(--md-sys-color-on-secondary-container);
126 | --md-filled-tonal-button-icon-color: var(--md-sys-color-on-secondary-container);
127 | }
128 |
129 | .variant-manual .restore-btn {
130 | --md-filled-tonal-button-container-color: var(--md-sys-color-primary-container);
131 | --md-filled-tonal-button-label-text-color: var(--md-sys-color-on-primary-container);
132 | --md-filled-tonal-button-icon-color: var(--md-sys-color-on-primary-container);
133 | }
134 |
135 | .empty-state {
136 | display: flex;
137 | flex-direction: column;
138 | align-items: center;
139 | justify-content: center;
140 | padding: 64px 24px;
141 | text-align: center;
142 | opacity: 0.8;
143 | }
144 |
145 | .empty-icon-wrapper {
146 | width: 80px;
147 | height: 80px;
148 | border-radius: 50%;
149 | background: var(--md-sys-color-surface-container-high);
150 | display: flex;
151 | align-items: center;
152 | justify-content: center;
153 | margin-bottom: 16px;
154 | color: var(--md-sys-color-outline);
155 | }
156 |
157 | .empty-icon-wrapper svg {
158 | width: 40px;
159 | height: 40px;
160 | fill: currentColor;
161 | }
162 |
163 | .empty-state h3 {
164 | margin: 0 0 8px 0;
165 | font-size: 20px;
166 | font-weight: 500;
167 | color: var(--md-sys-color-on-surface);
168 | }
169 |
170 | .empty-state p {
171 | margin: 0;
172 | color: var(--md-sys-color-on-surface-variant);
173 | }
174 |
175 | .spacer {
176 | flex: 1;
177 | }
178 |
179 | .full-width {
180 | width: 100%;
181 | }
182 |
183 | .create-content {
184 | padding-top: 8px;
185 | }
186 |
187 | .danger-btn {
188 | --md-sys-color-primary: var(--md-sys-color-error);
189 | }
--------------------------------------------------------------------------------
/webui/src/app.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --md-sys-shape-corner-medium: 12px;
3 | --md-sys-shape-corner-large: 16px;
4 | --md-sys-shape-corner-full: 9999px;
5 | --md-sys-color-shadow: #000000;
6 | --md-sys-elevation-1: 0 1px 2px 0 var(--md-sys-color-shadow), 0 1px 3px 1px var(--md-sys-color-shadow);
7 | --md-sys-elevation-2: 0 1px 2px 0 var(--md-sys-color-shadow), 0 2px 6px 2px var(--md-sys-color-shadow);
8 | --md-sys-elevation-3: 0 4px 8px 3px var(--md-sys-color-shadow), 0 1px 3px 0 var(--md-sys-color-shadow);
9 | --font-common: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10 | --md-ref-typeface-plain: var(--font-stack);
11 | --md-ref-typeface-mono: 'JetBrains Mono', 'Fira Code', monospace;
12 | }
13 | * {
14 | box-sizing: border-box;
15 | -webkit-tap-highlight-color: transparent;
16 | }
17 | html, body, #app {
18 | height: 100%;
19 | width: 100%;
20 | margin: 0;
21 | padding: 0;
22 | overflow: hidden;
23 | background-color: var(--md-sys-color-background);
24 | color: var(--md-sys-color-on-background);
25 | font-family: var(--md-ref-typeface-plain);
26 | font-size: 16px;
27 | line-height: 1.5;
28 | -webkit-font-smoothing: antialiased;
29 | -moz-osx-font-smoothing: grayscale;
30 | }
31 | input, select, textarea, button {
32 | font-family: inherit;
33 | }
34 | button {
35 | border: none;
36 | cursor: pointer;
37 | transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
38 | flex-shrink: 0;
39 | white-space: nowrap;
40 | }
41 | button:active {
42 | transform: scale(0.96);
43 | }
44 | .btn-filled {
45 | height: 44px;
46 | min-width: 90px;
47 | padding: 0 20px;
48 | border-radius: 12px;
49 | background-color: var(--md-sys-color-primary);
50 | color: var(--md-sys-color-on-primary);
51 | font-weight: 600;
52 | font-size: 14px;
53 | display: inline-flex;
54 | align-items: center;
55 | justify-content: center;
56 | gap: 8px;
57 | }
58 | .btn-filled:disabled {
59 | background-color: var(--md-sys-color-on-surface);
60 | opacity: 0.12;
61 | color: var(--md-sys-color-surface);
62 | cursor: not-allowed;
63 | box-shadow: none;
64 | }
65 | .btn-tonal {
66 | height: 44px;
67 | min-width: 90px;
68 | padding: 0 20px;
69 | border-radius: 12px;
70 | background-color: var(--md-sys-color-secondary-container);
71 | color: var(--md-sys-color-on-secondary-container);
72 | font-weight: 600;
73 | font-size: 14px;
74 | display: inline-flex;
75 | align-items: center;
76 | justify-content: center;
77 | gap: 8px;
78 | }
79 | .btn-icon {
80 | width: 40px;
81 | height: 40px;
82 | border-radius: 50%;
83 | background: transparent;
84 | color: var(--md-sys-color-on-surface-variant);
85 | display: flex;
86 | align-items: center;
87 | justify-content: center;
88 | }
89 | .btn-icon:hover {
90 | background-color: rgba(128,128,128, 0.1);
91 | }
92 | .md3-card {
93 | background-color: var(--md-sys-color-surface-container);
94 | border: 1px solid var(--md-sys-color-outline-variant);
95 | border-radius: var(--md-sys-shape-corner-large);
96 | padding: 20px;
97 | display: flex;
98 | flex-direction: column;
99 | gap: 16px;
100 | margin-bottom: 16px;
101 | box-shadow: none;
102 | }
103 | .text-field {
104 | position: relative;
105 | }
106 | .text-field input, .text-field select {
107 | width: 100%;
108 | height: 56px;
109 | background: var(--md-sys-color-surface-container-highest);
110 | border: 1px solid var(--md-sys-color-outline);
111 | border-bottom: 1px solid var(--md-sys-color-outline);
112 | border-radius: 4px 4px 0 0;
113 | padding: 0 16px;
114 | color: var(--md-sys-color-on-surface);
115 | font-size: 16px;
116 | outline: none;
117 | transition: all 0.2s;
118 | appearance: none;
119 | }
120 | .text-field input:focus, .text-field select:focus {
121 | border-bottom: 2px solid var(--md-sys-color-primary);
122 | border-color: var(--md-sys-color-primary);
123 | background: var(--md-sys-color-surface-container-highest);
124 | padding-left: 16px;
125 | padding-right: 16px;
126 | }
127 | .text-field label {
128 | position: absolute;
129 | top: 8px;
130 | left: 12px;
131 | background-color: transparent;
132 | padding: 0 4px;
133 | font-size: 12px;
134 | color: var(--md-sys-color-on-surface-variant);
135 | z-index: 1;
136 | pointer-events: none;
137 | }
138 | .text-field input:focus ~ label,
139 | .text-field select:focus ~ label {
140 | color: var(--md-sys-color-primary);
141 | }
142 | .bottom-actions {
143 | position: absolute;
144 | bottom: 0;
145 | left: 0;
146 | right: 0;
147 | padding: 16px 24px calc(16px + var(--safe-area-inset-bottom, 0px)) 24px;
148 | background: linear-gradient(to top, var(--md-sys-color-surface) 85%, transparent);
149 | display: flex;
150 | justify-content: flex-end;
151 | align-items: center;
152 | gap: 12px;
153 | z-index: 50;
154 | pointer-events: none;
155 | }
156 | .bottom-actions button {
157 | pointer-events: auto;
158 | }
159 | code, pre {
160 | font-family: var(--md-ref-typeface-mono);
161 | }
--------------------------------------------------------------------------------
/webui/src/routes/WinnowingTab.svelte:
--------------------------------------------------------------------------------
1 |
59 |
60 | {L_W.emptyDesc || 'No conflicts detected.'}
87 |