diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml new file mode 100755 index 0000000..25761f6 --- /dev/null +++ b/.github/workflows/pytest.yaml @@ -0,0 +1,33 @@ +name: MYPY TYPE CHECK + +on: + push: + branches: [ "devel" ] + pull_request: + branches: [ "main", "devel" ] + + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.11"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install mypy==0.991 + if [ -f REQUIREMENTS ]; then pip install -r REQUIREMENTS; fi + if [ -f DEV_REQUIREMENTS ]; then pip install -r DEV_REQUIREMENTS; fi + - name: Run mypy type check + run: | + mypy --strict $(git ls-files "*.py") --explicit-package-base + working-directory: . diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 03ea9d2..4ec22e6 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -11,18 +11,21 @@ include: Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks +* The use of sexualized language or imagery and unwelcome sexual +attention or advances +* Trolling, insulting/derogatory comments, and personal or political +attacks * Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission +* Publishing others' private information, such as a physical or +electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a - professional setting +professional setting ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html +This Code of Conduct is adapted from the +[Contributor Covenant][homepage], version 1.4, +available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 785bda9..3f90748 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,23 +1,22 @@ # Contribution -All pull requests are welcome, however, do note that the maintainer of the -project is a full time university student, thus may not be able to respond as -fast as you may expect. +All pull requests are welcome, however, do note that the maintainer of +the project is a full time university student, thus may not be able to +respond as fast as you may expect. # Notes -1. If you are planning to introduce a big change in codebase, open an issue -first for discussion, that waste of time would be avoided. +1. If you are planning to introduce a big change in codebase, open an +issue first for discussion, that waste of time would be avoided. -2. The project is currently written in Python 3.10, with strict compliance to -[PEP-0008](https://peps.python.org/pep-0008/) in terms of formatting, and to -[PEP-0257](https://peps.python.org/pep-0257/) in regards of docstring -formatting, with slight modification for sake of consistency: +2. The project is currently written in Python 3.10, with strict +compliance to [PEP-0008](https://peps.python.org/pep-0008/) in terms of +formatting, and to [PEP-0257](https://peps.python.org/pep-0257/) in +regards of docstring formatting, with slight modification for sake of +consistency. The modifications include: -The modifications include: - -In lists, as well as declaration of variables, there is always indent after -line break: +In lists, as well as declaration of variables, there is always indent +after line break: ```python hello_world: list[str] = [ @@ -39,11 +38,14 @@ hello_from_long_function( ) ``` -Rules to type checking is provided by [PEP-0484](https://peps.python.org/pep-0484/), -and is checked by [`mypy`](https://github.com/python/mypy), which requires +Moreover, the project strictly requires lines to be limited to 72 chars. + +Rules to type checking is provided by +[PEP-0484](https://peps.python.org/pep-0484/), and is checked by +[`mypy`](https://github.com/python/mypy), which requires `type-setuptools` and `type-requests`. -3. Be sure to run the checks and test your code first before opening pull -requests, the unit test can be invoked with `./check.sh`. +3. Be sure to run the checks and test your code first before opening +pull requests, the unit test can be invoked with `./check.sh`. 4. Always comply to [CODE_OF_CONDUCT](CODE_OF_CONDUCT.md). diff --git a/DEV_REQUIREMENTS b/DEV_REQUIREMENTS new file mode 100755 index 0000000..8b554cd --- /dev/null +++ b/DEV_REQUIREMENTS @@ -0,0 +1,3 @@ +mypy==0.991 +types-requests==2.28.11.7 +types-psutil==5.9.5.6 diff --git a/HACKING.md b/HACKING.md index 3d99fa6..c39c54d 100644 --- a/HACKING.md +++ b/HACKING.md @@ -1,7 +1,8 @@ # HACKING -To setup the project, clone the `devel` branch of the repository and create -your own branch with the name of feature/issue you want to introduce/work on: +To setup the project, clone the `devel` branch of the repository and +create your own branch with the name of feature/issue you want to +introduce/work on: ``` git clone -b devel https://github.com/iaacornus/Fedora-OSTree-Setup @@ -13,16 +14,16 @@ Then git branch ``` -The project is using Python >= 3.10, preferrably 3.11, thus to start working on -check if you have Python >= 3.10 with: +The project is using Python >= 3.10, preferrably 3.11, thus to start +working on check if you have Python >= 3.10 with: ``` python --version ``` -Although there can be workarounds, it is encouraged to use Python 3.10, but if not -possible, it is not enforced, upto Python >= 3.7 is acceptable. Then create a -virtual environment with +Although there can be workarounds, it is encouraged to use Python 3.10, +but if not possible, it is not enforced, upto Python >= 3.7 is +acceptable. Then create a virtual environment with ``` python -m venv venv @@ -34,21 +35,22 @@ Source it and start installing dependencies with pip install -r DEV_REQUIREMENTS && pip install -r REQUIREMENTS ``` -_Note that in other system `pip3` or `pip` is used instead, -e.g. `pip3.11`._ +_Note that in other system `pip3` or `pip` is used +instead, e.g. `pip3.11`._ -`DEV_REQUIREMENTS` include the modules needed for test, specifically `mypy` and -the `types` of other third party modules, while `REQUIREMENTS` contains the -modules used by project. +`DEV_REQUIREMENTS` include the modules needed for test, specifically +`mypy` and the `types` of other third party modules, while +`REQUIREMENTS` contains the modules used by project. -Finally start working in the project, refer to [CONTRIBUTING.md](CONTRIBUTING.md) -for guidelines about code formatting and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) -for acceptable interactions with in the community. +Finally start working in the project, refer to +[CONTRIBUTING.md](CONTRIBUTING.md) for guidelines about code formatting +and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for acceptable +interactions with in the community. # Notes -1. All functions are documented with docstrings, [PEP 0257](https://peps.python.org/pep-0257/), -which takes a format of: +1. All functions are documented with docstrings, +[PEP 0257](https://peps.python.org/pep-0257/), which takes a format of: ```python def function(x: int) -> int: @@ -73,7 +75,7 @@ def function(x: int) -> int: return x*x ``` -2. Use comments, preferrably if you can use the Better Comments syntax, please do: +2. Use comments, preferrably if you can use the Better Comments syntax: ``` #! FOR URGENT/WARNING @@ -82,4 +84,5 @@ def function(x: int) -> int: # TODO: THINGS THAT SHOULD BE DONE ``` - +3. Browse `/docs/` for further documentation of the project structure, +functions, classes and the code itself. diff --git a/INSTALLATION.md b/INSTALLATION.md new file mode 100644 index 0000000..d1db88d --- /dev/null +++ b/INSTALLATION.md @@ -0,0 +1,27 @@ +# Installation + +**The project is currently in active development** + +The project is written in Python >= 3.11, and does not need to be +compiled however, the dependencies are needed to be installed in a +`$PATH` accessible by python. + +The authors recommend to install the project dependencies inside +virtualbox and test the project inside: + +``` +python3.11 -m venv venv +``` + +Then activate the virtual environment, and install the dependencies +with: + +``` +python3.11 -m pip install -r REQUIREMENTS +``` + +And run the main module: + +``` +python -m src.main +``` diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 085d85d..b09c120 --- a/README.md +++ b/README.md @@ -1,2 +1,14 @@ # Fedora-OSTree-Setup -A small program making the install of Fedora Silverblue / Kionite easy. It lets you choose what to install or set. + +A python-program that automates the setup of Fedora Silverblue/Kinoite +based on given config file. + +# Contributing + +All contributions whether small or large is welcome! Just fork the +project and create a pull request when done. + +Refer to [HACKING.md](HACKING.md) to start in how to setup the project, +then in [CONTRIBUTING.md](CONTRIBUTING.md) for protocols/guidelines to +follow, and take a browse in [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) +to see what is the acceptable behavior in the community. diff --git a/REQUIREMENTS b/REQUIREMENTS new file mode 100755 index 0000000..c53bea1 --- /dev/null +++ b/REQUIREMENTS @@ -0,0 +1,4 @@ +rich==12.6.0 +requests==2.28.1 +psutil==5.9.4 +blkinfo==0.2.0 diff --git a/ToDo.md b/ToDo.md old mode 100644 new mode 100755 index 1603d9b..e36520a --- a/ToDo.md +++ b/ToDo.md @@ -1,39 +1,45 @@ # To Do -## 1. Ask what GPU -Intel internal: +## 1. GPU driver installation ([#5](/../../issues/5)) + +Intel: - some drivers missing? Nvidia: -- ask if proprietary or Foss drivers - akmod-nvidia xorg-x11-drv-nvidia -- OR akmod-nvodia xorg-x11-drv-nvidia-cuda - -`printf "sudo rpm-ostree install appname" > ~/bin/rpm-installscript.sh` +- OR akmod-nvodia xorg-x11-drv-nvidia-cuda (for CUDA gpus) +## 2. DE based optimizations ([#11](/../../issues/11)) -## 2. Ask what DE -Gnome +### Gnome (Silverblue) -- uninstall Firefox rpm +- remove gnome software from autostart -KDE +### KDE (Kinoite) - disable the annoying plopping sound -- ask if to uninstall kmousetool, kmag, firefox rpm -- ask to uninstall Gwenview (its kinda useless but easy to use and currently the only app supporting .jxl files) +- ask to uninstall `kmousetool`, `kmag`, `Gwenview` (its kinda useless but easy to use and currently the only app supporting .jxl files) + +## 3. Setup third party repo ([#6](/../../issues/6)) + +### flatpak (flathub) -## 3. Setup flathub - remove Fedora flatpak - add flathub repo -- ask to install KDE and Gnome nightly repos +- ask whether to install KDE and Gnome nightly repos + +### RPMFusion +- nonfree +- free +- rpmfusion-nonfree-tainted for `libdvdcss2` -## 4. Install flatpak apps -- lets decide on some apps, and all are installed with asking the user. -- Security stuff like ClamAV and Flatseal should be installed nonetheless, maybe one question "do you use the Terminal for Flatpak and Antivirus tasks? y/n" +## 4. Install recommended flatpak apps ([#7](/../../issues/7)) + +- Security stuff like ClamAV and Flatseal should be installed nonetheless, maybe one question `do you use the Terminal for Flatpak and Antivirus tasks? y/n` ## 5. Configure Snap + - "WARNING: This shoud work, but is still experimental" - Personally I found no app that I needed only on Snap - Maybe we need a startup script that mount-binds /var/home to /home to prevent breakages @@ -41,9 +47,9 @@ KDE - does it automatically add the repo to Discover / GNOME Software? ## 6. Configure settings + - Automatic rpm-ostree and Flatpak updates through integration of [this nice script](https://github.com/tonywalker1/silverblue-update) - enabling Mac-Adress randomization for privacy -- enabling - setting up a nice GRUB theme with asking, external github repo cloned - applying UEFI Firmware updates using a systemd timer ([Fedora Silverblue autoupdates](https://github.com/tonywalker1/silverblue-update) as example of a really nice integration) - theme flatpaks using your set GTK theme @@ -52,38 +58,42 @@ KDE - my laptop always has too high mic volume, so I set it to 40% with a start script, yes/no and volume asked. This is added to the postinstall script, so it can be repeated when sure what volume is wanted. Message "you can replace the mic-value in the postinstall.sh script you find in the "bin" folder." - grub menu has to be shown with 5 seconds to decide. Maybe the timespan can be asked, but if the system crashes, it has to be beginner-friendly to switch back to a previous tree -Enabling hibernation: +## 7. Hibernation: + - This is a big thing, very important, I asked already on ask fedora. - Have to test, if a slight modification of [this solution](https://fedoramagazine.org/hibernation-in-fedora-36-workstation/) works -## 7. Downloading MS Fonts +## 8. Downloading MS Fonts + - this is of course optional - still not working in my script - needs to support at least flatpak apps - ask if also install for rpm apps (different folder, probably not needed) -## 8. Setting up RPMfusion repos -this should be very optional -the rpmfusion-nonfree-tainted repo is needed for libdvdcss2, but VLC and others have it integrated, only handbrake doesnt (created an issue already) -the other repos are pretty unnesscary for normal users, this is to mention - ## 9. Downloading Lynis + - `mkdir ~/bin` (there is a wget option to auto-create missing folders!) - wget https://github.com/CISOfy/lynis/raw/master/lynis -P ~/bin/ - a systemd timer once a week, running only that command. Way better than cloning the whole repo -## 10. Installing rpm-packages +## 10. Installing `rpm-packages` + - NOTIFICATION: "You can do other stuff now, installing takes a while...", best as notification in the tray? -- TIP: To list available dnf packages, you can enter a toolbox (toolbox create 1 && toolbox enter 1 && dnf list) or layer dnfdragora (sudo rpm-ostree install dnfdragora), as rpm-ostree doesnt support that currently +- `zenity --info --text="Installing packages...\n\nYou can do other stuff now\." --title="Info\!"` + +- iaacornus: to me it seems better in notification tray? + - Speed: use extra script that is generated using appending the names - As if want to game and use the custom RPM Proton https://github.com/GloriousEggroll/proton-ge-custom or the flatpak one without patches -- sudo rpm-ostree override remove libavcodec-free --install exiftool perl-Image-ExifTool clamtk* fail2ban tlp make gcc-c++ qemu-kvm qemu-img qemu-user-static ffmpegthumbs kffmpegthumbnailer #libfprint unrar stacer pip android-tools btfs +- rpm-ostree override remove libavcodec-free --install exiftool perl-Image-ExifTool clamtk* fail2ban tlp make gcc-c++ qemu-kvm qemu-img qemu-user-static ffmpegthumbs kffmpegthumbnailer #libfprint unrar stacer pip android-tools btfs +- optional: install java, perl, not preinstalled `printf "appnamex " >> ~/Fedora-OSTree-setup/rpm-install.sh` at the end of all rpm-package y/n choices, the installscript is executed with sudo ### 10.1 Waydroid + - custom COPR repo! Display a small warning about that - Only works on Wayland! But wayland is still buggy, display that as a message - includes a few more steps, I got most of them ready @@ -91,25 +101,24 @@ at the end of all rpm-package y/n choices, the installscript is executed with su `sed -i `s/#settingname //g` ~/bin/postinstall.sh` -Keyboard +## 11. Keyboard - Layout chooseable after I found a solution where to get the .kml files from - big if elif loop, with 1= en-Qwerty (no changes), 2 = QWERTZ, 3 = ... -Folder-Sync +## 12. Folder-Sync - mount binded folders like Downloads, Pictures and Documents - - take the language from the keyboard language? question if this should be done - - manual: input of user "how is your Pictures folder called?", use that in mount-bind command +- take the language from the keyboard language? question if this should be done +- manual: input of user "how is your Pictures folder called?", use that in mount-bind command +## 13. reboot settings -## 11. reboot settings - the after-install script should be set as autostart script - the rpm-ostree processes took a while, display a window saying "you can reboot your pc now", as a popup window. With a `wait 30` command a reboot is initiated automatically +## 14. Second script after reboot - -## 12. Second script after reboot - inits automatically, but in background! can this be changed? - Waydroid settings - RPM app settings? diff --git a/script1.sh b/archives/scripts/script1.sh old mode 100644 new mode 100755 similarity index 100% rename from script1.sh rename to archives/scripts/script1.sh diff --git a/script2.sh b/archives/scripts/script2.sh old mode 100644 new mode 100755 similarity index 100% rename from script2.sh rename to archives/scripts/script2.sh diff --git a/assets/tests/test_block-app_remove b/assets/tests/test_block-app_remove new file mode 100644 index 0000000..08b9eab --- /dev/null +++ b/assets/tests/test_block-app_remove @@ -0,0 +1,191 @@ + +app = { + "Mailspring": { + "aid": "com.getmailspring.Mailspring", + "sdesc": "A simple email client.", + "source": "flathub" + }, + "LibreOffice": { + "aid": "org.libreoffice.LibreOffice", + "sdesc": "Office suite.", + "source": "flathub" + }, + "VLC": { + "aid": "org.videolan.VLC", + "sdesc": "Video player.", + "source": "flathub" + }, + "Okular": { + "aid": "org.kde.okular", + "sdesc": "A document viewer.", + "source": "flathub" + }, + "GIMP": { + "aid": "org.gimp.GIMP", + "sdesc": "Photo editing application.", + "source": "flathub" + }, + "ClamTk": { + "aid": "com.gitlab.davem.ClamTk", + "sdesc": "Front end for ClamAV.", + "source": "flathub" + }, + "FlatSeal": { + "aid": "com.github.tchx84.Flatseal", + "sdesc": "GUI for managing flathub applications permission", + "source": "flathub" + }, + "KeepassXC": { + "aid": "org.keepassxc.KeePassXC", + "sdesc": "Secure Password manager.", + "source": "flathub" + }, + "Cryptomator": { + "aid": "org.cryptomator.Cryptomator", + "sdesc": "Secure and easy encryption, especially for Cloud storages.", + "source": "flathub" + }, + "Easy-Effects": { + "aid": "com.github.wwmm.easyeffects", + "sdesc": "Modify your PulseAudio sound", + "source": "flathub" + }, + "Sync-Thingy": { + "aid": "com.github.zocker_160.SyncThingy", + "sdesc": "Sync folders securely between devices, no server.", + "source": "flathub" + }, + "XNView MP": { + "aid": "com.xnview.XnViewMP", + "sdesc": "Image Viewer and editor with many functions", + "source": "flathub" + }, + "Freetube": { + "aid": "io.freetubeapp.FreeTube", + "sdesc": "Watch YouTube privately with local subscriptions and playlists.", + "source": "flathub" + }, + "Inkscape": { + "aid": "org.inkscape.Inkscape", + "sdesc": "A Vector graphics program", + "source": "flathub" + }, + "GNOME Boxen": { + "aid": "org.gnome.Boxes", + "sdesc": "Manage fast Virtual machines with a simple interface.", + "source": "flathub" + }, + "Filelight": { + "aid": "org.kde.filelight ", + "sdesc": "View what files consume how much space in a pie chart.", + "source": "flathub" + }, + "Kdenlive": { + "aid": "org.kde.kdenlive", + "sdesc": "Video cut program", + "source": "flathub" + }, + "Onion Share": { + "aid": "org.onionshare.OnionShare", + "sdesc": "Share files, chat and create websites over Tor", + "source": "flathub" + }, + "qBittorrent": { + "aid": "org.qbittorrent.qBittorrent", + "sdesc": "Share files decentral with a big community.", + "source": "flathub" + }, + "Metadata-cleaner": { + "aid": "fr.romainvigier.MetadataCleaner", + "sdesc": "Easily remove all identifying information on Images using exiftool.", + "source": "flathub" + }, + "exiftool": { + "aid": "exiftool", + "sdesc": "", + "source": "rpm" + }, + "perl-Image-ExifTool": { + "aid": "perl-Image-ExifTool", + "sdesc": "", + "source": "rpm" + }, + "clamtk*": { + "aid": "clamtk*", + "sdesc": "", + "source": "rpm" + }, + "fail2ban": { + "aid": "fail2ban", + "sdesc": "", + "source": "rpm" + }, + "tlp": { + "aid": "tlp", + "sdesc": "", + "source": "rpm" + }, + "make": { + "aid": "make", + "sdesc": "", + "source": "rpm" + }, + "gcc-c++": { + "aid": "gcc-c++", + "sdesc": "", + "source": "rpm" + }, + "qemu-kvm": { + "aid": "qemu-kvm", + "sdesc": "", + "source": "rpm" + }, + "qemu-img": { + "aid": "qemu-img", + "sdesc": "", + "source": "rpm" + }, + "qemu-user-static": { + "aid": "qemu-user-static", + "sdesc": "", + "source": "rpm" + }, + "ffmpegthumbs": { + "aid": "ffmpegthumbs", + "sdesc": "", + "source": "rpm" + }, + "kffmpegthumbnailer": { + "aid": "kffmpegthumbnailer", + "sdesc": "", + "source": "rpm" + }, + "unrar": { + "aid": "unrar", + "sdesc": "", + "source": "rpm" + }, + "stacer": { + "aid": "stacer", + "sdesc": "", + "source": "rpm" + }, + "python-pip": { + "aid": "python-pip", + "sdesc": "", + "source": "rpm" + }, + "android-tools": { + "aid": "android-tools", + "sdesc": "", + "source": "rpm" + }, + "btfs": { + "aid": "btfs", + "sdesc": "", + "source": "rpm" + } +} + + +print(ProgramSetup(Console(), app, "install").setup()) diff --git a/check.sh b/check.sh new file mode 100755 index 0000000..c9529db --- /dev/null +++ b/check.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +command -v mypy >/dev/null 2>&1 || { echo -e "\033[1;31mmypy==0.991 is not installed!" && exit 1; } + +mypy --install-types +mypy --strict $(git ls-files "*.py") --explicit-package-base diff --git a/config/app_for_install.json b/config/app_for_install.json new file mode 100644 index 0000000..9fbce43 --- /dev/null +++ b/config/app_for_install.json @@ -0,0 +1,217 @@ +{ + "Mailspring": { + "aid": "com.getmailspring.Mailspring", + "sdesc": "A simple email client.", + "source": "flathub" + }, + "LibreOffice": { + "aid": "org.libreoffice.LibreOffice", + "sdesc": "Office suite.", + "source": "flathub" + }, + "VLC": { + "aid": "org.videolan.VLC", + "sdesc": "Video player.", + "source": "flathub" + }, + "Okular": { + "aid": "org.kde.okular", + "sdesc": "A document viewer.", + "source": "flathub" + }, + "GIMP": { + "aid": "org.gimp.GIMP", + "sdesc": "Photo editing application.", + "source": "flathub" + }, + "ClamTk": { + "aid": "com.gitlab.davem.ClamTk", + "sdesc": "Front end for ClamAV.", + "source": "flathub" + }, + "FlatSeal": { + "aid": "com.github.tchx84.Flatseal", + "sdesc": "GUI for managing flathub applications permission", + "source": "flathub" + }, + "KeepassXC": { + "aid": "org.keepassxc.KeePassXC", + "sdesc": "Secure Password manager.", + "source": "flathub" + }, + "Cryptomator": { + "aid": "org.cryptomator.Cryptomator", + "sdesc": "Secure and easy encryption, especially for Cloud storages.", + "source": "flathub" + }, + "Easy-Effects": { + "aid": "com.github.wwmm.easyeffects", + "sdesc": "Modify your PulseAudio sound", + "source": "flathub" + }, + "Sync-Thingy": { + "aid": "com.github.zocker_160.SyncThingy", + "sdesc": "Sync folders securely between devices, no server.", + "source": "flathub" + }, + "XNView MP": { + "aid": "com.xnview.XnViewMP", + "sdesc": "Image Viewer and editor with many functions", + "source": "flathub" + }, + "Freetube": { + "aid": "io.freetubeapp.FreeTube", + "sdesc": "Watch YouTube privately with local subscriptions and playlists.", + "source": "flathub" + }, + "Inkscape": { + "aid": "org.inkscape.Inkscape", + "sdesc": "A Vector graphics program", + "source": "flathub" + }, + "GNOME Boxen": { + "aid": "org.gnome.Boxes", + "sdesc": "Manage fast Virtual machines with a simple interface.", + "source": "flathub" + }, + "Filelight": { + "aid": "org.kde.filelight ", + "sdesc": "View what files consume how much space in a pie chart.", + "source": "flathub" + }, + "Kdenlive": { + "aid": "org.kde.kdenlive", + "sdesc": "Video cut program", + "source": "flathub" + }, + "Onion Share": { + "aid": "org.onionshare.OnionShare", + "sdesc": "Share files, chat and create websites over Tor", + "source": "flathub" + }, + "qBittorrent": { + "aid": "org.qbittorrent.qBittorrent", + "sdesc": "Share files decentral with a big community.", + "source": "flathub" + }, + "Metadata-cleaner": { + "aid": "fr.romainvigier.MetadataCleaner", + "sdesc": "Easily remove all identifying information on Images using exiftool.", + "source": "flathub" + }, + "perl-Image-ExifTool": { + "aid": "perl-Image-ExifTool", + "sdesc": "Remove identifying Data from images.", + "source": "rpm" + }, + "clamtk*": { + "aid": "clamtk", + "sdesc": "Scan Files for Viruses.", + "source": "rpm" + }, + "fail2ban": { + "aid": "fail2ban", + "sdesc": "Block brute force attacks when having opened ports on your Machine. Used for Servers worldwide.", + "source": "rpm" + }, + "tlp": { + "aid": "tlp", + "sdesc": "Advanced power management for your Laptop.", + "source": "rpm" + }, + "qemu-kvm": { + "aid": "qemu-kvm", + "sdesc": "Requirement for Boxes or Virt-Manager", + "source": "rpm" + }, + "ffmpegthumbs": { + "aid": "ffmpegthumbs", + "sdesc": "Enable ffmpeg to be used for video thumbnails.", + "source": "rpm" + }, + "kffmpegthumbnailer": { + "aid": "kffmpegthumbnailer", + "sdesc": "Display Video Thumbnails in KDEs File Manager.", + "source": "rpm" + }, + "unrar": { + "aid": "unrar", + "sdesc": "Allows unpacking the .rar archive format.", + "source": "rpm" + }, + "stacer": { + "aid": "stacer", + "sdesc": "Powerful System monitor, cleaner, startup configurator.", + "source": "rpm" + }, + "python-pip": { + "aid": "python-pip", + "sdesc": "Download python scripts from the Python Repository.", + "source": "rpm" + }, + "android-tools": { + "aid": "android-tools", + "sdesc": "Includes ADB and Fastboot, needed to install a custom OS on your Android phone.", + "source": "rpm" + }, + "btfs": { + "aid": "btfs", + "sdesc": "Mount .torrent files into your system and use them live.", + "source": "rpm" + }, + "cheat": { + "aid": "cheat", + "sdesc": "Command help like man but shorter", + "source": "rpm" + }, + "GNU-Debug": { + "aid": "gdb", + "sdesc": "Debug apps for creating helpful backtraces.", + "source": "rpm" + }, + "Boxes": { + "aid": "gnome-boxes", + "sdesc": "Easy KVM Qemu Virtul Machine management.", + "source": "rpm" + }, + "Virt-Manager": { + "aid": "virt-manager", + "sdesc": "Extended interface for KVM/Qemu, or intermediate users.", + "source": "rpm" + }, + "lm_sensors": { + "aid": "lm_sensors", + "sdesc": "Package to read hardware sensors, dependency for addons.", + "source": "rpm" + }, + "Performance-Measurements": { + "aid": "perf", + "sdesc": "Dependency for the App Hotspot, monitoring and visualizing app performance for debugging or curiosity.", + "source": "rpm" + }, + "Nextcloud-Client": { + "aid": "nextcloud-client", + "sdesc": "Sync your storage with Nextcloud, allows integration in file managers, which is not possible with the Flatpak", + "source": "rpm" + }, + "Pandoc": { + "aid": "pandoc", + "sdesc": "Convert Markdown Documents into HTML, PDF, ODT and LaTex with lots of customization. Dependency of some Dolphin Extensions.", + "source": "rpm" + }, + "RStudio": { + "aid": "R rstudio-desktop", + "sdesc": "Scientific data analysis", + "source": "rpm" + }, + "Waydroid": { + "aid": "waydroid", + "sdesc": "Run Android apps in a container.", + "source": "rpm" + }, + "Powertop": { + "aid": "powertop", + "sdesc": "Detailed CLI power monitor utility, more features than the GUI displays have.", + "source": "rpm" + } +} diff --git a/config/app_for_removal.json b/config/app_for_removal.json new file mode 100644 index 0000000..34923d4 --- /dev/null +++ b/config/app_for_removal.json @@ -0,0 +1,142 @@ +{ + "org.gnome.Calculator": { + "aid": "Calculator", + "sdesc": "", + "source": "flatpak" + }, + "org.gnome.Calendar": { + "aid": "Calendar", + "sdesc": "", + "source": "flatpak" + }, + "org.gnome.Characters": { + "aid": "Characters", + "sdesc": "", + "source": "flatpak" + }, + "org.gnome.Connections": { + "aid": "Connections", + "sdesc": "", + "source": "flatpak" + }, + "org.gnome.Contacts": { + "aid": "Contacts", + "sdesc": "", + "source": "flatpak" + }, + "org.gnome.Evince": { + "aid": "Evince", + "sdesc": "", + "source": "flatpak" + }, + "org.gnome.Extensions": { + "aid": "Extensions", + "sdesc": "", + "source": "flatpak" + }, + "org.gnome.Logs": { + "aid": "Logs", + "sdesc": "", + "source": "flatpak" + }, + "org.gnome.Maps": { + "aid": "Maps", + "sdesc": "", + "source": "flatpak" + }, + "org.gnome.TextEditor": { + "aid": "Text Editor", + "sdesc": "", + "source": "flatpak" + }, + "org.gnome.Weather": { + "aid": "Weather", + "sdesc": "", + "source": "flatpak" + }, + "org.gnome.baobab": { + "aid": "Disk Usage Analyzer", + "sdesc": "", + "source": "flatpak" + }, + "org.gnome.Clocks": { + "aid": "Clocks", + "sdesc": "", + "source": "flatpak" + }, + "org.gnome.eog": { + "aid": "Image Viewer", + "sdesc": "", + "source": "flatpak" + }, + "org.gnome.fonts-viewer": { + "aid": "Fonts Viewer", + "sdesc": "", + "source": "flatpak" + }, + "libavcodec-free": { + "aid": "", + "sdesc": "", + "source": "" + }, + "open-vm-tools-desktop": { + "aid": "", + "sdesc": "", + "source": "" + }, + "open-vm-tools": { + "aid": "", + "sdesc": "", + "source": "" + }, + "qemu-guest-agent": { + "aid": "", + "sdesc": "", + "source": "" + }, + "spice-vdagent": { + "aid": "", + "sdesc": "", + "source": "" + }, + "spice-webdavd": { + "aid": "", + "sdesc": "", + "source": "" + }, + "virtualbox-guest-additions": { + "aid": "", + "sdesc": "", + "source": "" + }, + "gnome-shell-extension-apps-menu": { + "aid": "", + "sdesc": "", + "source": "" + }, + "gnome-classic-session": { + "aid": "", + "sdesc": "", + "source": "" + }, + "gnome-shell-extension-window-list": { + "aid": "", + "sdesc": "", + "source": "" + }, + "gnome-shell-extension-background-logo": { + "aid": "", + "sdesc": "", + "source": "" + }, + "gnome-shell-extension-launch-new-instance": { + "aid": "", + "sdesc": "", + "source": "" + }, + "gnome-shell-extension-places-menu": { + "aid": "", + "sdesc": "", + "source": "" + } +} diff --git a/config/ostree_setup.json b/config/ostree_setup.json new file mode 100644 index 0000000..720952f --- /dev/null +++ b/config/ostree_setup.json @@ -0,0 +1,75 @@ +{ + "afi_conf": "iaacornus/Fedora-OSTree-Setup/devel/config/app_for_install.json", + "afu_conf": "iaacornus/Fedora-OSTree-Setup/devel/config/app_for_removal.json", + "tprepo-add": [ + "rpm_rfusion_f", + "rpm_rfusion_nf", + "rpm_rfusion_tainted", + "rpm_rfusion_nf_tained", + "fp_flathub", + "fp_fed_oci", + "fp_kde", + "fp_gnome_nightly" + ], + "disable-gui-software": true, + "rm-firefox-rpm": true, + "cdrv-add": true, + "wq_ssd-d": true, + "toolbox-replace": true, + "wayland_ff-e": true, + "ppd-rm": true, + "deep-sw": true, + "bpkgs-rm": true, + "fish-setup": true, + "fish-mod": { + "greetings": null, + }, + "rfusion-radd": true, + "add-fonts": { + "msfonts": true, + "noto-color-emoji": true, + "apple-emoji": false, + }, + "waydroid": { + "waydroid-setup": true, + "waydroid-folders": true, + "waydroid-freeform": true, + }, + "hardware": { + "laptop": true, + "thinkpad": false, + "framework": false, + "tuxedo": false, + "amd": false, + "intel": false, + "nvidia": false, + "intel-mesa": false, + "keyb-backlight": false, + "fprint-sensor": false, + "battery-thresh": 80, + }, + "helperlinks": { + "standard-dirs": true, + "advanced-dirs": false, + "userscripts-dir": true, + }, + "customization": { + "KDE-sddm2rpm": false, + "KDE-icon-theme": false, + "KDE-templates": false, + "homedir-.hidden": true, + }, + "security": { + "arkenfox": false, + "arkenbird": true, + "fail2ban": false, + "lynis-install": false, + "disable-bluetooth-default": true, + "disable-cups-default": false, + "hardened-kernel": false, + "hardened-malloc": false, + }, + "privacy": { + "disable-geoclue": true + }, +} diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..0649bdf --- /dev/null +++ b/docs/README.md @@ -0,0 +1,4 @@ +# DOCUMENTATION + +This directory contains all the documentation about the project +codebase. diff --git a/docs/project_struct.md b/docs/project_struct.md new file mode 100644 index 0000000..e8ded5d --- /dev/null +++ b/docs/project_struct.md @@ -0,0 +1,41 @@ +# Structure + +The project has an structure of: + +``` +src ++-- interface ++-- misc +`-- utils + +-- conf + +-- core + `-- shared + +-- log + `-- misc +``` + +`src/` contains the source code of the program, which is further broken +down into: + +1. `interface`, where the front-end related codes are stored, +particularly the commandline interface. + +2. `misc` are where the not really important codes are stored such as +type aliases. + +3. `utils` where the functions that is called in `main.py` is stored, +which is further broken down into: + +a. `conf` are everything related to the functions that deal with the +config file of the program. + +b. `core` are the most crucial piece that executes each feature such as +removal of applications as well as installation. + +c. `shared` are where the self written modules called by different +modules in `core` are stored, this is further broken down into: + +1. `log`, the logging system. + +2. `misc`, are another not really important codes, although plays a +crucial role. diff --git a/src/interface/__init__.py b/src/interface/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/src/interface/cli.py b/src/interface/cli.py new file mode 100755 index 0000000..8f0657d --- /dev/null +++ b/src/interface/cli.py @@ -0,0 +1,19 @@ +from argparse import ArgumentParser + +from src.utils.shared.log.logger import Logger + +class Cli: + """Commandline interface of the program.""" + + def __init__(self) -> None: + self.log: Logger = Logger() + + self.parser: ArgumentParser = ArgumentParser( + prog="ostree-setup", + usage="ostree-setup [ARGUMENTS]", + description=( + "A small program making the install of Fedora " + "Silverblue / Kionite easy. It lets you choose " + "what to install or set. " + ) + ) diff --git a/src/main.py b/src/main.py old mode 100644 new mode 100755 diff --git a/src/misc/__init__.py b/src/misc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/misc/alias.py b/src/misc/alias.py new file mode 100644 index 0000000..b170044 --- /dev/null +++ b/src/misc/alias.py @@ -0,0 +1,5 @@ +ProgData = dict[str, dict[str, str]] +ProgIndex = dict[int, str] +ConfigValues = list[ + list[dict[str, str | dict[str, str]]] | dict[str, str] + ] diff --git a/src/utils/__init__.py b/src/utils/__init__.py old mode 100644 new mode 100755 diff --git a/src/utils/conf/__init__.py b/src/utils/conf/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/utils/conf/__init__.py @@ -0,0 +1 @@ + diff --git a/src/utils/conf/conf-f.py b/src/utils/conf/conf-f.py new file mode 100644 index 0000000..e2b7460 --- /dev/null +++ b/src/utils/conf/conf-f.py @@ -0,0 +1,79 @@ +from typing import NoReturn, Optional + +from requests import get + +from src.utils.shared.log.logger import Logger + + +def fetch_missing_config( + log: Logger, + conf_name: str, + CONF_PATH: str, + conf_links: Optional[dict[str, str]] = None + ) -> None | NoReturn: + """Downloads the original config file from github if not found. + + Args: + log -- instance of Logger + conf_name -- name of the missing config + CONF_PATH -- path of the config file + conf_links -- links of config files + """ + + if not conf_links: + conf_links = { + "app_for_install": ( + "https://raw.githubusercontent.com/" + "iaacornus/Fedora-OSTree-Setup/" + "devel/config/app_for_install.json" + ), + "app_for_removal": ( + "https://raw.githubusercontent.com/" + "iaacornus/Fedora-OSTree-Setup/" + "devel/config/app_for_removal.json" + ), + "ostree_setup": ( + "https://raw.githubusercontent.com/" + "iaacornus/Fedora-OSTree-Setup/" + "devel/config/ostree_setup.json" + ) + } + + attempt: int + for attempt in range(3): + try: + log.logger( + "I", ( + "Fetching the config file " + f"({conf_name}) from Github." + ) + ) + if not (conf_link := conf_links.get(conf_name)): + log.logger( + "E", ( + "Cannot fetch the config: " + f"{conf_name}, aborting ..." + ) + ) + raise SystemExit + + with get(conf_link, stream=True) as d_file: + with open( + f"{CONF_PATH}/{conf_name}.json", "wb" + ) as conf_file: + for chunk in d_file.iter_content(chunk_size=1024): + if chunk: + conf_file.write(chunk) + except (ConnectionError, IOError, PermissionError) as Err: + if attempt < 2: + continue + + log.logger( + "E", f"{Err}. Cannot download {conf_name}, aborting ..." + ) + else: + log.logger("I", f"Sucessfully fetched: {conf_name}.") + return None + + raise SystemExit + diff --git a/src/utils/conf/conf-l.py b/src/utils/conf/conf-l.py new file mode 100644 index 0000000..8a65150 --- /dev/null +++ b/src/utils/conf/conf-l.py @@ -0,0 +1,72 @@ +from os import mkdir +from os.path import isdir, exists +from pathlib import Path +from json import load +from typing import NoReturn + +from utils.conf.fetch_conf import fetch_missing_config +from src.utils.shared.log.logger import Logger +from src.misc.alias import ConfigValues + + +class Conf: + """For parsing of the config file as well as checking.""" + + def __init__(self, log: Logger) -> None: + self.CONF_PATH: str = f"{Path.home()}/.config/ostree_setup" + self.CONF_ARR: list[str] = [ + "app_for_install", + "app_for_removal", + "ostree_setup" + ] + + self.log: Logger = log + + def check_missing(self) -> NoReturn | None: + """Checks the config file if missing or not, if missing + fetch the original config file from the repository.""" + + if not isdir(self.CONF_PATH): + try: + mkdir(self.CONF_PATH) + except (PermissionError, OSError) as Err: + self.log.logger( + "E", f"{Err}. Cannot make dir: {self.CONF_PATH}" + ) + raise SystemExit + + conf_name: str + for conf_name in self.CONF_ARR: + if not exists(f"{self.CONF_PATH}/{conf_name}.json"): + fetch_missing_config( + self.log, conf_name, self.CONF_PATH + ) + + return None + + def load_conf(self) -> ConfigValues | NoReturn: + """Load the config file, append it to list and return the list + containing the values of config file.""" + + try: + parsed_conf: ConfigValues = [] + for conf_name in self.CONF_ARR: + with open( + f"{self.CONF_PATH}/{conf_name}.json", + "r", + encoding="utf-8" + ) as t_conf: + parsed_conf.append( + load(t_conf) + ) + except (FileNotFoundError, PermissionError) as Err: + self.log.logger( + "I", ( + f"{Err}. Can't open config" + "file, run the program again." + ) + ) + else: + return parsed_conf + + raise SystemExit diff --git a/src/utils/core/__init__.py b/src/utils/core/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/src/utils/core/drv_codecs/__init__.py b/src/utils/core/drv_codecs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/core/drv_codecs/codecs-i.py b/src/utils/core/drv_codecs/codecs-i.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/core/drv_codecs/gpu_drv-i.py b/src/utils/core/drv_codecs/gpu_drv-i.py new file mode 100644 index 0000000..b3a243f --- /dev/null +++ b/src/utils/core/drv_codecs/gpu_drv-i.py @@ -0,0 +1,87 @@ +from subprocess import CalledProcessError +from typing import Optional + +from src.utils.shared.exec import exec_cmd +from src.utils.shared.log.logger import Logger + + +#! THIS IS EXPERIMENTAL AND NOT TESTED DUE TO LACK OF HARDWARE +#! SHOULD NOT BE CALLED YET ON THE MAIN FUNCTION IN MAIN.PY +#! ALTHOUGH THIS CAN BE ENABLED USING A FLAG `ex` IN THE CLI +#! BUT NOT IN DEFAULT OPTIONS, DO IT IN YOUR OWN DISCRETION + +def fetch_gpu(log: Logger) -> Optional[list[list[str]]]: + """Fetch the GPU of the system. + + Args: + log -- instance of Logger + + Returns: + The name of GPU or None. + """ + + try: + gpu_name: Optional[str] = exec_cmd( + log, + ["grep", "-i", "VGA"], + False, + False, + True, + ["lspci"] + ) + + except (CalledProcessError, FileNotFoundError) as Err: + log.logger("e", f"{Err}. Command lspci failed to execute.") + return None + else: + return [ + gpu.split(":") for gpu in gpu_name.split(r"\n") # type: ignore + ] + + +def install_gpu_drivers(log: Logger) -> list[str] | None: + """Append the appropriate driver in the list for GPU installation. + + Args: + log -- instance of Logger + + Returns: + true if the GPU was installed successfully or a temporary list + for the appropriate gpu drivers + """ + + #! THIS IS EXPERIMENTAL AND NOT TESTED DUE TO LACK OF HARDWARE + #! SHOULD NOT BE CALLED YET ON THE MAIN FUNCTION IN MAIN.PY + #! ALTHOUGH THIS CAN BE ENABLED USING A FLAG `ex` IN THE CLI + #! BUT NOT IN DEFAULT OPTIONS, DO IT IN YOUR OWN DISCRETION + + gpu_arr: Optional[list[list[str]]] = fetch_gpu(log) + + t_gpu_drv: list[str] = [] + + if not gpu_arr: + log.logger( + "I", "There is no GPU driver to install, skipping ..." + ) + return None + + gpu_drv: dict[str | list[str], list[str]] = { + "nvidia": ["akmod-nvidia", "xorg-x11-drv-nvidia"], + } + + gpu: list[str] + for gpu in gpu_arr: + vendor: str; gpu_info: list[str] + vendor, *gpu_info = gpu + + match [vendor, *gpu_info]: + case ["nvidia", *gpu_info]: + drv_id: str = "nvidia" + case ["intel", *gpu_info]: + ... + case ["advanced micro devices", *gpu_info]: + ... + + t_gpu_drv.append(gpu_drv.get(drv_id)) # type: ignore + + return t_gpu_drv diff --git a/src/utils/core/finalization.py b/src/utils/core/finalization.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/core/program/__init__.py b/src/utils/core/program/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/core/program/setup.py b/src/utils/core/program/setup.py new file mode 100644 index 0000000..003e9ab --- /dev/null +++ b/src/utils/core/program/setup.py @@ -0,0 +1,163 @@ +from typing import Any + +from rich.console import Console + +from src.utils.shared.misc.uinput import uinput +from src.utils.shared.misc.section import section +from src.misc.alias import ProgData, ProgIndex + + +class ProgramSetup: + """For installation of the recommended programs.""" + + def __init__( + self, + console: Console, + prog_data: ProgData, + action: str + ) -> None: + """ + Args: + console -- instance of Console + prog_data -- the database of applications + action -- whether uninstall or install + """ + + self.console: Console = console + + progname: str; aid: str; sdesc: str; source: str + + self.fp_PROGARR: ProgData = { + progname: { + "aid": proginfo.get("aid"), # type: ignore + "sdesc": proginfo.get("sdesc"), # type: ignore + "source": proginfo.get("source") # type: ignore + } for progname, proginfo in prog_data.items() + if proginfo.get("source").lower() == "flathub" # type: ignore + } + self.rpm_PROGARR: ProgData = { + progname: { + "aid": proginfo.get("aid"), # type: ignore + "sdesc": proginfo.get("sdesc"), # type: ignore + "source": proginfo.get("source") # type: ignore + } for progname, proginfo in prog_data.items() + if proginfo.get("source").lower() in [ # type: ignore + "rpm", "rfusion_free", "rfusion_nfree" + ] + } + + self.action: str = action + + self.fp_PROGIND: ProgIndex = { # ind: program id of flatpak applications + ind: aid for ind, aid in zip( + range(len(self.fp_PROGARR.items())), + self.fp_PROGARR.keys() + ) + } + self.rpm_PROGIND: ProgIndex = { # ind: program id of flatpak applications + ind: aid for ind, aid in zip( + range(len(self.rpm_PROGARR.items())), + self.rpm_PROGARR.keys() + ) + } + + def _enum_prog( + self, + progindex: ProgIndex, + progdata: ProgData, + progtype: str + ) -> Any: + """Enumerate the programs in the list and print out with a + format. + + Args: + progindex -- ind of applications and their name + progdata -- lists of the recommended applications including + their application id (aid) and description + progtype -- type of application, where flatpak or rpm + """ + + section( + f"select recommended programs ({progtype})", None + ) + + ind: int; progname: str + for ind, progname in progindex.items(): + self.console.print( + ( + f"[bold cyan]{ind:4}[/bold cyan] " + f"[bold]{progname}[/bold] -- " + f"{progdata.get(progname).get('sdesc')}" # type: ignore + ) + ) + + return uinput( + self.console, + "Input the number of applications to install", + 2 + ) + + def setup(self) -> tuple[list[list[str]], dict[str, list[str]]]: + """For add/remove of recommended program selected by user.""" + + t_fp_cmd: list[list[str]] = [] + t_rpm_prog: dict[str, list[str]] = { + "r_m_repo": [], #* main repo + "r_rf_free": [], #* rpmfusion free + "r_rf_nfree": [] #* rpmfusion nonfree + } + + # fp_ind -> flatpak programs ind + # rappsindex -> rpm programs ind + fp_ind: int; rpm_ind: int + + #* FOR FLATPAK PROGRAMS + #* appends the flatpak commands that needs to be executed in + #* flatpak_cmd_list for a single execution of commands + for fp_ind in self._enum_prog( + self.fp_PROGIND, self.fp_PROGARR, "flatpak" + ): + fp_aid: str = self.fp_PROGARR.get( + self.fp_PROGIND.get(fp_ind) # type: ignore + ).get("aid") + + if self.action.lower() == "install": + fp_cmd: list[str] = [ + "flatpak", + "install", + "flathub", + fp_aid, + "--assumeyes" + ] + else: + fp_cmd = [ + "flatpak", + "uninstall", + fp_aid, + "--system", + "--delete-data", + "--assumeyes" + ] + t_fp_cmd.append(fp_cmd) + + #* FOR RPM PROGRAM + #* appends the list of name of the selected rpm applications + for rpm_ind in self._enum_prog( + self.rpm_PROGIND, self.rpm_PROGARR, "rpm" + ): + + r_aid: str = self.rpm_PROGARR.get( # type: ignore + self.rpm_PROGIND.get(rpm_ind) # type: ignore + ).get("aid") + + match self.rpm_PROGARR.get( # type: ignore + self.rpm_PROGIND.get(rpm_ind) # type: ignore + ).get("source").lower(): + case "r_m_repo": + t_rpm_prog["r_m_repo"].append(r_aid) + case "r_rf_free": + t_rpm_prog["r_rf_free"].append(r_aid) + case "r_rf_nfree": + t_rpm_prog["r_rf_nfree"].append(r_aid) + + return t_fp_cmd, t_rpm_prog diff --git a/src/utils/core/program/tprepo-i.py b/src/utils/core/program/tprepo-i.py new file mode 100644 index 0000000..57c3043 --- /dev/null +++ b/src/utils/core/program/tprepo-i.py @@ -0,0 +1,124 @@ +from rich.console import Console + +from src.utils.shared.misc.uinput import uinput + + +def tp_repo_install( + console: Console + ) -> tuple[list[list[str]], list[str]]: + """Install third party repositories. + + Args: + log -- instance of Logger + console -- instance of Console + """ + + tp_repo: dict[int, dict[str, str]] = { + # id and name of the repo and the address + 1: { + "name": "rpm_RPMFusion Free", + "id": "rpm_rfusion_f", + "desc": ( + "Software that uses a free " + "license, but is not accepted " + "in Fedora for various reasons." + ), + "address": ( + r"https://mirrors.rpmfusion.org/" + r"free/fedora/rpmfusion-free-release" + r"-$(rpm -E %fedora).noarch.rpm" + ) + }, + 2: { + "name": "rpm_RPMFusion Non-free", + "id": "rpm_rfusion_nf", + "desc": ( + "Software that uses a nonfree " + "license, but is otherwise redistributable." + ), + "address": ( + r"https://mirrors.rpmfusion.org/" + r"nonfree/fedora/rpmfusion-nonfree" + r"-release-$(rpm -E %fedora).noarch.rpm" + ) + }, + 7: { + "name": "RPMFusion Free (Tainted)", + "id": "rpm_rfusion_f_tainted", + "desc": ( + "Software that use a free license, but may" + " have usage restriction in some countries" + ), + "address": "rpmfusion-free-release-tainted" + }, + 3: { + "name": "RPMFusion Non-free (Tainted)", + "id": "rpm_rfusion_nf_tainted", + "desc": ( + "Software that uses a nonfree license and " + "which is not explicitly redistributable, " + "but is allowed for inter-operability " + "purposes in some countries." + ), + "address": "rpmfusion-nonfree-release-tainted" + }, + 4: { + "name": "fp_Flathub", + "id": "fp_flathub", + "desc": "Unfiltered repository for flatpaks.", + "address": ( + "https://flathub.org/repo" + "/flathub.flatpakrepo" + ) + }, + 5: { + "name": "Fedora OCI (Flatpak)", + "id": "fp_fed_oci", + "desc": "For Open Containers Initiative (OCI)", #? what's this for? + "address": "oci+https://registry.fedoraproject.org" + }, + 6: { + "name": "KDE (Flatpak)", + "id": "fp_kde", + "desc": "KDE Applications.", + "address": ( + "https://distribute.kde.org/" + "skdeapps.flatpakrepo" + ) + }, + 7: { + "name": "GNOME Nightly (Flatpak)", + "id": "fp_gnome_nightly", + "desc": "For cutting edge builds from GNOME.", + "address": ( + "https://nightly.gnome.org/" + "gnome-nightly.flatpakrepo" + ) + }, + } + + t_fp_cmd: list[list[str]] = [] + t_rfusion: list[str] = [] + + for repo in tp_repo.values(): + if uinput( + console, + f"Install {repo.get('name')} ({repo.get('desc')})", + 1 + ): + repo_id: str = repo.get("id") # type: ignore + if repo_id.startswith("fp_"): + t_fp_cmd.append( + [ + "flatpak", + "remote-add", + "--if-not-exists", + repo.get("name"), # type: ignore + repo.get("address") # type: ignore + ] + ) + continue + + t_rfusion.append(repo.get("address")) # type: ignore + + return t_fp_cmd, t_rfusion diff --git a/src/utils/core/system/__init__.py b/src/utils/core/system/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/core/system/laptop/__init__.py b/src/utils/core/system/laptop/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/core/system/laptop/batt_thresh.py b/src/utils/core/system/laptop/batt_thresh.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/core/system/laptop/kbd_backlight.py b/src/utils/core/system/laptop/kbd_backlight.py new file mode 100644 index 0000000..5457de1 --- /dev/null +++ b/src/utils/core/system/laptop/kbd_backlight.py @@ -0,0 +1,63 @@ +from glob import glob +from os.path import isfile +from typing import Optional, NoReturn + +from rich.console import Console + +from src.utils.shared.exec import exec_cmd +from src.utils.shared.misc.uinput import uinput +from src.utils.shared.log.logger import Logger + + +def kbd_backlit_support_check(log: Logger) -> str | None: + """Check if the device has a keyboard backlight. + + Returns: + A boolean value corresponding to whether the kbd backlight works + """ + + try: + file: str + for file in glob("/sys/class/leds"): + if isfile(file) and file.endswith("::kbd_backlight"): + return file + except (PermissionError, OSError, FileNotFoundError) as Err: + log.logger( + "E", + ( + f"{Err}. Cannot determine whether" + " the device supports backlight." + ) + ) + + return None + + +def check_kbd_backlit(log: Logger, console: Console) -> bool | NoReturn: + """Check whether the kbd backlit works by default keybinding, + if not install brightnessctl and bind to a specific command.""" + + fname: Optional[str]; i: int + if fname := kbd_backlit_support_check(log): + for i in range(1, 4): + exec_cmd( + log, + [ + "sudo", + "tee", + "-a", + f"/sys/class/leds/{fname}" + ], + False, + False, + True, + [ + "echo", + i + ] + ) + + if uinput(console, "Does the keyboard backlight work?", 1): + return True + + return False diff --git a/src/utils/core/system/optimizations/__init__.py b/src/utils/core/system/optimizations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/core/system/optimizations/general_opt.py b/src/utils/core/system/optimizations/general_opt.py new file mode 100644 index 0000000..f9b8f67 --- /dev/null +++ b/src/utils/core/system/optimizations/general_opt.py @@ -0,0 +1,86 @@ +from typing import Any + +from rich.console import Console +from psutil import disk_partitions +from blkinfo import BlkDiskInfo # type: ignore + +from src.utils.shared.exec import exec_cmd +from src.utils.shared.fetch_env import fetch_env +from src.utils.shared.misc.uinput import uinput +from src.utils.shared.log.logger import Logger + + +class SysOpt: + """For system optimizations""" + + def __init__( + self, log: Logger, console: Console, verbose: bool = False + ) -> None: + """ + Args: + log -- instance of logger + console -- instance of console + verbose -- whether to show the stdout of cmds + """ + + self.log: Logger = log + self.console: Console = console + + self.verbose: bool = verbose + + def disable_services(self) -> None: + """Disable autostart/systemd services that is not necessary""" + + cmd_arr: list[list[str]] = [ + [ + "sudo", + "systemctl", + "disable", + "NetworkManager-wait-online.service" + ], + ] + + cmd: str | list[str] + for cmd in cmd_arr: + if uinput(self.console, f"Execute: {cmd}", 1): + exec_cmd(self.log, cmd, self.verbose) + + def disable_workqueue(self) -> None: + """Disable workqueue to improve ssd performance""" + + def check_if_ssd() -> bool: # type: ignore + """Check if the system is installed on ssd or not. + + Returns + A boolean value pertaining to whether it is or not. + """ + + ... + + def check_if_encrypted() -> str | bool: # type: ignore + """Check if the devices are encrypted or not. + + Returns + if the device is encrypted, return the name of the + device, + if otherwise, return false. + """ + + dev_name: str = [ + dev.device for dev in disk_partitions() + if ( + dev.device.startswith("/dev/mapper") + and dev.mountpoint.strip() == "/" + ) + ][0] #* get the first encrypted device mounted in / + + # if not dev_info : + # return False + + # dev_info: BlkDiskInfo = BlkDiskInfo().get_disks( + # { + # "name": + # } + # ) + + diff --git a/src/utils/core/system/optimizations/variant_based_opt.py b/src/utils/core/system/optimizations/variant_based_opt.py new file mode 100644 index 0000000..8dfa546 --- /dev/null +++ b/src/utils/core/system/optimizations/variant_based_opt.py @@ -0,0 +1,81 @@ +from typing import Any, IO, NoReturn + +from rich.console import Console + +from src.utils.shared.misc.uinput import uinput +from src.utils.shared.log.logger import Logger + + +class VariantBasedOpt: + """Optimizations for specific variant of Fedora OSTree.""" + + def __init__(self, log: Logger, console: Console) -> None: + self.log: Logger = log + self.console: Console = console + + def _fetch_variant(self) -> str | Any: + """Fetch the name of variant using /etc/os-release + + Returns: + The VARIANT_ID or VARIANT declared in /etc/os-release + """ + + try: + osr: IO[Any]; lines: str + with open("/etc/os-release", "r", encoding="UTF-8") as osr: + for lines in osr.readlines(): + if lines.lower().startswith(("variant_id", "variant")): + return ( + lines + .removeprefix("variant_id") + .removeprefix("variant") + .replace(r"\n", "") + ) + except (FileNotFoundError, PermissionError) as Err: + self.log.logger( + "E", f"{Err}. Cannot find /etc/os-release file." + ) + + return uinput( + self.console, + "Kindly input the Fedora OSTree variant you are using", + 3 + ) + + def remove_base_programs(self, variant: str) -> NoReturn | None: # type: ignore + """Remove programs layered in base image of a given variant.""" + + base_programs: dict[str, list[str]] = { + "kinoite": [ + "" + ], + "silverblue": [ + "" + ], + "vauxite": [ #! from what ive heard this variant is still not yet official + "" + ] + } + + def sys_opt(self, variant: str) -> NoReturn | None: # type: ignore + """Removal/alteration of default settings that are + known to be detrimental in terms of performance.""" + + sys_opts: dict[str, list[str | list[str]]] = { + "kinoite": [ + "" + ], + "silverblue": [ + [ # disable gnome software from autostart + "sudo", + "rm", + ( + "/etc/xdg/autostart/" + "org.gnome.Software.desktop" + ) + ] + ], + "vauxite": [ + "" + ] + } diff --git a/src/utils/core/system/sw_shell.py b/src/utils/core/system/sw_shell.py new file mode 100644 index 0000000..b2e54d2 --- /dev/null +++ b/src/utils/core/system/sw_shell.py @@ -0,0 +1 @@ +# def change_shell(log: Logger) diff --git a/src/utils/shared/__init__.py b/src/utils/shared/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/src/utils/shared/exec.py b/src/utils/shared/exec.py new file mode 100755 index 0000000..2e98c17 --- /dev/null +++ b/src/utils/shared/exec.py @@ -0,0 +1,84 @@ +from shutil import which +from subprocess import ( + run, + CalledProcessError, + DEVNULL, + Popen, + PIPE, + check_output +) +from typing import NoReturn, Optional, Any + +from src.utils.shared.log.logger import Logger + + +def exec_cmd( + log: Logger, + cmd: list[Any], + verbose: bool = False, + break_proc: bool = False, + pipe_: bool = False, + init_cmd: Optional[list[Any]] = None + ) -> NoReturn | None | str: + """For command execution/system calls with error handling + + Args: + log -- Logger instance + cmd -- command to execute with arguments + verbose -- whether to show command output or not + break_proc -- whether to raise systemexit or not + pipe_ -- whether to pipe a command or not + init_cmd -- initial command to be piped to the main command + + Returns: + None, the output of the command or raise system exit + """ + + try: + if not which(cmd[0]): + log.logger( + "E", f"Program: {cmd[0]} does not exists, aborting ..." + ) + raise SystemExit + + if pipe_ and init_cmd: + init_cmd_out = Popen(init_cmd, stdout=PIPE) + pipe_cmd: bytes = check_output( + cmd, stdin=init_cmd_out.stdout + ) + init_cmd_out.wait() + + if init_cmd_out.returncode != 0: + raise CalledProcessError( + init_cmd_out.returncode, init_cmd + ) + + return ( + pipe_cmd + .decode("utf-8") + .strip() + .replace(r"\n", "") + ) + + if verbose: + ret: int = run(cmd).returncode + else: + ret = run(cmd, stdout=DEVNULL).returncode + + if ret != 0: + raise CalledProcessError(ret, cmd) + + log.logger("I", f"Successfully executed the command: {cmd}") + except (OSError, CalledProcessError) as Err: + log.logger( + "E", + ( + f"{Err} encountered, cannot execute" + " command: {cmd} ..." + ) + ) + + if break_proc: + raise SystemExit + + return None diff --git a/src/utils/shared/fetch_env.py b/src/utils/shared/fetch_env.py new file mode 100755 index 0000000..9903651 --- /dev/null +++ b/src/utils/shared/fetch_env.py @@ -0,0 +1,29 @@ +from os import getenv +from typing import Optional + +from rich.console import Console + +from src.utils.shared.misc.uinput import uinput +from src.utils.shared.log.logger import Logger + + +def fetch_env(log: Logger, console: Console, env_var: str) -> str: + """Fetch the value of given env variable. + + Args: + log -- instance of Logger + env_var -- variable to fetch + + Returns: + The value of env variable + """ + + try: + env_val: Optional[str] = getenv(env_var.upper()) + except OSError as Err: + log.logger("e", f"{Err}. No value found for {env_var}.") + env_val_u = uinput( + console, f"Kindly input the value for {env_var}", 3 + ) + + return env_val or env_val_u diff --git a/src/utils/shared/log/__init__.py b/src/utils/shared/log/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/src/utils/shared/log/logger.py b/src/utils/shared/log/logger.py new file mode 100755 index 0000000..555655d --- /dev/null +++ b/src/utils/shared/log/logger.py @@ -0,0 +1,66 @@ +import logging +from pathlib import Path +from os import mkdir +from os.path import exists + +from rich.logging import RichHandler + + +class Logger: + """Custom logger.""" + + def __init__(self, filename: str = "log") -> None: + """ + Args: + filename -- filename to use. + """ + + logging.basicConfig( + format="%(message)s", + level=logging.INFO, + datefmt="[%X]", + handlers=[ + RichHandler( + show_time=False, + show_path=False + ) + ] + ) + + self.log: logging.Logger = logging.getLogger("rich") + + BASE_PATH: Path = Path.home()/".log" + if not exists(BASE_PATH): + try: + mkdir(BASE_PATH) + except (PermissionError, OSError, IOError) as Err: + self.logger( + "E", f"Cannot create directory: {Err}, aborting ..." + ) + + file_log: logging.FileHandler = logging.FileHandler( + filename=f"{BASE_PATH}/{filename}.log" + ) + + file_log.setLevel(logging.INFO) + file_log.setFormatter( + logging.Formatter("%(levelname)s %(message)s") + ) + self.log.addHandler(file_log) + + def logger(self, exception_: str, message: str) -> None: + """Log the proccesses using passed message and exception_ + variable. + + Args: + exception_ -- determines what type of log level to use + message -- message to be logged. + """ + + match exception_: + case "E": # for major error + self.log.critical("%s" % (message)) + case "e": + self.log.error("%s" % (message)) + case "I": # to print information in the terminal + self.log.info("%s" % (message)) diff --git a/src/utils/shared/misc/__init__.py b/src/utils/shared/misc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/shared/misc/section.py b/src/utils/shared/misc/section.py new file mode 100644 index 0000000..0d4341f --- /dev/null +++ b/src/utils/shared/misc/section.py @@ -0,0 +1,34 @@ +from typing import Optional + +from rich.console import Console +from rich.panel import Panel +from rich.align import Align +from rich.text import Text + + +def section(title_: str, msg_: Optional[str] = None) -> None: + """For displaying of title in major operation. + + Args: + txt -- main txt to display + title -- title of the section + """ + + if not msg_: + msg_, title_ = title_, None # type: ignore + + Console().print( + Panel( + Align( + Text( + msg_.upper(), + justify="center" + ), + vertical="middle", + align="center" + ), + title=title_ + ) + ) + + return None diff --git a/src/utils/shared/misc/uinput.py b/src/utils/shared/misc/uinput.py new file mode 100755 index 0000000..409fb25 --- /dev/null +++ b/src/utils/shared/misc/uinput.py @@ -0,0 +1,55 @@ +from typing import Any + +from rich.console import Console + + +def uinput(console: Console, msg: str, qtype: int) -> Any: + """Takes user input and return the evaluated output. + + Args: + console -- Console instance + msg -- question to ask the user + qtype -- question type, whether y/N, string input or number + input + 1 is yes or no input + 2 is number/list input + 3 is for string or char input + + Returns: + The evaluated input based on the user response, e.g.: + y -> True + N -> False + 1, 2, 3 -> list[int] + """ + + match qtype: + case 1: + console.print( + ( + f"{msg} " + "[bold][[green]y[/green]/" + "[red]N[/red]][/bold]" + ), + end=" " + ) + if input().lower().strip() == "y": + return True + case 2: + console.print( + ( + f"{msg} [bold][NUMBER/LIST INPUT" + ", separate by comma ','][/bold]" + ), end=" " + ) + items: str = input() + return [int(item) for item in items.strip().split(",")] + case 3: + console.print( + ( + f"{msg} [bold][CHAR/STRING INPUT" + ", separate by comma ','][/bold]" + ), end=" " + ) + return input().strip().lower().replace(r"\n", "") + + return False