Thursday, June 18, 2026

rewrite of restic backup script and adding email alert

Found that our restic backup script had stopped running from Apr 20th - the log files were dated as last modified on that date. This could have been due to 
(a) an update overwriting the restic binary with an older version of ubuntu's restic via apt, which did not work, giving "backend not supported" errors for Azure Storage
(b) an additional issue that our Azure Storage account had become unavailable at some point of time due to a lapsed subscription 

So, created a new Storage account in an active subscription in the same resource group, which defaulted to the same Central India region for the storage account as we wanted. Used the same name as the earlier script's storage account name, and created a Blob container also with the same name as used by the script. 

Due to the point (a) above, this was not sufficient to make the script work, even for it to initialize the storage with lines like
restic -r azure:our-data-bk:/ourdata init
Fatal: create repository at azure:our-data-bk:/ourdata failed: invalid backend

As suggested by Gemini, the solution was to uninstall restic via apt, and to then download the latest restic and copy it to /usr/local/bin

wget https://github.com/restic/restic/releases/download/v0.18.1/restic_0.18.1_linux_amd64.bz2
bzip2 -d restic_0.18.1_linux_amd64.bz2
chmod +x restic_0.18.1_linux_amd64
sudo mv restic_0.18.1_linux_amd64 /usr/local/bin/restic

sudo apt remove restic

Then,
restic version
-bash: /usr/bin/restic: No such file or directory

We needed to tell bash to refresh the location - 
hash -r

Then the restic version showed the correct version, and the init also was successful. 

I wanted to get emails on future failures instead of the script failing silently, so Gemini helped with this script, which I have modified to change the passwords etc

#!/bin/bash
#This will run Restic backups from cron.
export RESTIC_PASSWORD=ourpw
export AZURE_ACCOUNT_NAME=ourname
#export AZURE_ACCOUNT_SAS="sv=2022-11-02&ss=bfqt&srt=c&sp=not_used_this_time%3D"
export AZURE_ACCOUNT_KEY="mVthisisthekey99999wPlEA=="
# not using rclone+gdrive due to slow,timeouts,rate-limits
#RCLONE_CONFIG=/home/user/.config/rclone/rclone.conf
#create new repo
# https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#microsoft-azure-blob-storage
#restic -r azure:our-data-bk:/ourdata init
# take backups
/usr/local/bin/restic  -r azure:sssvv-data-bk:/1data  --verbose backup  /var/www/1_data_disk/1_data/filedir > /home/user/1resticlog.txt 2>&1
/usr/local/bin/restic  -r azure:sssvv-data-bk:/2data  --verbose backup  /var/www/1_data_disk/2_data/filedir > /home/user/2resticlog.txt 2>&1
/usr/local/bin/restic  -r azure:sssvv-data-bk:/3data  --verbose backup  /var/www1_data_disk/3_data/filedir > /home/user/3resticlog.txt 2>&1
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
    SUBJECT="SUCCESS: Weekly Restic Backup"
    MESSAGE="Your weekly restic backup completed successfully."
else
    SUBJECT="ALERT: Weekly Restic Backup FAILED"
    MESSAGE="WARNING: Your restic backup FAILED with exit code $EXIT_CODE. Please investigate immediately."
fi
mail -s "$SUBJECT" "my@email.org" << EOF
$MESSAGE

Here is the log output:
--------------------------------------------------
$(cat "/home/user/3resticlog.txt")
EOF



Wednesday, June 17, 2026

Updating expiring Azure Linux Virtual Machine Secure Boot 2011 certificates

Microsoft Azure's email notification asked us to update the secure boot certificates before the end of the month, and pointed us to verification, and if necessary updating, steps. The "vendor recommended" documentation for Ubuntu support was a bit contradictory - saying that rollout had been paused - so took the help of ChatGPT and Gemini for completing the process. First took up a non-critical VM, completed that, and then went on to the others.

sudo snap install fwupd
sudo fwupdmgr refresh
sudo fwupdmgr update
#(say yes, yes, and yes to reboot)

Gemini reassured that the devices listed with "no updates" are not a concern, we should only check whether the mokutil tests below work OK.

As per the verification link above, 
Tested with
mokutil --db | grep "2023"
            Not Before: Jun 13 19:21:47 2023 GMT
        Subject: C=US, O=Microsoft Corporation, CN=Microsoft UEFI CA 2023
mokutil --kek | grep "2023"
            Not Before: Mar  2 20:21:35 2023 GMT
        Subject: C=US, O=Microsoft Corporation, CN=Microsoft Corporation KEK 2K CA 2023

Removed the fwupd snap, and also removed snap itself to prevent bloat
sudo snap remove fwupd
(and remove snap itself on ELS)
snap list
(if nothing other than core, core20, lxd, or snapd, can remove)

sudo systemctl disable --now snapd.service snapd.socket
sudo apt-get purge -y snapd
sudo rm -rf /snap /var/snap /var/lib/snapd /var/cache/snapd /usr/lib/snapd

Found that the L VM was already up-to-date since it was a newer VM, created in 2025.

SDev2 had to be updated in the same manner as for the HAPROXY VM above.

SSS web server also had to be updated in the same manner.

On the AWS VM, I see

mokutil --sb-state
EFI variables are not supported on this system

Gemini says,

You do not need to do anything for this AWS VM. You are completely in the clear.
Seeing EFI variables are not supported on this system means that this specific EC2 instance is not using UEFI Secure Boot at all. In fact, it is likely booting using Legacy BIOS rather than UEFI.


With that, all the VMs seem to be accounted for.

Tuesday, June 16, 2026

Samsung phone Gboard voice typing icon vanished

At some point of time, after some upgrades / updates, the voice-typing mic icon vanished, for the Gboard keyboard on my Samsung M34 5G phone. 

There were lots of contradictory (and perhaps out-dated) info online on how to re-enable voice-typing, but what worked for me was as follows. Gboard was already set as the default keyboard, and not Samsung keyboard in the settings. 

