Initial commit

This commit is contained in:
2024-10-30 01:50:38 +01:00
commit 587ca23374
147 changed files with 7521 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
#!/usr/bin/python
# Copyright (c) 2017 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {
'metadata_version': '1.0',
'status': ['preview'],
'supported_by': '@gekmihesg'
}
DOCUMENTATION = '''
---
module: nohup
short_description: Starts a command in background and returns
description:
- The M(nohup) module start runs a command in a shell using OpenWRTs C(start-stop-daemon).
- The module will dispatch the command and return.
author: Markus Weippert (@gekmihesg)
options:
command:
description:
- command to execute. Execution takes place in a shell.
required: true
aliases:
- cmd
delay:
description:
- seconds to wait, before command is run.
default: 0
note:
- This module does not support check_mode.
'''
EXAMPLES = '''
- name: wait 3 seconds, then restart network
nohup:
command: /etc/init.d/network restart
delay: 3
'''

View File

@@ -0,0 +1,63 @@
#!/bin/sh
# Copyright (c) 2021 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
PARAMS="
cmd=raw_params=_raw_params/str/r
uses_shell=_uses_shell/bool//false
chdir/str
executable/str
creates/str
removes/str
"
RESPONSE_VARS="
start end delta cmd
stdout/str/a stderr/str/a rc/int/a
"
SUPPORTS_CHECK_MODE=""
init() {
stdout=""
stderr=""
start=""
end=""
delta=""
rc="0"
[ -n "$executable" ] || executable="/bin/sh"
out="$(mktemp)" && err="$(mktemp)"
}
main() {
local ts_start ts_end s_delta
[ -z "$chdir" ] || try cd "$chdir"
[ -z "$creates" ] || ! ls -d -- $creates >/dev/null 2>/dev/null || {
stdout="skipped, since $creates exists"; exit 0
}
[ -z "$removes" ] || ls -d -- $removes >/dev/null 2>/dev/null || {
stdout="skipped, since $removes does not exist"; exit 0
}
ts_start="$(date +%s)"
[ -z "$uses_shell" ] && {
echo "$cmd" | xargs sh -c 'exec "$@"' -- >"$out" 2>"$err"; rc=$?; :
} || {
"$executable" -c "$cmd" >"$out" 2>"$err"; rc=$?; :
}
ts_end="$(date +%s)"
s_delta=$((ts_end - ts_start))
start="$(date -d "@$ts_start" "+%Y-%m-%d %H:%M:%S").000000"
end="$(date -d "@$ts_end" "+%Y-%m-%d %H:%M:%S").000000"
delta="$(printf "%d:%.2d:%.2d.000000" \
$((delta / 3600)) $((delta % 3600 / 60)) $((delta % 60)))"
stdout="$(cat "$out")"
stderr="$(cat "$err")"
changed
test "$rc" -eq 0 || fail "non-zero return code"
return 0
}
cleanup() {
rm -f -- "$out" "$err"
}

View File

@@ -0,0 +1,83 @@
#!/bin/sh
# Copyright (c) 2017 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
PARAMS="
backup/bool
dest/str/r
directory_mode/str
force=thirsty/bool//true
original_basename=_original_basename/str
src/str
validate/str
$FILE_PARAMS
"
RESPONSE_VARS="src dest md5sum=md5sum_src checksum backup_file"
init() {
md5sum_src=""
checksum=""
backup_file=""
}
main() {
local tmp _IFS
[ -e "$src" ] || fail "Source $src not found"
[ -r "$src" ] || fail "Source $src not readable"
[ ! -d "$src" ] || fail "Remote copy does not support recursive copy of directory: $src"
checksum_src="$(dgst sha1 "$src")" || :
md5sum_src="$(md5 "$src")"
md5sum_dest=""
[ -z "$original_basename" -o "${dest%/}" = "$dest" ] || {
dest="$dest/$original_basename"
tmp="$(dirname -- "$dest")"
[ -d "$tmp" ] || {
_IFS="$IFS"; IFS="/"; set -- $tmp; IFS="$_IFS"
tmp="$mode"; mode="$directory_mode"
local d
local p=""
for d; do
[ -n "$d" ] || continue
p="$p/$d"
[ ! -d "$p" ] || continue
try mkdir "$p"
set_file_attributes "$p"
done
mode="$tmp"
}
}
[ ! -d "$dest" ] || {
dest="${dest%/}"
[ -z "$original_basename" ] &&
dest="$dest/$(basename -- "$src")" ||
dest="$dest/$original_basename"
}
tmp="$(dirname -- "$dest")"
[ -e "$dest" ] && {
[ ! -h "$dest" -o -z "$follow" ] || dest="$(realpath "$dest")"
[ -n "$force" ] || fail "file already exists"
[ ! -r "$dest" ] || md5sum_dest="$(md5 "$dest")"
} || [ -d "$tmp" ] || fail "Destination directory $tmp does not exist"
[ -w "$tmp" ] || fail "Destination $tmp not writeable"
[ "$md5sum_src" = "$md5sum_dest" -a ! -h "$dest" ] || {
[ -n "$_ansible_check_mode" ] || {
[ -z "$backup" ] || backup_file="$(backup_local "$dest")"
[ ! -h "$dest" ] || { rm -f -- "$dest"; touch -- "$dest"; }
[ -z "$validate" ] || {
[ "${validate/%s/}" != "$validate" ] ||
fail "validate must contain %s: $validate"
tmp="$($(printf "$validate" "$src") 2>&1)" ||
fail "failed to validate: $tmp"
}
try 'cat -- "$src" > "$dest"'
}
changed
}
[ -n "$_ansible_check_mode" ] || set_file_attributes "$dest"
}

