You are not logged in.
Couldn't you acheive the same goal by just looking for broken links:
find -L /etc/systemd/ -type l
...and I could then feed this to pkgfile, in case I really want to know where the link came from. Nice.
Last edited by Awebb (2023-08-16 05:51:38)
Offline
Small script to calculate when I should start charging my EV to make sure its ready by 8:00 AM.
#!/bin/bash
#in kwh
battery_capacity=60
#in kw
charge_rate=2.8
charge_complete_time=8
current_battery_level=$1
if [[ $# -ne 1 ]]; then
echo 'Too many/few arguments, expecting one' >&2
exit 1
fi
if [[ $(echo "if (( $current_battery_level < 0 ) || ( $current_battery_level > 1 )) 1 else 0" | bc) -eq 1 ]]
then
echo "current charge level must be between 0 and 1"
exit 1
fi
echo "
scale=2;
print \"current charge level= \" ; $current_battery_level;
charge_duration=$battery_capacity*(1 - $current_battery_level)/$charge_rate;
print \"charge_duration = \" ;charge_duration;
charge_complete_time=$charge_complete_time
print \"target complete time = \" ;charge_complete_time;
start_time=charge_complete_time-charge_duration - 1;
if (start_time <= 0) start_time +=24;
print \"start_time = \"; start_time;" | bc
Offline
Script to delete orphaned packages (and their configs) but retain packages installed as makedepends for AUR builds. On a measure-twice-cut-once basis, it can be used to list orphans cum makedepends, makedepends and orphans ex makedepends in lieu of actually deleting.
This works on the assumption that the .SRCINFO for AUR packages is kept in /home/AUR/builds/cache/<pkgname>/ and the variable AUR_SRCINFO may need to be modified accordingly.
#! /bin/bash
AUR_SRCINFO='/home/AUR/builds/cache/*/.SRCINFO'
MAKEDEPENDS=$(mktemp)
ORPHANS=$(mktemp)
TRUE_ORPHANS=$(mktemp)
for i in ${AUR_SRCINFO}; do
if sed -n '/pkgname/ s/^pkgname = //p' "${i}" | pacman -Qi - > /dev/null 2>&1 ; then
sed -n '/makedepends/ s/^.*makedepends = //p' ${i} >> "${MAKEDEPENDS}"
fi
done
sort -u "${MAKEDEPENDS}" -o "${MAKEDEPENDS}"
pacman -Qqtd > "${ORPHANS}"
grep -Fvxf "${MAKEDEPENDS}" "${ORPHANS}" > "${TRUE_ORPHANS}"
case ${1} in
"M")
echo "makedepends packages:"
cat "${MAKEDEPENDS}"
;;
"O")
echo "orphaned and makedepends packages:"
cat "${ORPHANS}"
;;
"R")
echo "Removing true orphaned packages and their config files and dependencies"
cat "${TRUE_ORPHANS}" | pacman -Rns -
;;
"T")
echo "true orphaned packages:"
cat "${TRUE_ORPHANS}"
;;
*)
echo "Parameters:"
echo "M for makedepends packages"
echo "O for orphaned and makedepends packages"
echo "R to remove true orphaned packages and their config files and dependencies. THIS MUST BE RUN AS ROOT"
echo "T for true orphaned packages"
;;
esac
rm "${MAKEDEPENDS}"
rm "${ORPHANS}"
rm "${TRUE_ORPHANS}"
Offline
An interactive Yes/No script similar to fzf, gum choose / gum confirm. Entirely in bash.
#!/bin/bash
# simple script for an interactive YES/NO prompt
# set -x
menu() {
tput civis
index=0
def="Continue? "
clear
key="j"
prompt="${1:-$def}"
prompt2="${2:-}"
while true; do
tput cup 0 0 && printf '\e[35m%s\e[0m\n' "${prompt}"
tput cup 1 0 && printf '\e[33m%s\n' "${prompt2}"
if ((index == 1)); then
tput cup 2 0 && printf '\e[2k' && printf '\e[31m%s\e[0m\n' "> Yes"
tput cup 3 0 && printf '\e[2k' && printf '\e[32m%s\e[0m\n' " No"
elif ((index == 0)); then
tput cup 2 0 && printf '\e[2k' && printf '\e[32m%s\e[0m\n' " Yes"
tput cup 3 0 && printf '\e[2k' && printf '\e[31m%s\e[0m\n' "> No"
fi
case "$key" in
j|$'\x1b\x5b\x41') if [[ "$index" = 0 ]]; then index=1 ; elif [[ "$index" = 1 ]]; then index=0 ; fi ;;
k|$'\x1b\x5b\x42') if [[ "$index" = 0 ]]; then index=1 ; elif [[ "$index" = 1 ]]; then index=0 ; fi ;;
$'\x0a') break ;;
*) echo >/dev/null ;;
esac
unset K1 K2 K3
read -sN1
K1="$REPLY"
read -sN2 -t 0.001
K2="$REPLY"
read -sN1 -t 0.001
K3="$REPLY"
key="$K1$K2$K3"
done
# index will be 1 or 0 which is also return codes for success and fail
# used for the last condition on whether to start the game or not
stty sane
echo "$index"
return "$index"
}
cleanup() {
# clear
tput cnorm
stty echo
}
trap cleanup EXIT
menu "${@}"
Improvements and advice welcomed
Last edited by sweet_tea (2023-09-16 23:51:25)
Offline
Wrapper around bluetoothctl
#!/bin/bash
ACTION() {
echo "Choose device using id"
read id
echo "What do you want to do?
provide space separated command/s
pair unpair trust untrust connect disconnect remove"
read action
xdotool search --name blue && xdotool windowclose $(xdotool search --name blue)
for x in $action; do
bluetoothctl "$x" "$id"
done
}
bluetoothctl devices
echo "choose what to do?
a. act on known device b. scan for new"
read ans
if [[ $ans = a ]]; then
ACTION
else
lxterminal -t blue --geometry=70x15 -e bluetoothctl scan on
ACTION
fi
Arch is home!
https://github.com/Docbroke
Offline
Quite simple, but - list packages by repo (for, say, re-installation, or to help to remember removing clutter). Pretty much made using the wiki!
#!/bin/bash
OUTDIR=pacmanlists
OUTFILE=explicit.lst # edit name to your liking
until [ -z "$1" ] ; do
case $1 in
-d)
OUTDIR="$2"
shift
shift
;;
-f)
OUTFILE="$2"
shift
shift
;;
*)
echo unknown parameter $1
exit 1
;;
esac
done
myerrorfunc () {
echo $1
exit 1
}
# List explicitly installed packages from repo $1
list_repo_explicit () {
comm -12 <(pacman -Qqe | sort) <(pacman -Sql $1 | sort)
}
if [ ! -e $OUTDIR ] ; then
mkdir "$OUTDIR" || myerrorfunc "can not mkdir $OUTDIR"
elif [ -e "$OUTDIR" -a ! -d "$OUTDIR" ] ; then
myerrorfunc "$OUTDIR exist but not a dir!"
fi
# list ALL explicitly installed packages; TODO: remove base group!
pacman -Qqe > ${OUTFILE}
# explicitly installed
pacman -Qqe > ${OUTDIR}/${OUTFILE}
# Orphans
pacman -Qtdq > ${OUTDIR}/orphans_${OUTFILE}
# External (often: packages deleted from repos)
pacman -Qqem > ${OUTDIR}/externalpkgs_${OUTFILE}
# list the repos you want to list into separate files here:
for REPO in core extra multilib archzfs custom ; do
list_repo_explicit $REPO > ${REPO}_$OUTFILE
done
Ideas/TODO:
put to cron (say, weekly?).
Send periodic reminders to sysadmin about line count(s) of these files!
EDIT: Improved the script a bit and corrected errors
Last edited by Wild Penguin (2023-12-05 10:58:03)
Offline
Script for display archlinux bugs
Why ? I want packages name (hard to read on gitlab) et want a filter for only packages installed on my arch
It's only for archlinux gitlab users use a TOKEN
return are as:
python-pandas: Importing pandas crashes python with SIGILL on AMD A6-7310 2022-01-30 (2) (3)
2-high confirmed
https://gitlab.archlinux.org/archlinux/packaging/packages/python-pandas/-/issues/1
linux: ntfs3 module flushes changes to files only on unmount 2023-11-18 (4) (38)
3-medium waiting
https://gitlab.archlinux.org/archlinux/packaging/packages/linux/-/issues/6
mesa: radeonsi python-pytorch ROCm segfaults 2023-11-22 (3) (3)
4-low confirmed
https://gitlab.archlinux.org/archlinux/packaging/packages/mesa/-/issues/3
usage: arch-issues [-h] [-p {1,2,3,4,5}] [-n N] [-f] [-r] [-s] [-c] [SEARCH_TEXT]
options:
-p {1,2,3,4,5} Minimum severity (default: lowest) # display filter only
-n 25 Bugs to load (maximum: 100)
-f, --filter Display Only package installed
-r, --reversed Display order by date created
Positional argument = text to search
#!/usr/bin/env python
import argparse
from enum import Enum
import json
import os
from pathlib import Path
from urllib import request, error
import sys
TOKEN = os.getenv('API_GITLAB_ARCH', "XXXX-XXXXXXXXXXXXXXX")
class Colors(Enum):
BLUE = '\033[94m'
GREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
GRAY = '\033[90m'
SOMBRE = '\033[96m'
ENDC = '\033[0m'
BOLD = '\033[1m'
def format(self, text) -> str:
return f"{self.value}{text}{self.ENDC.value}"
class Severities(Enum):
critical = 1
high = 2
medium = 3
low = 4
lowest = 5
@classmethod
def from_txt(cls, txt):
return cls(int(txt[0]))
def __str__(self):
return f"{self.value}-{self.name}"
def format_severity(severity: Severities) -> str:
if severity.value < 3:
return Colors.FAIL.format(severity)
return str(severity)
def download(search: str, count: int, severity_mini: int) -> list:
search = f"&search={search}" if search else ""
count = count if count < 101 else 25
url = f"https://gitlab.archlinux.org/api/v4/groups/11323/issues?scope=all&sort=desc{search}&order_by=created_at&state=opened&per_page={count}&page=1&labels=scope::bug"
# url = url + ",severity::1-critical,severity::2-high,severity::3-medium" # TODO ?
hdr = {"PRIVATE-TOKEN": TOKEN, "User-Agent": "Arch-issues/1.0 (archlinux; Linux)"}
req = request.Request(url, headers=hdr)
try:
with request.urlopen(req) as response:
return json.loads(response.read())
except (error.HTTPError, error.URLError) as err:
print("Web error:", err)
return []
def get_package_name(url: str) -> str:
url = url.removeprefix("https://gitlab.archlinux.org/archlinux/packaging/packages/")
return url.split("/", maxsplit=2)[0]
def get_labels(labels: list):
ret = ['', '']
for label in labels:
if label.startswith("severity::"):
ret[0] = Severities.from_txt(label[10:])
elif label.startswith("status::"):
ret[1] = label[8:]
return ret[0], ret[1]
def my_packages():
for dir in Path("/var/lib/pacman/local/").iterdir():
parts = dir.name.split("-")
if len(parts) < 3:
continue
yield "-".join(parts[:len(parts)-2])
if __name__ == "__main__":
parser = argparse.ArgumentParser(
prog="arch-issues",
description="Archlinux bugtracker",
epilog="Positional argument = text to search",
)
parser.add_argument("-p", type=int, choices=[x.value for x in Severities], default=Severities.lowest.value, help="Minimum severity (default: lowest)")
parser.add_argument("-n", type=int, default=25, help="Bugs to load (maximum: 100)")
parser.add_argument("-f", "--filter", action="store_true", help="Only package installed", default=False)
parser.add_argument("-r", "--reversed", action="store_true", help="Display order by date created", default=False)
# -s , -c : only for dev
parser.add_argument("-s", "--save", action="store_true", help="Save in cache", default=False)
parser.add_argument("-c", "--cache", action="store_true", help="Use cache", default=False)
args, search = parser.parse_known_args()
search = " ".join(search)
datas = []
# exit(0)
if not args.cache:
datas = download(search, args.n, args.p)
else:
with open("arch.issues.json") as fjson:
datas = json.load(fjson)
if not datas:
exit(2)
if args.save:
with open("arch.issues.json", "w") as fjson:
fjson.write(json.dumps(datas, indent=2))
if args.filter:
packages = tuple(my_packages())
for item in sorted(datas, key=lambda x: x["created_at"], reverse=args.reversed):
severity, status = get_labels(item['labels'])
if severity.value > args.p:
continue
pkg = get_package_name(item['web_url'])
if args.filter and pkg not in packages:
# print("# NO", pkg)
continue
print()
severity = format_severity(severity)
nb_assign = len(item['assignees'])
nb_assign = f"{nb_assign}" if nb_assign > 0 else ''
# `user_notes_count` == responses in issue
print(
f"{Colors.BOLD.format(pkg)}:",
f"{Colors.BLUE.format(item['title'])}",
f"\t{item['created_at'][0:10]}",
f" ({nb_assign}) ({item['user_notes_count'] if item['user_notes_count'] else ''})"
)
print(f" {severity} {status}")
print(f" {Colors.GRAY.format(item['web_url'])}")
thanks to this topic for the idea
lts - zsh - Kde - Intel Core i3 - 6Go RAM - GeForce 405 video-nouveau
Offline
// IRC text-to-speech server
// needs ffmpeg and piper-tts (https://github.com/rhasspy/piper)
//
// Example stream:
// https://ugjka.net/irc-radio/
// mpv https://ugjka.net/ircradio
// mpg123 https://ugjka.net/ircradio
//
// configuration? change the constants and build
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"net/http"
"net/mail"
"net/url"
"os"
"os/exec"
"strings"
"sync"
"time"
"github.com/bogem/id3v2/v2"
"github.com/tcolgate/mp3"
kitty "github.com/ugjka/kittybot"
"gopkg.in/inconshreveable/log15.v2"
"mvdan.cc/xurls/v2"
)
const IRC_SERVER = "irc.libera.chat:6697"
const IRC_NICK = "guest556677"
const IRC_PASSWORD = ""
var IRC_CHANNELS = []string{
"##english",
"#android",
"#archlinux",
"#archlinux-offtopic",
"#bash",
"#go-nuts",
"#kde",
"#kde-devel",
"#libera",
"#linux",
"#linux-offtopic",
"#networking",
"#newpipe",
"#nextcloud",
"#security",
"#systemd",
"#ubuntu",
"#ubuntu-server",
"#ubuntu-offtopic",
"#yt-dlp",
"#ugjka",
}
const PIPER_TTS_VOICE = "/home/ugjka/amy/medium/en_US-amy-medium.onnx"
// Silence PCM
const SPR = 22050
const PCM_SECOND = SPR * 2
const PCM_BUFFER = PCM_SECOND / 10
const PCM_BUFFER_DUR = time.Second / 10
// Mp3
const BITRATE = 40
// Buffers
const INIT_BUFF_SEC = 15 //seconds
const INIT_BUFF = BITRATE * 1000 / 8 * INIT_BUFF_SEC
const NETW_BUFF = INIT_BUFF / 4
const WORK_BUFF = INIT_BUFF / 32
const MAX_BACKLOG = INIT_BUFF * 2
const STREAM_PORT = 8089
// TMP DIR
const TMP_FOLDER = "ircwebradiotmp"
var tmpdb = newFiles()
var log = log15.New()
var start = make(chan struct{})
func main() {
exes := []string{"piper-tts", "ffmpeg"}
for _, exe := range exes {
_, err := exec.LookPath(exe)
errExit(err)
}
os.RemoveAll(TMP_FOLDER)
err := os.Mkdir(TMP_FOLDER, 0755)
errExit(err)
err = os.Chdir(TMP_FOLDER)
errExit(err)
if len(os.Args) > 1 {
IRC_CHANNELS = []string{"#" + os.Args[1]}
}
bot := kitty.NewBot(IRC_SERVER, IRC_NICK,
func(bot *kitty.Bot) {
bot.Password = IRC_PASSWORD
bot.Channels = IRC_CHANNELS
bot.SSL = true
},
)
h := log15.StreamHandler(
os.Stderr,
log15.FormatFunc(
func(r *log15.Record) []byte {
buf := bytes.NewBuffer(nil)
fmt.Fprintf(buf, "[%v] ", r.Lvl)
fmt.Fprintf(buf, "[%v] ", r.Msg)
for i, v := range r.Ctx {
if i%2 == 0 {
fmt.Fprintf(buf, "%v=", v)
} else {
fmt.Fprintf(buf, "%v ", v)
}
}
fmt.Fprintln(buf)
return buf.Bytes()
},
),
)
log.SetHandler(log15.LvlFilterHandler(log15.LvlInfo, h))
bot.Logger = log
line := make(chan string, 5000)
block := make(chan struct{}, 1)
var prevchan string
var prevuser string
// privmsg
bot.AddTrigger(
kitty.Trigger{
Condition: func(bot *kitty.Bot, m *kitty.Message) bool {
return m.Command == "PRIVMSG" && strings.HasPrefix(m.To, "#") && !strings.HasPrefix(m.Content, "\u0001ACTION")
},
Action: func(bot *kitty.Bot, m *kitty.Message) {
block <- struct{}{}
defer func() { <-block }()
var msg string
if m.To != prevchan {
prevchan = m.To
prevuser = m.Name
msg = fmt.Sprintf("%s, %s says: %s", m.To, m.Name, m.Content)
line <- msg
return
}
if m.Name != prevuser {
prevuser = m.Name
msg = fmt.Sprintf("%s says: %s", m.Name, m.Content)
line <- msg
return
}
line <- m.Content
},
},
)
// /me action messages
bot.AddTrigger(
kitty.Trigger{
Condition: func(bot *kitty.Bot, m *kitty.Message) bool {
return m.Command == "PRIVMSG" && strings.HasPrefix(m.To, "#") && strings.HasPrefix(m.Content, "\u0001ACTION")
},
Action: func(bot *kitty.Bot, m *kitty.Message) {
block <- struct{}{}
defer func() { <-block }()
var msg string
if m.To != prevchan {
prevchan = m.To
prevuser = m.Name
msg = fmt.Sprintf("%s, %s %s", m.To, m.Name, m.Content[8:len(m.Content)-1])
line <- msg
return
}
if m.Name != prevuser {
prevuser = m.Name
msg = fmt.Sprintf("%s %s", m.Name, m.Content[8:len(m.Content)-1])
line <- msg
return
}
line <- fmt.Sprintf("%s %s", m.Name, m.Content[8:len(m.Content)-1])
},
},
)
httpserver := &http.Server{
Addr: fmt.Sprintf(":%d", STREAM_PORT),
}
stream := new(streamer)
stream.init()
http.Handle("/stream", stream)
log.Info("stream", "url", fmt.Sprintf("http://0.0.0.0:%d/stream", STREAM_PORT))
go func() {
<-start
httpserver.ListenAndServe()
}()
// TTS PCM generation
go generateTTS(line)
// push the mp3 feed
r, w := io.Pipe()
go generateMP3(w)
go stream.streamMP3(r)
for {
bot.Run()
time.Sleep(time.Second * 30)
}
}
func generateTTS(line <-chan string) {
for {
msg := <-line
msg = stripURLpath(msg)
log.Debug(msg)
piper := exec.Command(
"piper-tts", "--output-raw", "-m",
PIPER_TTS_VOICE,
)
piper.Stdin = strings.NewReader(msg)
filename := fmt.Sprintf("%d.pcm", time.Now().UnixNano())
f, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, 0644)
errExit(err)
piper.Stdout = f
err = piper.Run()
errExit(err)
err = f.Close()
errExit(err)
tmpdb.add(filename)
}
}
func generateMP3(stream io.Writer) {
ffmpeg := exec.Command("ffmpeg",
"-f", "s16le",
"-ar", fmt.Sprint(SPR),
"-ac", "1",
"-i", "-",
"-b:a", fmt.Sprintf("%dk", BITRATE),
"-f", "mp3",
"-")
ffmpeg.Stdout = stream
r, w := io.Pipe()
ffmpeg.Stdin = r
err := ffmpeg.Start()
errExit(err)
silence := make([]byte, PCM_BUFFER)
buf := make([]byte, PCM_BUFFER)
var total time.Duration
var start time.Time
tick := func() {
total += PCM_BUFFER_DUR
time.Sleep(total - time.Since(start))
}
start = time.Now()
for {
file := tmpdb.get()
if file == "" {
tick()
_, err := w.Write(silence)
errExit(err)
continue
}
f, err := os.Open(file)
errExit(err)
loop:
for {
n, err := f.Read(buf)
switch err {
case nil:
tick()
_, err := w.Write(append(buf[:n], silence[n:]...))
errExit(err)
case io.EOF:
err = f.Close()
errExit(err)
err = os.Remove(file)
errExit(err)
break loop
default:
errExit(err)
}
}
}
}
func (s *streamer) streamMP3(r *io.PipeReader) {
dec := mp3.NewDecoder(r)
frame := &mp3.Frame{}
skipped := new(int)
var tmp []byte
var buflen int
s.Lock()
for {
err := dec.Decode(frame, skipped)
if err == nil {
tmp, err = io.ReadAll(frame.Reader())
if err == nil {
s.buffer = append(s.buffer, tmp)
buflen += len(tmp)
if buflen >= INIT_BUFF {
buflen = 0
break
}
}
}
}
log.Info("initial buffer built", "length", fmt.Sprintf("%ds", INIT_BUFF_SEC))
s.Unlock()
close(start)
var buf [][]byte
buflen = 0
for {
err := dec.Decode(frame, skipped)
if err == nil {
tmp, err = io.ReadAll(frame.Reader())
if err == nil {
buf = append(buf, tmp)
buflen += len(tmp)
if buflen >= WORK_BUFF {
s.send(buf)
buf = [][]byte{}
buflen = 0
}
}
}
}
}
func (s *streamer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
id, recieve := s.addClient()
defer s.delClient(id)
// Set some headers
w.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Add("Pragma", "no-cache")
w.Header().Add("Expires", "0")
w.Header().Add("Content-Type", "audio/mpeg")
// buffered writes
buffw := bufio.NewWriterSize(w, NETW_BUFF)
tag := id3v2.NewEmptyTag()
tag.SetArtist(IRC_SERVER)
tag.SetAlbum("IRC tts service")
tag.SetGenre("voice")
tag.SetYear(fmt.Sprint(time.Now().Year()))
tag.SetTitle(strings.Join(IRC_CHANNELS, " "))
_, err := tag.WriteTo(buffw)
if err != nil {
return
}
for {
chunk := <-recieve
if _, err := buffw.Write(chunk); err != nil {
return
}
}
}
type streamer struct {
sync.RWMutex
clients map[uint64]chan []byte
id uint64
buffer [][]byte
}
func (s *streamer) init() {
s.Lock()
defer s.Unlock()
s.clients = make(map[uint64]chan []byte)
s.buffer = make([][]byte, 0)
}
func (s *streamer) addClient() (id uint64, in chan []byte) {
s.Lock()
defer s.Unlock()
s.id++
s.clients[s.id] = make(chan []byte, MAX_BACKLOG/WORK_BUFF)
s.clients[s.id] <- bytes.Join(s.buffer, nil)
log.Info("listeners", "count", len(s.clients))
return s.id, s.clients[s.id]
}
func (s *streamer) delClient(id uint64) {
s.Lock()
defer s.Unlock()
close(s.clients[id])
delete(s.clients, id)
log.Info("listeners", "count", len(s.clients))
}
func (s *streamer) send(frames [][]byte) {
s.Lock()
buf := bytes.Join(frames, nil)
for _, v := range s.clients {
select {
case v <- buf:
default:
}
}
s.buffer = append(s.buffer[len(frames):], frames...)
s.Unlock()
}
type tmpfiles struct {
sync.Mutex
files []string
}
func (tmp *tmpfiles) add(name string) {
tmp.Lock()
tmp.files = append(tmp.files, name)
tmp.Unlock()
}
func (tmp *tmpfiles) get() (out string) {
tmp.Lock()
defer tmp.Unlock()
if len(tmp.files) > 0 {
out = tmp.files[0]
tmp.files = tmp.files[1:]
}
return
}
func newFiles() tmpfiles {
return tmpfiles{
files: make([]string, 0),
}
}
var urlreg = xurls.Relaxed()
// strip URLs down to hostname
func stripURLpath(in string) string {
matches := urlreg.FindAllString(in, -1)
var tmp string
for i := range matches {
_, err := mail.ParseAddress(matches[i])
if err == nil {
continue
}
tmp = matches[i]
if !strings.HasPrefix(tmp, "http://") && !strings.HasPrefix(tmp, "https://") {
tmp = "https://" + tmp
}
link, err := url.Parse(tmp)
if err == nil {
in = strings.Replace(in, matches[i], link.Hostname(), -1)
}
}
return in
}
func errExit(err error) {
if err != nil {
fmt.Fprintln(os.Stderr, "Exiting, fatal:")
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
Last edited by ugjka (2023-12-18 13:31:43)
https://ugjka.net
paru > yay | vesktop > discord
pacman -S spotify-launcher
mount /dev/disk/by-...
Offline
Simple script for displaying Caps Lock and Num Lock status in Xfce panel (useful if your keyboard doesn't have led indicators):
#!/bin/bash
PANEL=`xset -q | awk '/Caps/{print $2 $3 $4 $6 $7 $8}'`
PANEL="${PANEL//CapsLock:off/ a-z}"
PANEL="${PANEL//CapsLock:on/ A-Z}"
PANEL="${PANEL//NumLock:off/ --- }"
PANEL="${PANEL//NumLock:on/ 0-9}"
echo "<txt>$PANEL</txt>"
echo "<tool>Caps Lock and Num Lock statuses</tool>"
Save script as, say, CapsNum.sh, make it executable, add it to Generic Monitor on Xfce panel, set refresh interval to 0,5 or similar and save settings.
Offline
xset -q | awk '/Caps/{printf "%s %s", ($4=="on")?"A-Z":"a-z", ($8=="on")?"0-9":"---"}'
Offline
https://github.com/ugjka/X/blob/main/dlna-send/main.go
// send a url to your dlna receiver
// Usage:
// dlna-send [options] URL
// -dev string
// dlna device to use (friendly name))
// -stop
// stop playback
package main
import (
"flag"
"fmt"
"net/url"
"os"
"path"
"strconv"
"github.com/huin/goupnp"
"github.com/huin/goupnp/dcps/av1"
)
const USAGE = `Usage:
%s [options] URL
-dev string
dlna device to use (friendly name))
-stop
stop playback
`
func main() {
dev := flag.String("dev", "", "dlna device to use (friendly name))")
stop := flag.Bool("stop", false, "stop playback")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, USAGE, path.Base(os.Args[0]))
}
flag.Parse()
var url string
var err error
if !*stop {
if len(flag.Args()) > 0 {
url = flag.Args()[0]
} else {
stderr(fmt.Errorf("no url given"))
}
err = checkURL(url)
if err != nil {
stderr(err)
}
}
var dlna *goupnp.MaybeRootDevice
if *dev == "" {
dlna = chooseDLNADevice()
} else {
dlna, err = findDLNAreceiver(*dev)
if err != nil {
stderr(err)
}
}
if dlna.Location != nil && !*stop {
av1SetAndPlay(dlna.Location, url)
return
}
if dlna.Location != nil && *stop {
av1Stop(dlna.Location)
return
}
stderr(fmt.Errorf("device not ready"))
}
func chooseDLNADevice() *goupnp.MaybeRootDevice {
fmt.Println("Loading...")
roots, err := goupnp.DiscoverDevices(av1.URN_AVTransport_1)
fmt.Print("\033[1A\033[K")
fmt.Println("----------")
stderr(err)
if len(roots) == 0 {
stderr(fmt.Errorf("no dlna devices on the network found"))
}
fmt.Println("DLNA receivers")
for i, v := range roots {
fmt.Printf("%d: %s\n", i, v.Root.Device.FriendlyName)
}
fmt.Println("----------")
fmt.Println("Select the DLNA device:")
selected := selector(roots)
return &roots[selected]
}
func findDLNAreceiver(friedlyName string) (*goupnp.MaybeRootDevice, error) {
roots, err := goupnp.DiscoverDevices(av1.URN_AVTransport_1)
if err != nil {
return nil, err
}
for _, root := range roots {
if root.Root.Device.FriendlyName == friedlyName {
return &root, nil
}
}
return nil, fmt.Errorf("'%s' not found", friedlyName)
}
func selector[slice any](s []slice) int {
var choice int
for {
var choiceStr string
_, err := fmt.Fscanln(os.Stdin, &choiceStr)
if err != nil {
fmt.Print("\033[1A\033[K")
continue
}
choice, err = strconv.Atoi(choiceStr)
if err != nil || choice >= len(s) {
fmt.Print("\033[1A\033[K")
} else {
break
}
}
fmt.Print("\033[1A\033[K")
fmt.Printf("[%d]\n", choice)
return choice
}
func stderr(err error) {
if err == nil {
return
}
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
func av1SetAndPlay(loc *url.URL, stream string) {
client, err := av1.NewAVTransport1ClientsByURL(loc)
stderr(err)
err = client[0].SetAVTransportURI(0, stream, "")
stderr(err)
err = client[0].Play(0, "1")
stderr(err)
}
func av1Stop(loc *url.URL) {
client, err := av1.NewAVTransport1ClientsByURL(loc)
if err != nil {
return
}
client[0].Stop(0)
}
func checkURL(link string) error {
parsed, err := url.Parse(link)
if err != nil {
return err
}
if parsed.Scheme == "" {
return fmt.Errorf("no url scheme")
}
if parsed.Host == "" {
return fmt.Errorf("no host")
}
return nil
}
https://ugjka.net
paru > yay | vesktop > discord
pacman -S spotify-launcher
mount /dev/disk/by-...
Offline
I always struggle to remember OpenRC commands so I wrote the script to simplify the service control:
Usage:
Usage: rcc <action> [service]
Actions:
rcc start [service] starts service
rcc stop [service] stops service
rcc restart [service] restarts service
rcc status [service] service status
rcc enable [service] enable service
rcc disable [service] disable service
rcc list shows status of all services
/usr/bin/rcc:
#!/bin/sh
set -eu
_usage() {
_bn="$(basename "${0}")"
printf '%s\n' "Usage: ${_bn} <action> [service]" \
"" \
"Actions:" \
" ${_bn} start [service] starts service" \
" ${_bn} stop [service] stops service" \
" ${_bn} restart [service] restarts service" \
" ${_bn} status [service] service status" \
" ${_bn} enable [service] enable service" \
" ${_bn} disable [service] disable service" \
" ${_bn} list shows status of all services"
exit 1
}
if [ "${#}" -eq 2 ]
then
# start
if [ "${1}" = "start" ]
then
rc-service "${2}" start
# stop
elif [ "${1}" = "stop" ]
then
rc-service "${2}" stop
# restart
elif [ "${1}" = "restart" ]
then
rc-service "${2}" restart
# status
elif [ "${1}" = "status" ]
then
rc-service "${2}" status
# enable
elif [ "${1}" = "enable" ]
then
rc-update add "${2}" default
# disable
elif [ "${1}" = "disable" ]
then
rc-update del "${2}" default
else
_usage
fi
elif [ "${#}" -eq 1 ]
then
# list
if [ "${1}" = "list" ]
then
rc-update -v show
else
_usage
fi
else
_usage
fi
Offline
I always struggle to remember OpenRC commands so I wrote the script to simplify the service control
And in doing so, you highlighted the only thing that systemd is actually good at: verb choice.
Well ... almost. What the hell is "status". That is surely a naming-things fail (by systemd ... given that it's the only one that is not a verb). It could be "describe", "report", "summarize", etc. But programmers seem to really like the word "status".
Last edited by Trilby (2023-12-21 15:33:03)
"UNIX is simple and coherent" - Dennis Ritchie; "GNU's Not Unix" - Richard Stallman
Offline
Have a several scripts, that I wrote after reading the wiki. Hope it will work on a common setup of Archlinux. Let's start with firewall script.
Description: Setting up iptables for ipv4 and ipv6 (if exist). Ask which of ports (TCP and UDP) should to be opened (ipv4 only!), detect all ports that already used by running services and suggest them for opening. You could delete port or add some manually during execution. For setting new config you need start this script again.
Note: "Prevent SYN scan attack" commented - prevent access very aggressively when someone (even you either) want connect to host simultaneously by several services/programs, but you could try uncomment.
#!/bin/bash
#https://wiki.archlinux.org/title/Simple_stateful_firewall
#isRoot
if [ "${EUID}" -ne 0 ]; then echo "You need to run this script as root"; exit 1; fi
echo " * Flushing all rules"
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
iptables -t raw -F
iptables -t raw -X
iptables -t security -F
iptables -t security -X
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
echo " * Setting default policies"
iptables -N TCP
iptables -N UDP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
iptables -P INPUT DROP
echo " * Allowing traffic that belongs to established connections, or new valid traffic"
iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
echo " * Allowing loopback devices"
iptables -A INPUT -i lo -j ACCEPT
echo " * Drop all traffic with an INVALID state match"
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
echo " * Allowing ping responses"
iptables -A INPUT -p icmp --icmp-type 8 -m conntrack --ctstate NEW -j ACCEPT
echo " * Setting TCP and UDP chains"
iptables -A INPUT -p udp -m conntrack --ctstate NEW -j UDP
iptables -A INPUT -p tcp --syn -m conntrack --ctstate NEW -j TCP
#echo " * Prevent SYN scan attack for TCP"
#iptables -I TCP -p tcp -m recent --update --rsource --seconds 60 --name TCP-PORTSCAN -j REJECT --reject-with tcp-reset
#iptables -A INPUT -p tcp -m recent --set --rsource --name TCP-PORTSCAN -j REJECT --reject-with tcp-reset
#echo " * Prevent SYN scan attack for UDP"
#iptables -I UDP -p udp -m recent --update --rsource --seconds 60 --name UDP-PORTSCAN -j REJECT --reject-with icmp-port-unreachable
#iptables -A INPUT -p udp -m recent --set --rsource --name UDP-PORTSCAN -j REJECT --reject-with icmp-port-unreachable
echo " * Rejecting all other traffic"
iptables -A INPUT -j REJECT --reject-with icmp-proto-unreachable
for p in tcp udp
do
echo
echo "Open ${p^^} ports by processes:"
echo
ops=$(ss -ln --$p|grep -Ev "(127\.|\[::\])"|grep -Po '(?<=:)(\d+)'|sort -nu | tr '\n' ' ')
ss -lnp --$p|tail --lines +2|grep -Ev "(127\.|\[::\])"|sed 's/users:(("//g;s/:/ /;s/"/ /'|awk '{print $4,$5,$7}'|(echo "Address Port Process";sort -nk3,3 -nk2)|column -t -R1
echo
read -rp "Please enter the ${p^^} ports (v4 only) for opening outside: " -e -i "${ops}" ops
for op in $ops
do
echo " * Allowing port $op"
iptables -A ${p^^} -p $p --dport $op -j ACCEPT
done
echo
done
echo " * Setting FORWARD policies"
iptables -N fw-interfaces
iptables -N fw-open
iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -j fw-interfaces
iptables -A FORWARD -j fw-open
iptables -A FORWARD -j REJECT --reject-with icmp-host-unreachable
iptables -P FORWARD DROP
echo " * SAVING RULES for iptables v4"
iptables-save -f /etc/iptables/iptables.rules
echo " * STARTING IPTABLES v4"
systemctl enable --now iptables
ipv6gateway=$(ip -6 route ls | grep default | grep -Po '(?<=via )(\S+)' | head -1)
if [ "$ipv6gateway" != "" ]; then
echo " * SAVING RULES for iptables v6"
cat << EOF > /etc/iptables/ip6tables.rules
*raw
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [22:2432]
-A PREROUTING -m rpfilter -j ACCEPT
-A PREROUTING -j DROP
COMMIT
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
:TCP - [0:0]
:UDP - [0:0]
:fw-interfaces - [0:0]
:fw-open - [0:0]
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate INVALID -j DROP
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 128 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -s fe80::/10 -p ipv6-icmp -j ACCEPT
-A INPUT -s fd42::/10 -p ipv6-icmp -j ACCEPT
-A INPUT -p udp --sport 547 --dport 546 -j ACCEPT
-A INPUT -p udp -m conntrack --ctstate NEW -j UDP
-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j TCP
-A INPUT -p udp -j REJECT --reject-with icmp6-adm-prohibited
-A INPUT -p tcp -j REJECT --reject-with tcp-reset
-A INPUT -j REJECT --reject-with icmp6-adm-prohibited
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -j fw-interfaces
-A FORWARD -j fw-open
-A FORWARD -j REJECT
COMMIT
EOF
echo " * Setting ICMPv6 Neighbor Discovery Protocol"
echo " Default IPv6 gateway is:" $ipv6gateway
sed -i 's/fd42::\/10/'$ipv6gateway'\/128/' /etc/iptables/ip6tables.rules
echo " * STARTING IPTABLES v6"
systemctl enable --now ip6tables
else
echo " * No default IPv6 gateway found. Skip starting IPv6 iptables"
fi
echo "All set. Good luck!"
Offline
Script for detecting the age of Archlinux setup:
There are three methods of detecting, you could choose that works for you.
#!/bin/bash
DATE=$(/usr/bin/ls -ctl --time-style +"%Y-%m-%d" /etc | tail -1 | grep -Po "[0-9]+-[0-9]+-[0-9]+")
#or
#DATE=$(stat /etc | tail -1 | cut -d" " -f3)
#or
#DATE=$(head -1 /var/log/pacman.log | cut -c 2-11)
LOCALE=$(echo $DATE | sed 's/-/ /g' | awk '{print $3"."$2"."$1}')
DAYSBETWEEN=$(( ($(date -d $(date +%Y-%m-%d) +%s) - $(date -d $DATE +%s)) / 86400 ))
YEARSCALC=$(( $DAYSBETWEEN / 365 ))
MONTHSCALC=$(( ($DAYSBETWEEN - ($YEARSCALC * 365)) / 30 ))
DAYSCALC=$(( $DAYSBETWEEN -($YEARSCALC * 365) - ($MONTHSCALC * 30) ))
[[ $YEARSCALC > 0 ]] && YEARS=$YEARSCALC"y "
[[ $MONTHSCALC > 0 ]] && MONTHS=$MONTHSCALC"m "
[[ $DAYSCALC > 0 ]] && DAYS=$DAYSCALC"d"
echo "System age: "$YEARS$MONTHS$DAYS" (since $LOCALE)"
Offline
List of explicit installing packages from repositories and AUR (just more eyecandy than in the wiki):
#!/bin/bash
# https://wiki.archlinux.org/title/Pacman/Tips_and_tricks#Packages_and_dependencies
## sudo pacman -Syu --needed expac
echo -e "\033[1mFrom repository:\033[0m"
expac -H M "%011m\t%-20n\t%10d" $(comm -23 <(pacman -Qqen | sort) <((expac -l '\n' '%E' base-devel; expac -l '\n' '%E' base) | sort -u))
[ -n "$(pacman -Qm)" ] && ( echo -e "\n\033[1mFrom AUR:\033[0m"; expac -H M "%011m\t%-20n\t%10d" $(pacman -Qqm) )
Offline
logger.sh - shows new events from journal when you start new bash/ssh session. Can use for ssh greatings (like motd):
For example, if some service not started logger will show message about it.
#!/bin/bash
# https://wiki.archlinux.org/title/Systemd/Journal#Priority_level
## From 0: Emergency, 1: Alert, 2: Critical, 3: Error, 4: Warning, 5: Notice, 6: Informational, 7: Debug
LEVELTILL=3
SKIPWORDS="(TSC)" #messages about watchdog and other
OLDLOG=$HOME/.cache/oldjournal.log
NOWLOG=$HOME/.cache/journal.log
[ -z $OLDLOG ] || touch $OLDLOG
JRNLLOG=$(journalctl --no-pager -b0 -p$LEVELTILL | cut -d" " -f5- | grep -Ev $SKIPWORDS)
echo "$JRNLLOG" | sort | uniq -u > $NOWLOG
NOWDIFF=$(diff $OLDLOG $NOWLOG | grep -E '^>')
if [ "$NOWDIFF" != "" ]
then
echo -n " Changes in system journal:"
echo $NOWDIFF | tr ">" "\n" | sed -E 's/\[[0-9]+\]//g;s/ *$//g' | uniq
echo "$JRNLLOG" | sort |uniq -u > $OLDLOG
fi
And add this in .bashrc
if [ -n "$SSH_CLIENT" ] && [ -z "$TMUX" ]; then
if [ -f $HOME/scripts/logger.sh ]; then
echo -ne "\n";
$HOME/scripts/logger.sh
fi
fi
Last edited by Nebulosa (2023-12-25 17:14:17)
Offline
topmem.sh - list process descending by used memory and swap. Top 10 processes shows by default.
#!/bin/bash
TOP=$([ -z $1 ] && echo 10 || echo $1)
declare -a mem=($(ps -e -orss=,args= | awk '{print $1 " " $2 }' | awk '{tot[$2]+=$1;count[$2]++} END {for (i in tot) {print tot[i],i,count[i]}}' | sort -n | tail -n $TOP | sort -nr | awk '{hr=$1/1024; printf("%13.2fM|%s", hr, $2)}' | tr '\n' ' '))
declare -a swp=($(cat /proc/*/status | grep -E 'VmSwap:|Name:' | grep -B1 'VmSwap' | cut -d':' -f2 | grep -v -- '--' | grep -o -E '[a-zA-Z0-9]+.*$' | cut -d' ' -f1 | xargs -n2 echo | sort -hrk2 | awk '{hr=$2/1024; if (hr>0) printf("%13.2fM|%s", hr,$1)}' | tr '\n' ' '))
printf "%-9s %35s %-9s %20s\n" "MEMORY" "Top $TOP processes " "SWAP" ""
for (( j=0; j<${#mem[@]}; j++ ));
do
printf "%9s %-35s %9s %-20s\n" $(echo ${mem[$j]} | sed 's/|/ /;s|usr/||;s|bin/||;s|lib/||;s|/||;s|systemd/||') $(echo ${swp[$j]} | sed 's/|/ /')
done
echo
printf "%9s %-35s %9s %-20s\n" $(free -m | awk '/Mem/{print($3"M "$1)}'| sed 's/Mem:/MemTotal/') $(free -m | awk '/Swap/{print($3"M "$1)}' | sed 's/Swap:/SwapTotal/')
Offline
TOP=$([ -z $1 ] && echo 10 || echo $1)
I think you mean:
TOP=${1:-10}
(actually I think you mean "top=${1:-10}" but that's a style issue)
On top of this, there is absolutely no reason to pipe through several instances of awk, sort, cut, etc. Just use one properly.
But then going further, just spending a little time reading `man ps` you'd realize this whole script should just be one proper `ps` command (maybe two if you want one list each for RAM and swap use).
Last edited by Trilby (2023-12-25 18:18:41)
"UNIX is simple and coherent" - Dennis Ritchie; "GNU's Not Unix" - Richard Stallman
Offline
I think you mean:
TOP=${1:-10}
Good point! Didn't know about that.
On top of this, there is absolutely no reason to pipe through several instances of awk, sort, cut, etc. Just use one properly.
I use this script for a couple years and noted that awk eat a lot of memory when executed, so main idea is to use awk as much less as possible.
Example of using:
$ topmem
MEMORY Top 10 processes SWAP
23.13M sshd:
18.76M systemd-journald
12.96M sinit
11.62M awk
11.50M systemd
9.75M systemd-udevd
9.25M systemd-networkd
8.50M systemd-timesyncd
8.50M systemd-logind
7.07M sslh:
90M MemTotal 0M SwapTotal
So, vice versa, script need to be rewrited without using awk.
But then going further, just spending a little time reading `man ps` you'd realize this whole script should just be one proper `ps` command (maybe two if you want one list each for RAM and swap use).
I already use ps for taking memory info, so it could be shorter but will look the same. Anyway, thanks for feedback!
Last edited by Nebulosa (2023-12-26 08:23:55)
Offline
tgsay.sh - send message or some information from a bash script throught your own Telegram bot.
#!/bin/bash
TG_BOT_API_TOKEN=""
TG_BOT_CHAT_ID=""
if [ -z "$TG_BOT_CHAT_ID" ]; then
echo 'Please, define TG_BOT_CHAT_ID and TG_BOT_API_TOKEN first! See "chat":{"id":xxxxxxx string below from request: curl https://api.telegram.org/bot$TG_BOT_API_TOKEN/getUpdates'
exit 1
fi
MSG=$(echo "$@" | tr '\n' '[' | sed 's/ /%20/g;s/\[/%0A/g') #Urlencoding some simbols for curl
#/usr/bin/wget -qO- "https://api.telegram.org/bot$TG_BOT_API_TOKEN/sendMessage?chat_id=$TG_BOT_CHAT_ID&parse_mode=html&text=$@" 2>&1
/usr/bin/curl --no-progress-meter "https://api.telegram.org/bot$TG_BOT_API_TOKEN/sendMessage?chat_id=$TG_BOT_CHAT_ID&parse_mode=HTML&text=$MSG" 2>&1
Offline
Script to download RFCs and open them in lynx. Caches them to disk, so opening an RFC is instant after the first time. I have it aliased to "rfc", so I can just type "rfc (NUMBER)" to read. Assumes that there's a directory in $HOME called ".rfc-cache". Maybe it should create that directory automatically but I don't really care.
#!/bin/bash
URLPREFIX='https://www.rfc-editor.org/rfc/rfc' # where the RFCs are
# (just append number to that url)
if echo "$1" | grep '^[0-9]+$' -Ev &>/dev/null || test "$2"; then
echo 'Usage: rfc (NUMBER)'
echo Fetches rfc pages from rfc-editor.org if not already saved.
exit
fi
RFCPATH="$HOME/.rfc-cache/$1.html"
if [[ ! -e "$RFCPATH" ]]; then
echo Not cached.
if ! curl -f "$URLPREFIX$1" >"$RFCPATH"; then
echo Could not download RFC "$1".
rm "$RFCPATH"
exit
fi
fi
lynx "$RFCPATH"
"Don't comment bad code - rewrite it." - The Elements of Programming Style (1978), Brian W. Kernighan & P. J. Plauger, p. 144.
Offline
usage() {
echo 'Usage: rfc (NUMBER)'
echo Fetches rfc pages from rfc-editor.org if not already saved.
exit
}
(( 10#${1} )) 2>/dev/null || usage
…
What's "test $2" for?
Offline
I'm also curious why you're deliberately downloading an html (and thus larger) version of the RFCs only to require a browser to then render this html as plain text ... just use the plain text source in the first place (e.g., ietf.org/rfc/rfc####) and open it in an editor or pager rather than a browser.
Last edited by Trilby (2023-12-29 23:27:45)
"UNIX is simple and coherent" - Dennis Ritchie; "GNU's Not Unix" - Richard Stallman
Offline
What's "test $2" for?
To see if you accidentally put a second argument. Is there a more idiomatic way to do that?
I'm also curious why you're deliberately downloading an html (and thus larger) version of the RFCs only to require a browser to then render this html as plain text ... just use the plain text source in the first place (e.g., ietf.org/rfc/rfc####) and open it in an editor or pager rather than a browser.
Some of the HTML versions have links to sections in their table of contents. I was about to point out that they also link to other RFCs, but those are local links so they wouldn't work. Hmm.
I don't mind the increased size, though; for comparison, RFC 1337 is 36K vs. 24K. Compression would probably make a bigger difference.
I'm non-committal about this, though. Plaintext might be neater.
"Don't comment bad code - rewrite it." - The Elements of Programming Style (1978), Brian W. Kernighan & P. J. Plauger, p. 144.
Offline