-
Notifications
You must be signed in to change notification settings - Fork 3
/
app.py
482 lines (418 loc) · 21 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
# Fedora Workstation NATTD Not Another "Things To Do"!
# Initial System Setup Shell Script Builder for Fedora Workstation
#
# This application is a Streamlit-based web interface that allows users to customize
# and generate a shell script for setting up a fresh Fedora Workstation installation.
# It provides options for system configuration, essential and additional app installation,
# and system customization. The app uses predefined profiles and allows users to select
# individual options or apply preset profiles. It generates a downloadable shell script
# based on the user's selections, which can be run on a Fedora system to automate the
# setup process.
#
# This tool aims to simplify the post-installation process for Fedora users,
# allowing for easy customization and automation of common setup tasks.
#
# Author: Karl Stefan Danisz
# Contact: https://mktr.sbs/linkedin
# GitHub: https://mktr.sbs/github
# Buy me a coffee: https://mktr.sbs/coffee
# Version: 24.10
#
#
# Use responsibly, and always check the script you are about to run.
# This script is licensed under the GNU General Public License v3.0
#
import os
import logging
import streamlit as st
from typing import Dict, Any
import builder
import re
# Configure logging
logging.basicConfig(
level=logging.DEBUG, # Change to INFO in production
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Constants
SCRIPT_TEMPLATE_PATH = 'template.sh'
st.set_page_config(
page_title="Fedora Things To Do",
page_icon="🛠️",
layout="wide",
initial_sidebar_state="expanded",
menu_items={
'Get Help': 'https://github.com/k-mktr/fedora-things-to-do/issues',
'Report a bug': "https://github.com/k-mktr/fedora-things-to-do/issues",
'About': """
#### Not Another "Things To Do"!
**Fedora Workstation Setup Script Builder**
A Shell Script Builder for setting up Fedora Workstation after a fresh install.
If you find this tool useful, consider sharing it with others.
Created by [Karl Stefan Danisz](https://mktr.sbs/linkedin)
[GitHub Repository](https://github.com/k-mktr/fedora-things-to-do)
"""
}
)
def load_template() -> str:
with open(SCRIPT_TEMPLATE_PATH, 'r') as file:
return file.read()
def load_bonus_scripts():
bonus_scripts = {}
bonus_dir = "bonus"
for filename in os.listdir(bonus_dir):
if filename.endswith(".sh"):
with open(os.path.join(bonus_dir, filename), 'r') as file:
script_content = file.read()
script_name = os.path.splitext(filename)[0].replace("_", " ").title()
description = "This script provides additional customization options." # Default description
if filename == "template_files.sh":
description = "Creates common file templates in your home directory for quick access."
elif filename == "install_nvidia.sh":
description = "Installs NVIDIA drivers. Run this script only after performing a full system upgrade and reboot of your system."
elif filename == "system_cleanup.sh":
description = "Cleans up your system from unnecessary files."
bonus_scripts[script_name] = {
"filename": filename,
"content": script_content,
"description": description
}
return bonus_scripts
def render_sidebar() -> Dict[str, Any]:
# Add centered, clickable logo to the top of the sidebar using HTML
st.sidebar.markdown(
"""
<div style="display: flex; justify-content: center; align-items: center; padding: 10px;">
<a href="/" target="_self">
<img src="https://github.com/k-mktr/fedora-things-to-do/blob/master/assets/logo.png?raw=true" width="250" alt="Logo">
</a>
</div>
""",
unsafe_allow_html=True
)
st.sidebar.header("Configuration Options")
options = {"system_config": {}, "essential_apps": {}, "additional_apps": {}, "customization": {}}
output_mode = st.sidebar.radio("Output Mode", ["Quiet", "Verbose"], index=0, help="Select the output mode for the script.")
all_options = builder.generate_options()
nattd_data = builder.load_nattd()
logging.debug(f"all_options: {all_options}")
logging.debug(f"nattd_data['system_config']: {nattd_data['system_config']}")
# Add search bar at the top of the sidebar
search_query = st.sidebar.text_input("Search options and apps", "")
# Function to check if an item matches the search query
def matches_search(item_name: str, description: str) -> bool:
if not search_query:
return True
pattern = re.compile(search_query, re.IGNORECASE)
return pattern.search(item_name) is not None or pattern.search(description) is not None
# System Configuration section
system_config_matches = any(matches_search(nattd_data["system_config"][option]["name"], nattd_data["system_config"][option]["description"]) for option in all_options["system_config"])
with st.sidebar.expander("System Configuration", expanded=system_config_matches and bool(search_query)):
for option in all_options["system_config"]:
if matches_search(nattd_data["system_config"][option]["name"], nattd_data["system_config"][option]["description"]):
logging.debug(f"Processing option: {option}")
logging.debug(f"nattd_data['system_config'][{option}]: {nattd_data['system_config'][option]}")
# Special handling for RPM Fusion
if option == "enable_rpmfusion":
rpm_fusion_checkbox = st.checkbox(
nattd_data["system_config"][option]["name"],
key=f"system_config_{option}",
help=nattd_data["system_config"][option]["description"]
)
options["system_config"][option] = rpm_fusion_checkbox
else:
options["system_config"][option] = st.checkbox(
nattd_data["system_config"][option]["name"],
key=f"system_config_{option}",
help=nattd_data["system_config"][option]["description"]
)
if option == "set_hostname" and options["system_config"][option]:
options["hostname"] = st.text_input("Enter the new hostname:")
elif search_query:
st.empty() # Placeholder to keep expander visible
# Check if any codec option is selected and update RPM Fusion checkbox
codec_options = ["install_multimedia_codecs", "install_intel_codecs", "install_amd_codecs"]
if any(options["system_config"].get(option, False) for option in codec_options):
options["system_config"]["enable_rpmfusion"] = True
if not rpm_fusion_checkbox:
st.sidebar.markdown("**RPM Fusion** has been automatically selected due to codec choices.")
# Essential Apps section
essential_apps_matches = any(matches_search(app["name"], app["description"]) for app in nattd_data["essential_apps"]["apps"])
with st.sidebar.expander("Essential Applications", expanded=essential_apps_matches and bool(search_query)):
essential_apps = nattd_data["essential_apps"]["apps"]
for app in essential_apps:
if matches_search(app["name"], app["description"]):
options["essential_apps"][app["name"]] = st.checkbox(
app["name"],
key=f"essential_app_{app['name']}",
help=app["description"]
)
elif search_query:
st.empty() # Placeholder to keep expander visible
# Additional Applications section
additional_apps_matches = any(matches_search(app_info['name'], app_info['description'])
for category_data in nattd_data["additional_apps"].values()
for app_info in category_data["apps"].values())
with st.sidebar.expander("Additional Applications", expanded=additional_apps_matches and bool(search_query)):
for category, category_data in nattd_data["additional_apps"].items():
st.subheader(category_data["name"])
options["additional_apps"][category] = {}
category_has_matches = False
for app_id, app_info in category_data["apps"].items():
if matches_search(app_info['name'], app_info['description']):
app_selected = st.checkbox(app_info['name'], key=f"app_{category}_{app_id}", help=app_info['description'])
options["additional_apps"][category][app_id] = {'selected': app_selected}
if app_selected and 'installation_types' in app_info:
installation_type = st.radio(
f"Choose {app_info['name']} installation type:",
list(app_info['installation_types'].keys()),
key=f"{category}_{app_id}_install_type"
)
options["additional_apps"][category][app_id]['installation_type'] = installation_type
category_has_matches = True
if not category_has_matches and search_query:
st.empty() # Placeholder to keep category visible
# Customization section
customization_matches = any(matches_search(app_info['name'], app_info['description']) for app_info in nattd_data["customization"]["apps"].values())
with st.sidebar.expander("Customization", expanded=customization_matches and bool(search_query)):
customization_apps = nattd_data["customization"]["apps"]
for app_id, app_info in customization_apps.items():
if matches_search(app_info['name'], app_info['description']):
options["customization"][app_id] = st.checkbox(
app_info['name'],
key=f"customization_{app_id}",
help=app_info['description']
)
# Special handling for Windows Fonts
if app_id == "install_microsoft_fonts" and options["customization"][app_id]:
options["customization"][app_id] = {
'selected': True,
'installation_type': st.radio(
"Windows Fonts Installation Method",
('core', 'windows'),
format_func=lambda x: "Core Fonts" if x == "core" else "Windows Fonts",
key=f"customization_{app_id}_install_type",
help="Choose how to install Windows fonts."
)
}
if options["customization"][app_id]['installation_type'] == 'windows':
st.warning("⚠️ This method requires a valid Windows license. "
"Please ensure you comply with Microsoft's licensing terms.")
st.markdown("[Learn more about Windows fonts licensing](https://learn.microsoft.com/en-us/typography/fonts/font-faq)")
elif search_query:
st.empty() # Placeholder to keep expander visible
# Advanced section for custom script
with st.sidebar.expander("Advanced"):
st.warning("""
⚠️ **Caution**: This section is for advanced users. Incorrect shell commands can potentially harm your system. Use with care.
""")
st.markdown("""
Guidelines for custom commands:
- Ensure each command is on a new line
- Test your commands before adding them here
- Be aware that these commands will run with sudo privileges
""")
default_custom_script = 'echo "Created with ❤️ for Open Source"'
options["custom_script"] = st.text_area(
"Custom Shell Commands",
value=default_custom_script,
help="Enter any additional shell commands you want to run at the end of the script.",
height=200,
key="custom_script_input"
)
if options["custom_script"].strip() != default_custom_script:
st.info("Remember to review your custom commands in the script preview before downloading.")
# Bonus Scripts section
with st.sidebar.expander("Bonus Scripts"):
st.warning("""
⚠️ **Caution**: These are standalone scripts for additional customization.
Download and run them separately after your initial setup and system reboot.
""")
bonus_scripts = load_bonus_scripts()
for script_name, script_data in bonus_scripts.items():
st.markdown(f"**{script_name}**")
st.markdown(script_data["description"])
st.download_button(
label=f"Download {script_name} Script",
data=script_data["content"],
file_name=script_data["filename"],
mime="text/plain",
key=f"download_{script_name.lower().replace(' ', '_')}"
)
st.markdown("---")
# Placeholder at the bottom of the sidebar
sidebar_bottom = st.sidebar.empty()
sidebar_bottom.markdown("""
<style>
.link-bar {
display: flex;
justify-content: center;
animation: fadeIn 1s ease-out 0.9s;
opacity: 0;
animation-fill-mode: forwards;
text-align: center;
}
.link-bar a {
text-decoration: none;
font-weight: bold;
color: #8da9c4;
}
.link-bar a:hover {
text-decoration: underline;
}
.separator {
width: 100%;
border-top: 1px solid #8da9c4;
margin: 21px 0;
}
@media (max-width: 600px) {
.link-bar {
flex-direction: column;
gap: 10px;
}
}
</style>
<div class="link-bar">
<a href="https://fedoraproject.org/workstation/" target="_blank" style="text-decoration: none;" aria-label="Fedora Workstation">Still on the fence?<br>Grab your Fedora now!</a>
</div>
<div class="separator"></div>
<div style="text-align: center; padding: 21px 0;">
<p style="margin-bottom: 5px;">Created with ❤️ for Open Source</p>
<a href="https://mktr.sbs/linkedin" target="_blank" style="text-decoration: none; color: #8da9c4;" aria-label="Karol Stefan Danisz LinkedIn">
<i>by Karol Stefan Danisz</i>
</a>
</div>
""", unsafe_allow_html=True)
return options, output_mode
def build_script(options: Dict[str, Any], output_mode: str) -> str:
script_parts = {
"system_upgrade": builder.build_system_upgrade(options, output_mode),
"system_config": builder.build_system_config(options, output_mode),
"app_install": builder.build_app_install(options, output_mode),
"customization": builder.build_customization(options, output_mode),
"custom_script": builder.build_custom_script(options, output_mode),
}
preview_script = "(...) # Script header and initial setup\n\n"
for placeholder, content in script_parts.items():
if content and content.strip(): # Check if content is not None and not empty
preview_script += f"# {placeholder.replace('_', ' ').title()}\n"
preview_script += content + "\n\n"
preview_script += "(...) # Script footer"
# Replace the hostname placeholder if it exists
if "hostname" in options:
preview_script = preview_script.replace("{hostname}", options["hostname"])
return preview_script
def build_full_script(template: str, options: Dict[str, Any], output_mode: str) -> str:
script_parts = {
"system_upgrade": builder.build_system_upgrade(options, output_mode),
"system_config": builder.build_system_config(options, output_mode),
"app_install": builder.build_app_install(options, output_mode),
"customization": builder.build_customization(options, output_mode),
"custom_script": builder.build_custom_script(options, output_mode),
}
for placeholder, content in script_parts.items():
template = template.replace(f"{{{{{placeholder}}}}}", content)
# Replace the hostname placeholder if it exists
if "hostname" in options:
template = template.replace("{hostname}", options["hostname"])
return template
def main():
logging.info("Starting main function")
# Add a header with a logo and links
st.markdown("""
<style>
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.header-container {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 2rem;
}
.logo {
width: 400px;
height: auto;
margin-bottom: 1rem;
animation: fadeIn 1s ease-out;
}
.main-header {
font-size: 2.5em;
font-weight: bold;
text-align: center;
margin-bottom: 0.5rem;
animation: fadeIn 1s ease-out 0.3s;
opacity: 0;
animation-fill-mode: forwards;
}
.sub-header {
font-size: 1.5em;
text-align: center;
font-style: italic;
margin-bottom: 1rem;
animation: fadeIn 1s ease-out 0.6s;
opacity: 0;
animation-fill-mode: forwards;
}
</style>
<div class="header-container">
<img src="https://fedoraproject.org/assets/images/fedora-workstation-logo.png" alt="Fedora Logo" class="logo">
<h1 class="main-header">Not Another <i>'Things To Do'!</i></h1>
<h2 class="sub-header">Initial System Setup Shell Script Builder for Fedora Workstation</h2>
</div>
""", unsafe_allow_html=True)
# Initialize session state
if 'script_built' not in st.session_state:
st.session_state.script_built = False
logging.info("Loading template")
template = load_template()
logging.info("Rendering sidebar")
options, output_mode = render_sidebar()
logging.info("Creating script preview")
script_preview = st.empty()
logging.info("Building script")
updated_script = build_script(options, output_mode)
logging.info("Displaying script preview")
script_preview.code(updated_script, language="bash")
if st.button("Build Your Script"):
logging.info("Building full script")
full_script = build_full_script(template, options, output_mode)
st.session_state.full_script = full_script
st.session_state.script_built = True
# Display download button and instructions if script has been built
if st.session_state.script_built:
st.download_button(
label="Download Your Script",
data=st.session_state.full_script,
file_name="fedora_things_to_do.sh",
mime="text/plain"
)
st.markdown("""
### Your Script Has Been Created!
Follow these steps to use your script:
1. **Download the Script**: Click the 'Download Your Script' button above to save the script to your computer.
2. **Make the Script Executable**: Open a terminal, navigate to the directory containing the downloaded script, and run:
```
chmod +x fedora_things_to_do.sh
```
3. **Run the Script**: Execute the script with sudo privileges:
```
sudo ./fedora_things_to_do.sh
```
⚠️ **Caution**: This script will make changes to your system. Please review the script contents before running and ensure you understand the modifications it will make.
""")
st.markdown("""
### Optional Bonus Scripts
If you want to further customize your system, you can find a "Bonus Scripts" section in the sidebar. This section includes additional standalone scripts that are not mandatory but can be useful for extra customization. The scripts available are:
- **NVIDIA Drivers Script**: Installs NVIDIA drivers. It's recommended to run this script after performing a full system upgrade and rebooting your system.
- **File Templates Script**: Creates a set of commonly used file templates in your home directory.
If you decide to use these scripts, follow these steps:
1. Download the desired script from the sidebar.
2. Make it executable: `chmod +x script_name.sh`
3. Run it: `./script_name.sh` (or with sudo if required)
**Important**: These scripts are optional and should be run after completing the main setup and rebooting your system.
""")
logging.info("Main function completed")
if __name__ == "__main__":
main()