View File

@@ -0,0 +1,172 @@
#!/bin/sh
# Copyright (c) 2017 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
PARAMS="
diff_peek/str
force/bool//false
original_basename=_original_basename/str
path=dest=name/str/r
recurse/bool//false
src/str
state/str
$FILE_PARAMS
"
RESPONSE_VARS="state path appears_binary/bool"
init() {
appears_binary=""
}
get_state() {
local state
state="$(ls -ld -- "$1" 2>/dev/null)" && {
case "${state:0:1}" in
l) state="link";;
d) state="directory";;
*) set -- $state; [ "$2" -gt 1 ] &&
state="hard" || state="file";;
esac
echo "$state"
} || { echo "absent"; }
}
get_inode() {
set -- $(ls -id -- "$1")
echo "$1"
}
main() {
[ -z "$diff_peek" ] || {
appears_binary="0"
hexdump -e '16/1 " %02x" " \n"' -c 8192 -- "$path" 2>/dev/null |
grep -q " 00 " && appears_binary="1" || :
exit 0
}
prev_state="$(get_state "$path")"
[ -n "$state" ] ||
case "$prev_state" in
absent) [ -z "$recurse" ] && state="file" || state="directory";;
*) state="$prev_state";;
esac
[ -n "$src" -o "$state" != "link" -a "$state" != "hard" ] || {
[ "$state" != "link" -o -z "$follow" ] ||
fail "src and dest are required for creating links"
src="$(realpath "$path")"
}
[ "$state" = "link" -o "$state" = "absent" -o ! -d "$path" ] || {
basename="$original_basename"
[ -n "$basename" -o -z "$src" ] || basename="$(basename -- "$src")"
[ -z "$basename" ] || {
path="${path%/}/$basename"
prev_state="$(get_state "$path")"
}
}
[ -z "$recurse" -o "$state" = "directory" ] ||
fail "recurse options requires state to be directory"
[ "$state" = "$prev_state" ] && state_change="" || state_change="y"
case "$state" in
absent)
[ ! -e "$path" ] || changed
[ -n "$_ansible_check_mode" ] ||
result="$(rm -rf -- "$path" 2>&1)" ||
fail "removing failed: $result";;
file)
[ -z "$state_change" -o -z "$follow" -o "$prev_state" != "link" ] || {
path="$(realpath "$path")"
prev_state="$(get_state "$path")"
}
[ "$prev_state" = "file" -o "$prev_state" = "hard" ] ||
fail "file ($path) is $prev_state, cannot continue"
set_file_attributes "$path";;
directory)
[ -z "$follow" -o "$prev_state" != "link" ] || {
path="$(realpath "$path")"
prev_state="$(get_state "$path")"
}
[ -e "$path" ] || changed
case "$prev_state" in
absent)
[ -n "$_ansible_check_mode" ] || {
oIFS="$IFS"; IFS="/"; set -- $path; IFS="$oIFS"
path=""
for p; do
[ -n "$p" ] || continue
path="$path/$p"
[ ! -e "$path" ] || continue
result="$(mkdir -p -- "$path" 2>&1)" ||
fail "error creating $path: $result"
changed
set_file_attributes "$path"
done
};;
directory) set_file_attributes "$path" "$recurse";;
*) fail "$path already exists as a $prev_state";;
esac;;
link|hard)
is_abs "$src" && abssrc="$src" || {
[ ! -h "$path" -a -d "$path" ] && abs="$path/$src" ||
abssrc="$(dirname "$path")/$src"
}
[ -e "$abssrc" ] && {
[ ! -d "$abssrc" -o "$state" != "hard" ] ||
fail "src is a directory, cannot hard link $abssrc"
} || {
[ "$state" != "hard" ] ||
fail "src file does not exist, cannot hard link $abssrc"
[ -n "$force" ] ||
fail "src file does not exist, use force=yes if you want to link $abssrc"
}
if [ "$prev_state" = "absent" ]; then
changed
elif [ "$prev_state" != "$state" -a -z "$force" ]; then
fail "refusing to convert between $prev_state and $state for $path"
elif [ "$prev_state" = "link" ]; then
[ "$state" = "link" -a "$(readlink "$path")" = "$src" ] ||
changed
elif [ "$prev_state" = "hard" ]; then
[ "$state" = "hard" -a "$(get_inode "$path")" = "$(get_inode "$abssrc")" ] || {
[ -n "$force" ] ||
fail "cannot link, different hard link exists at destination"
changed
}
elif [ "$prev_state" = "directory" ]; then
[ -z "$(find "$path" -mindepth 1 -maxdepth 1 2>/dev/null)" ] ||
fail "the directory $path is not empty refusing to convert it"
changed
else
changed
fi
[ -n "$_ansible_check_mode" ] || {
[ -z "$CHANGED" ] || {
[ "$state" = "hard" ] && flags="" || flags="-s"
[ "$prev_state" = "absent" ] ||
result="$(rm -rf -- "$path" 2>&1)" ||
fail "error replacing $path: $result"
result="$(ln $flags -- "$src" "$path" 2>&1)" ||
fail "error while linking $path to $src: $result"
}
set_file_attributes "$path"
};;
touch)
[ -n "$follow" -a "$prev_state" = "link" ] || {
path="$(realpath "$path")"
[ -n "$_ansible_check_mode" ] || {
result="$(touch -- "$path" 2>&1)" ||
fail "error touching $path: $result"
set_file_attributes "$path"
}
changed
};;
esac
}