When Gboard was visible, the path to set this was,
the icon to show more items - 
then the Gboard settings icon,

 followed by voice-typing

and finally slide to enable "Use voice typing" as below.

Then the mic icon at the top right corner of Gboard becomes visible.


Sunday, June 07, 2026

income tax filing has become simplified

Income tax filing for us in India has now become further simplified. Bank interest and salary income are automatically being pre-filled via the AIS, so only capital gains if any, from sale of mutual fund units, needs to be entered by us. No need to keep track of any other deductions, since we've now moved to the new tax regime. Also, no need to report gift from parents / sister. So, my workflow was:

1. Download AIS from the income tax site
2. Check the salary component in AIS against the Form16 given to us from the office
3. Verify that the AIS contains the interest statements from all three banks in which I have accounts
4. Download Capital Gains statement from MFCentral 
5. Go through ITR2, entering only minor info (like "secondary address is same as primary address") and the capital gains schedules (which have also become simplified for me since most of the current redemptions are of assets purchased after 2018 and can be entered just as single consolidated figures).

Saturday, June 06, 2026

in-place upgrade Ubuntu 22.04 web server to Ubuntu 24.04

One of our web servers was running Ubuntu 22.04 and showed that an upgrade was available via 'do-release-upgrade'

I took the plunge, running the upgrade via screen in case our connection broke. (The upgrade process also auto-starts sshd on port 1022 also, in case the upgrade needs recovery. But thankfully, I didn't need it.)

Chose the default options every time for retaining the configuration files. Noticed that apache showed a "syntax error" in config files.

After the restart, checked apache status with

sudo systemctl status apache2
× apache2.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/apache2.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Sat 2026-06-06 04:18:46 UTC; 1min 46s ago
       Docs: https://httpd.apache.org/docs/2.4/
    Process: 797 ExecStart=/usr/sbin/apachectl start (code=exited, status=1/FAILURE)
        CPU: 28ms
Jun 06 04:18:45 sssihms-web-vm2023 systemd[1]: Starting apache2.service - The Apache HTTP Server...
Jun 06 04:18:45 sssihms-web-vm2023 apachectl[821]: apache2: Syntax error on line 146 of /etc/apache2/apache2.conf: Syntax error on line 3 of /etc/apache2/mods-enabled/php8.1.load:>
Jun 06 04:18:46 sssihms-web-vm2023 systemd[1]: apache2.service: Control process exited, code=exited, status=1/FAILURE
Jun 06 04:18:46 sssihms-web-vm2023 systemd[1]: apache2.service: Failed with result 'exit-code'.
Jun 06 04:18:46 sssihms-web-vm2023 systemd[1]: Failed to start apache2.service - The Apache HTTP Server.

Claude pointed out that Ubuntu 24.04 has php8.3, so loading php8.1 would fail. So, 

sudo a2dismod php8.1

# Install php8.3
sudo apt install php8.3 libapache2-mod-php8.3
# this had already been installed during the upgrade

# Enable the new module
sudo a2enmod php8.3
sudo systemctl restart apache2

All good. Wordpress is also running fine.

Monday, June 01, 2026

lobby Raspberry Pi not booting

We had multi-day issues booting the Raspberry Pi in the lobby, which would play a video loop. Disconnecting the USB power and re-connecting it, or taking out the micro-sdcard, checking it for errors on Linux Mint and then re-inserting it would make it work.

Suspecting power supply issues, the current idea is to
1. add a separate switch to turn on power to the Pi after the TVs are powered on.
2. change the power cord as well as the USB power adapter

With these two changes, the Pi booted up without trouble today, we would probably need to monitor it for a week or so to be sure that this has solved the problem.

Friday, May 22, 2026

visuals on planetarium dome getting washed out

Copy-pasting from an email thread - 

Just a follow-up on the issues with washed-out visuals we were facing.

When slowly fading in particular scenes, we can clearly see a point where parts of the scene get washed out, with the projector suddenly jumping in brightness. Looks like this projector, Optoma ZK-507-W, has some poorly executed "high-brightness" handling, which we don't seem to be able to disable. As long as we keep large areas below 50% brightness, small bright areas like stars, trails etc can be viewed with good contrast. 

I think it is some sort of local dimming similar to what is used in some TVs, https://www.academia.edu/88311604/Backlight_local_dimming_algorithm_for_high_contrast_LCD_TV but with very poor results.

Perhaps a warning line can be added at https://paulbourke.net/dome/faq/#projectors for this projector, indicating its poor performance.

To which Paul replied,

I  assume you have tried

1. Turning off the DynamicBlack settings in Display ->  Image setting -> Brightness mode menu.

2. Turning off the HDR/HLG in Display -> Image settings -> Dynamic range menu.

Yes for the 2nd, there was no such menu for the first point. Since my source is not HDR, I also tried "Auto" in addition to "Off".

Also, have tried all the different modes, including "Film" - even that has the noticeable brightness jump once a large enough area has a pixel value high enough (perhaps >50%). 

(Interestingly, some planetarium shows have more problems than others.

Eg. Unseen Earth, https://www.unseenearth.eu/ - has some drone shots etc which look terrible on our dome if I don't mask out most of it to <50%

Mars - the Ultimate Voyage - https://www.bellmuseum.umn.edu/mars-ultimate-voyage/ - has gamma settings, lighting etc such that most of the show looks pretty good on our dome.

In our own productions using OpenSpace, https://www.openspaceproject.com/ , there seems to be a difference between scenes rendered on Mac and scenes rendered on PC (Linux). Maybe gamma differences.

These micro-organisms are rendered fine, while I had to darken these scenes rendered on Mac for Jupiter, Saturn and Europa to not become completely washed out.

Interestingly, this scene, rendered on Linux, is fine.)

Wednesday, May 20, 2026

OpenSpace builds on Github actions getting "terminated" - exit code 143

Some of my build github action workflows were consistently getting terminated after 10 minutes of run time or so. 

gmake[2]: *** Waiting for unfinished jobs....
gmake[2]: *** wait: No child processes.  Stop.
gmake[2]: *** [ext/spice/CMakeFiles/spice.dir/build.make:3607: ext/spice/CMakeFiles/spice.dir/src/common/dafopw_c.c.o] Terminated
gmake[2]: *** [ext/spice/CMakeFiles/spice.dir/build.make:3635: ext/spice/CMakeFiles/spice.dir/src/common/dafps_c.c.o] Terminated
gmake[2]: *** [ext/spice/CMakeFiles/spice.dir/build.make:3663: ext/spice/CMakeFiles/spice.dir/src/common/dafrcr.c.o] Terminated
gmake[2]: *** [ext/spice/CMakeFiles/spice.dir/build.make:3649: ext/spice/CMakeFiles/spice.dir/src/common/dafra.c.o] Terminated
gmake[2]: *** [ext/spice/CMakeFiles/spice.dir/build.make:3691: ext/spice/CMakeFiles/spice.dir/src/common/dafrda_c.c.o] Terminated
gmake[2]: *** [ext/spice/CMakeFiles/spice.dir/build.make:3565: ext/spice/CMakeFiles/spice.dir/src/common/dafgsr_c.c.o] Terminated
gmake[2]: *** [modules/globebrowsing/ext/geos/CMakeFiles/geos.dir/build.make:2514: modules/globebrowsing/ext/geos/CMakeFiles/geos.dir/src/io/StringTokenizer.cpp.o] Terminated
Error: Process completed with exit code 143.

Gemini - via aistudio.google.com free plan - pointed out that 

Exit code 143 indicates that a process was terminated by a SIGTERM signal. In GitHub Actions, this almost always happens when the build runner runs out of memory (OOM) and the operating system or the GitHub runner agent terminates the compiler processes to save the system.
Compiling large C++ projects like OpenSpace with heavy submodules (such as Assimp/Draco, Boost, and Spicelib) using GCC 13 can easily exceed the 7 GB RAM limit of the standard GitHub-hosted runner, especially when building in parallel.
Here are the two most effective ways to resolve this:
1. Limit Build Parallelism
By default, cmake --build . --parallel attempts to use all available virtual CPUs. While GitHub runners have 2 vCPUs, compiling templates in parallel can still exceed 7 GB of RAM.
Limit the concurrent build jobs to 2 etc etc

So I changed 
 cmake --build . --parallel
to
 cmake --build . --parallel 3

like the workflow which was working, and that solved the problem. Told Gemini that, and the reply was,

You are correct. Standard GitHub-hosted runners for public repositories were updated to 4 vCPUs and 16 GB of RAM.
When you run cmake --build . --parallel without specifying a limit, it attempts to spawn 4 compilation jobs. With heavy C++ headers and templates, 4 concurrent GCC processes can still spike past the 16 GB memory limit, triggering the termination.
Limiting the build to --parallel 3 (or --parallel 2 if memory usage remains high) is a solid approach to keep the peak memory consumption within the runner's limits.

Then I asked Gemini,
Same codebase, same runner.
cmake --build . --parallel causes termination.
make -j $(nproc) does not cause termination, why?

Then Gemini changed the answer to specify that parallel without a number means unlimited parallel jobs - 

The difference lies in how GNU Make and CMake interpret the omission of a job
number.

1. cmake --build . --parallel maps to unlimited jobs

When you run cmake --build . --parallel without specifying a number, CMake
translates this to the underlying build tool's default parallel flag.

For GNU Make, this translates to running make -j (with no number). In GNU Make,
omitting the number instructs it to run with infinite/unlimited parallel jobs.

Instead of spawning 4 compiler processes (the number of CPU cores), it will try
to compile dozens of source files simultaneously. This immediately exhausts
the 16 GB of RAM and triggers the OOM killer.

2. make -j $(nproc) strictly limits the jobs

When you run make -j $(nproc):

1.  $(nproc) evaluates to 4.
2.  The command becomes make -j 4.
3.  GNU Make is strictly limited to running a maximum of 4 compiler processes at
    any given time, which usually fits within the 16 GB RAM limit.

Solution

If you want to use the CMake CLI wrapper safely, you should explicitly pass the
CPU count to it, similar to what you did with make:

cmake --build . --parallel $(nproc)




Saturday, May 16, 2026

Moodle course order - manual sorting option vanished

One of our Moodle instances had the manual sorting option for courses disappear, at server/course/management.php

ChatGPT gave some hallucinatory suggestions, Gemini via aistudio.google.com gave the first suggestion of removing pagination by using the dropdown to show all courses instead of only the first 20 - the number of courses had become 21. Even after doing this, the sorting option did not return. But once I added a dummy category and some dummy courses and then deleted them, the sorting option returned. Gemini said, "When you created the dummy category and dummy courses, and then deleted them,
you triggered a core Moodle background script called fix_course_sortorder()." So that has fixed it.

Reading up on this function, it looks like for very early versions, this function was also buggy - https://moodle.org/mod/forum/discuss.php?d=126896 - but in current versions, this seems to be rewritten, like in the current main branch.

Tuesday, May 12, 2026

msmtp instead of mail

Noticed only today that for some scripts on our internal server, the mail command was commented out and replaced by msmtp like 

#mail -s "Automatic Encoded file Statistics for : date" -t $MAIL_COPY $MAIL_DESTINATION < $MAIL_FILE 

{ echo "To: $MAIL_DESTINATION"; echo "Cc: $MAIL_COPY"; echo "Subject: Automatic Encoded file Statistics: $(date)"; echo; cat "$MAIL_FILE"; } | /usr/bin/msmtp -t

ChatGPT explains,

Yes, on many modern Linux systems, using msmtp directly is now preferred over the traditional mail command, especially for scripts and automated systems.

The reason is that the mail command historically depended on a local Mail Transfer Agent (MTA) such as:

Sendmail
Postfix
Exim

Many newer minimal/server/container installations no longer install or configure a full local MTA.

This was mentioned in this previous post, which mentions a replacement of ssmtp with msmtp. 

Monday, May 11, 2026

UTM and the mouse

When switching between machines using a KVM switch, found that Ubuntu 24.04 running on UTM on Mac, full screen, would ask, "Allow UTM to use the mouse" or something like that. We should choose cancel instead of Yes. If we choose Yes, the mouse cursor would not be seen, though mouse-clicks would be registered correctly on Ubuntu 24.04. If we choose cancel, the mouse would work properly.

Saturday, April 25, 2026

two or three ways to make LLMs help with Blender scene creation

If we directly ask Gemini to create a Blender file, it says it can't, but it can generate a python script which can be run inside Blender to create a scene. This is useful if we need to generate repetitive visuals or something which can be programmatically generated. Example, CirclesVideos.blend - the prompt for Gemini 3, via the free tier of aistudio.google.com, is below.

Can you now generate a python script to create a blender scene with

  1. Fisheye 180 degree equidistant camera in the centre, pointing straight up

  2. equidistant circular screens subtending just less than 40 degrees so that they do not touch, having a small gap between them, 9 circular screens nearest the equator, and more rows of similarly sized circular screens, but fewer as we go up the hemisphere since the circumference of higher latitudes would be lower

  3. The lowest row of circular screens must be completely above the horizon for the viewport of the 180 degree fisheye equidistant camera

  4. The screens should fade in one by one starting from the lowest row, left to right, sequentially, one after the other, with a one second fade in time. So, circle one fades in by the 00:00:01 mark, then circle two fades in and finishes fading in by the 00:00:02 mark, and so on. The total duration of the animation can be one second greater than the time required to fade in all the circles.

  5. Each circular screen should have an image texture mapped to a unique file - named seq01.jpg for circle one, seq02.jpg for circle two and so on, with the jpg textures located in a subdirectory of the project directory called seq. The image texture would be square in shape, but should be zoomed in so that the entire circle is filled with the texture.


This resulted in the following:

Here is the complete Python script to generate your exact fulldome scene.

It handles the math for perfectly spacing the rows along the hemisphere without
overlapping, scales the circular cutouts to exactly 38 degrees (leaving
a 2-degree gap so they don't touch), sets up the sequential 1-second fade-ins,
and auto-links your seq/seqXX.jpg files.

How to use it:

1.  Open a new Blender file.
2.  Go to the Scripting tab.
3.  Click New, paste the code below, and click the Run Script (Play) button.
4.  Set your 3D Viewport to Rendered View to see the fade-in math and image
    textures working perfectly.

import bpy
import math

# ==========================================
# 1. CLEANUP PREVIOUS SCENE
# ==========================================
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

# ==========================================
# 2. SCENE & ANIMATION SETTINGS
# ==========================================
FPS = 24
TOTAL_SCREENS = 14
FADE_DURATION = FPS * 1 # 1 second fade in per screen

scene = bpy.context.scene
scene.render.engine = 'CYCLES'
scene.render.resolution_x = 2048
scene.render.resolution_y = 2048
scene.render.fps = FPS

# Total duration = 1 second per screen, plus 1 second at the end
TOTAL_FRAMES = (TOTAL_SCREENS * FADE_DURATION) + FADE_DURATION
scene.frame_start = 1
scene.frame_end = TOTAL_FRAMES

# ==========================================
# 3. FULLDOME CAMERA (Equidistant)
# ==========================================
cam_data = bpy.data.cameras.new("Fulldome_Camera")
cam_data.type = 'PANO'
cam_data.panorama_type = 'FISHEYE_EQUIDISTANT'
cam_data.fisheye_fov = math.radians(180)

cam_obj = bpy.data.objects.new("Fulldome_Camera", cam_data)
bpy.context.collection.objects.link(cam_obj)
scene.camera = cam_obj

# Camera at center (0,0,0) pointing straight UP (Zenith)
cam_obj.location = (0, 0, 0)
cam_obj.rotation_euler = (math.radians(180), 0, 0)

# Dark Background
world = scene.world
world.use_nodes = True
bg_node = world.node_tree.nodes.get("Background")
if bg_node:
    bg_node.inputs[0].default_value = (0.01, 0.01, 0.02, 1)

# ==========================================
# 4. MATH: SCREEN PLACEMENTS
# ==========================================
# Dome Radius
R = 10.0

# To subtend exactly 38 degrees (leaving a gap under 40deg),
# the physical radius of the plane at distance R is R * tan(19 degrees)
screen_radius = R * math.tan(math.radians(19))

# Calculate positions (Elevation and Azimuth)
# Row 1: 9 screens at 20 degrees elevation. 
# (Bottom edge is 20 - 19 = 1 degree above horizon. Perfect!)
# Row 2: 5 screens at 60 degrees elevation.
# (Bottom edge is 60 - 19 = 41 degrees. Top of Row 1 is 39. Gap of 2 degrees!)

screen_data = []

# Row 1 (9 Screens, lowest row)
for i in range(9):
    azimuth = i * (360.0 / 9.0)
    screen_data.append({'el': 20.0, 'az': azimuth})

# Row 2 (5 Screens, upper row)
# Offset azimuth slightly so they interlock beautifully
for i in range(5):
    azimuth = i * (360.0 / 5.0) + 36.0 
    screen_data.append({'el': 60.0, 'az': azimuth})

# ==========================================
# 5. MATERIAL GENERATOR
# ==========================================
def create_fade_circle_material(index, start_frame, end_frame):
    mat_name = f"Mat_Screen_{index:02d}"
    mat = bpy.data.materials.new(name=mat_name)
    mat.use_nodes = True
    mat.blend_method = 'CLIP' # Helps viewport transparency
    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    nodes.clear()

    # Base Nodes
    output = nodes.new(type='ShaderNodeOutputMaterial')
    output.location = (1000, 0)

    # Fade Setup (Mix Shader 2)
    fade_mix = nodes.new(type='ShaderNodeMixShader')
    fade_mix.location = (800, 0)
    
    fade_trans = nodes.new(type='ShaderNodeBsdfTransparent')
    fade_trans.location = (600, 100)

    # Cutout Setup (Mix Shader 1)
    cutout_mix = nodes.new(type='ShaderNodeMixShader')
    cutout_mix.location = (600, -100)
    
    cutout_trans = nodes.new(type='ShaderNodeBsdfTransparent')
    cutout_trans.location = (400, 0)

    # Video/Image Setup
    emission = nodes.new(type='ShaderNodeEmission')
    emission.location = (400, -200)
    emission.inputs['Strength'].default_value = 1.5

    img_tex = nodes.new(type='ShaderNodeTexImage')
    img_tex.location = (100, -200)
    # Assuming relative path in a "seq" folder next to your .blend file
    img_path = f"//seq/seq{index:02d}.jpg"
    
    # Try to load the image if it exists, otherwise leave placeholder
    try:
        img = bpy.data.images.load(img_path)
        img_tex.image = img
    except:
        print(f"Warning: Could not load {img_path}. Placeholder created.")

    # Math for the Circle Mask
    tex_coord = nodes.new(type='ShaderNodeTexCoord')
    tex_coord.location = (-300, 0)

    distance = nodes.new(type='ShaderNodeVectorMath')
    distance.operation = 'DISTANCE'
    distance.location = (-100, 0)
    distance.inputs[1].default_value = (0.5, 0.5, 0.0)

    less_than = nodes.new(type='ShaderNodeMath')
    less_than.operation = 'LESS_THAN'
    less_than.location = (100, 0)
    less_than.inputs[1].default_value = 0.5

    # Wire it all up
    links.new(tex_coord.outputs['UV'], distance.inputs[0])
    links.new(distance.outputs['Value'], less_than.inputs[0])
    
    links.new(tex_coord.outputs['UV'], img_tex.inputs['Vector'])
    links.new(img_tex.outputs['Color'], emission.inputs['Color'])

    links.new(less_than.outputs['Value'], cutout_mix.inputs['Fac'])
    links.new(cutout_trans.outputs['BSDF'], cutout_mix.inputs[1])
    links.new(emission.outputs['Emission'], cutout_mix.inputs[2])

    links.new(fade_trans.outputs['BSDF'], fade_mix.inputs[1])
    links.new(cutout_mix.outputs['Shader'], fade_mix.inputs[2])
    
    links.new(fade_mix.outputs['Shader'], output.inputs['Surface'])

    # --- ANIMATE THE FADE ---
    # At start of animation: completely transparent
    fade_mix.inputs['Fac'].default_value = 0.0
    fade_mix.inputs['Fac'].keyframe_insert(data_path="default_value", frame=1)
    
    # Just before its turn: still transparent
    fade_mix.inputs['Fac'].default_value = 0.0
    fade_mix.inputs['Fac'].keyframe_insert(data_path="default_value", frame=start_frame)

    # 1 second later: fully visible
    fade_mix.inputs['Fac'].default_value = 1.0
    fade_mix.inputs['Fac'].keyframe_insert(data_path="default_value", frame=end_frame)

    return mat

# ==========================================
# 6. SPAWN SCREENS
# ==========================================
for i, data in enumerate(screen_data):
    el_rad = math.radians(data['el'])
    az_rad = math.radians(data['az'])
    
    # Convert polar to Cartesian XYZ
    z = R * math.sin(el_rad)
    r_xy = R * math.cos(el_rad)
    x = r_xy * math.cos(az_rad)
    y = r_xy * math.sin(az_rad)
    
    # 1. Create the Plane
    bpy.ops.mesh.primitive_plane_add(size=1.0, location=(x, y, z))
    plane = bpy.context.active_object
    
    # Index is i+1 (so it starts at 1)
    idx = i + 1
    plane.name = f"Screen_{idx:02d}"
    
    # 2. Scale to exact angular size (38 degrees)
    # We multiply by 2 because screen_radius is from the center to edge.
    plane.scale = (screen_radius * 2, screen_radius * 2, 1.0)
    
    # 3. Add Track To Constraint (Always face camera)
    track = plane.constraints.new(type='TRACK_TO')
    track.target = cam_obj
    track.track_axis = 'TRACK_Z'
    track.up_axis = 'UP_Y'
    
    # 4. Create and assign animated material
    start_f = (i * FADE_DURATION) + 1
    end_f = start_f + FADE_DURATION
    
    mat = create_fade_circle_material(idx, start_f, end_f)
    plane.data.materials.append(mat)

print(f"Fulldome scene generated! Total screens: {TOTAL_SCREENS}. Animation frames: {TOTAL_FRAMES}")



LLMs can even give us alternate ideas. For example, this conversation, 

Prompt for Gemini,

I'd like to show a montage of videos in Blender to be rendered for fulldome (180
degree fisheye equisolid camera). The script is "We constantly seek happiness in
the form of pleasure, peace, or the satisfaction of curiosity." My current idea
is to depict a pyramid with video textures on the four sides depicting
"pleasure, peace, or the satisfaction of curiosity." Any other ideas?

Which resulted in this reply - 

Designing for a fulldome (180° fisheye) is a completely different beast than
designing for a flat screen.

Your pyramid idea is conceptually strong, but in a fulldome environment, a
single object floating in the center of the screen can sometimes feel a bit
"small," leaving the vast periphery of the dome empty. In fulldome, you want to
surround the viewer or give them a sense of scale and immersion.

Here are a few alternative concepts specifically tailored to make the most of
the 180° fisheye camera in Blender, incorporating your video montages and
script.

1. The Canopy of Spheres (Focus on "Looking Up")

  - The Visual: The camera sits at the base of a massive, glowing, abstract tree
    or DNA-like structure that stretches up to the "zenith" (the dead center of
    your fisheye circle, which is the top of the dome). Instead of leaves,
    giant, slow-moving translucent bubbles or glass orbs hang above the viewer.
  - The Montage: Inside these orbs are your videos.
      - Pleasure videos can be in warm, rapidly pulsing orange/red orbs.
      - Peace videos in slow-spinning, tranquil blue/teal orbs.
      - Curiosity videos in geometric, bright white/purple orbs.
  - Why it works for Fulldome: Audiences in planetariums naturally look up. By
    placing the anchor of the structure at the bottom edge of the fisheye (the
    springline) and having it branch out overhead, you create a massive sense of
    scale and immersion.

2. The Monolith Portals (Surrounding the Viewer)

  - The Visual: Instead of looking at a pyramid from the outside, place the
    camera inside a massive, open space (like a serene desert, a starry void, or
    a calm ocean). Standing out of the ground, surrounding the camera in
    a 360-degree radius (or semi-circle), are three monolithic, towering
    screens/portals.
  - The Montage: As the narrator speaks, the camera slowly rotates.
      - "Pleasure" lights up the monolith to the left, reflecting its video off
        the glossy floor.
      - "Peace" lights up the center monolith.
      - "Curiosity" lights up the right monolith.
  - Why it works for Fulldome: It places the viewer inside the geometry rather
    than watching it from afar. Wide, towering objects that wrap around the
    peripheral vision (the edges of the fisheye circle) look incredibly imposing
    and immersive in a dome.

3. The Tunnel of Seeking (Forward Motion)

  - The Visual: "Constantly seek" implies forward momentum. The camera is moving
    slowly and continuously forward through a massive, abstract, hexagonal or
    circular tunnel.
  - The Montage: The walls of the tunnel are made of floating video panels.
      - As the word "Pleasure" is spoken, panels whiz by showing vibrant,
        sensory clips.
      - For "Peace," the tunnel widens, the movement slows slightly, and the
        panels show calming nature or meditative clips.
      - For "Curiosity," the tunnel breaks apart into a constellation of
        floating screens showing science, exploration, or complex data.
  - Why it works for Fulldome: Forward motion (called vection) is the single
    most powerful optical illusion in a fulldome. A slow, steady forward push
    through a tunnel makes the audience feel like they are physically moving
    through space.

4. The Fluid Kaleidoscope (Abstract Blending)

  - The Visual: Instead of harsh geometric shapes (like a pyramid), map your
    videos onto fluid, undulating mesh surfaces—like ribbons of silk or waves of
    water—that drift right over the camera lens.
  - The Montage:
      - A vibrant ribbon of video (Pleasure) swoops across the dome.
      - It is intercepted by a slow-moving, massive, calm wave of video (Peace)
        that washes over the whole screen.
      - Finally, a sharply focused, fractal-like geometric burst of video
        screens (Curiosity) shatters through the peaceful wave.
  - Why it works for Fulldome: Sharp edges and straight lines (like a pyramid)
    are heavily curved and distorted by fisheye lenses. Organic, fluid shapes
    hide this distortion and feel incredibly natural and dreamlike in a dome.

5. If You Stick With the Pyramid: The Interior Pyramid

If you really love the pyramid motif, flip the perspective. Place the camera
inside a colossal pyramid.

  - The Visual: The camera looks up toward the apex of the pyramid (which sits
    in the center of your fisheye render). The walls of the pyramid surround the
    viewer.
  - The Montage: The three visible interior walls of the pyramid become massive
    projection mapping surfaces. One wall illuminates with "Pleasure," the next
    with "Peace," and the third with "Curiosity." As the script concludes, the
    apex of the pyramid opens up, revealing a starry sky or a bright light
    (representing the overarching "Happiness").

💡 Quick Blender Fulldome Tips:

1.  Camera Setup: Set your camera to Panoramic -> Fisheye Equisolid. Field of
    View: 180 degrees. Render resolution must be perfectly square (e.g., 4096
    x 4096).
2.  The Sweet Spot: In a dome, the audience is usually looking about 30 to 45
    degrees up from the bottom edge. Don't put your most important video exactly
    in the dead center (zenith), as it forces the audience to crane their necks
    straight up.
3.  Avoid Fast Camera Pans: Panning left or right in a dome causes severe motion
    sickness. Keep camera movements to slow forward/backward tracking, or very
    slow rotations. Let the objects (the video panels) move around the camera
    instead.

Tuesday, April 21, 2026

false alarm emails from github pages

We got three emails from github pages in March and April, saying Please verify your Pages domain (ACTION NEEDED) - "We are unable to find a verification TXT record  .... Please verify your domain within 7 days by following our instructions before it becomes unverified."

Found in each case by going to the github settings page for gh-pages, that the domain was verified OK, and the verification txt record was also fine. 

Since this has only happened for a domain which has DNS operated by Microsoft - Azure DNS - I guess the errors were DNS failures from Azure.

Friday, April 17, 2026

Blender 5 VSE difference for extending clips

 Blender 5 Video Sequence Editor (VSE) handles extension of clips differently than earlier versions. In earlier versions, if we extended a clip past its end by dragging its boundary, it would create still frames from the last frame of the clip, and if we used a speed control modifier, we could stretch clips both to extend them as well as to make them shorter. With Blender 5, we can't do that directly. Now, we need to go to clip properties, increase duration of the clip there. Just using speed control is no longer enough. Via  https://blender.stackexchange.com/questions/69679/extend-video-strip-in-vse

Sunday, April 05, 2026

can't have a show all apps button on old Realme phone

There was no way to install a button to "show all apps" like on my Samsung M34, on the old Realme phone: in settings -> home screen, there is an option to show drawer instead of home screen icons - all the apps are listed in the drawer. Chose that instead.

Saturday, April 04, 2026

how to remove sim from moto phone

This video helped me - https://www.youtube.com/watch?v=Ddb-PMm58Oo

We need to use the lever / slider mechanism on the sim tray to slide the sim out, since we can't get a grip on the sim in any other way.



Thursday, April 02, 2026

Wordpress website giving critical error

One of our wordpress websites was giving a critical error, but only when trying to edit a particular page. A few days later, the admin for that site fixed it, probably taking help from some LLM etc - 

I enabled safe mode and also edited the configuration wp-config.php file, 

define('WP_MEMORY_LIMIT', '512M');
define('WP_MAX_MEMORY_LIMIT', '512M');

This seems to have solved the problem for now.

Logged in, saw that Wordpress and some plugins and themes needed to be updated, and updated them too. After updating, when I tried clicking on Edit for the home page, it seemed to work OK, probably because of the fix applied above.

Friday, March 27, 2026

using cloudflared to connect to our internal server

Got this message from PB:

Have done the following to do the cloudflare (ZeroTrust) tunnelling to ourservername. 

Cloudflare Zero Trust + Google Workspace + ourservername (SSH)


Architecture Overview

User (Client Machine)
        │
Cloudflare Access (Zero Trust)
        │
Google Workspace (Authentication + MFA)
        │
Cloudflare Tunnel (cloudflared)
        │
ourservername (SSH)



Authentication Flow Chart

User runs SSH (like ssh  ssh-ourservername.ourdomain.org)
        │
Cloudflare Access intercepts request
        │
Redirect to Google Login
        │
User authenticates + MFA
        │
Cloudflare verifies identity + policy
        │
Cloudflare establishes Tunnel
        │
Access granted to ourservername ssh


On the client machine 
Install cloudflared 
Create a config file in .ssh folder

$ ~/.ssh/config

Host ourservername

  HostName ssh-ourservername.ourdomain.org

  User ourusername

  ProxyCommand cloudflared access ssh --hostname %h


Then you the connection would be like

$ ssh ourservername

Connection was successful with ssh with the configuration as above. Connection was initially not successful with Filezilla. 

ChatGPT told me that filezilla does not understand the proxycommand instruction, hence we can run cloudflared in proxy mode by running this in a terminal:

cloudflared access ssh --hostname ssh-ourservername.ourdomain.org --url localhost:2244

This creates a local listener on localhost:2244 which forwards via Cloudflare to the remote SSH server

With this, Filezilla could connect to localhost 2244 successfully (and I could see the files on ourservername with Filezilla.)

Thursday, March 26, 2026

OpenSpace - workarounds for toggle trails causing Pluto trail to appear

 Using the default action by pressing the 'T' key, Pluto trail starts to appear if we disable it before we start. 

One way might be to remove the Pluto Trail asset from openspace before starting the playback?

Another way would be to edit the recording to replace 

openspace.toggleFade("Scene.*Trail.Renderable");    openspace.toggleFade("Scene.*trail.Renderable");

with

openspace.setPropertyValue("Scene.*Trail.Renderable.Fade", 0, 1.0); openspace.setPropertyValue("Scene.*trail.Renderable.Fade", 0, 1.0)


Thursday, March 19, 2026

openspace command convention

Quick note to self - when we want to use the built-in fadeIn or fadeOut commands, we can't use the name of the object as seen in the info tab - like Scene.Moon here,


We will get errors like
Error when calling script 'openspace.fadeIn': Could not find property 'Scene.Moon.Enabled'

Checking the script log, we need to append .Renderable to that name, like

openspace.fadeOut("Scene.Moon.Renderable", 5)

Edit - Note: All of the above were with respect to the 0.21.x version. In version 0.22, things may change - for eg this commit.

Monday, March 16, 2026

tumblerd prevents disk from being ejected on Linux

When trying to eject a USB external disk, a message along the lines of "process tumblerd is currently using the disk, eject anyway?"


Since I had a very large number of images on that drive, and I had just saved an mp4 file onto the drive, tumblerd was probably creating thumbnails for all of the multimedia files. So, did 

killall -HUP tumblerd

and then the disk could be ejected.

OpenSpace session recording "save frames" crash

On my Lenovo ThinkCentre M93 machine with an NVidia GTX1050 graphics card, OpenSpace session recording playback with "save frames" was crashing after a few hundred frames being saved. Each frame was taking around 1000 to 1500 ms for 4096x4096 frames.

According to Claude.ai, the crashes might be due to CEF "being starved" between updates - and I submitted this fix as an issue on the OpenSpace repo.

Claude suggested workaround was in SGCT.

// sgct/src/image.cpp

png_set_compression_level(png, -1);  // changed to:

png_set_compression_level(png, 1);

As the comment just above that line says, -1 sets the compression level to default, which seems to be 6. Setting this to 1 made the frame export only 300 ms per frame (3x faster) and also fixed the crashes - so CEF was not "being starved" of updates.

The increase in file size was not much, 4 MB increased to 5.1 MB for a typical frame.

Another change I made before exporting again, was to close all other windows and apps like the Edge Browser, which I had used earlier for triggering the exports - now OpenSpace has some checkboxes in the export settings which allows us to hide the UI while exporting, so we don't need to trigger the export via a web browser.

Edit: No, the problem is not solved. No crash when saving frames for this recording, ts_003_moon_to_Earth.osrectxt

but crashes after a few hundred frames when saving frames for this recording, ts_002_india_rotate_zoom.osrectxt

So my current workaround was to split the recording into multiple smaller recordings.

Edit - another workaround to get around the crashes, which seem to be caused by CEF, seems to be to disable the module CEFui inside OpenSpace > Settings - issue thread is at https://github.com/OpenSpace/OpenSpace/issues/3954 

Thursday, March 12, 2026

uninstalling ollama

https://github.com/ollama/ollama/issues/986

ran all the lines suggested there, got some 15 GB freed.

Copy-pasting from the above,

Here's a general guideline on how to uninstall it:

Delete the Ollama binary:
Use the rm command to remove the Ollama binary. For example:

sudo rm /usr/local/bin/ollama

If the script created a systemd service, disable and remove it:
If the script created a systemd service for Ollama, you should disable and remove it using the following commands:

sudo systemctl stop ollama
sudo systemctl disable ollama
sudo rm /etc/systemd/system/ollama.service
sudo systemctl daemon-reload

Remove any created user and group (if applicable):
The script might have created a user and group named "ollama." You can remove them using the following commands:

sudo userdel ollama
sudo groupdel ollama

-----------------------

More lines from other people, esp if location of ollama and downloaded models are different - 

sudo rm -v /usr/local/bin/ollama
sudo rm -rv /usr/local/lib/ollama

sudo systemctl stop ollama
sudo systemctl disable ollama
sudo rm -v /etc/systemd/system/ollama.service
sudo systemctl daemon-reload

sudo userdel ollama
sudo groupdel ollama

sudo rm -rv /usr/share/ollama
# for downloaded models

rm -rv ~/.ollama

sudo rm -rv /usr/lib/ollama/
sudo rm -rv /usr/local/lib/ollama


Tuesday, March 10, 2026

domain transfers - godaddy to nettigritty and one account to another

We had a few domains which needed to be transferred. A couple of domains needed to be transferred from one account to another on nettigritty, and a domain needed to be transferred from godaddy to nettigritty.

Thursday, March 05, 2026

downloading from a server via http (no SSL) with auth

Downloading some planetarium files from a server in Sweden, which had password protected directories but no SSL (no https) - 
  1. Directly downloading via the browser - slow, prompts us to "Keep" or delete the file every time due to being downloaded unsecurely

  2. Using aria2

     aria2c -x16 -s16 --http-user=USERNAME --http-passwd=PASSWORD url

    Need ProtonVPN, since without it, http downloads are severely throttled to less than 100 kbps.

  3. Better option, running 

    aria2c -x16 -s16 --http-user=USERNAME --http-passwd=PASSWORD -i urls.txt

    on one of our AWS servers - all the URLs get downloaded in a jiffy, then we can use Filezilla and download to our local machine over sftp without throttling issues.

Wednesday, March 04, 2026

change of email id in CMS

One of the users in our CMS (google apps script with php/pgsql authentication) changed their email id in the profile, but was then unable to find any of the assets they had been working on.

Now changed the 'username' field in the postgresql database for this user from
old@gmail to new@new.email
(the email id field had already been changed by the user.)

That seems to have solved the issue. This is a duplicate of an earlier post, https://hnsws.blogspot.com/2024/06/unable-to-see-old-assets-after-email.html

Monday, March 02, 2026

OpenSpace - hover over a planet and allow it to rotate under you - OpenSpace seminar

As noted in an earlier post (which I had forgotten), the relevant option is in Settings > Navigation Handler > "Follow Anchor Node Rotation" 

When I asked Gemini via aistudio.google.com, it found a transcript from an OpenSpace seminar, which is a useful link - https://www.ppadomes.org/events/online-seminars/ppsarchive/2025-archive/2025-01-31-chat

Friday, February 27, 2026

github 14 day traffic stats

Since github has a page which shows 14-day stats, like https://github.com/hn-88/OpenSpace-AppImage/graphs/traffic but which is behind the login of the user, I asked Claude to create a stats page for the traffic to all my public repos. The prompt is here, 
and the github traffic stats page is here

The "clones" number is so high for the https://github.com/hn-88/OpenSpace-AppImage/ repo because of the multiple daily build workflows.

Friday, February 20, 2026

adjusting sound volume on Mac

Since I don't have an Apple keyboard, adjusting the volume on the Mac mini was not very straightforward. An LLM's advice seemed to be a bit sarcastic when taken in the context of how easy it should be versus the method suggested - 

Adjusting the volume using the menu bar is a simple and straightforward method. Here are the steps to follow:

  1. Click on the Apple logo in the top-left corner of your screen.
  2. Select "System Preferences" from the drop-down menu.
  3. In the System Preferences window, click on "Sound."
  4. Go to the "Output" tab.
  5. Drag the volume slider to adjust the volume level.
On my machine, it was "System Settings" and not Preferences. And, there seemed to be a slightly faster way, which is clicking on the "Control Centre" in the menu bar - screenshot below - the "edit controls" button allows us to drag the icon into the menu bar, too.






Wednesday, February 18, 2026

SSL certificates for an internal web server as backend for app

There was a query about installing SSL certificates for an internal web server, which was to be used as backend for app. Copy-pasting my reply.

Since the app needed trusted certificates, just using a self-signed certificate would be unwieldy, probably needing the installation of our own cert chain on each device or something like that. 

Also, since free SSL certificates from https://letsencrypt.org/ need updating every 60/90 days, buying a certificate may be an option with fewer hassles for this internal server with no external ip address.

has low-cost options. Most probably the lowest cost one will work, can ask nettigritty support if you have doubts.

There are lots of step-by-step tutorials on how to install, like


A week later, the reply was,

it is just a java application running with java.
NO Apache. nginix is found. so tried installing with let's encrypt website using ngnix
getting thie error... 
 Detail: DNS problem: NXDOMAIN looking up A for theinternaldomain.net - check that a DNS record exists for this domain; DNS problem: SERVFAIL looking up AAAA for theinternaldomain.net - the domain's nameservers may be malfunctioning

Hint: The Certificate Authority failed to verify the temporary nginx configuration changes made by Certbot. Ensure the listed domains point to this nginx server and that it is accessible from the internet.

My reply was,

You will probably need to buy a certificate and install it, as mentioned in the first email in this thread.

Since theinternaldomain.net is not a publicly available DNS record, certbot cannot resolve it.

If the server is a java application running on some port, say 5000, you will need to run ngnix or apache as a reverse proxy, and install the certificate for apache/ngnix.

The reverse-proxy config for apache is mentioned here, 
(but of course, don't use certbot, purchase and install a cert instead as mentioned in the first email in this thread.)