There have been multiple accounts created with the sole purpose of posting advertisement posts or replies containing unsolicited advertising.

Accounts which solely post advertisements, or persistently post them may be terminated.

butitsnotme

@[email protected]

This profile is from a federated server and may be incomplete. Browse more on the original instance.

Help with deployment

Hello nerds! I’m hosting a lot of things on my home lab using docker compose. I have a private repo in GitHub for the config files. This is working fine for me, but every time I want to make a change I have to push the changes, then ssh to the lab, pull the changes, and run docker compose up. This is of course working fine,...

butitsnotme ,

For no 1, that shouldn’t be dind, the container would be controlling the host docker, wouldn’t it?

If so, keep in mind that this is the same as giving root SSH access to the host machine.

As far as security goes, anything that allows GitHub to cause your server to download (pull) and use a set of arbitrary of Docker images with arbitrary configuration is remote code execution. It doesn’t really matter what you to secure access to the machine, if someone compromises your GitHub account.

I would probably set up SSH with a key dedicated to GitHub, specifically for deploying. If SSH is configured to only allow keys for access, it’s not much of a security risk to open it up to the internet. I would then configure that key to only be able to run a single command, which I would make a very simple bash script which runs git fetch, and then git verify-commit origin/main (or whatever branch you deploy), befor checking out the latest commit on that branch.

You can sign commits fairly easily using SSH keys now, which combined with the above allows you to store your data on GitHub without having to trust them to have RCE on your host.

butitsnotme ,

My recommendation would be to utilize LVM. Set up a PV on the new drive and create an LV filling the drive (wit an FS), then move all the data off of one drive onto this new drive, reformat the first old drive as a second PV in the volume group, and expand the size of the LV. Repeat the process for the second old drive. Then, instead of extending the LV, set the parity option on the LV to 1. You can add further disks, increasing the LV size or adding parity or mirroring in the future, as needed. This also gives you the advantage that you can (once you have some free space) create another LV that has different mirroring or parity requirements.

butitsnotme ,

I use the first option, but with the addition of using an LVM snapshot to guarantee that the database (or anything else in the backup) isn’t changed while taking the backup.

butitsnotme ,

Getting a domain name may not be enough, if you don’t have a static IP you’ll still need a DDNS service.

What do you get for the paid no-ip service? Is it just a nice subdomain? You can get a custom domain and use a CNAME record to point one or more subdomains to a free DDNS subdomain.

butitsnotme ,

If you want to change the name of the directory without breaking your volumes (or running services, etc), you can specify the name of the project inside the compose file

butitsnotme ,

TiddlyWiki might be a good option. Technically it’s a wiki, but it is a single HTML page with all functionality built in JavaScript, you could host it on GH pages, though you wouldn’t be able to use its save feature there (you would have to save to your local machine and the deploy a new version). It stores text in little (or large) cards which can be given a title, tags and other metadata, and it providesa full search system.

butitsnotme ,

It saves into the tiddlywiki HTML file. The default behaviour is to then trigger the browser to download the file. You can absolutely store it in a git repository.

butitsnotme ,

I use WireGaurd, it’s set to on demand for any network or cellular data (so effectively always on), no DNS records (I just use public DNS providing private range IP addresses). It doesn’t make any sort of dent in my battery life. Also, only the wiregaurd network traffic is routed through it, so if my server is down the phone/laptop’s internet continues to work. I borrowed my wife’s phone and laptop for 15 minutes to set it up, and now no one has to think about it.

butitsnotme ,

The peer range shouldn’t be your LAN, it should be a new network range, just for WireGaurd. Make sure that the server running Immich is part of the WireGaurd network.

My phone and laptop see three networks: the internet, the lan (192.168.1.0/24, typically) and WireGaurd (10.30.0.0/16). I can anonymize and share my WireGaurd config if that would help.

butitsnotme ,

Here are a few more details of my setup:

Components:

  • server
  • clients (phone/laptop)
  • domain name (we’ll call it custom.domain)
  • home router
  • dynamic DNS provider

The home router has WireGuard port forwarded to server, with no re-mapping (I’m using the default 51820). It’s also providing DHCP services to my home network, using the 192.168.1.0/24 network.

The server is running the dynamic DNS client (keeping the dynamic domain name updated to my public IP), and I have a CNAME record on the vpn.custom.domain pointing to the dynamic DNS name (which is an awful random string of characters). I also have server.custom.domain with an A record pointing to 10.30.0.1. All my DNS records are in public DNS (so no need to change the DNS settings on the computer or phone or use DNS overrides with WireGuard.)

Immich config:


<span style="color:#63a35c;">version</span><span style="color:#323232;">: </span><span style="color:#183691;">"3.8"
</span><span style="color:#323232;">
</span><span style="color:#63a35c;">services</span><span style="color:#323232;">:
</span><span style="color:#323232;">  </span><span style="color:#63a35c;">immich-server</span><span style="color:#323232;">:
</span><span style="color:#323232;">    </span><span style="color:#63a35c;">container_name</span><span style="color:#323232;">: </span><span style="color:#183691;">immich_server
</span><span style="color:#323232;">    </span><span style="color:#63a35c;">image</span><span style="color:#323232;">: </span><span style="color:#183691;">ghcr.io/immich-app/immich-server:release
</span><span style="color:#323232;">    </span><span style="color:#63a35c;">entrypoint</span><span style="color:#323232;">: [</span><span style="color:#183691;">"/bin/sh"</span><span style="color:#323232;">, </span><span style="color:#183691;">"./start-server.sh"</span><span style="color:#323232;">]
</span><span style="color:#323232;">    </span><span style="color:#63a35c;">volumes</span><span style="color:#323232;">:
</span><span style="color:#323232;">      - </span><span style="color:#183691;">${UPLOAD_LOCATION}:/usr/src/app/upload
</span><span style="color:#323232;">    </span><span style="color:#63a35c;">env_file</span><span style="color:#323232;">:
</span><span style="color:#323232;">      - </span><span style="color:#183691;">.env
</span><span style="color:#323232;">    </span><span style="color:#63a35c;">ports</span><span style="color:#323232;">:
</span><span style="color:#323232;">      - </span><span style="color:#63a35c;">target</span><span style="color:#323232;">: </span><span style="color:#0086b3;">3001
</span><span style="color:#323232;">        </span><span style="color:#63a35c;">published</span><span style="color:#323232;">: </span><span style="color:#0086b3;">2283
</span><span style="color:#323232;">        </span><span style="color:#63a35c;">host_ip</span><span style="color:#323232;">: </span><span style="color:#0086b3;">10.30.0.1
</span><span style="color:#323232;">    </span><span style="color:#63a35c;">depends_on</span><span style="color:#323232;">:
</span><span style="color:#323232;">      - </span><span style="color:#183691;">redis
</span><span style="color:#323232;">      - </span><span style="color:#183691;">database
</span><span style="color:#323232;">    </span><span style="color:#63a35c;">restart</span><span style="color:#323232;">: </span><span style="color:#183691;">always
</span><span style="color:#323232;">    </span><span style="color:#63a35c;">networks</span><span style="color:#323232;">:
</span><span style="color:#323232;">      - </span><span style="color:#183691;">immich
</span>

WireGuard is configured using wg-quick (/etc/wireguard/wg0.conf):


<span style="color:#323232;">[Interface]
</span><span style="color:#323232;">Address = 10.30.0.1/16
</span><span style="color:#323232;">PrivateKey = <server-private-key>
</span><span style="color:#323232;">ListenPort = 51820
</span><span style="color:#323232;">
</span><span style="color:#323232;">[Peer]
</span><span style="color:#323232;">PublicKey = <phone-public-key>
</span><span style="color:#323232;">AllowedIPs = 10.30.0.12/32
</span><span style="color:#323232;">
</span><span style="color:#323232;">[Peer]
</span><span style="color:#323232;">PublicKey = <laptop-public-key>
</span><span style="color:#323232;">AllowedIPs = 10.30.0.11/32
</span>