View File

@@ -0,0 +1,122 @@
#!/bin/sh
# Copyright (c) 2017 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
PARAMS="
backrefs/bool//false
create/bool//false
insertafter/str
insertbefore/str
line=value/str
path=dest=destfile=name/str/r
regex=regexp/str
state/str//present
$FILE_PARAMS
"
RESPONSE_VARS=""
escape_slash() {
echo "$1" | sed -e 's|^/|\\/|;:a;s|\([^\]\(\\\\\)*\)/|\1\\/|g;ta;q'
}
escape_chars() {
echo "$1" | sed 's|['"${2:-}"'\]|\\&|g;q'
}
get_last_match() {
echo "$2" | sed -nre "/$1/=" | sed '$!d'
}
save_changes() {
[ -n "$_ansible_check_mode" -o -z "$CHANGED" ] ||
echo "$1" > "$path" 2>/dev/null ||
fail "path $path not writeable"
}
line_present() {
local index old new mode tmp
[ -f "$path" ] && {
old="$(cat "$path")" || fail "path $path not readable"
} || {
[ -n "$create" ] || fail "path $path does not exist"
[ -n "$_ansible_check_mode" ] ||
mkdir -p "$(dirname "$path")"
old=""
}
index="$(get_last_match "$line_match" "$old")"
[ -n "$index" ] && {
tmp="$(echo "$old" | sed -n "${index}{p;q}")"
[ -z "$backrefs" ] || line="$(echo "$tmp" |
sed -re "s/$line_match/$(escape_slash "$line")/")"
[ "$tmp" = "$line" ] || {
new="$(echo "$old" | sed "${index}c $(escape_chars "$line" |
sed 's|^\s|\\&|;q')")"
changed
}
} || [ -n "$backrefs" ] || {
[ -n "${insertafter#[EB]OF}" ] && {
index="$(get_last_match "$(escape_slash "$insertafter")" "$old")"
mode="a"
} || [ -z "${insertbefore#BOF}" ] || {
index="$(get_last_match "$(escape_slash "$insertbefore")" "$old")"
mode="i"
}
[ -n "$index" ] && {
new="$(echo "$old" | sed "$index$mode $(escape_chars "$line" |
sed 's|^\s|\\&|;q')")"
} || {
[ "$insertafter" = "BOF" -o "$insertbefore" = "BOF" ] &&
new="$line$N$old" || {
tmp="$(echo "$old" | sed '${/^$/d}')"
new="$tmp${tmp:+$N}$line"
}
}
changed
}
[ -z "$CHANGED" ] || {
[ -z "$_ansible_diff" ] || set_diff "$old" "$new" "$path" "$path"
[ -n "$_ansible_check_mode" ] || save_changes "$new"
}
}
line_absent() {
local index new old
[ -f "$path" ] || return 0
old="$(cat "$path")" || fail "path $path not readable"
index="$(get_last_match "$line_match" "$old")"
[ -z "$index" ] || {
new="$(echo "$old" | sed -re "/$line_match/d")"
changed
}
[ -z "$_ansible_diff" ] || set_diff "$old" "$new" "$path" "$path"
[ -n "$_ansible_check_mode" -o -z "$CHANGED" ] || save_changes "$new"
}
main() {
case "$state" in
absent|present) :;;
*) fail "state must be absent or present";;
esac
[ -n "$regex" ] &&
line_match="$(escape_slash "$regex")" ||
line_match="^$(escape_chars "$line" '/.[*^$()+?{|')\$"
[ ! -d "$file" ] || fail "path $path is a directory"
case "$state" in
present)
[ -z "$backrefs" -o -n "$regex" ] ||
fail "regexp is required with backrefs"
[ -n "$line" ] || fail "line is required with state present"
line_present;;
absent)
[ -n "$line" -o -n "$regex" ] ||
fail "line or regexp is required with state absent"
line_absent;;
esac
set_file_attributes "$path" "" "y"
}

View File

@@ -0,0 +1,12 @@
#!/bin/sh
# Copyright (c) 2017 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
PARAMS="command=cmd/str/r delay/int//0"
SUPPORTS_CHECK_MODE=""
main() {
try /sbin/start-stop-daemon -Sbqp /dev/null -x /bin/sh -- -c \
"sleep $delay; $command"
changed
}

View File

