haoshoku 2.0.0
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.
- package/.github/workflows/publish-to-npm.yml +32 -0
- package/README.md +85 -0
- package/bun.lock +25 -0
- package/common/flatpacks_arch.txt +4 -0
- package/common/paru_applist.txt +52 -0
- package/configs/__init__.py +1 -0
- package/configs/alacritty/alacritty.toml +5 -0
- package/configs/fastfetch/config.jsonc +160 -0
- package/configs/fish/config.fish +66 -0
- package/configs/ghostty/config +6 -0
- package/configs/kde_shortcuts.kksrc +368 -0
- package/configs/kitty/kitty.conf +41 -0
- package/configs/vencord/trans.theme.css +90 -0
- package/deskback/20250228_234138~2.jpg +0 -0
- package/deskback/32977-1920x1080-desktop-full-hd-the-garden-of-words-wallpaper-photo.jpg +0 -0
- package/deskback/a_spring_night_by_bisbiswas_deekkf0.jpg +0 -0
- package/deskback/adia___east_hallegian_aurorae_by_rajavlitra_dasfxtu.png +0 -0
- package/deskback/anew_beginning_by_thekayeman_ddyp7ti.jpg +0 -0
- package/deskback/arachnophobia_by_bisbiswas_detjqyf.jpg +0 -0
- package/deskback/arise_by_bisbiswas_ddwcnjg.jpg +0 -0
- package/deskback/aurora_borealis__by_xxartymusexx_ddzt4e0.jpg +0 -0
- package/deskback/aurora_borealis_by_gaudibuendia_dajesxg.jpg +0 -0
- package/deskback/aurora_borealis_by_sephiroth_art_dawxmlz.jpg +0 -0
- package/deskback/aurora_by_rav89_dau22zv.jpg +0 -0
- package/deskback/aurora_lights_by_bisbiswas_dedov93.jpg +0 -0
- package/deskback/aurora_lights_by_bisbiswas_dedov93~3.jpg +0 -0
- package/deskback/bizarre_winter_night_by_bisbiswas_dfnsfxe.jpg +0 -0
- package/deskback/blazing_moon_by_bisbiswas_def6rgz.jpg +0 -0
- package/deskback/burning_tower_by_bisbiswas_dfciho6.jpg +0 -0
- package/deskback/christmas_night_commissioned__by_bisbiswas_deb2fgh.jpg +0 -0
- package/deskback/cold_night_drive_by_bisbiswas_dfqw4ev.jpg +0 -0
- package/deskback/creature_of_fantasyland_by_bisbiswas_dgjrpn6.jpg +0 -0
- package/deskback/crossing_road_by_bisbiswas_deksqsf.jpg +0 -0
- package/deskback/d9gpwju-04121643-6c14-4503-b2e3-60995901caf9.jpg +0 -0
- package/deskback/dreamy_night_by_bisbiswas_deaka30.jpg +0 -0
- package/deskback/emerald_moon_by_bisbiswas_demugqf.jpg +0 -0
- package/deskback/free_use_background__nebula__5400_by_ted_drakness_dfqq9bd.jpg +0 -0
- package/deskback/happy_new_year_2023_by_bisbiswas_dfly13l.jpg +0 -0
- package/deskback/hidden_between_the_mountans_by_bisbiswas_dhkgcji.jpg +0 -0
- package/deskback/long_night_drive_by_bisbiswas_defo5d1.jpg +0 -0
- package/deskback/magical_hour__commissioned__by_bisbiswas_dfi6fz5.jpg +0 -0
- package/deskback/magical_meteor_night1_by_bisbiswas_dfwosp2.jpg +0 -0
- package/deskback/melstrom_by_thekayeman_de0z6em.jpg +0 -0
- package/deskback/mermaid_s_arrival_by_bisbiswas_deko21g.jpg +0 -0
- package/deskback/meteor_shower_by_bisbiswas_deogvao.jpg +0 -0
- package/deskback/midnight_train_by_bisbiswas_dengl62.jpg +0 -0
- package/deskback/nebula_of_solitude_by_thekayeman_dedfpmk.jpg +0 -0
- package/deskback/night_lights_by_bisbiswas_de3gb5p.jpg +0 -0
- package/deskback/night_mirror_by_clearvector_d4s66cp.png +0 -0
- package/deskback/night_rider_by_bisbiswas_deiy9z0.jpg +0 -0
- package/deskback/nobara-41-1.png +0 -0
- package/deskback/nobara-41-2.png +0 -0
- package/deskback/nobara-41-3.png +0 -0
- package/deskback/nobara-41-4.png +0 -0
- package/deskback/nobara-41-5.png +0 -0
- package/deskback/northern_ambience_by_zizzyart_df0kj8f.jpg +0 -0
- package/deskback/northern_lights_by_goatsforbreakfast_de90t8s.jpg +0 -0
- package/deskback/northern_lights_by_lapis_lazuri_dcprksa.jpg +0 -0
- package/deskback/northern_magic_by_lapis_lazuri_dbm99nv.jpg +0 -0
- package/deskback/northern_splendour_by_lapis_lazuri_ddlygb0.jpg +0 -0
- package/deskback/old_lighthouse_by_bisbiswas_dg5he0x.jpg +0 -0
- package/deskback/on_a_spring_vacation_by_bisbiswas_dhv9h4o.jpg +0 -0
- package/deskback/polar_dreams_by_avogadium_de2s60i.jpg +0 -0
- package/deskback/purple_night_by_bisbiswas_de4ql0w.jpg +0 -0
- package/deskback/quiet_ocean_night_by_ebenezer42_de4h9nd.jpg +0 -0
- package/deskback/rail_gate_by_bisbiswas_dehq31u.jpg +0 -0
- package/deskback/the_aurora_stones_by_hyokka_dcyokk2 (1).png +0 -0
- package/deskback/the_aurora_stones_by_hyokka_dcyokk2.png +0 -0
- package/deskback/the_aurora_stones_by_hyokka_dcyokk2mirror (1).png +0 -0
- package/deskback/the_aurora_stones_by_hyokka_dcyokk2mirror.png +0 -0
- package/deskback/the_midnight_by_thekayeman_de36pew.jpg +0 -0
- package/deskback/through_the_haze_by_traemore_deqdnrp.png +0 -0
- package/deskback/tri_system_by_thekayeman_ddyfscv.jpg +0 -0
- package/deskback/verdant_moonlight__commissioned__by_bisbiswas_degzd3v.jpg +0 -0
- package/deskback/verdant_mountain_by_bisbiswas_derlepn.jpg +0 -0
- package/deskback/winter_scenery_by_bisbiswas_dgwj8rb.jpg +0 -0
- package/docs/haoshoku.md +47 -0
- package/haoshoku.js +88 -0
- package/icons/Gemini_Generated_Image_kwrza7kwrza7kwrz.png +0 -0
- package/icons/Google_Tasks_2021.svg +17 -0
- package/icons/artificial-intelligence.png +0 -0
- package/icons/chatgpt-icon.png +0 -0
- package/icons/favicon.svg +12 -0
- package/icons/icons8-chatgpt-50.png +0 -0
- package/icons/icons8-discord-96.png +0 -0
- package/icons/icons8-feed-64.png +0 -0
- package/icons/icons8-google-calendar-48.png +0 -0
- package/icons/icons8-messages-96.png +0 -0
- package/icons/icons8-notion-64.png +0 -0
- package/icons/icons8-whatsapp-96.png +0 -0
- package/icons/zed.png +0 -0
- package/info.txt +3 -0
- package/package.json +14 -0
- package/src/common/utils.js +46 -0
- package/src/helpers/configure_git.js +130 -0
- package/src/os_scripts/cachyos.js +342 -0
- package/src/os_scripts/debian_server.js +146 -0
- package/tests/test_cachyos.py +37 -0
- package/tests/test_common.py +73 -0
- package/tests/test_kde_config.py +69 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { log, runCommand, commandExists } from "../common/utils.js";
|
|
2
|
+
import prompts from "prompts";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
|
|
7
|
+
// --- Constants ---
|
|
8
|
+
const HOME = homedir();
|
|
9
|
+
const CARGO_HOME = path.join(HOME, ".cargo");
|
|
10
|
+
const PARU_BUILD_DIR = "/tmp/paru";
|
|
11
|
+
const STARSHIP_CONFIG_PATH = path.join(HOME, ".config", "starship.toml");
|
|
12
|
+
const FISH_CONFIG_DIR = path.join(HOME, ".config", "fish");
|
|
13
|
+
const PYENV_ROOT = path.join(HOME, ".pyenv");
|
|
14
|
+
const GHOSTTY_CONFIG_DIR = path.join(HOME, ".config", "ghostty");
|
|
15
|
+
const FASTFETCH_CONFIG_DIR = path.join(HOME, ".config", "fastfetch");
|
|
16
|
+
const KITTY_CONFIG_DIR = path.join(HOME, ".config", "kitty");
|
|
17
|
+
const ALACRITTY_CONFIG_DIR = path.join(HOME, ".config", "alacritty");
|
|
18
|
+
|
|
19
|
+
// Project paths (assuming running from project root)
|
|
20
|
+
const PROJECT_ROOT = process.cwd();
|
|
21
|
+
const COMMON_DIR = path.join(PROJECT_ROOT, "common");
|
|
22
|
+
const CONFIGS_DIR = path.join(PROJECT_ROOT, "configs");
|
|
23
|
+
const HELPERS_DIR = path.join(PROJECT_ROOT, "helpers");
|
|
24
|
+
|
|
25
|
+
const PARU_APPLIST_PATH = path.join(COMMON_DIR, "paru_applist.txt");
|
|
26
|
+
const FLATPAK_APPLIST_PATH = path.join(COMMON_DIR, "flatpacks_arch.txt");
|
|
27
|
+
const KDE_SHORTCUTS_PATH = path.join(CONFIGS_DIR, "kde_shortcuts.kksrc");
|
|
28
|
+
const CUSTOM_FISH_CONFIG_PATH = path.join(CONFIGS_DIR, "fish", "config.fish");
|
|
29
|
+
const CUSTOM_GHOSTTY_CONFIG_PATH = path.join(CONFIGS_DIR, "ghostty", "config");
|
|
30
|
+
const CUSTOM_FASTFETCH_CONFIG_PATH = path.join(CONFIGS_DIR, "fastfetch", "config.jsonc");
|
|
31
|
+
const CUSTOM_KITTY_CONFIG_PATH = path.join(CONFIGS_DIR, "kitty", "kitty.conf");
|
|
32
|
+
const CUSTOM_ALACRITTY_CONFIG_PATH = path.join(CONFIGS_DIR, "alacritty", "alacritty.toml");
|
|
33
|
+
|
|
34
|
+
// URLs
|
|
35
|
+
const RUSTUP_URL = "https://sh.rustup.rs";
|
|
36
|
+
const PARU_AUR_URL = "https://aur.archlinux.org/paru.git";
|
|
37
|
+
const UV_INSTALL_URL = "https://astral.sh/uv/install.sh";
|
|
38
|
+
const FOUNDRY_INSTALL_URL = "https://foundry.paradigm.xyz";
|
|
39
|
+
const UOSC_INSTALL_URL = "https://raw.githubusercontent.com/tomasklaen/uosc/HEAD/installers/unix.sh";
|
|
40
|
+
|
|
41
|
+
// --- Helper Functions ---
|
|
42
|
+
|
|
43
|
+
async function promptUser(message, initial = false) {
|
|
44
|
+
const response = await prompts({
|
|
45
|
+
type: "confirm",
|
|
46
|
+
name: "value",
|
|
47
|
+
message: message,
|
|
48
|
+
initial: initial,
|
|
49
|
+
});
|
|
50
|
+
return response.value;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function refreshSudo() {
|
|
54
|
+
log.info("Checking sudo access. You may be prompted for your password.");
|
|
55
|
+
return await runCommand("sudo -v");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function installPackagesFromFile(filePath, installerCmd) {
|
|
59
|
+
if (!fs.existsSync(filePath)) {
|
|
60
|
+
log.warning(`Package file not found at ${filePath}`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
65
|
+
const packages = content
|
|
66
|
+
.split("\n")
|
|
67
|
+
.map((line) => line.trim())
|
|
68
|
+
.filter((line) => line && !line.startsWith("#"));
|
|
69
|
+
|
|
70
|
+
if (packages.length > 0) {
|
|
71
|
+
log.info(`Installing ${packages.length} packages...`);
|
|
72
|
+
await runCommand(`${installerCmd} ${packages.join(" ")}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// --- Installation Functions ---
|
|
77
|
+
|
|
78
|
+
async function installBaseDependencies() {
|
|
79
|
+
log.info("Refreshing keyrings...");
|
|
80
|
+
// // Initialize and populate keys first to ensure we can verify signatures
|
|
81
|
+
// await runCommand("sudo pacman-key --init");
|
|
82
|
+
// await runCommand("sudo pacman-key --populate archlinux cachyos");
|
|
83
|
+
// // Then update the keyring packages
|
|
84
|
+
// await runCommand("sudo pacman -Sy --noconfirm archlinux-keyring cachyos-keyring");
|
|
85
|
+
|
|
86
|
+
log.info("Updating system and installing base-devel...");
|
|
87
|
+
// await runCommand("sudo pacman -Syu base-devel --noconfirm");
|
|
88
|
+
|
|
89
|
+
log.info("Installing Rust via rustup...");
|
|
90
|
+
await runCommand(`curl ${RUSTUP_URL} -sSf | sh -s -- -y`);
|
|
91
|
+
|
|
92
|
+
if (await commandExists("pyenv") && await commandExists("fish")) {
|
|
93
|
+
log.info("Configuring Pyenv for Fish...");
|
|
94
|
+
await runCommand(`fish -c 'set -Ux PYENV_ROOT "${PYENV_ROOT}"'`);
|
|
95
|
+
// Use set -U fish_user_paths as it is more robust than fish_add_path in some environments
|
|
96
|
+
await runCommand(`fish -c 'if not contains "${PYENV_ROOT}/bin" $fish_user_paths; set -Ua fish_user_paths "${PYENV_ROOT}/bin"; end'`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function installAurHelper() {
|
|
101
|
+
if (await commandExists("paru")) {
|
|
102
|
+
log.info("Paru is already installed.");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
log.info("Installing paru...");
|
|
106
|
+
await runCommand(
|
|
107
|
+
`git clone ${PARU_AUR_URL} ${PARU_BUILD_DIR} && cd ${PARU_BUILD_DIR} && makepkg -si --noconfirm`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function installDevTools() {
|
|
112
|
+
if (!(await commandExists("uv"))) {
|
|
113
|
+
log.info("Installing uv...");
|
|
114
|
+
await runCommand(`curl -LsSf ${UV_INSTALL_URL} | sh`);
|
|
115
|
+
} else {
|
|
116
|
+
log.info("uv already installed.");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!(await commandExists("foundryup"))) {
|
|
120
|
+
log.info("Installing Foundry...");
|
|
121
|
+
await runCommand(`curl -L ${FOUNDRY_INSTALL_URL} | bash`);
|
|
122
|
+
} else {
|
|
123
|
+
log.info("Foundry (foundryup) already installed.");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function setupFlatpakRemotes() {
|
|
128
|
+
if (await commandExists("flatpak")) {
|
|
129
|
+
log.info("Setting up Flatpak remotes...");
|
|
130
|
+
await runCommand(
|
|
131
|
+
"flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo"
|
|
132
|
+
);
|
|
133
|
+
} else {
|
|
134
|
+
log.warning("Flatpak not found. Skipping remote setup.");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// --- Configuration Functions ---
|
|
139
|
+
|
|
140
|
+
async function configureFishShell() {
|
|
141
|
+
if (!(await commandExists("fish"))) {
|
|
142
|
+
log.info("Installing Fish shell...");
|
|
143
|
+
await runCommand("paru -S fish --noconfirm");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (await promptUser("Set Fish as the default shell?", true)) {
|
|
147
|
+
const fishPathProc = Bun.spawn(["which", "fish"]);
|
|
148
|
+
const fishPath = (await new Response(fishPathProc.stdout).text()).trim();
|
|
149
|
+
|
|
150
|
+
if (fishPath) {
|
|
151
|
+
log.info("Setting Fish as the default shell...");
|
|
152
|
+
await runCommand(`chsh -s ${fishPath}`);
|
|
153
|
+
} else {
|
|
154
|
+
log.warning("Could not find fish executable.");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
log.info("Installing Fisher and plugins...");
|
|
159
|
+
const fisherPlugins = [
|
|
160
|
+
"jorgebucaran/fisher",
|
|
161
|
+
"meaningful-ooo/sponge",
|
|
162
|
+
"jorgebucaran/nvm.fish",
|
|
163
|
+
"franciscolourenco/done",
|
|
164
|
+
"joseluisq/gitnow@2.12.0",
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
for (const plugin of fisherPlugins) {
|
|
168
|
+
await runCommand(`fish -c "fisher install ${plugin}"`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (await commandExists("starship")) {
|
|
172
|
+
log.info("Configuring Starship prompt...");
|
|
173
|
+
await runCommand(`starship preset nerd-font-symbols -o ${STARSHIP_CONFIG_PATH}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fs.mkdirSync(FISH_CONFIG_DIR, { recursive: true });
|
|
177
|
+
if (fs.existsSync(CUSTOM_FISH_CONFIG_PATH)) {
|
|
178
|
+
fs.copyFileSync(CUSTOM_FISH_CONFIG_PATH, path.join(FISH_CONFIG_DIR, "config.fish"));
|
|
179
|
+
log.info("Copied custom fish config.");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function configureTerminals() {
|
|
184
|
+
log.info("Configuring Kitty terminal...");
|
|
185
|
+
fs.mkdirSync(KITTY_CONFIG_DIR, { recursive: true });
|
|
186
|
+
if (fs.existsSync(CUSTOM_KITTY_CONFIG_PATH)) {
|
|
187
|
+
fs.copyFileSync(CUSTOM_KITTY_CONFIG_PATH, path.join(KITTY_CONFIG_DIR, "kitty.conf"));
|
|
188
|
+
log.info("Copied custom Kitty config.");
|
|
189
|
+
} else {
|
|
190
|
+
log.warning(`Custom Kitty config not found at ${CUSTOM_KITTY_CONFIG_PATH}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
log.info("Configuring Alacritty terminal...");
|
|
194
|
+
fs.mkdirSync(ALACRITTY_CONFIG_DIR, { recursive: true });
|
|
195
|
+
if (fs.existsSync(CUSTOM_ALACRITTY_CONFIG_PATH)) {
|
|
196
|
+
fs.copyFileSync(CUSTOM_ALACRITTY_CONFIG_PATH, path.join(ALACRITTY_CONFIG_DIR, "alacritty.toml"));
|
|
197
|
+
log.info("Copied custom Alacritty config.");
|
|
198
|
+
} else {
|
|
199
|
+
log.warning(`Custom Alacritty config not found at ${CUSTOM_ALACRITTY_CONFIG_PATH}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
log.info("Configuring Ghostty terminal...");
|
|
203
|
+
fs.mkdirSync(GHOSTTY_CONFIG_DIR, { recursive: true });
|
|
204
|
+
if (fs.existsSync(CUSTOM_GHOSTTY_CONFIG_PATH)) {
|
|
205
|
+
fs.copyFileSync(CUSTOM_GHOSTTY_CONFIG_PATH, path.join(GHOSTTY_CONFIG_DIR, "config"));
|
|
206
|
+
log.info("Copied custom Ghostty config.");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function enableServices() {
|
|
211
|
+
if (await promptUser("Enable Bluetooth?", false)) {
|
|
212
|
+
log.info("Enabling Bluetooth service...");
|
|
213
|
+
await runCommand("sudo systemctl enable --now bluetooth");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if ((await commandExists("docker")) && (await promptUser("Enable Docker?", true))) {
|
|
217
|
+
log.info("Enabling and starting Docker service...");
|
|
218
|
+
await runCommand("sudo systemctl enable --now docker");
|
|
219
|
+
const user = process.env.USER;
|
|
220
|
+
if (user) {
|
|
221
|
+
await runCommand(`sudo usermod -aG docker ${user}`);
|
|
222
|
+
log.warning(`User ${user} added to docker group. Please log out and back in.`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function configureFastfetch() {
|
|
228
|
+
if (!(await commandExists("fastfetch"))) {
|
|
229
|
+
log.warning("Fastfetch command not found. Skipping configuration.");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
log.info("Configuring Fastfetch...");
|
|
234
|
+
fs.mkdirSync(FASTFETCH_CONFIG_DIR, { recursive: true });
|
|
235
|
+
if (fs.existsSync(CUSTOM_FASTFETCH_CONFIG_PATH)) {
|
|
236
|
+
fs.copyFileSync(CUSTOM_FASTFETCH_CONFIG_PATH, path.join(FASTFETCH_CONFIG_DIR, "config.jsonc"));
|
|
237
|
+
log.info("Fastfetch user config updated.");
|
|
238
|
+
} else {
|
|
239
|
+
log.warning(`Custom Fastfetch config not found at ${CUSTOM_FASTFETCH_CONFIG_PATH}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function configureKde() {
|
|
244
|
+
if (await promptUser("Apply custom KDE Shortcuts?", false)) {
|
|
245
|
+
log.info("Applying custom KDE shortcuts...");
|
|
246
|
+
if (fs.existsSync(KDE_SHORTCUTS_PATH)) {
|
|
247
|
+
const kglobalshortcutsrc = path.join(HOME, ".config", "kglobalshortcutsrc");
|
|
248
|
+
if (fs.existsSync(kglobalshortcutsrc)) {
|
|
249
|
+
fs.copyFileSync(kglobalshortcutsrc, kglobalshortcutsrc + ".bak");
|
|
250
|
+
log.info(`Backed up existing shortcuts to ${kglobalshortcutsrc}.bak`);
|
|
251
|
+
}
|
|
252
|
+
fs.copyFileSync(KDE_SHORTCUTS_PATH, kglobalshortcutsrc);
|
|
253
|
+
log.info("KDE shortcuts applied. Please log out and log back in.");
|
|
254
|
+
} else {
|
|
255
|
+
log.warning(`KDE shortcuts file not found at ${KDE_SHORTCUTS_PATH}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
log.info("Applying KDE Connect fix...");
|
|
260
|
+
await runCommand("sudo iptables -I INPUT -p tcp --dport 1714:1764 -j ACCEPT");
|
|
261
|
+
await runCommand("sudo iptables -I INPUT -p udp --dport 1714:1764 -j ACCEPT");
|
|
262
|
+
if (await commandExists("ufw")) {
|
|
263
|
+
await runCommand("sudo ufw allow 1714:1764/udp");
|
|
264
|
+
await runCommand("sudo ufw allow 1714:1764/tcp");
|
|
265
|
+
await runCommand("sudo ufw reload");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (await promptUser("Install KDE Force Blur effect (requires build)?", false)) {
|
|
269
|
+
log.info("Installing prerequisites for KDE Force Blur...");
|
|
270
|
+
await runCommand("paru -S base-devel git extra-cmake-modules qt6-tools --noconfirm");
|
|
271
|
+
log.info("Cloning and building KDE Force Blur...");
|
|
272
|
+
await runCommand(
|
|
273
|
+
"cd /tmp && " +
|
|
274
|
+
"git clone https://github.com/taj-ny/kwin-effects-forceblur && " +
|
|
275
|
+
"cd kwin-effects-forceblur && " +
|
|
276
|
+
"mkdir build && cd build && " +
|
|
277
|
+
"cmake ../ -DCMAKE_INSTALL_PREFIX=/usr && " +
|
|
278
|
+
"make && sudo make install"
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function installSystemPackages() {
|
|
284
|
+
log.info("Preparing for package installation...");
|
|
285
|
+
if (!(await refreshSudo())) {
|
|
286
|
+
log.error("Sudo authentication failed. Skipping sudo-dependent installations.");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
log.info("Installing packages from file lists...");
|
|
291
|
+
// await installPackagesFromFile(
|
|
292
|
+
// PARU_APPLIST_PATH,
|
|
293
|
+
// "paru -S --noconfirm --sudoloop --batchinstall"
|
|
294
|
+
// );
|
|
295
|
+
|
|
296
|
+
log.info("Installing Nerd Fonts...");
|
|
297
|
+
// await runCommand("sudo pacman -S $(pacman -Sgq nerd-fonts) --noconfirm");
|
|
298
|
+
|
|
299
|
+
if (await promptUser("Enable gaming configuration?", false)) {
|
|
300
|
+
// await runCommand(
|
|
301
|
+
// "paru -S cachyos-gaming-meta cachyos-gaming-applications protonup-rs-bin --noconfirm"
|
|
302
|
+
// );
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function installFlatpakApps() {
|
|
307
|
+
await setupFlatpakRemotes();
|
|
308
|
+
await installPackagesFromFile(
|
|
309
|
+
FLATPAK_APPLIST_PATH,
|
|
310
|
+
"flatpak install --user -y flathub"
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function configureUserApps() {
|
|
315
|
+
await configureFishShell();
|
|
316
|
+
|
|
317
|
+
if (await promptUser("Configure git?", true)) {
|
|
318
|
+
const { configureGit } = await import("../helpers/configure_git.js");
|
|
319
|
+
await configureGit();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
await configureTerminals();
|
|
323
|
+
await configureKde();
|
|
324
|
+
|
|
325
|
+
log.info("Installing uosc for MPV...");
|
|
326
|
+
await runCommand(`curl -fsSL ${UOSC_INSTALL_URL} | bash`);
|
|
327
|
+
|
|
328
|
+
await enableServices();
|
|
329
|
+
await configureFastfetch();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export async function runCachyOSSetup() {
|
|
333
|
+
await installBaseDependencies();
|
|
334
|
+
await installAurHelper();
|
|
335
|
+
await installDevTools();
|
|
336
|
+
|
|
337
|
+
await installSystemPackages();
|
|
338
|
+
await installFlatpakApps();
|
|
339
|
+
await configureUserApps();
|
|
340
|
+
|
|
341
|
+
log.success("CachyOS setup finished. Please restart your terminal or log out.");
|
|
342
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { log, runCommand, commandExists } from "../common/utils.js";
|
|
2
|
+
import prompts from "prompts";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
|
|
7
|
+
// --- Constants ---
|
|
8
|
+
const HOME = homedir();
|
|
9
|
+
const FISH_CONFIG_DIR = path.join(HOME, ".config", "fish");
|
|
10
|
+
const STARSHIP_CONFIG_PATH = path.join(HOME, ".config", "starship.toml");
|
|
11
|
+
|
|
12
|
+
// Project paths
|
|
13
|
+
const PROJECT_ROOT = process.cwd();
|
|
14
|
+
const CONFIGS_DIR = path.join(PROJECT_ROOT, "configs");
|
|
15
|
+
const CUSTOM_FISH_CONFIG_PATH = path.join(CONFIGS_DIR, "fish", "config.fish");
|
|
16
|
+
|
|
17
|
+
// --- Helper Functions ---
|
|
18
|
+
|
|
19
|
+
async function promptUser(message, initial = false) {
|
|
20
|
+
const response = await prompts({
|
|
21
|
+
type: "confirm",
|
|
22
|
+
name: "value",
|
|
23
|
+
message: message,
|
|
24
|
+
initial: initial,
|
|
25
|
+
});
|
|
26
|
+
return response.value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function installEssentials() {
|
|
30
|
+
log.info("Updating system and installing essentials...");
|
|
31
|
+
await runCommand("sudo apt update && sudo apt upgrade -y");
|
|
32
|
+
await runCommand("sudo apt install -y curl wget git vim ufw fail2ban software-properties-common");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function setupSsh() {
|
|
36
|
+
log.info("Setting up SSH...");
|
|
37
|
+
// User requested NOT to disable SSH login, so we just ensure keys are added.
|
|
38
|
+
const sshDir = path.join(HOME, ".ssh");
|
|
39
|
+
if (!fs.existsSync(sshDir)) {
|
|
40
|
+
fs.mkdirSync(sshDir, { mode: 0o700 });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const authorizedKeysPath = path.join(sshDir, "authorized_keys");
|
|
44
|
+
if (!fs.existsSync(authorizedKeysPath)) {
|
|
45
|
+
fs.writeFileSync(authorizedKeysPath, "", { mode: 0o600 });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// We could prompt to add a key here, or just leave it for manual addition.
|
|
49
|
+
// For now, we'll just ensure the service is enabled.
|
|
50
|
+
await runCommand("sudo systemctl enable ssh");
|
|
51
|
+
await runCommand("sudo systemctl start ssh");
|
|
52
|
+
|
|
53
|
+
log.info("SSH setup complete. Remember to add your public key to ~/.ssh/authorized_keys");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function configureFishShell() {
|
|
57
|
+
if (!(await commandExists("fish"))) {
|
|
58
|
+
log.info("Installing Fish shell...");
|
|
59
|
+
await runCommand("sudo apt-add-repository -y ppa:fish-shell/release-3");
|
|
60
|
+
await runCommand("sudo apt update");
|
|
61
|
+
await runCommand("sudo apt install -y fish");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (await promptUser("Set Fish as the default shell?", true)) {
|
|
65
|
+
const fishPathProc = Bun.spawn(["which", "fish"]);
|
|
66
|
+
const fishPath = (await new Response(fishPathProc.stdout).text()).trim();
|
|
67
|
+
|
|
68
|
+
if (fishPath) {
|
|
69
|
+
log.info("Setting Fish as the default shell...");
|
|
70
|
+
await runCommand(`sudo chsh -s ${fishPath} ${process.env.USER}`);
|
|
71
|
+
} else {
|
|
72
|
+
log.warning("Could not find fish executable.");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
log.info("Installing Fisher and plugins...");
|
|
77
|
+
// Ensure fish config dir exists
|
|
78
|
+
fs.mkdirSync(FISH_CONFIG_DIR, { recursive: true });
|
|
79
|
+
|
|
80
|
+
const fisherPlugins = [
|
|
81
|
+
"jorgebucaran/fisher",
|
|
82
|
+
"meaningful-ooo/sponge",
|
|
83
|
+
"jorgebucaran/nvm.fish",
|
|
84
|
+
"franciscolourenco/done",
|
|
85
|
+
"joseluisq/gitnow@2.12.0",
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
for (const plugin of fisherPlugins) {
|
|
89
|
+
await runCommand(`fish -c "fisher install ${plugin}"`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Starship
|
|
93
|
+
if (!(await commandExists("starship"))) {
|
|
94
|
+
log.info("Installing Starship...");
|
|
95
|
+
await runCommand("curl -sS https://starship.rs/install.sh | sh -s -- -y");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
log.info("Configuring Starship prompt...");
|
|
99
|
+
await runCommand(`starship preset nerd-font-symbols -o ${STARSHIP_CONFIG_PATH}`);
|
|
100
|
+
|
|
101
|
+
if (fs.existsSync(CUSTOM_FISH_CONFIG_PATH)) {
|
|
102
|
+
fs.copyFileSync(CUSTOM_FISH_CONFIG_PATH, path.join(FISH_CONFIG_DIR, "config.fish"));
|
|
103
|
+
log.info("Copied custom fish config.");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function installDocker() {
|
|
108
|
+
if (await promptUser("Install Docker?", true)) {
|
|
109
|
+
if (await commandExists("docker")) {
|
|
110
|
+
log.info("Docker already installed.");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
log.info("Installing Docker...");
|
|
115
|
+
await runCommand("curl -fsSL https://get.docker.com | sh");
|
|
116
|
+
|
|
117
|
+
const user = process.env.USER;
|
|
118
|
+
if (user) {
|
|
119
|
+
await runCommand(`sudo usermod -aG docker ${user}`);
|
|
120
|
+
log.warning(`User ${user} added to docker group.`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function setupFirewall() {
|
|
126
|
+
log.info("Setting up UFW...");
|
|
127
|
+
await runCommand("sudo ufw default deny incoming");
|
|
128
|
+
await runCommand("sudo ufw default allow outgoing");
|
|
129
|
+
await runCommand("sudo ufw allow ssh");
|
|
130
|
+
await runCommand("sudo ufw allow http");
|
|
131
|
+
await runCommand("sudo ufw allow https");
|
|
132
|
+
|
|
133
|
+
if (await promptUser("Enable UFW now?", true)) {
|
|
134
|
+
await runCommand("sudo ufw enable");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function runDebianServerSetup() {
|
|
139
|
+
await installEssentials();
|
|
140
|
+
await setupSsh();
|
|
141
|
+
await configureFishShell();
|
|
142
|
+
await installDocker();
|
|
143
|
+
await setupFirewall();
|
|
144
|
+
|
|
145
|
+
log.success("Debian Server setup finished.");
|
|
146
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import patch, MagicMock
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
# Add project root to path
|
|
7
|
+
sys.path.append(str(Path(__file__).parent.parent))
|
|
8
|
+
|
|
9
|
+
from os_scripts.cachyos import command_exists, install_packages_from_file
|
|
10
|
+
|
|
11
|
+
def test_command_exists():
|
|
12
|
+
"""Test checking for command existence."""
|
|
13
|
+
with patch('shutil.which') as mock_which:
|
|
14
|
+
mock_which.return_value = "/usr/bin/git"
|
|
15
|
+
assert command_exists("git") is True
|
|
16
|
+
|
|
17
|
+
mock_which.return_value = None
|
|
18
|
+
assert command_exists("nonexistentcommand") is False
|
|
19
|
+
|
|
20
|
+
@patch('os_scripts.cachyos.run_command')
|
|
21
|
+
@patch('pathlib.Path.is_file')
|
|
22
|
+
@patch('pathlib.Path.read_text')
|
|
23
|
+
def test_install_packages_from_file(mock_read, mock_is_file, mock_run):
|
|
24
|
+
"""Test reading packages from file and installing them."""
|
|
25
|
+
mock_is_file.return_value = True
|
|
26
|
+
mock_read.return_value = "package1\npackage2\n#comment\n\npackage3"
|
|
27
|
+
|
|
28
|
+
install_packages_from_file("dummy_path", "pacman -S")
|
|
29
|
+
|
|
30
|
+
# Verify the command was called with the correct packages
|
|
31
|
+
# Should include package1, package2, package3, ignoring comments and empty lines
|
|
32
|
+
args = mock_run.call_args[0][0]
|
|
33
|
+
assert "package1" in args
|
|
34
|
+
assert "package2" in args
|
|
35
|
+
assert "package3" in args
|
|
36
|
+
assert "pacman -S" in args
|
|
37
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
# Add project root to path
|
|
6
|
+
sys.path.append(str(Path(__file__).parent.parent))
|
|
7
|
+
|
|
8
|
+
def test_common_directory_structure():
|
|
9
|
+
"""Test that the common directory exists and has required files."""
|
|
10
|
+
# When running tests locally
|
|
11
|
+
root_dir = Path(__file__).parent.parent
|
|
12
|
+
common_dir = root_dir / "common"
|
|
13
|
+
|
|
14
|
+
assert common_dir.exists(), "common directory must exist"
|
|
15
|
+
assert common_dir.is_dir(), "common must be a directory"
|
|
16
|
+
|
|
17
|
+
required_files = [
|
|
18
|
+
"paru_applist.txt",
|
|
19
|
+
"flatpacks_arch.txt",
|
|
20
|
+
"apt_applist.txt",
|
|
21
|
+
"dnf_applist.txt",
|
|
22
|
+
"flatpacks.txt",
|
|
23
|
+
"snap_applist.txt",
|
|
24
|
+
"__init__.py"
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
for filename in required_files:
|
|
28
|
+
file_path = common_dir / filename
|
|
29
|
+
assert file_path.exists(), f"{filename} must exist in common directory"
|
|
30
|
+
assert file_path.is_file(), f"{filename} must be a file"
|
|
31
|
+
|
|
32
|
+
def test_common_files_content():
|
|
33
|
+
"""Test that common files are not empty and have valid content."""
|
|
34
|
+
root_dir = Path(__file__).parent.parent
|
|
35
|
+
common_dir = root_dir / "common"
|
|
36
|
+
|
|
37
|
+
# Test package list files
|
|
38
|
+
package_lists = [
|
|
39
|
+
"paru_applist.txt",
|
|
40
|
+
"flatpacks_arch.txt",
|
|
41
|
+
"apt_applist.txt",
|
|
42
|
+
"dnf_applist.txt",
|
|
43
|
+
"flatpacks.txt",
|
|
44
|
+
"snap_applist.txt"
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
for filename in package_lists:
|
|
48
|
+
file_path = common_dir / filename
|
|
49
|
+
content = file_path.read_text().strip()
|
|
50
|
+
|
|
51
|
+
# Check file is not empty (unless it's intended to be empty, but usually these shouldn't be)
|
|
52
|
+
assert len(content) > 0, f"{filename} should not be empty"
|
|
53
|
+
|
|
54
|
+
# Check content format - lines should not start with whitespace
|
|
55
|
+
lines = content.splitlines()
|
|
56
|
+
for i, line in enumerate(lines):
|
|
57
|
+
if line.strip() and not line.startswith('#'):
|
|
58
|
+
assert not line.startswith(' '), f"Line {i+1} in {filename} has leading whitespace"
|
|
59
|
+
|
|
60
|
+
def test_package_data_access():
|
|
61
|
+
"""Test that we can access these files via package resources (simulating installed package)."""
|
|
62
|
+
# This test simulates how the code would access files when installed
|
|
63
|
+
# Note: This depends on the package being installed or editable install,
|
|
64
|
+
# but we can check if the mechanism works relative to the package root
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
import common
|
|
68
|
+
common_path = Path(common.__file__).parent
|
|
69
|
+
assert common_path.exists()
|
|
70
|
+
assert (common_path / "paru_applist.txt").exists()
|
|
71
|
+
except ImportError:
|
|
72
|
+
pytest.skip("common package not importable - skipping package access test")
|
|
73
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import patch, MagicMock
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import shutil
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
# Add project root to path
|
|
8
|
+
sys.path.append(str(Path(__file__).parent.parent))
|
|
9
|
+
|
|
10
|
+
# Manually define KDE_SHORTCUTS_PATH for testing purposes if import fails,
|
|
11
|
+
# or better, mock it entirely since we're patching it anyway.
|
|
12
|
+
# The ImportError happens because os_scripts/cachyos.py relies on project structure
|
|
13
|
+
# that might look different in the test environment or due to circular imports?
|
|
14
|
+
# Actually, let's just import the module and patch attributes.
|
|
15
|
+
|
|
16
|
+
import os_scripts.cachyos as cachyos_module
|
|
17
|
+
|
|
18
|
+
def test_configure_kde_shortcuts_apply(tmp_path):
|
|
19
|
+
"""Test that KDE shortcuts are applied correctly."""
|
|
20
|
+
# Setup mock home directory
|
|
21
|
+
mock_home = tmp_path / "home"
|
|
22
|
+
mock_home.mkdir()
|
|
23
|
+
mock_config = mock_home / ".config"
|
|
24
|
+
mock_config.mkdir()
|
|
25
|
+
|
|
26
|
+
# Create existing config file with some realistic content
|
|
27
|
+
existing_conf = mock_config / "kglobalshortcutsrc"
|
|
28
|
+
existing_conf.write_text("[General]\nTest=ExistingShortcut")
|
|
29
|
+
|
|
30
|
+
# Mock KDE_SHORTCUTS_PATH to point to a real file
|
|
31
|
+
mock_shortcuts_file = tmp_path / "new_shortcuts.kksrc"
|
|
32
|
+
mock_shortcuts_file.write_text("[General]\nTest=NewShortcut")
|
|
33
|
+
|
|
34
|
+
with (
|
|
35
|
+
patch("os_scripts.cachyos.HOME", mock_home),
|
|
36
|
+
patch("os_scripts.cachyos.prompt_user", return_value=True),
|
|
37
|
+
patch("os_scripts.cachyos.KDE_SHORTCUTS_PATH", mock_shortcuts_file),
|
|
38
|
+
patch("os_scripts.cachyos.run_command"),
|
|
39
|
+
):
|
|
40
|
+
|
|
41
|
+
cachyos_module.configure_kde()
|
|
42
|
+
|
|
43
|
+
# Check if backup was created
|
|
44
|
+
backup_file = existing_conf.with_suffix(".bak")
|
|
45
|
+
assert backup_file.exists()
|
|
46
|
+
assert backup_file.read_text() == "[General]\nTest=ExistingShortcut"
|
|
47
|
+
|
|
48
|
+
# Check if new content was copied
|
|
49
|
+
assert existing_conf.read_text() == "[General]\nTest=NewShortcut"
|
|
50
|
+
|
|
51
|
+
def test_configure_kde_shortcuts_skip():
|
|
52
|
+
"""Test that shortcuts are skipped when user says no."""
|
|
53
|
+
with patch('os_scripts.cachyos.prompt_user', return_value=False), \
|
|
54
|
+
patch('os_scripts.cachyos.run_command'):
|
|
55
|
+
|
|
56
|
+
# Should run without errors and not attempt any file operations
|
|
57
|
+
cachyos_module.configure_kde()
|
|
58
|
+
|
|
59
|
+
def test_configure_kde_shortcuts_missing_file(tmp_path):
|
|
60
|
+
"""Test handling when source shortcut file is missing."""
|
|
61
|
+
# Mock KDE_SHORTCUTS_PATH to non-existent file
|
|
62
|
+
non_existent = tmp_path / "does_not_exist"
|
|
63
|
+
|
|
64
|
+
with patch('os_scripts.cachyos.prompt_user', return_value=True), \
|
|
65
|
+
patch('os_scripts.cachyos.KDE_SHORTCUTS_PATH', non_existent), \
|
|
66
|
+
patch('os_scripts.cachyos.run_command'):
|
|
67
|
+
|
|
68
|
+
# Should log warning but not fail
|
|
69
|
+
cachyos_module.configure_kde()
|