Start WireGuard with systemctl enable --now wg-quick@wg0.

Phone WireGuard configuration (iOS):


<span style="color:#323232;">[Interface]
</span><span style="color:#323232;">Name = vpn.custom.domain
</span><span style="color:#323232;">
</span><span style="color:#323232;">Private Key = <phone private key>
</span><span style="color:#323232;">Public Key = <phone public key>
</span><span style="color:#323232;">
</span><span style="color:#323232;">Addresses = 10.30.0.12/32
</span><span style="color:#323232;">Listen port = <blank>
</span><span style="color:#323232;">MTU = <blank>
</span><span style="color:#323232;">DNS servers = <blank>
</span><span style="color:#323232;">
</span><span style="color:#323232;">[Peer]
</span><span style="color:#323232;">Public Key = <server public key>
</span><span style="color:#323232;">Pre-shared key = <blank>
</span><span style="color:#323232;">Endpoint = vpn.custom.domain:51820
</span><span style="color:#323232;">Allowed IPs = 10.30.0.0/16
</span><span style="color:#323232;">Persistent Keepalive = 25
</span><span style="color:#323232;">
</span><span style="color:#323232;">[On Demand Activation]
</span><span style="color:#323232;">Cellular = On
</span><span style="color:#323232;">Wi-Fi = On
</span><span style="color:#323232;">SSIDs = Any SSID
</span>

This connection is then left always enabled, and comes on whenever my phone has any kind of network connection.

My laptop (running Linux), is also using wg-quick (/etc/wireguard/wg0.conf):


<span style="color:#323232;">[Interface]
</span><span style="color:#323232;">Address = 10.30.0.14
</span><span style="color:#323232;">PrivateKey = <laptop private key>
</span><span style="color:#323232;">
</span><span style="color:#323232;">[Peer]
</span><span style="color:#323232;">PublicKey = <server-public-key>
</span><span style="color:#323232;">Endpoint = vpn.custom.domain:51820
</span><span style="color:#323232;">AllowedIPs = 10.30.0.0/16
</span>

My wife’s window’s laptop is configured using the official WireGuard windows app, with similar settings.

No matter where we are (at home, on a WiFi hotspot, or using cellular data) we access Immich over the VPN: server.custom.comain:2283.

Let me know if you have any further questions.

butitsnotme ,

If you’re seeing an OOM killer messsage note that it doesn’t necessarily kill the problem process, by default the kernel hands out memory upon requestt, regardless of whether it has ram to back the allocation. When a process then writes to the memory (at some later time) and the kernel determines that there is no physical ram to store that write, it then invokes OOM Killer. This then selects a process and kills it. MySQL (and MariaDB) use large quantities of ram for cache, and by default the kernel lies about how much is available, so they often end up using more than the system can handle.

If you have many databases in containers, set memory limits for those containers, that should make all the databases play nicer together. Additionally , you may want to disable overcommit in the kernel, this will cause the kernel to return out of memory to a process attempting to allocate ram and stop lying about free ram to processes that ask, often greatly increasing stability.

butitsnotme ,

I backup to a external hard disk that I keep in a fireproof and water resistant safe at home. Each service has its own LVM volume which I snapshot and then backup the snapshots with borg, all into one repository. The backup is triggered by a udev rule so it happens automatically when I plug the drive in; the backup script uses ntfy.sh (running locally) to let me know when it is finished so I can put the drive back in the safe. I can share the script later, if anyone is interested.

butitsnotme ,

I followed the guide found here, however with a few modifications.

Notably, I did not encrypt the borg repository, and heavily modified the backup script.