@@ -0,0 +1,70 @@
#!/bin/sh
# Copyright (c) 2017 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
PARAMS="
name=pkg/str/r
state/str//present
force/str
update_cache/bool
autoremove/bool
nodeps/bool
"
query_package() {
[ -n "$(opkg status "$1")" ]
}
install_packages() {
local _IFS pkg
_IFS="$IFS"; IFS=","; set -- $name; IFS="$_IFS"
for pkg; do
! query_package "$pkg" || continue
[ -n "$_ansible_check_mode" ] || {
try opkg install$force $nodeps "$pkg"
query_package "$pkg" || fail "failed to install $pkg: $_result"
}
changed
done
}
remove_packages() {
local _IFS pkg
_IFS="$IFS"; IFS=","; set -- $name; IFS="$_IFS"
for pkg; do
query_package "$pkg" || continue
[ -n "$_ansible_check_mode" ] || {
try opkg remove$force $autoremove $nodeps "$pkg"
! query_package "$pkg" || fail "failed to remove $pkg: $_result"
}
changed
done
}
main() {
case "$state" in
present|installed|absent|removed) :;;
*) fail "state must be present or absent";;
esac
[ -z "$force" ] || {
case "$force" in
depends|maintainer|reinstall|overwrite|downgrade|space) :;;
postinstall|remove|checksum|removal-of-dependent-packages) :;;
*) fail "unknown force option";;
esac
force=" --force-$force"
}
[ -z "$autoremove" ] || {
autoremove=" --autoremove"
}
[ -z "$nodeps" ] || {
nodeps=" --nodeps"
}
[ -z "$update_cache" -o -n "$_ansible_check_mode" ] || try opkg update
case "$state" in
present|installed) install_packages;;
absent|removed) remove_packages;;
esac
}

View File

@@ -0,0 +1,25 @@
#!/bin/sh
# Copyright (c) 2017 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
PARAMS="data/any"
RESPONSE_VARS="ping data"
__exit() {
[ -z "$NO_EXIT_JSON" ] || return $?
echo -n "{\"ping\":\"$ping\""
[ -z "$data" ] || echo -n ",\"data\":\"${data//\"/\\\"}\""
echo "}"
}
main() {
[ "$data" != "crash" ] ||
{ NO_EXIT_JSON="y"; echo "boom"; exit 1; }
ping="pong"
}
[ -n "$_ANSIBLE_PARAMS" ] || {
. "$1"
trap __exit EXIT
main
}

View File

@@ -0,0 +1,63 @@
#!/bin/sh
# Copyright (c) 2017 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
PARAMS="
enabled/bool
name/str/r
pattern/str
state/str
"
RESPONSE_VARS="name enabled state"
is_running() {
[ -z "$pattern" ] || { pgrep -f "$pattern" >/dev/null 2>&1; return $?; }
"$init_script" running >/dev/null 2>&1
}
is_enabled() {
! "$init_script" enabled >/dev/null 2>&1 || echo 1
}
set_enabled() {
local status result
status="$(is_enabled)"
[ "$enabled" = "$status" ] || {
changed
[ -n "$_ansible_check_mode" ] || {
[ -n "$enabled" ] && action="enable" || action="disable"
result="$("$init_script" "$action" 2>&1)"
status="$(is_enabled)"
[ "$enabled" = "$status" ] ||
fail "Unable to $action service $name: $result"
}
}
case "$status" in
1) enabled="yes";;
*) enabled="no";;
esac
}
set_state() {
local action result running
is_running && running="y" || running=""
case "$state" in
started) [ -n "$running" ] || action="start";;
stopped) [ -z "$running" ] || action="stop";;
restarted|reloaded) action="${state%ed}";;
*) fail "Unknown action $action";;
esac
[ -z "$action" ] || {
changed
[ -n "$_ansible_check_mode" ] ||
result="$("$init_script" "$action" 2>&1)" ||
fail "Unable to $action service $name: $result"
}
}
main() {
init_script="/etc/init.d/$name"
[ -f "$init_script" ] || fail "service $name does not exist"
[ -z "$_orig_enabled" ] || set_enabled
[ -z "$state" ] || set_state
}

View File

@@ -0,0 +1,67 @@
#!/bin/sh
# Copyright (c) 2017 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
NO_EXIT_JSON="1"
add_ubus_fact() {
set -- ${1//!/ }
ubus list "$2" > /dev/null 2>&1 || return
local json="$($ubus call "$2" "$3" 2>/dev/null)"
echo -n "$seperator\"$1\":$json"
seperator=","
}
main() {
ubus="/bin/ubus"
seperator=","
echo '{"changed":false,"ansible_facts":'
dist="OpenWRT"
dist_version="NA"
dist_release="NA"
test -f /etc/openwrt_release && {
. /etc/openwrt_release
dist="${DISTRIB_ID:-$dist}"
dist_version="${DISTRIB_RELEASE:-$dist_version}"
dist_release="${DISTRIB_CODENAME:-$dist_release}"
} || test ! -f /etc/os-release || {
. /etc/os-release
dist="${NAME:-$dist}"
dist_version="${VERSION_ID:-$dist_version}"
}
dist_major="${dist_version%%.*}"
json_set_namespace facts
json_init
json_add_string ansible_hostname "$(cat /proc/sys/kernel/hostname)"
json_add_string ansible_distribution "$dist"
json_add_string ansible_distribution_major_version "$dist_major"
json_add_string ansible_distribution_release "$dist_release"
json_add_string ansible_distribution_version "$dist_version"
json_add_string ansible_os_family OpenWRT
json_add_boolean ansible_is_chroot "$([ -r /proc/1/root/. ] &&
{ [ / -ef /proc/1/root/. ]; echo $?; } ||
{ [ "$(ls -di / | awk '{print $1}')" -eq 2 ]; echo $?; }
)"
dist_facts="$(json_dump)"
json_cleanup
json_set_namespace result
echo "${dist_facts%\}*}"
for fact in \
info!system!info \
devices!network.device!status \
services!service!list \
board!system!board \
wireless!network.wireless!status \
; do
add_ubus_fact "openwrt_$fact"
done
echo "$seperator"'"openwrt_interfaces":{'
seperator=""
for net in $($ubus list); do
[ "${net#network.interface.}" = "$net" ] ||
add_ubus_fact "${net##*.}!$net!status"
done
echo '}}}'
}
[ -n "$_ANSIBLE_PARAMS" ] || main

