how do I read and write the framebuffer screen without errors? - ZenFone 2 Accessories

I get this:
Code:
[email protected]:/ # cat /dev/urandom > /dev/graphics/fb0
tmp-mksh: cat: <stdout>: No space left on device
or with direct access
Code:
[email protected]:/ # ln -n /dev/graphics/fb0 /dev/fb0
[email protected]:/ # cat /dev/urandom > /dev/fb0
tmp-mksh: cat: <stdout>: No space left on device
How do I do it?

The error is normal and the expected behaviour because you are writing more data to the framebuffer than actually fits on it.
However I assume you see no change to the displayed image despite writing to the framebuffer. (I see no change on my display.)
I think this might be because surfaceflinger takes full control of the framebuffer and replaces the static noise (caused by cat /dev/urandom) with a composited android screen image before refreshing the screen.
As for the correct way to write to the framebuffer:
I think you need to kill surfaceflinger first (and make sure it doesn't restart) and then use the cat command(though the error will remain). Alternatively you can write a small program in C that does the same by opening fb0, mapping it to memory and then writing onto that.
To read from fb0, a simple "cat /dev/graphics/fb0 > /sdcard/fbimage" should work.
However I am not too sure about most of this.

Related

[HOWTO] script autostart Froyo xxJPK Galaxy S I9000

Not guaranteed it will work, use this at your risk, messing with /system may brik your phone!! Better you run this only if you understand what you're doing or wait somebody to automate it somehow.
Open a root shell and run in this sequence.
# mount /system -o remount,rw
# cd /system/bin
# cp -a playlogos1 playlogos1.orig
# echo '#!/system/bin/sh' >playlogos1
# echo 'if [ -x /system/etc/autostart.sh ]; then /system/etc/autostart.sh >/dev/null 2>&1 & fi' >>playlogos1
# echo 'exec /system/bin/playlogos1.orig' >>playlogos1
# mount /system -o remount,ro
Then put anything you want in /system/etc/autostart.sh (after creating it don't forget to make it executable with "chmod +x /system/bin/autostart.sh").
I didn't find a cleaner way (and searching it seems nobody knows how to do it on froyo I thought of posting this...).
My /system/etc/autostart.sh looks like this:
#!/system/bin/sh
exec sysctl -w -p /system/etc/sysctl.conf
/system/etc/sysctl.conf looks like this:
vm.dirty_writeback_centisecs = 500
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 20
vm.dirty_background_ratio = 10
This restore the upstream kernel values for write throttling and writeback but (3000 means you may lose the last 30 seconds of updates if phone crashes, but it should provide for smoother performance).
Sorry I didn't attach a script for the autostart, too lazy . You better do it only if you understand what you're doing unless somebody attaches a script.
Good tip. I prefer to put my startup script in /data/ so that if I mess it up and the phone gets stuck at boot, I can do a recovery boot to clear data and the startup script will be wiped. It won't be wiped if you put it in /system/.

[SOLVED] Shell scripting problem: unexpected "fi"

SOLVED: The file had Windows EOL formatting. *nix no likely that.
In the init script there are several instances of if statements like this:
Code:
testvar=1
if [ "$testvar" = "1" ] ; then
echo "Testvar is equal to one"
fi
Yet, when I put if statements exactly like this into a shell script and execute them in an adb shell (or connectbox shell), I get the following error:
line 4: syntax error: unexpected "fi" (expecting "then")
I've also taken if statements directly from init and they throw the same error.
Any busybox gurus here know what's up with this?
toadlife said:
In the init script there are several instances of if statements like this:
Code:
testvar=1
if [ "$testvar" = "1" ] ; then
echo "Testvar is equal to one"
fi
Yet, when I put if statements exactly like this into a shell script and execute them in an adb shell (or connectbox shell), I get the following error:
line 4: syntax error: unexpected "fi" (expecting "then")
I've also taken if statements directly from init and they throw the same error.
Any busybox gurus here know what's up with this?
Click to expand...
Click to collapse
Remove the ; and enter the then
Code:
testvar=1
if [ "$testvar" = "1" ]
then
echo "Testvar is equal to one"
fi
Nope. Still throws the same error.
This is driving me nuts. What I'm doing is editing the /init script. The loops work just fine when executed at bootup in / init, but remounting the filesystem, editing /init and rebooting just the test a change takes forever.
For now I've got my changes to init working, but it would be nice to be able to test before editing /init
I just realized that I forgot to put `#!/bin/sh` at the top of my script. But doing so doesn't seem to help. Instead I get not found error.
Weird!
toadlife said:
Nope. Still throws the same error.
This is driving me nuts. What I'm doing is editing the /init script. The loops work just fine when executed at bootup in / init, but remounting the filesystem, editing /init and rebooting just the test a change takes forever.
For now I've got my changes to init working, but it would be nice to be able to test before editing /init
Click to expand...
Click to collapse
Code:
testvar=1
if [ $testvar = `1` ] (`=~ key)
then
echo "Testvar is equal to one"
fi
[/QUOTE]
It gives the echo, but also 1: not found.
Here is the actual code I put into my init:
(this works)
Code:
#Super cool battery thingy
RUNSCBS=`/bin/grep -o "run.scbs=.*" /proc/cmdline | /bin/sed -e "s/.*run.scbs=//g" -e "s/ .*//g"`
if [ "$RUNSCBS" = "1" ] ; then
dev=$(cat /sys/class/scbs/0/dev | sed -e "s/:/ /g")
mknod /dev/scbs0 c $dev
scbs -d -co /sdcard/scbs.conf
fi
# Debug logs
TAKELOGS=`/bin/grep -o "take.logs=.*" /proc/cmdline | /bin/sed -e "s/.*take.logs=//g" -e "s/ .*//g"`
if [ "$TAKELOGS" = "1" ] ; then
logfiledate=`expr substr \`date -Iseconds|tr -d :|tr -d \+|tr -d \-|tr -d T\` 1 14`
tar -cz -f "$card"/debuglogs_"$logfiledate".tar.gz "$card"/debuglogs_*.txt
cat /proc/kmsg>"$card"/debuglogs_kmsg_"$logfiledate".txt &
logcat -v time >"$card"/debuglogs_logcat_time_"$logfiledate".txt &
logcat -v time -b radio>"$card"/debuglogs_logcat_time_radio_"$logfiledate".txt &
fi
Ooooo I really like the idea of having logging running from init. Does that work well? Do the logs ever 'loop' and you end up missing things, or do they just keep on churnin?
arrrghhh said:
Ooooo I really like the idea of having logging running from init. Does that work well? Do the logs ever 'loop' and you end up missing things, or do they just keep on churnin?
Click to expand...
Click to collapse
AFAIK, they go forever. I started doing it last night by just putting the static command in the /init and the logging was still going when I got up this morning.
This morning wanted to see if I could make logging and scbs triggerable by an option in the command line.
Both are working for me now.
Know if where talking about scripts, i have a question too.
This is my RIL logs script:
Code:
#! /system/bin/sh
i="0"
while [ $i -lt 2 ]
do
date=`date +%Y%m%d%H%M`
if [ -d /sdcard/logs/ril/$date/ ]
then
echo "/sdcard/logs/ril/$date/ exists!"
else
mkdir /sdcard/logs/ril/$date/
echo " Made direction /sdcard/logs/ril/$date!"
fi
logcat -v time > /sdcard/logs/ril/$date/logcat-time.txt &
logcat -v time -b radio > /sdcard/logs/ril/$date/logcat-time.txt &
sleep 18000 && kill -0 $! && kill $!
cd /sdcard/logs/ril/
tar -czf ril$date.tar.gz $date
rm -r $date
cd /
done
The thing, i believe, is that only the logcat -v time -b radio is killed and when it's sleeping it's not doing that in the background.
Sleeping for 18000 seconds (5h), because if it's running for a day the logcat log will be more than 10 mb or so.
lol.
Your date string...
Code:
date +%Y%m%d%H%M
...is a bit simpler than mine...
Code:
expr substr `date -Iseconds|tr -d :|tr -d \+|tr -d \-|tr -d T` 1 14
(I couldn't figure out the `date` syntax)
Christiaan91 said:
Know if where talking about scripts, i have a question too.
This is my RIL logs script:
Code:
#! /system/bin/sh
i="0"
while [ $i -lt 2 ]
do
date=`date +%Y%m%d%H%M`
if [ -d /sdcard/logs/ril/$date/ ]
then
echo "/sdcard/logs/ril/$date/ exists!"
else
mkdir /sdcard/logs/ril/$date/
echo " Made direction /sdcard/logs/ril/$date!"
fi
logcat -v time > /sdcard/logs/ril/$date/logcat-time.txt &
logcat -v time -b radio > /sdcard/logs/ril/$date/logcat-time.txt &
sleep 18000 && kill -0 $! && kill $!
cd /sdcard/logs/ril/
tar -czf ril$date.tar.gz $date
rm -r $date
cd /
done
The thing, i believe, is that only the logcat -v time -b radio is killed and when it's sleeping it's not doing that in the background.
Sleeping for 18000 seconds (5h), because if it's running for a day the logcat log will be more than 10 mb or so.
Click to expand...
Click to collapse
Hmm. I haven't set up gscript myself yet, but you might need to nohup the logcats to get them to stay once gscript shuts down.
arrrghhh said:
Ooooo I really like the idea of having logging running from init. Does that work well? Do the logs ever 'loop' and you end up missing things, or do they just keep on churnin?
Click to expand...
Click to collapse
I have found one issue. It's seems that whenever scbs runs, locat fails to open the log devices. I just disabled scbs and logcat worked. I'm going to try swapping the code around so scbs runs after the logging starts.
It seems scbs blows up logcat, even when it is triggered after logging starts. Ughhh.
Entropy512 said:
Hmm. I haven't set up gscript myself yet, but you might need to nohup the logcats to get them to stay once gscript shuts down.
Click to expand...
Click to collapse
logcat will be going, even when you close the terminal, but when you deleted the files they stop. So that's not a problem, cuz i won't delete.
BTW: is it possible to silently run scripts? Like this one?
./pathtoscript1 & ./pathtoscript2 & ./pathtoscript3
Christiaan91 said:
logcat will be going, even when you close the terminal, but when you deleted the files they stop. So that's not a problem, cuz i won't delete.
BTW: is it possible to silently run scripts? Like this one?
./pathtoscript1 & ./pathtoscript2 & ./pathtoscript3
Click to expand...
Click to collapse
What do you mean "silently run" - do you mean disable any printing to stdout?
./pathtoscript1 &>/dev/null & ; ./pathtoscript2 &>/dev/null &
should do the trick The &>/dev/null routes all output to /dev/null
Man, you guys go way overboard on this stuff. This is my gscript logging script.
Code:
cd /sdcard
mv logg.txt logg.0.txt
mv logr.txt logr.0.txt
nohup logcat -v time > logg.txt &
nohup logcat -v time -b radio > logr.txt &
nohup klogd > klog.txt
highlandsun said:
Man, you guys go way overboard on this stuff. This is my gscript logging script.
Code:
cd /sdcard
mv logg.txt logg.0.txt
mv logr.txt logr.0.txt
nohup logcat -v time > logg.txt &
nohup logcat -v time -b radio > logr.txt &
nohup klogd > klog.txt
Click to expand...
Click to collapse
Yeah, well we don't all have kung-fu coding ablities like you, so we go overboard with what we can overboard with.
Besides, I think it might be beneficial to put something like the code I wrote into the official init, so noobs can more easily provide debug logs.
One thing to keep in mind, busybox = /bin/sh, statically compiled sh is /system/bin/sh. I got tired of not having arrow key support whenever I su'ed (since /bin/su spawns a root /system/bin/sh regardless of what's in /etc/passwd) so I bind mounted /bin/sh (which is a symlink to busybox) over the one in /system in my user.conf. I haven't come across any ill effect, but if you feel that the shell difference may be causing issues, you could try that route, since it's very easy to undo.
-- Starfox
Starfox said:
One thing to keep in mind, busybox = /bin/sh, statically compiled sh is /system/bin/sh. I got tired of not having arrow key support whenever I su'ed (since /bin/su spawns a root /system/bin/sh regardless of what's in /etc/passwd) so I bind mounted /bin/sh (which is a symlink to busybox) over the one in /system in my user.conf. I haven't come across any ill effect, but if you feel that the shell difference may be causing issues, you could try that route, since it's very easy to undo.
-- Starfox
Click to expand...
Click to collapse
Thanks for the tip. I'll give that a shot.
LOL. I'm an idiot
Figured out what the problem was. The file had Windows EOL formatting.
That's what I get for using Windows to edit shell scripts.

The Android Shell

The Android Shell
A "shell" is a program that listens to keyboard input from a user and performs actions as directed by the user. Android devices come with a simple shell program. This shell program is mostly undocumented. Since many people are curious about it I thought I'd write up some documentation for it.
Currently this documentation is incomplete, sorry!
Common problems
The built-in shell has very limited error handling. When you type a command name incorrectly it will say "permission denied", even though the real problem is that it couldn't find the command:
$ dir
dir: permission denied <---- this is a misleading error message, should say 'dir: not found'
$ ls
... listing of current directory
The PATH variable
The Android shell will run any program it finds in its PATH. The PATH is a colon (':') seperated list of directories. You can find out what your shell's PATH is set to by using the built-in echo command:
$ echo $PATH
/data/local/bin:/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
Depending upon your shell, you may see a different result.
Built in Commands
Every shell has a few built-in commands. Some common built-in commands are:
echo -- prints text to stdout.
set -- sets shell variables
export -- makes shell variables available to command-line programs
cd -- change the current directory.
pwd -- print name of the current directory.
Commands
To find out what commands you have available to you, use the "ls" command on each of the directories in the PATH variable.
Finding documentation for the Android commands.
Many of the Android commands are based on standard Linux (or bsd) commands. If you're curious about a command, you can sometimes learn how it works by using the "man" command on a desktop Linux or OSX (Apple Macintosh) computer. The Linux or OSX version of the command may be different in details, but much of the documentation will still apply to the Android version of the command.
Another source of documentation for people without a Linux or OSX machine handy is to use a web browser and use a web search engine to search for the text: "man Linux command-name".
List of commands
The following is a list of the commands that are present on a Nexus S phone running an Android 2.3.3 "user-debug" build. Many of these commands are not present on a "user" phone. (They are missing from a "user" phone because they are specific to developing or debugging the Android operating system.)
$ ls /data/local/bin
/data/local/bin: No such file or directory
Notice that by default there is no /data/local/bin directory. You can create this directory using the "mkdir" command if you like.
$ ls /sbin
opendir failed, Permission denied
The /sbin directory exists, but you don't have permission to access it. You need root access. If you have a developer phone, or otherwise have root access to your phone you can see what's in this directory.
$ su
# ls /sbin
ueventd
adbd
# exit
$
Notice that the shell prompt changes from a '$' to a '#' to indicate that you have root access.
Notice also that neither of the /sbin commands are useful to the shell -- the adb and ueventd files are 'daemon' programs used to implement the Android Debugger "adb" program that is used by developers.
$ ls /vendor/bin
gpsd
pvrsrvinit
Vendor/bin is where device vendors can put device-specific executables. These files are from a Nexus S.
$ ls /system/sbin
/system/sbin: No such file or directory
This directory does not exist on a Nexus S.
$ ls /system/bin
am
am is the Android Activity Manager. It's used to start and stop Android activities (e.g. applications) from the command line. Type am by itself to get a list of options.
amix
aplay
Command line audio file player.
app_process
applypatch
Used to apply patches to android files.
arec
Command line audio recorder.
audioloop
bluetoothd
BlueTooth daemon
bmgr
Backup manager - type command by itself to get documentation.
bootanimation
Draws the boot animation. You may have to reset your phone to get out of this.
brcm_patchram_plus
bugreport
cat
Copy the contents of a file to standard output.
chmod
Change the mode of a file (e.g. whether it can be read or written.)
chown
Change the owner of a file.
cmp
Compare two files byte-by-byte
dalvikvm
The dalvik virtual machine. (Used to run Android applications.)
date
Prints the current date and time
dbus-daemon
dd
Convert and copy a file. By default copies standard in to standard out.
debuggerd
dexopt
df
Shows how much space is free on different file systems on your device.
dhcpcd
dmesg
dnsmasq
dumpstate
dumpsys
dvz
fsck_msdos
gdbserver
getevent
getprop
gzip
hciattach
hd
id
ifconfig
Shows the current configuration of network interfaces (IP, MAC address etc)
iftop
Shows the current processes using the network interfaces (top, but for networks)
ime
input
insmod
installd
ioctl
ionice
iptables
Manage the firewall
keystore
keystore_cli
kill
Send signals to processes.
linker
ln
Used to set up a file system link.
log
logcat
Prints the Android runtime log.
logwrapper
ls
Lists files.
lsmod
lsof
make_ext4fs
mediaserver
mkdir
Make a directory.
monkey
A program that sends random events, used to test applications. (Like having a monkey playing with the device.)
mount
mtpd
mv
Move a file from one directory to another. (Only on the same file system. Use "cat a > b" to copy a file between file systems.
nandread
ndc
netcfg
netd
netstat
newfs_msdos
notify
omx_tests
pand
ping
pm
pppd
printenv
ps
List active processes.
qemu-props
qemud
racoon
radiooptions
reboot
Reboot the device.
record
renice
rild
rm
Remove a file.
rmdir
Remove a directory.
rmmod
route
rtp_test
run-as
schedtest
schedtop
sdcard
sdptool
sendevent
service
servicemanager
setconsole
setprop
setup_fs
sh
showlease
sleep
smd
stagefright
start
Starts the Android runtime.
stop
Stops the Android runtime.
surfaceflinger
svc
sync
system_server
tc
testid3
toolbox
top
Shows which processes are currently using the most CPU time.
umount
uptime
Prints how long your device has been running since it was last booted.
vdc
vmstat
vold
watchprops
wipe
wpa_cli
wpa_supplicant
$ ls /system/xbin
add-property-tag
btool
check-lost+found
dexdump
dhdutil
hcidump
latencytop
librank
opcontrol
oprofiled
procmem
procrank
rawbu
scp
Secure copy program. (Used to copy files over the network.)
showmap
showslab
sqlite3
Used to administer SQLite databases.
strace
System trace command - use to see what system calls a program makes.
su
Start a shell with root privileges.
Versions of the Android Shell
Android 1.0 used a shell that had no tab completion or history editing.
Android 2.3 added history editing. You can for example use the up/down arrows to edit previous commands.

[TIP] Running random scripts at boot

HTC One M8.
I have done HTCdev with http://htc-one.wonderhowto.com/how-to/unlock-bootloader-root-your-htc-one-m8-0154444/
then rooted
Then did S-OFF with http://firewater-soff.com/instructions/
Then, to be able to write to /system, you need more steps. From recovery, it works easily. From live running system, the default stock ROM has a write protection. This WP can be removed easily by loading a kernel module:
http://forum.xda-developers.com/showthread.php?t=2701816
then:
Code:
insmod /mnt/sdcard/Download/wp_mod_m8.ko
mount -o remount,rw /system
Now, I want to run random scripts.
First, check if the stock ROM has the same issue as my previous phone:
Code:
[email protected]_m8:/ # echo $PATH
/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin:/vendor/bin
[email protected]_m8:/ # ls -l /system/sbin
/system/sbin: No such file or directory
1|[email protected]_m8:/ #
That's exactly what we need: a folder that is in PATH, that does not exist, and should exist somewhere we can create it. So:
Code:
insmod /mnt/sdcard/Download/wp_mod_m8.ko
mount -o remount,rw /system
mkdir /data/local/bin
ln -s /data/local/bin/ /system/sbin
Then, create two small scripts in there: do vi /system/sbin/vibrate , then i to enter insert mode, paste this code, esc, :x ... or use any other editor if you want:
Code:
#!/system/xbin/ash
#
i="$1"
[ "$i" = "" ] && i=400
# default value for voltage_level at boot is 3100 mV.
v=3100
vmin=1200
[ $i -gt 0 ] 2>/dev/null || {
echo "Invalid argument '$i'; should be a number below 9999 ms."
exit 1
}
[ $i -gt 9999 ] && {
echo "Invalid argument '$i'; should be below 9999 ms."
exit 1
}
[ "$2" != "" ] && {
[ $2 -ge $vmin ] 2>/dev/null || {
echo "Invalid argument '$2'; should be a number between $vmin and ${v} mV."
exit 1
}
[ $2 -gt $v ] && {
echo "Invalid argument '$2'; should be below ${v} mV."
exit 1
}
}
[ "$i" = "" ] && { echo "Provide argument: time in ms." ; exit 1 ; }
[ "$2" = "" ] && {
echo "Voltage not provided. Using default $v mV."
} || {
v=$2
echo "Using voltage argument $v mV."
}
echo $v > /sys/devices/virtual/timed_output/vibrator/voltage_level
echo "$i" > /sys/devices/virtual/timed_output/vibrator/enable
It was an old code for HTC Sensation; voltage does not work anymore, but duration does.
A shorter script for flash, and create the init script:
Code:
echo "echo 255 > /sys/class/leds/flashlight/brightness" > /system/sbin/flashlight
echo "#!/system/bin/sh
# /system/etc/init.qcom.bt.sh
/data/local/bin/vibrate
/system/sbin/flashlight" > /data/local/bin/rc.init
chmod 755 /system/sbin/flashlight
chmod 755 /system/sbin/vibrate
chmod 755 /data/local/bin/rc.init
Now, the tricky part: make the init script run at boot. Edit /system/etc/init.qcom.bt.sh , and below the comment, insert this line:
Code:
/data/local/bin/rc.init &
Reboot, enjoy Your phone flashes and vibrates at boot. As is, this is almost useless. But, now you can create system scripts, and run them at boot ... you can do pretty much anything you do on a classic Linux machine.
Because scripts are put in a folder which is in the default system $PATH, all apps or widgets can run those scripts. This vibrate script can be called for example from a Tasker or ScriptManager widget.
I love vibrate, because it's a small noise; I tail it to any command that is likely to take more than 1mn to run. Having a ScriptManager widget running it also helps me check the system load. If phone seems slow, and vibration does not happen at once, then the phone has big load, and I shall wait for things to settle.
/system/etc/init.qcom.bt.sh was not a random choice. To understand why: open /init.target.rc , and now, search a script that is run ... at boot ... with user root ... and that lays in /system. Many scripts are run as user, or stored outside /system.
Now, it's up to you to create scripts that are usefull to you.
Hey thanks for the directions but I tried running the command mnt/sdcard/download/wp_mod.ko and I got a "failed exec format error"....
rgolnazarian said:
Hey thanks for the directions but I tried running the command mnt/sdcard/download/wp_mod.ko and I got a "failed exec format error"....
Click to expand...
Click to collapse
This is completely unrelated and offtopic. Either your file does not have exec bit, or wrong interpreter, or stored on partition that has noexec.
Hmmm ... why is the quoted message different from the one I received by email ???
Any way, even if you are in /, "mnt/sdcard/download/wp_mod.ko" is an invalid command. A module can not be executed, it must be inserted; see tutos about it.

[GUIDE] Run Sickbeard/Transmission/sabnzbd/SSH/Samba/More on Shield

I've seen lots of people saying its not possible to make the shield an all in one solution for downloading, but after hours of tinkerering I've got a semi easy way of running the above services (and tons more) from the shield. This does requrie a bit of command line-fu , but I think I've got most of the hard work done. When its all said and done, we'll have a working entware-ng installation ( https://github.com/Entware-ng/Entware-ng)
--This guide is a work in progress, there are a few other items I'll add that will improve user experience, but as it stands now it should work as intended. I also haven't gotten a samba config to work yet, so if anyone can figure it out, let me know and I'll update a section on it
I've addapted this guide from a few github projects , but that likely means some commands/steps are actually useless on the shield:
https://github.com/erichlf/AndroidSeedBox
(will add other sources later)
AS ALWAYS MAKE A BACKUP OF DATA -- I AM NOT RESPONSIBLE IF YOUR DEVICE LOSES DATA (to my knowledge, there is no risk of bricking your device doing this, at worst a factory reset/reflash)
Pre-reqs:
Shield has to have ROOT
ADB set up on PC
Busybox : http://www.apkmirror.com/apk/jrummy-apps-inc/busybox-for-android/
Rom Toolbox Lite : Not on apk mirror, so side load from your favorite place
For this process, I recommend having your shield next to your computer, and share inputs with your monitor. You can do 90% of it from an ADB shell, but a few parts you will need to use a terminal on the shield itself, and keyboard is way easier than controller
Install Busybox on the shield, but use the oldest version available (I think the wget for 1.26 messes with the process)
run "ADB Shell" and run these commands on the shield (You can copy/paste multiple lines into the cmd window):
Code:
su
mount -o rw,remount /
ls /data/entware-ng >/dev/null 2>&1 || mkdir /data/entware-ng
cd .; ln -s /data/entware-ng /opt
ls /data/entware-ng/rootbin >/dev/null 2>&1 || mkdir /data/entware-ng/rootbin
cd .; ln -s /data/entware-ng/rootbin /bin
ls /data/entware-ng/rootlib >/dev/null 2>&1 || mkdir /data/entware-ng/rootlib
cd .; ln -s /data/entware-ng/rootlib /lib
ls /data/entware-ng/tmp >/dev/null 2>&1 || mkdir /data/entware-ng/tmp
cd .; ln -s /data/entware-ng/tmp /tmp
ls /data/entware-ng/home >/dev/null 2>&1 || mkdir /data/entware-ng/home
ls /data/entware-ng/home/root >/dev/null 2>&1 || mkdir /data/entware-ng/home/root
ls /data/entware-ng/home/user >/dev/null 2>&1 || mkdir /data/entware-ng/home/user
chmod 0755 /data/entware-ng/home/root
chown root.root /data/entware-ng/home/root
chmod 0755 /data/entware-ng/home/user
We've set up our staging area, and created a new home directory.
Now lets install Entware
Code:
ls /data/entware-ng/bin >/dev/null 2>&1 || mkdir /data/entware-ng/bin
ls /data/entware-ng/lib >/dev/null 2>&1 || mkdir /data/entware-ng/lib
ln -s /system/bin/sh /bin/sh
wget http://pkg.entware.net/binaries/armv7/installer/entware_install.sh -O /data/entware-ng/entware_install.sh
sh /data/entware-ng/entware_install.sh
Now lets install a new Busybox and Wget
Code:
/opt/bin/opkg install busybox
/opt/bin/opkg install wget
cd /bin; ln -s /data/entware-ng/bin/busybox sh
cd /bin; ln -s /data/entware-ng/bin/busybox echo
cd /bin; ln -s /data/entware-ng/bin/busybox rm
cd /bin; ln -s /data/entware-ng/bin/busybox rmdir
cd /bin; ln -s /data/entware-ng/bin/busybox sed
cd /bin; ln -s /data/entware-ng/bin/busybox mkdir
cd /bin; ln -s /data/entware-ng/bin/busybox head
cd /bin; ln -s /data/entware-ng/bin/busybox sort
cd /bin; ln -s /data/entware-ng/bin/busybox dirname
cd /bin; ln -s /data/entware-ng/bin/busybox ln
cd /bin; ln -s /data/entware-ng/bin/busybox mv
cd /bin; ln -s /data/entware-ng/bin/busybox cat
cd /bin; ln -s /data/entware-ng/bin/busybox chown
cd /bin; ln -s /data/entware-ng/bin/busybox chmod
cd /bin; ln -s /data/entware-ng/bin/busybox pgrep
This next step may be optional. Sets up resolv.conf (which may already exist, I'm not sure) and mtab (I don't know what this is)
Code:
echo nameserver 8.8.8.8 >/data/entware-ng/etc/resolv.conf
ls /etc >/dev/null 2>&1 || mkdir /etc
mount -o rw,remount /system
ls /etc/resolv.conf >/dev/null 2>&1 && rm /etc/resolv.conf
cd .; ln -s /data/entware-ng/etc/resolv.conf /etc/resolv.conf
cd .; ln -s /proc/mounts /etc/mtab
Create Passwd file
Code:
echo root:x:0:0:root:/opt/home/root:/bin/sh >/data/entware-ng/etc/passwd
echo shell:x:2000:2000:shell:/opt/home/user:/bin/sh >>/data/entware-ng/etc/passwd
cd .; ln -s /data/entware-ng/etc/passwd /etc/passwd
echo root:x:0:root >/data/entware-ng/etc/group
echo shell:x:2000:shell >>/data/entware-ng/etc/group
cd .; ln -s /data/entware-ng/etc/group /etc/group
echo /bin/sh > /etc/shells
echo PATH=/usr/bin:/usr/sbin:/bin:/sbin:/system/sbin:/system/bin:/system/xbin:/system/xbin/bb:/data/local/bin > /etc/profile
echo export PATH >> /etc/profile
OPTIONAL: If you want to use Open SSH with password instead of certs you can do the following step. I have done this, and haven't noticed any issues, but it may lessen the security of Root
Code:
/data/entware-ng/bin/busybox passwd root
Now let's create a script that will initialize Entware-ng after reboot
Code:
echo \#\!/system/bin/sh > /data/entware-ng/entware-init.sh
echo mount -o rw,remount rootfs / >> /data/entware-ng/entware-init.sh
echo ln -s /data/entware-ng /opt >> /data/entware-ng/entware-init.sh
echo ln -s /data/entware-ng/rootlinb /lib >> /data/entware-ng/entware-init.sh
echo ln -s /data/entware-ng/rootbin /bin >> /data/entware-ng/entware-init.sh
echo ln -s /data/entware-ng/tmp /tmp >> /data/entware-ng/entware-init.sh
echo ln -s /system/bin/sh /bin/sh >> /data/entware-ng/entware-init.sh
echo mount -o ro,remount rootfs >> /data/entware-ng/entware-init.sh
chmod 755 /data/entware-ng/entware-init.sh
Now lets create a start script that calls the initialize script we just made, but also returns a shell in the new environment
Code:
echo \#\!/system/bin/sh > /data/entware-ng/start.sh
echo ls '/opt >/dev/null 2>&1 ||' su -c /data/entware-ng/entware-init.sh >> /data/entware-ng/start.sh
echo export PATH=/opt/sbin:/opt/bin:/opt/rootbin:/opt/local/bin:/system/bin >> /data/entware-ng/start.sh
echo if busybox test $(busybox id -u) = 0; then HOME=/opt/home/root; else HOME=/opt/home/user; fi >> /data/entware-ng/start.sh
echo export HOME >> /data/entware-ng/start.sh
echo '/opt/etc/init.d/rc.unslung start' >> /data/entware-ng/start.sh
echo /bin/sh >> /data/entware-ng/start.sh
chmod 755 /data/entware-ng/start.sh
Now, lets install different services. These are optional, and there are tons more, but this will get transmission/sickbeard/ssh going
Code:
PATH=/data/entware-ng/bin:/bin /data/entware-ng/bin/opkg install vim
PATH=/data/entware-ng/bin:/bin /data/entware-ng/bin/opkg install samba36-server
PATH=/data/entware-ng/bin:/bin /data/entware-ng/bin/opkg install transmission-web transmission-daemon-openssl
PATH=/data/entware-ng/bin:/bin /data/entware-ng/bin/opkg install python
PATH=/data/entware-ng/bin:/bin /data/entware-ng/bin/opkg install python-setuptools
PATH=/data/entware-ng/bin:/bin /data/entware-ng/bin/opkg install python-pip
PATH=/data/entware-ng/bin:/bin /data/entware-ng/bin/opkg install python-cheetah
PATH=/data/entware-ng/bin:/bin /data/entware-ng/bin/opkg install openssh-server
Copy the start.sh and sysinit to the home environment
Code:
cp /data/entware-ng/start.sh /data/entware-ng/home/root/start.sh
cp /data/entware-ng/start.sh /data/entware-ng/home/root/sysinit
chown root.root /data/entware-ng/home/root/start.sh
chmod 755 /data/entware-ng/home/root/start.sh
chown root.root /data/entware-ng/home/root/sysinit
chmod 755 /data/entware-ng/home/root/sysinit
mount -o ro,remount /
mount -o ro,remount /system
Start the new environment
Code:
sh /data/entware-ng/home/root/sysinit
SICKBEARD CONIG
Install a few pre-reqs for sickbeard
Code:
pip install transmissionrpc
pip install cherrypy
Create a file in init.d to allow sickbeard to start on reboot. Please note, you will need to change the path to where your sickbeard directory is.
I'm not going to cover setting up sickbeard, there are other guides for that. I did find that it couldn't be bound to 0.0.0.0 , or local host, it needed to be hard coded for the shields IP, so I recommend setting it up as a static IP in your router.
Code:
echo PATH=/opt/bin:/opt/sbin:$PATH > /opt/etc/init.d/S96sickbeard
echo /opt/bin/python <YOUR PATH TO>/SickBeard.py -d --port 8081 >> /opt/etc/init.d/S96sickbeard
chmod 755 /opt/etc/init.d/S96sickbeard
chmod +x /opt/etc/init.d/S96sickbeard
OPENSSH CONFIG
OPTIONAL - If you want to use SSH we need to generate keys
Code:
ssh-keygen -f /opt/etc/ssh/ssh_host_rsa_key -N '' -t rsa
ssh-keygen -f /opt/etc/ssh/ssh_host_dsa_key -N '' -t dsa
ssh-keygen -f /opt/etc/ssh/ssh_host_ecdsa_key -N '' -t ecdsa -b 521
Now you'll have to get on the shield and use a terminal emulator to edit your sshd_config file. Here's a copy of mine, but I do not promise how secure it is.
Code:
# $OpenBSD: sshd_config,v 1.99 2016/07/11 03:19:44 tedu Exp $
# This is the sshd server system-wide configuration file. See
# sshd_config(5) for more information.
# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin:/opt/bin
# The strategy used for options in the default sshd_config shipped with
# OpenSSH is to specify options with their default value where
# possible, but leave them commented. Uncommented options override the
# default value.
Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
# The default requires explicit activation of protocol 1
Protocol 2
# HostKey for protocol version 1
#HostKey /opt/etc/ssh/ssh_host_key
#HostKeys for protocol version 2
HostKey /opt/etc/ssh/ssh_host_rsa_key
HostKey /opt/etc/ssh/ssh_host_dsa_key
HostKey /opt/etc/ssh/ssh_host_ecdsa_key
#HostKey /opt/etc/ssh/ssh_host_ed25519_key
# Lifetime and size of ephemeral version 1 server key
#KeyRegenerationInterval 1h
#ServerKeyBits 1024
# Ciphers and keying
#RekeyLimit default none
# Logging
#SyslogFacility AUTH
#LogLevel INFO
# Authentication:
#LoginGraceTime 2m
PermitRootLogin yes
StrictModes no
#MaxAuthTries 6
#MaxSessions 10
#RSAAuthentication yes
#PubkeyAuthentication yes
# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
AuthorizedKeysFile .ssh/authorized_keys
#AuthorizedPrincipalsFile none
#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody
# For this to work you will also need host keys in /opt/etc/ssh/ssh_known_hosts
#RhostsRSAAuthentication no
# similar for protocol version 2
#HostbasedAuthentication no
# Change to yes if you don't trust ~/.ssh/known_hosts for
# RhostsRSAAuthentication and HostbasedAuthentication
#IgnoreUserKnownHosts no
# Don't read the user's ~/.rhosts and ~/.shosts files
#IgnoreRhosts yes
# To disable tunneled clear text passwords, change to no here!
PasswordAuthentication yes
PermitEmptyPasswords yes
# Change to no to disable s/key passwords
#ChallengeResponseAuthentication yes
# Kerberos options
#KerberosAuthentication no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes
#KerberosGetAFSToken no
# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes
# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the ChallengeResponseAuthentication and
# PasswordAuthentication. Depending on your PAM configuration,
# PAM authentication via ChallengeResponseAuthentication may bypass
# the setting of "PermitRootLogin without-password".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and ChallengeResponseAuthentication to 'no'.
#UsePAM no
#AllowAgentForwarding yes
#AllowTcpForwarding yes
#GatewayPorts no
#X11Forwarding no
#X11DisplayOffset 10
#X11UseLocalhost yes
#PermitTTY yes
#PrintMotd yes
#PrintLastLog yes
#TCPKeepAlive yes
#UseLogin no
UsePrivilegeSeparation no
#PermitUserEnvironment no
#Compression delayed
#ClientAliveInterval 0
#ClientAliveCountMax 3
#UseDNS no
#PidFile /opt/var/run/sshd.pid
#MaxStartups 10:30:100
#PermitTunnel no
#ChrootDirectory none
#VersionAddendum none
# no default banner path
#Banner none
# enable DSCP QoS values (per RFC-4594)
#IPQoS AF21 AF11
# override default of no subsystems
Subsystem sftp /opt/lib/sftp-server
# Example of overriding settings on a per-user basis
#Match User anoncvs
# X11Forwarding no
# AllowTcpForwarding no
# PermitTTY no
# ForceCommand cvs server
To edit this, on the shield (Rom Toolbox Lite has a terminal emulator) run
Code:
su
cd /opt
sh ./start.sh
cd /opt/etc/ssh
vi ./sshd_config
TRANSMISSION
You'll have to configure your transmission settings, but they're located
/opt/etc/transmission/settings.json
Persist after reboot / Start Transmission/SSH/Sickbeard on boot
On the shield, open Rom Toolbox lite, and go down to "Scripter"
Import the sysinit script located /opt/home/root/sysinit and set the script to run at boot as root
Reboot and you should be able to connect via SSH, and have
Why do we need the passwd and group file ? Won't we use android's UID/GID ?
Can this method somehow be used to create custom group where we could put android's UID ?
I don't know why that step is needed, I got it from the guide I listed, and it worked without any apparent issues, so I left it in. My guess is openssh wants it to be there, but I'm not sure
So after you run all that is there a Interface for Sickbeard etc?
ahoslc said:
So after you run all that is there a Interface for Sickbeard etc?
Click to expand...
Click to collapse
It would be running on <shield IP>:8081 which you could access from the shield, or any other device on your network. Transmission would be :9091
Thanks for this. I'm trying to get python3-pip, acd_cli, and encfs installed on my Shield TV so I can mount my Amazon Cloud Drive and decrypt files for use with Plex. I have this set up on my NAS but it is too weak to do transcoding. I did set up the NAS as a middleman and mounted shares from it on the Shield TV, and while it does work, the extra step is really slow.
edit: I managed to get acd_cli working but I cannot mount my Amazon Cloud Drive share, I get I/O errors when I try to cd into it. Wonder if there's a kernel issue.
psycho_asylum said:
Thanks for this. I'm trying to get python3-pip, acd_cli, and encfs installed on my Shield TV so I can mount my Amazon Cloud Drive and decrypt files for use with Plex. I have this set up on my NAS but it is too weak to do transcoding. I did set up the NAS as a middleman and mounted shares from it on the Shield TV, and while it does work, the extra step is really slow.
edit: I managed to get acd_cli working but I cannot mount my Amazon Cloud Drive share, I get I/O errors when I try to cd into it. Wonder if there's a kernel issue.
Click to expand...
Click to collapse
So I was able to get this working https://github.com/dsoprea/GDriveFS and could cd into my google drive (But couldn't get Plex to see any files in the directory)
Soooo, even if you do get it working, its possible Plex won't be able to see it
Edit-- Did you install fuse-utils ?
chasx003 said:
Edit-- Did you install fuse-utils ?
Click to expand...
Click to collapse
Not specifically. I would think it would have been listed as a dependency and automatically installed, libfuse was though. I ended up factory restoring my Shield after I botched something, so now I'm just at 5.1 stock using the built-in Samba for now.
which version of busybox works? I am having trouble with wget and I tried v1.21.0
chasx003 said:
I've seen lots of people saying its not possible to make the shield an all in one solution for downloading, but after hours of tinkerering I've got a semi easy way of running the above services (and tons more) from the shield. This does requrie a bit of command line-fu , but I think I've got most of the hard work done. When its all said and done, we'll have a working entware-ng installation ( https://github.com/Entware-ng/Entware-ng)
[..]
FIRST
Now let's create a script that will initialize Entware-ng after reboot
Code:
echo \#\!/system/bin/sh > /data/entware-ng/entware-init.sh
echo mount -o rw,remount rootfs / >> /data/entware-ng/entware-init.sh
echo ln -s /data/entware-ng /opt >> /data/entware-ng/entware-init.sh
echo ln -s /data/entware-ng/rootlinb /lib >> /data/entware-ng/entware-init.sh
echo ln -s /data/entware-ng/rootbin /bin >> /data/entware-ng/entware-init.sh
echo ln -s /data/entware-ng/tmp /tmp >> /data/entware-ng/entware-init.sh
echo ln -s /system/bin/sh /bin/sh >> /data/entware-ng/entware-init.sh
echo mount -o ro,remount rootfs >> /data/entware-ng/entware-init.sh
chmod 755 /data/entware-ng/entware-init.sh
[..]
SECOND
Now lets create a start script that calls the initialize script we just made, but also returns a shell in the new environment
Code:
echo \#\!/system/bin/sh > /data/entware-ng/start.sh
echo ls '/opt >/dev/null 2>&1 ||' su -c /data/entware-ng/entware-init.sh >> /data/entware-ng/start.sh
echo export PATH=/opt/sbin:/opt/bin:/opt/rootbin:/opt/local/bin:/system/bin >> /data/entware-ng/start.sh
echo if busybox test $(busybox id -u) = 0; then HOME=/opt/home/root; else HOME=/opt/home/user; fi >> /data/entware-ng/start.sh
echo export HOME >> /data/entware-ng/start.sh
echo 'for file in /data/opt/etc/init.d/*' >> /data/entware-ng/start.sh
echo do >> /data/entware-ng/start.sh
echo ' $file start' >> /data/entware-ng/start.sh
echo done >> /data/entware-ng/start.sh
echo /bin/sh >> /data/entware-ng/start.sh
chmod 755 /data/entware-ng/start.sh
[..]
THIRD
Copy the start.sh and sysinit to the home environment
Code:
chown root.root /data/entware-ng/home/root/start.sh
chmod 755 /data/entware-ng/home/root/start.sh
chown root.root /data/entware-ng/home/root/sysinit
chmod 755 /data/entware-ng/home/root/sysinit
mount -o ro,remount /
mount -o ro,remount /system
[..]
FOURTH
Start the new environment
Code:
sh /data/opt/home/root/sysinit
Reboot and you should be able to connect via SSH, and have
Click to expand...
Click to collapse
In my quote there has to be something missing between the "first" and "second" steps and the "third" one.. are the two files we've just made the missing files in the home/root directory? Or where they supposed to come from somewhere else?
Also the "fourth" step, there are no /data/opt directory in my installation.
MartiniGM said:
In my quote there has to be something missing between the "first" and "second" steps and the "third" one.. are the two files we've just made the missing files in the home/root directory? Or where they supposed to come from somewhere else?
Also the "fourth" step, there are no /data/opt directory in my installation.
Click to expand...
Click to collapse
Ah! Sorry for not replying until now, life has been busy.
You are correct, there are some typos / things out of order! I'm going to go through this and fix it and will update the OP
I've taken this guide and installed rTorrent (due to superior web client and RSS capability). If anyone needs help on that, I can chime in.
Great tuto !
Working fine one Nvidia Shield TV 2017 with latest update (whithout reboot)
But after reboot I lost /opt and /bin on root :-O
mkdir /opt working fine after mount -o rw,remount /
but if i reboot it disappear
any idea ?
android.stackexchange.com said:
(root) directory is not a persistent filesystem on Android. It's a initramfs, which is packed into the boot image on your device. Although you can remount it with write permissions, changes will always be lost the next time you boot because the original ramdisk will be re-extracted from the boot image on the next boot.
Click to expand...
Click to collapse
So we need to :
$ mkboot boot.img /output-folder
$ cd /output-folder
$ gunzip -c ramdisk | cpio -i
... make some changes in the ramdisk and possibly /output-folder/img_info ...
$ find . | cpio -o -H newc | gzip > newramdisk.cpio.gz
$ cd ..
$ mkboot /output-folder newboot.img
If you're rooted, a better solution is to simply install in a chroot, either using debootstrap or other; you can obtain a nearly complete Linux system this way (init in a chroot is weird, stuff like openssh will still have to be started separately after boot, either manually or by an app/script).
If you're not rooted, you can use proot for simple path redirection; this is how termux installs arch on unrooted devices.
Using either option (chroot, proot) requires having binary files that aren't in a noexec partition; generally this means private app storage, not sdcard that's accessible to other apps. If you're building a chroot, you should be able to include the external/public storage folder in it, however a chroot also requires the partition not be mounted with nodev option.
***Note that I don't actually have a shield TV*** (I'm just interested in getting one) so I can't say if the shield's storage is mounted noexec, but the android data partition generally is. I can, however, verify that the process in general works on Android, however, as I've got two tablets running Lineage/Nougat with chroots, and a stock Moto G6 with archlinux arm in proot. To check for partitions mounted as nodev or noexec, run `mount|TERM=xterm grep --color -E 'nodev|noexec'`. You might check to see if you can use /data/local instead of app's private storage.
For installing BusyBox, *should* be a `busybox --install -s [DIR]` option that copies the binary to the destination, then symlinks applets. This should be simpler than symlinking a bunch of applets manually.
If you want a system-wide BusyBox I recommend stericson busybox: https://play.google.com/store/apps/details?id=stericson.busybox
For a terminal emulator on Android, I highly recommend termux, which is available on Google play and F-Droid. It has support for 256 color, styles, a package manager, Android integration (ie notifications from Linux scripts), boot scripts, widgets, etc: https://play.google.com/store/apps/details?id=com.termux
Another alternative: you can set up user-mode Linux for something closer to virtualization, but I have yet to see any UML binaries for use with Android; this would also make it difficult to run networking and to access files from outside the guest, but will provide a full working system with init support, and would not require root to set up and run--however, I think UML networking requires root and/or kernel support, though, and UML generally requires a disk image much like other virtualization tools. Qemu might be workable instead of UML with fewer issues.
Note that all of these solutions are still running under an Android app, and as such are subject to the android task killer. Not sure if there's any way around this without having something run directly by Android's own init system.
Efreak2004 said:
If you're rooted, a better solution is to simply install in a chroot, either using debootstrap or other; you can obtain a nearly complete Linux system this way (init in a chroot is weird, stuff like openssh will still have to be started separately after boot, either manually or by an app/script).
If you're not rooted, you can use proot for simple path redirection; this is how termux installs arch on unrooted devices.
Using either option (chroot, proot) requires having binary files that aren't in a noexec partition; generally this means private app storage, not sdcard that's accessible to other apps. If you're building a chroot, you should be able to include the external/public storage folder in it, however a chroot also requires the partition not be mounted with nodev option.
***Note that I don't actually have a shield TV*** (I'm just interested in getting one) so I can't say if the shield's storage is mounted noexec, but the android data partition generally is. I can, however, verify that the process in general works on Android, however, as I've got two tablets running Lineage/Nougat with chroots, and a stock Moto G6 with archlinux arm in proot. To check for partitions mounted as nodev or noexec, run `mount|TERM=xterm grep --color -E 'nodev|noexec'`. You might check to see if you can use /data/local instead of app's private storage.
For installing BusyBox, *should* be a `busybox --install -s [DIR]` option that copies the binary to the destination, then symlinks applets. This should be simpler than symlinking a bunch of applets manually.
If you want a system-wide BusyBox I recommend stericson busybox: https://play.google.com/store/apps/details?id=stericson.busybox
For a terminal emulator on Android, I highly recommend termux, which is available on Google play and F-Droid. It has support for 256 color, styles, a package manager, Android integration (ie notifications from Linux scripts), boot scripts, widgets, etc: https://play.google.com/store/apps/details?id=com.termux
Another alternative: you can set up user-mode Linux for something closer to virtualization, but I have yet to see any UML binaries for use with Android; this would also make it difficult to run networking and to access files from outside the guest, but will provide a full working system with init support, and would not require root to set up and run--however, I think UML networking requires root and/or kernel support, though, and UML generally requires a disk image much like other virtualization tools. Qemu might be workable instead of UML with fewer issues.
Note that all of these solutions are still running under an Android app, and as such are subject to the android task killer. Not sure if there's any way around this without having something run directly by Android's own init system.
Click to expand...
Click to collapse
Using chroot isn`t good solution. Emulators not effective too.
Stericon`s busybox is paid, meefik`s busybox is free and has more utils.
Termux is heavy, I use Teeminal Emulator: https://f-droid.org/app/jackpal.androidterm
You be able to install a lot of lightweight linux utils by installing entware-ng. For example, git pkg has 300 Mb size in termux and 15 Mb in entware.
Entware has a lot of conmon with optware and openwrt
this is a wonderful guide I'm surprised more people don't use it great job!

Categories

Resources