<span style="font-style:italic;color:#969896;">#!/bin/bash -ue
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;"># The udev rule is not terribly accurate and may trigger our service before
</span><span style="font-style:italic;color:#969896;"># the kernel has finished probing partitions. Sleep for a bit to ensure
</span><span style="font-style:italic;color:#969896;"># the kernel is done.
</span><span style="font-style:italic;color:#969896;">#
</span><span style="font-style:italic;color:#969896;"># This can be avoided by using a more precise udev rule, e.g. matching
</span><span style="font-style:italic;color:#969896;"># a specific hardware path and partition.
</span><span style="color:#323232;">sleep 5
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;">#
</span><span style="font-style:italic;color:#969896;"># Script configuration
</span><span style="font-style:italic;color:#969896;">#
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;"># The backup partition is mounted there
</span><span style="color:#323232;">MOUNTPOINT</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">/mnt/external
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;"># This is the location of the Borg repository
</span><span style="color:#323232;">TARGET</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">$</span><span style="color:#323232;">MOUNTPOINT</span><span style="color:#183691;">/backups/backups.borg
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;"># Archive name schema
</span><span style="color:#323232;">DATE</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">$(</span><span style="color:#323232;">date </span><span style="color:#183691;">'+%Y-%m-%d-%H-%M-%S')-$(</span><span style="color:#323232;">hostname</span><span style="color:#183691;">)
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;"># This is the file that will later contain UUIDs of registered backup drives
</span><span style="color:#323232;">DISKS</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">/etc/backups/backup.disk
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;"># Find whether the connected block device is a backup drive
</span><span style="font-weight:bold;color:#a71d5d;">for</span><span style="color:#323232;"> uuid </span><span style="font-weight:bold;color:#a71d5d;">in </span><span style="color:#323232;">$(lsblk --noheadings --list --output uuid)
</span><span style="font-weight:bold;color:#a71d5d;">do
</span><span style="color:#323232;">        </span><span style="font-weight:bold;color:#a71d5d;">if </span><span style="color:#323232;">grep --quiet --fixed-strings $uuid $DISKS</span><span style="font-weight:bold;color:#a71d5d;">; then
</span><span style="color:#323232;">                </span><span style="font-weight:bold;color:#a71d5d;">break
</span><span style="color:#323232;">        </span><span style="font-weight:bold;color:#a71d5d;">fi
</span><span style="color:#323232;">        uuid</span><span style="font-weight:bold;color:#a71d5d;">=
</span><span style="font-weight:bold;color:#a71d5d;">done
</span><span style="color:#323232;">
</span><span style="font-weight:bold;color:#a71d5d;">if </span><span style="color:#62a35c;">[ </span><span style="font-weight:bold;color:#a71d5d;">! </span><span style="color:#323232;">$uuid </span><span style="color:#62a35c;">]</span><span style="font-weight:bold;color:#a71d5d;">; then
</span><span style="color:#323232;">        </span><span style="color:#62a35c;">echo </span><span style="color:#183691;">"No backup disk found, exiting"
</span><span style="color:#323232;">        </span><span style="color:#62a35c;">exit</span><span style="color:#323232;"> 0
</span><span style="font-weight:bold;color:#a71d5d;">fi
</span><span style="color:#323232;">
</span><span style="color:#62a35c;">echo </span><span style="color:#183691;">"Disk $</span><span style="color:#323232;">uuid</span><span style="color:#183691;"> is a backup disk"
</span><span style="color:#323232;">partition_path</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">/dev/disk/by-uuid/$</span><span style="color:#323232;">uuid
</span><span style="font-style:italic;color:#969896;"># Mount file system if not already done. This assumes that if something is already
</span><span style="font-style:italic;color:#969896;"># mounted at $MOUNTPOINT, it is the backup drive. It won't find the drive if
</span><span style="font-style:italic;color:#969896;"># it was mounted somewhere else.
</span><span style="color:#323232;">(mount </span><span style="font-weight:bold;color:#a71d5d;">| </span><span style="color:#323232;">grep $MOUNTPOINT) </span><span style="font-weight:bold;color:#a71d5d;">|| </span><span style="color:#323232;">mount $partition_path $MOUNTPOINT
</span><span style="color:#323232;">drive</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">$(</span><span style="color:#323232;">lsblk --inverse --noheadings --list --paths --output</span><span style="color:#183691;"> name $</span><span style="color:#323232;">partition_path </span><span style="font-weight:bold;color:#a71d5d;">| </span><span style="color:#323232;">head --lines</span><span style="color:#183691;"> 1)
</span><span style="color:#62a35c;">echo </span><span style="color:#183691;">"Drive path: $</span><span style="color:#323232;">drive</span><span style="color:#183691;">"
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;"># Log Borg version
</span><span style="color:#323232;">borg --version
</span><span style="color:#323232;">
</span><span style="color:#62a35c;">echo </span><span style="color:#183691;">"Starting backup for $</span><span style="color:#323232;">DATE</span><span style="color:#183691;">"
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;"># Make sure all data is written before creating the snapshot
</span><span style="color:#323232;">sync
</span><span style="color:#323232;">
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;"># Options for borg create
</span><span style="color:#323232;">BORG_OPTS</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">"--stats --one-file-system --compression lz4 --checkpoint-interval 86400"
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;"># No one can answer if Borg asks these questions, it is better to just fail quickly
</span><span style="font-style:italic;color:#969896;"># instead of hanging.
</span><span style="font-weight:bold;color:#a71d5d;">export </span><span style="color:#323232;">BORG_RELOCATED_REPO_ACCESS_IS_OK</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">no
</span><span style="font-weight:bold;color:#a71d5d;">export </span><span style="color:#323232;">BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">no
</span><span style="color:#323232;">
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;">#
</span><span style="font-style:italic;color:#969896;"># Create backups
</span><span style="font-style:italic;color:#969896;">#
</span><span style="color:#323232;">
</span><span style="font-weight:bold;color:#a71d5d;">function </span><span style="font-weight:bold;color:#795da3;">backup </span><span style="color:#323232;">() {
</span><span style="color:#323232;">  </span><span style="font-weight:bold;color:#a71d5d;">local </span><span style="color:#323232;">DISK</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">"$</span><span style="color:#323232;">1</span><span style="color:#183691;">"
</span><span style="color:#323232;">  </span><span style="font-weight:bold;color:#a71d5d;">local </span><span style="color:#323232;">LABEL</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">"$</span><span style="color:#323232;">2</span><span style="color:#183691;">"
</span><span style="color:#323232;">  </span><span style="color:#62a35c;">shift</span><span style="color:#323232;"> 2
</span><span style="color:#323232;">
</span><span style="color:#323232;">  </span><span style="font-weight:bold;color:#a71d5d;">local </span><span style="color:#323232;">SNAPSHOT</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">"$</span><span style="color:#323232;">DISK</span><span style="color:#183691;">-snapshot"
</span><span style="color:#323232;">  </span><span style="font-weight:bold;color:#a71d5d;">local </span><span style="color:#323232;">SNAPSHOT_DIR</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">"/mnt/snapshot/$</span><span style="color:#323232;">DISK</span><span style="color:#183691;">"
</span><span style="color:#323232;">
</span><span style="color:#323232;">  </span><span style="font-weight:bold;color:#a71d5d;">local </span><span style="color:#323232;">DIRS</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">""
</span><span style="color:#323232;">  </span><span style="font-weight:bold;color:#a71d5d;">while </span><span style="color:#323232;">(( </span><span style="color:#183691;">"$</span><span style="color:#323232;">#</span><span style="color:#183691;">" </span><span style="color:#323232;">))</span><span style="font-weight:bold;color:#a71d5d;">; do
</span><span style="color:#323232;">    DIRS</span><span style="font-weight:bold;color:#a71d5d;">=</span><span style="color:#183691;">"$</span><span style="color:#323232;">DIRS </span><span style="color:#183691;">$</span><span style="color:#323232;">SNAPSHOT_DIR</span><span style="color:#183691;">/$</span><span style="color:#323232;">1</span><span style="color:#183691;">"
</span><span style="color:#323232;">    </span><span style="color:#62a35c;">shift
</span><span style="color:#323232;">  </span><span style="font-weight:bold;color:#a71d5d;">done
</span><span style="color:#323232;">
</span><span style="color:#323232;">  </span><span style="font-style:italic;color:#969896;"># Make and mount the snapshot volume
</span><span style="color:#323232;">  mkdir -p $SNAPSHOT_DIR
</span><span style="color:#323232;">  lvcreate --size 50G --snapshot --name $SNAPSHOT /dev/data/$DISK
</span><span style="color:#323232;">  mount /dev/data/$SNAPSHOT $SNAPSHOT_DIR
</span><span style="color:#323232;">
</span><span style="color:#323232;">  </span><span style="font-style:italic;color:#969896;"># Create the backup
</span><span style="color:#323232;">  borg create $BORG_OPTS $TARGET::$DATE-$DISK $DIRS
</span><span style="color:#323232;">
</span><span style="color:#323232;">
</span><span style="color:#323232;">  </span><span style="font-style:italic;color:#969896;"># Check the snapshot usage before removing it
</span><span style="color:#323232;">  lvs
</span><span style="color:#323232;">  umount $SNAPSHOT_DIR
</span><span style="color:#323232;">  lvremove --yes /dev/data/$SNAPSHOT
</span><span style="color:#323232;">}
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;"># usage: backup <lvm volume> <snapshot name> <list of folders to backup>
</span><span style="color:#323232;">backup photos immich immich
</span><span style="font-style:italic;color:#969896;"># Other backups listed here
</span><span style="color:#323232;">
</span><span style="color:#62a35c;">echo </span><span style="color:#183691;">"Completed backup for $</span><span style="color:#323232;">DATE</span><span style="color:#183691;">"
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;"># Just to be completely paranoid
</span><span style="color:#323232;">sync
</span><span style="color:#323232;">
</span><span style="font-weight:bold;color:#a71d5d;">if </span><span style="color:#62a35c;">[ </span><span style="color:#323232;">-f /etc/backups/autoeject </span><span style="color:#62a35c;">]</span><span style="font-weight:bold;color:#a71d5d;">; then
</span><span style="color:#323232;">        umount $MOUNTPOINT
</span><span style="color:#323232;">        udisksctl power-off -b $drive
</span><span style="font-weight:bold;color:#a71d5d;">fi
</span><span style="color:#323232;">
</span><span style="font-style:italic;color:#969896;"># Send a notification
</span><span style="color:#323232;">curl -H </span><span style="color:#183691;">'Title: Backup Complete'</span><span style="color:#323232;"> -d </span><span style="color:#183691;">"Server backup for $</span><span style="color:#323232;">DATE</span><span style="color:#183691;"> finished" 'http://10.30.0.1:28080/backups'
</span>

Most of my services are stored on individual LVM volumes, all mounted under /mnt, so immich is completely self-contained under /mnt/photos/immich/. The last line of my script sends a notification to my phone using ntfy.

butitsnotme ,

See my other reply here.

butitsnotme ,

See my other reply here.

butitsnotme ,

See my other reply here.

butitsnotme ,

Something that LVM supports but ZFS and BTRFS don’t, is the ability to reduce your storage. (That is, to empty and remove a drive from the array, without having to completely destroy the storage array.) As a home user without sufficient storage to have complete duplicates of everything, I find this an important feature.

butitsnotme ,

I did not know that, last I looked it was still in development, I believe.

butitsnotme ,

In Settings -> Accessibility-> Touch -> Assistive Touch you can configure a small button the floats on the screen (you can move it around), providing access to all sort of gestures and functions one handed.

  • All
  • Subscribed
  • Moderated
  • Favorites
  • random
  • lifeLocal
  • goranko
  • All magazines