View File

@@ -0,0 +1,21 @@
#!/bin/sh
# Copyright (c) 2017 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
PARAMS="
src=path/str/r
"
RESPONSE_VARS="source=src content encoding"
init() {
content=""
encoding=""
}
main() {
[ -e "$src" ] || fail "file not found: $src"
[ -r "$src" ] || fail "file not readable: $src"
try base64 "$src"
content="$_result"
encoding="base64"
}

View File

@@ -0,0 +1,137 @@
#!/bin/sh
# Copyright (c) 2017 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
PARAMS="
checksum_algorithm=checksum_algo=checksum/str//sha1
get_checksum/bool//true
get_md5/bool//true
get_mime/bool//true
path/str/r
"
RESPONSE_VARS="
charset/str
checksum/str
ctime/int
dev/int
executable/bool
exists/bool
gid/int
gr_name/str
inode/int
isblk/bool
ischr/bool
isdir/bool
isfifo/bool
isgid/bool
islnk/bool
isreg/bool
issock/bool
isuid/bool
lnk_source/str
md5/str
mime_type/str
mode/str
mtime/int
nlink/int
pw_name/str
readable/bool
rgrp/bool
roth/bool
rusr/bool
size/int
uid/int
wgrp/bool
woth/bool
writeable/bool
wusr/bool
xgrp/bool
xoth/bool
xusr/bool
"
init() {
local var
for var in $RESPONSE_VARS; do eval "${var%%/*}=\"\""; done
RESPONSE_VARS="path/str $RESPONSE_VARS"
}
parse_priv() {
local priv="$1"
local part="$2"
local octal="0"
[ -z "${priv#-??}" ] || { eval "r$part=\"1\""; octal=$((octal + 4)); }
[ -z "${priv#?-?}" ] || { eval "w$part=\"1\""; octal=$((octal + 2)); }
[ -z "${priv#??[-ST]}" ] || { eval "x$part=\"1\""; octal=$((octal + 1)); }
mode="$mode$octal"
[ -z "${priv#??[-x]}" ] || high=$((high + high_mod))
high_mod=$((high_mod / 2))
}
main() {
local var privs _IFS tmp
[ ! -h "$path" -o -z "$follow" ] || {
lnk_source="$path"
path="$(readlink -f "$path")" || :
}
[ -n "$path" -a -e "$path" -o -h "$path" ] || {
exists="0"
return 0
}
for var in $RESPONSE_VARS; do
_IFS="$IFS"; IFS="/"; set -- $var; IFS="$_IFS"
[ "${2#b}" = "$2" ] || eval "$1=\"0\""
done
exists="1"
charset="unknown"
mime_type="unknown"
set -- $(ls -lid "$path")
inode="$1"
privs="$2"
nlink="$3"
pw_name="$4"
gr_name="$5"
size="$6"
set -- $(ls -lidn "$path")
uid="$4"
gid="$5"
[ ! -x "$path" ] || executable="1"
[ ! -r "$path" ] || readable="1"
[ ! -w "$path" ] || writeable="1"
case "$privs" in
d*) isdir="1";;
l*) islnk="1";;
s*) issock="1";;
c*) ischr="1";;
b*) isblk="1";;
p*) isfifo="1";;
-*)
[ "$readable" != "1" ] || {
[ -z "$get_md5" ] || md5="$(md5 "$path")"
[ -z "$get_checksum" ] ||
checksum="$(dgst "$checksum_algorithm" "$path")" ||
[ "$checksum_algorithm" = "sha1" ] ||
fail "Could not hash file '$path' with algorithm '$checksum_algorithm'."
}
isreg="1";;
esac
mtime="$(date -r "$path" +%s)"
ctime="$mtime"
atime="$mtime"
[ "$(id -u)" -ne "$uid" ] || isuid="1"
[ "$(id -g)" -ne "$gid" ] || isgid="1"
high="0"; high_mod=4
privs="${privs#?}"; parse_priv "${privs%??????}" usr
privs="${privs#???}"; parse_priv "${privs%???}" grp
privs="${privs#???}"; parse_priv "$privs" oth
mode="$high$mode"
}
cleanup() {
json_set_namespace result
json_add_object stat
_exit_add_vars $RESPONSE_VARS
json_close_object
json_set_namespace params
RESPONSE_VARS=""
}

View File

@@ -0,0 +1,90 @@
#!/bin/sh
# Copyright (c) 2017 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
PARAMS="
ignore_errors=ignoreerrors/bool
name=key/str/r
reload/bool//true
state/str//present
sysctl_file/str
sysctl_set/bool//false
value=val/str
"
RESPONSE_VARS="name value"
init() {
sysctl="/sbin/sysctl"
default_sysctl_file="/etc/sysctl.conf"
tmp_file=""
}
sysctl_set() {
local result
[ "$(echo $($sysctl -n "$name"))" = "$value" ] || {
changed
[ -n "$_ansible_check_mode" ] ||
result="$($sysctl -w "$name=$value" 2>/dev/null)" ||
fail "failed to set $name to $value: $result"
}
}
sysctl_write() {
local found line v k
tmp_file="$(mktemp)"
while read line; do
set -- $line
[ -z "$1" -o "${1:0:1}" = "#" -o "${1/=/}" = "$1" ] || {
k="${1%%=*}"
[ "$k" != "$name" ] || {
found="1"
[ "$state" = "present" ] || { changed; continue; }
v="${1#*=}"
shift; while [ $# -ge 1 ]; do
[ "${1:0:1}" != "#" ] || break; v="$v $1"; shift
done
[ "$v" = "$value" ] || {
line="$k=$value${1:+ $*}"
changed
}
}
}
echo "$line" >> "$tmp_file"
done < "$sysctl_file"
[ "$state" != "present" -o -n "$found" ] || {
echo "$name=$value" >> "$tmp_file"
changed
}
}
main() {
local result
[ -n "$sysctl_file" ] || sysctl_file="$default_sysctl_file"
case "$state" in
absent) :;;
present)
[ -n "$value" ] ||
fail "value must be given with state present";;
*) fail "state must be present or absent";;
esac
[ -w "$sysctl_file" ] || fail "sysctl file $sysctl_file not writeable"
[ "$state" = "present" ] || ignore_errors="y"
[ -n "$ignore_errors" -a -z "$sysctl_set" ] ||
[ $($sysctl "$name" 2>/dev/null | wc -l) -eq 1 ] ||
fail "unknown sysctl key $name"
[ -z "$sysctl_set" ] || sysctl_set
sysctl_write
[ -z "$CHANGED" -o -n "$_ansible_check_mode" ] || {
cat "$tmp_file" > "$sysctl_file"
[ -z "$reload" -o "$state" != "present" ] ||
result="$($sysctl -p "$sysctl_file" 2>&1)" ||
fail "failed to reload: $result"
}
}
cleanup() {
[ -z "$tmp_file" ] || rm -f "$tmp_file"
}

View File

@@ -0,0 +1,384 @@
#!/bin/sh
# Copyright (c) 2017 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
WANT_JSON="1"
PARAMS="
autocommit/bool
command=cmd/str
config/str
find=find_by=search/any
keep_keys=keep/any
key/str
merge/bool//false
name/str
option/str
replace/bool//false
section/str
set_find/bool//true
type/str
unique/bool//false
value/any
"
RESPONSE_VARS="result=_result command config section option"
init() {
state_path=""
[ -z "$_ansible_check_mode" ] || state_path="$(mktemp -d)" ||
fail "could not create state path"
changes="$(uci_change_hash)"
case "$_type_keep_keys" in
object) fail "keep_keys must be list or string";;
array) json_get_values keep_keys "$_keep_keys" || :;;
esac
[ -z "$key" ] &&
key="${config:+$config${section:+.$section${option:+.$option}}}" ||
{ oIFS="$IFS"; IFS="."; set -- $key; IFS="$oIFS"
config="$1"; section="$2"; option="$3"; }
[ -z "$_ansible_diff" -o -z "$config" ] ||
set_diff "$(uci export "$config")"
[ -n "$command" ] || { [ -z "$value" ] && command="get" || command="set"; }
}
uci() {
[ -z "$state_path" ] || set -- -P "$state_path" "$@"
command uci "$@"
}
uci_change_hash() {
uci changes | md5
}
uci_result_do() {
json_set_namespace result
"$@"
json_set_namespace params
}
uci_get_safe() {
local tmp opts
while [ "${1#-}" != "$1" ]; do opts="$opts $1"; shift; done
tmp="$(uci $opts show "$1")" || return $?
echo "${tmp#*=}"
}
uci_check_type() {
local key="$1"; local type="$2"; local t
t="$(uci -q get "$key")" || return 1
[ -n "$type" -a "$t" != "$type" ] || return 0
fail "$key exists with $t instead of $type"
}
uci_compare_list() {
local k="${1:-$key}"
local match="1"
local keys values v i
json_get_keys keys
! values="$(uci_get_safe -q "$k")" || {
eval "set -- $values"
for i in $keys; do
json_get_var v "$i"
[ $# -gt 0 -a "$v" = "$1" ] || { match=""; break; }
shift
done
[ $# -eq 0 ] || match=""
[ -z "$match" ] || return 0
}
return 1
}
uci_add() {
section="${section:-$value}"
[ -n "$name" -o -z "$type" ] || name="$section"
type="${type:-$section}"
[ -n "$type" ] || fail "type required for $command"
[ -n "$name" ] && {
uci_check_type "$config.$name" "$type" || {
try uci add "$config" "$type"
try uci rename "$config.$_result=$name"
}
} || try uci add "$config" "$type"
}
uci_set_list() {
local k="${1:-$key}"
local keys v i
! uci_compare_list "$k" || return 0
uci -q delete "$k" || :
json_get_keys keys
for i in $keys; do
json_get_var v "$i"
try uci add_list "$k=$v"
done
}
uci_set_dict() {
local keys k v t
json_get_keys keys
keep_keys="$keep_keys $keys"
for k in $keys; do
json_get_type t "$k"
case "$t" in
array)
json_select "$k"
uci_set_list "$key.$k"
json_select ..;;
object) fail "cannot set $k to dict";;
*)
json_get_var v "$k"
try uci set "$key.$k=$v";;
esac
done
}
uci_set() {
local var="${1:-value}"
local var_type
eval "var_type=\"\$_type_$var\""
[ -z "$option" ] || keep_keys="$keep_keys $option"
case "$var_type" in
array)
[ -n "$config" -a -n "$section" -a -n "$option" ] ||
fail "config, section and option required for $command"
json_select_real "$var"
uci_set_list
json_select ..;;
object)
[ -n "$config" -a -n "$section" -a -z "$option" ] ||
fail "config and section but not option required for $command"
json_select_real "$var"
uci_set_dict
json_select ..;;
*) try "uci set \"\$key=\$$var\"";;
esac
}
uci_get() {
local entry
try uci get "$key"
eval "set -- $(uci_get_safe -q "$key")"
json_set_namespace result
json_add_array result_list
for entry; do json_add_string . "$entry"; done
json_close_array
json_set_namespace params
}
uci_find() {
local keys i c v k tmp
case "$_type_find" in
array|object)
[ -n "$config" -a -n "$type" ] ||
fail "config and type required for $command"
json_select_real find
json_get_keys keys;;
*)
[ -n "$config" -a -n "$type" ] &&
[ -n "$option" -o "$command" = find_all ] ||
fail "config, type and option required for $command";;
esac
[ "$command" != "find_all" ] || uci_result_do json_add_array result
type="${type:-$section}"
section=""; i=0
while [ -n "$(uci -q get "$config.@$type[$i]")" ]; do
c="@$type[$((i++))]"
case "$_type_find" in
array)
[ -z "$option" ] && {
for k in $keys; do
json_get_var v "$k"
uci -q get "$config.$c.$v" >/dev/null || continue 2
done
} || {
uci_compare_list "$config.$c.$option" || continue
};;
object)
for k in $keys; do
json_get_type tmp "$k"
case "$tmp" in
array)
json_select "$k"
uci_compare_list "$config.$c.$k" &&
tmp="1" || tmp=""
json_select ..
[ -n "$tmp" ] || continue 2;;
object)
fail "cannot compare $k with dict";;
*)
json_get_var v "$k"
tmp="$(uci -q get "$config.$c.$k")" &&
[ "$tmp" = "$v" ] || continue 2
esac
done;;
*)
[ -z "$option" ] || {
v="$(uci -q get "$config.$c.$option")" &&
[ -z "$find" -o "$find" = "$v" ] || continue
};;
esac
[ "$command" = "find_all" ] || { section="$c"; break; }
uci_result_do json_add_string . "$c"
done
case "$_type_find" in
array|object) json_select ..;;
esac
case "$command" in
find_all)
uci_result_do json_close_array
_result="";;
*)
_result="$section"
[ -n "$section" ] && return 0 || return 1;;
esac
}
uci_ensure() {
local keys k v
[ -n "$name" -o -z "$type" ] || name="$section"
type="${type:-$section}"
[ -n "$config" -a -n "$type" ] ||
fail "config, type and name required for $command"
[ -n "$name" ] && uci_check_type "$config.$name" "$type" || {
[ "$_type_find" = "object" -o -n "$option" ] && uci_find && {
[ -z "$name" ] || try uci rename "$config.$section=$name"
} || {
[ "$command" = absent ] && return 0 || uci_add
}
}
section="${name:-$_result}"
key="$config.$section${option:+.$option}"
[ "$command" = "absent" ] && {
[ -z "$_defined_value" ] &&
{ uci -q delete "$key" || :; } ||
case "$_type_value" in
array|object)
json_select_real value
json_get_keys keys
for k in $keys; do
json_get_var v "$k"
case "$_type_value" in
array) uci -q delete "$config.$section.$v";;
object) uci -q delete "$config.$section.$k=$v";;
esac
done
json_select ..;;
*) uci -q delete "$config.$section.$value";;
esac
return 0
}
[ -z "$set_find" -o "$_type_find" != "object" -a -z "$option" ] || {
uci_set find
[ "$_type_value" != "object" ] || {
key="$config.$section"
option=""
}
}
[ -z "$_defined_value" ] || uci_set
_result="$section"
}
uci_cleanup_section() {
case "$command" in
set|ensure|section) :;;
*) return 0;;
esac
local k v
[ -n "$replace" -a -n "$config" -a -n "$section" ] || return 0
for k in $(uci -q show "$config.$section" |
sed -n 's/^..*\...*\.\([^.][^.]*\)=.*$/\1/p')
do
for v in $keep_keys; do
[ "$k" != "$v" ] || continue 2
done
uci -q delete "$config.$section.$k"
done
}
uci_revert() {
[ -z "$key" ] && rm -f -- "/tmp/.uci"/* 2>/dev/null || uci revert "$key"
}
uci_autocommit() {
[ -n "$autocommit" ] || return 0
case "$command" in
commit|revert) return 0;;
esac
[ "$1" -eq 0 ] && {
[ -n "$config" ] && uci commit "$config" || uci commit
} || uci_revert
}
main() {
case "$command" in
batch|import)
[ -n "$value" ] || fail "value required for $command";;
add_list|del_list|rename|reorder)
[ -n "$key" -a -n "$value" ] ||
fail "key and value required for $command";;
add|get|delete|ensure|absent)
[ -n "$key" ] || fail "key required for $command";;
esac
case "$command" in
batch)
echo "$value" | final uci $command;;
export|changes|show|commit)
local cmd="final uci $command"
[ -z "$key" ] && $cmd || $cmd "$key";;
import)
local cmd="final uci ${merge:+-m }$command"
[ -z "$key" ] && echo "$value" | $cmd ||
echo "$value" | $cmd "$key";;
add)
uci_add; exit 0;;
add_list)
[ -z "$unique" ] || {
eval "set -- $(uci_get_safe -q "$key")"
for entry; do [ "$entry" = "$value" ] && exit 0; done
}
final uci add_list "$key=$value";;
del_list|reorder)
final uci $command "$key=$value";;
get)
uci_get; exit 0;;
delete)
final uci $command "$key${value:+=$value}";;
rename)
final uci $command "$key=${name:-$value}";;
revert)
final uci_revert;;
set)
uci_set; exit 0;;
find|find_all)
uci_find; exit $?;;
ensure|section)
uci_ensure; exit 0;;
absent)
[ -n "$_defined_find" ] || {
uci -q delete "$key${value:+=$value}"; exit 0
}
uci_ensure; exit 0;;
*) fail "unknown command: $command";;
esac
}
cleanup() {
local ec="$1"
[ "$ec" -ne 0 ] || uci_cleanup_section
[ "$changes" = "$(uci_change_hash)" ] || {
changed
[ "$_ansible_verbosity" -lt 2 ] || {
local _IFS line
_IFS="$IFS"; IFS="$N"; set -- $(uci changes); IFS="$_IFS"
json_set_namespace result
json_add_array changes
for line; do json_add_string . "$line"; done
json_close_array
json_set_namespace params
}
uci_autocommit "$ec"
}
[ -z "$_ansible_diff" -o -z "$config" ] ||
set_diff "" "$(uci export "$config")"
[ -z "$_ansible_check_mode" -o -z "$state_path" -o ! -d "$state_path" ] ||
rm -rf "$state_path"
}

View File

@@ -0,0 +1,190 @@
#!/usr/bin/python
# Copyright (c) 2017 Markus Weippert
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {
'metadata_version': '1.0',
'status': ['preview'],
'supported_by': '@gekmihesg'
}
DOCUMENTATION = '''
---
module: uci
short_description: Controls OpenWRTs UCI
description:
- The M(uci) module is a Ansible wrapper for OpenWRTs C(uci).
- It supports all the command line functionality plus some extra commands.
author: Markus Weippert (@gekmihesg)
options:
autocommit:
description:
- Whether to automatically commit changes
type: bool
default: false
command:
description:
- Command to execute. Execution takes place in a shell.
default: set if value else get
choices:
- absent
- add
- add_list
- batch
- changes
- commit
- del_list
- export
- find
- get
- import
- rename
- reorder
- revert
- section
- set
- show
aliases:
- cmd
config:
description:
- Config part of the I(key).
default: extracted from I(key)
find:
description:
- Value(s) to match sections against.
- Option value to find if I(option) is set. May be list.
- Dict of options/values if I(option) is not set. Values may be list.
- Lists are compared in order.
- Required when I(command=find) or I(command=section)
aliases:
- find_by
- search
keep_keys:
description:
- Space seperated list or list of keys not in I(value) or I(find) to
keep when I(replace=yes).
aliases:
- keep
key:
description:
- The C(uci) key to operate on.
- Takes precedence over I(config), I(section) and I(option)
default: I(config).I(section).I(option)
merge:
description:
- Whether to merge or replace when I(command=import)
type: bool
default: false
name:
description:
- New name when I(command=rename) or I(command=add).
- Desired name when I(command=section). If a matching section is
found it is renamed, if not it is created with that name.
option:
description:
- Option part of the I(key).
default: extracted from I(key)
replace:
description:
- When I(command=set) or I(command=section), whether to delete all
options not mentioned in I(keep_keys), I(value) or find when
I(set_find=true).
type: bool
default: false
section:
description:
- Section part of the I(key).
default: extracted from I(key)
set_find:
description:
- When I(command=section) whether to set the options used to search
a matching section in the newly created section when no match was
found.
type: bool
default: false
type:
description:
- Section type for I(command=section), I(command=find) and
I(command=add).
default: I(section)
unique:
description:
- When I(command=add_list), whether to add the value if it is already
contained in the list.
type: bool
default: false
value:
description:
- The value for various commands.
'''
EXAMPLES = '''
# Find a section of type wifi-iface with matching name or matching attributes.
# If not found create it and set the attributes from find.
# Unconditionally set the attributes from value and delete all other options.
- uci:
command: section
config: wireless
type: wifi-iface
name: ap0
find:
device: radio0
ssid: My SSID
value:
encryption: none
replace: yes
# Find a matching wifi-iface and delete it.
- uci:
command: absent
config: wireless
type: wifi-iface
find:
ssid: My SSID broken
# Find a matching wifi-iface and delete the options key and encryption.
- uci:
command: absent
config: wireless
type: wifi-iface
find:
ssid: My SSID public
value:
- key
- encryption
# commit changes and notify
- uci: cmd=commit
notify: restart wifi
'''
RETURN = '''
result:
description: output of the C(uci) command
returned: always
type: string
sample: cfg12523
result_list:
description: the list form of result
returned: when I(command=get)
type: list of string
sample: ['0.pool.ntp.org','1.pool.ntp.org']
config:
description: config part of I(key)
returned: when given
type: string
sample: wireless
section:
description: section part of I(key)
returned: when given
type: string
sample: '@wifi-iface[0]'
option:
description: option part of I(key)
returned: when given
type: string
sample: ssid
command:
description: command executed
returned: always
type: string
sample: section
'''