Merge branch 'sictiru' of github.com:samuelclay/NewsBlur into sictiru

This commit is contained in:
sictiru 2024-04-01 09:26:17 -07:00
commit 57296704b2
316 changed files with 7188 additions and 5001 deletions

4
.gitignore vendored
View file

@ -19,6 +19,7 @@ certbot.conf
task_env.py
app_env.py
data/
api/ip_addresses.txt
.prom_cache
config/certificates
**/*.xcuserstate
@ -53,10 +54,12 @@ docker/haproxy/haproxy.consul.cfg
docker/nginx/nginx.consul.conf
docker/prometheus/prometheus.yml
docker/redis/redis_replica.conf
docker/redis/redis_*_replica.conf
docker/postgres/postgres.conf
# Local configuration file (sdk path, etc)
/originals
/node/originals
media/safari/NewsBlur.safariextz
# IDE files
@ -66,3 +69,4 @@ media/safari/NewsBlur.safariextz
*.tfstate*
.terraform*
grafana.ini
apps/api/ip_addresses.txt

29
.vscode/settings.json vendored
View file

@ -1,12 +1,16 @@
{
"python.linting.enabled": true,
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"python.linting.pylamaEnabled": false,
"python.linting.flake8Args": [
"--ignore=E501,W293,W503,W504,E302,E722,E226,E221,E402,E401"
"isort.args": [
"--profile",
"black"
],
"python.pythonPath": "~/.virtualenvs/newsblur3/bin/python",
// "python.linting.enabled": true,
// "python.linting.pylintEnabled": false,
// "python.linting.flake8Enabled": true,
// "python.linting.pylamaEnabled": false,
// "python.linting.flake8Args": [
// "--ignore=E501,W293,W503,W504,E302,E722,E226,E221,E402,E401"
// ],
// "python.pythonPath": "~/.virtualenvs/newsblur/bin/python",
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": "active",
"git.ignoreLimitWarning": true,
@ -27,15 +31,12 @@
"docker/volumes": true,
"requirements.txt": true, // It's just a symlink to config/requirements.txt, which has git history
},
"python.formatting.blackArgs": [
"--line-length=110",
"--skip-string-normalization"
],
// "python.formatting.blackArgs": [
// "--line-length=110",
// "--skip-string-normalization"
// ],
"files.associations": {
"*.yml": "ansible"
},
"nrf-connect.toolchain.path": "${nrf-connect.toolchain:1.9.1}",
"C_Cpp.default.configurationProvider": "nrf-connect",
"editor.formatOnSave": false,
"ansible.python.interpreterPath": "/opt/homebrew/bin/python3",
}

View file

@ -176,3 +176,17 @@ Provision a new redis server, replicate the data, take newsblur down for mainten
aps -l db-redis-story1,db-redis-story2 -t consul
make maintenance_off
make task
### Switching to a new postgres server
# Old
docker exec -it -u postgres postgres psql -c "SELECT pg_start_backup('label', true)"
# New
## Install `openssh-client` and `rsync`
docker stop postgres
rsync -Pav --stats --progress db-postgres.service.consul:/srv/newsblur/docker/volumes/postgres/data /srv/newsblur/docker/volumes/postgres/ --exclude postmaster.pid
docker start postgres
# New
docker exec -it -u postgres postgres /usr/lib/postgresql/13/bin/pg_ctl -D /var/lib/postgresql/data promote
# Old
docker exec -it -u postgres postgres psql -c "SELECT pg_stop_backup()"

View file

@ -115,7 +115,8 @@ inventory:
./ansible/utils/generate_inventory.py
oldinventory:
OLD=1 ./ansible/utils/generate_inventory.py
hinventory:
./ansible/utils/generate_hetzner_inventory.py
# Docker
pull:
docker pull newsblur/newsblur_python3
@ -184,7 +185,7 @@ maintenance_off:
# Provision
firewall:
ansible-playbook ansible/all.yml -l db --tags firewall
ansible-playbook ansible/all.yml -l db --tags ufw
oldfirewall:
ANSIBLE_CONFIG=/srv/newsblur/ansible.old.cfg ansible-playbook ansible/all.yml -l db --tags firewall
repairmongo:

View file

@ -2,7 +2,7 @@
host_key_checking = False
remote_user = nb
retry_files_enabled = False
inventory = /srv/newsblur/ansible/inventories/digital_ocean.ini, /srv/newsblur/ansible/inventories/digital_ocean.yml
inventory = /srv/newsblur/ansible/inventories/digital_ocean.ini, /srv/newsblur/ansible/inventories/digital_ocean.yml, /srv/newsblur/ansible/inventories/hetzner.ini, /srv/newsblur/ansible/inventories/hetzner.yml
private_key_file = /srv/secrets-newsblur/keys/docker.key
remote_tmp = ~/.ansible/tmp
forks = 20

View file

@ -1 +1 @@
*.ini
digital_ocean*.ini

View file

@ -32,7 +32,9 @@ groups:
search: inventory_hostname.startswith('db-elasticsearch')
elasticsearch: inventory_hostname.startswith('db-elasticsearch')
redis: inventory_hostname.startswith('db-redis')
redis_story: inventory_hostname.startswith('db-redis-story')
redis_story: inventory_hostname.startswith('db-redis-story') or inventory_hostname.startswith('hdb-redis-story')
redis_session: inventory_hostname.startswith('db-redis-session') or inventory_hostname.startswith('hdb-redis-session')
redis_user: inventory_hostname.startswith('db-redis-user') or inventory_hostname.startswith('hdb-redis-user')
postgres: inventory_hostname.startswith('db-postgres')
mongo: inventory_hostname.startswith('db-mongo') and not inventory_hostname.startswith('db-mongo-analytics')
mongo_analytics: inventory_hostname.startswith('db-mongo-analytics')

View file

@ -0,0 +1 @@
/srv/secrets-newsblur/configs/hetzner.ini

View file

@ -0,0 +1,60 @@
plugin: constructed
strict: False
groups:
hall: inventory_hostname.startswith('h')
haproxy: inventory_hostname.startswith('hwww')
app: inventory_hostname.startswith('happ')
happ: inventory_hostname.startswith('happ')
web: inventory_hostname.startswith('happ')
hweb: inventory_hostname.startswith('happ')
django: inventory_hostname.startswith('happ-web')
hdjango: inventory_hostname.startswith('happ-web')
refresh: inventory_hostname.startswith('happ-refresh')
hrefresh: inventory_hostname.startswith('happ-refresh')
count: inventory_hostname.startswith('happ-count')
hcount: inventory_hostname.startswith('happ-count')
push: inventory_hostname.startswith('happ-push')
hpush: inventory_hostname.startswith('happ-push')
blogs: inventory_hostname.startswith('blog')
node: inventory_hostname.startswith('hnode')
hnode: inventory_hostname.startswith('hnode')
node_socket: inventory_hostname.startswith('hnode-socket')
hnode_socket: inventory_hostname.startswith('hnode-socket')
node_images: inventory_hostname.startswith('hnode-images')
hnode_images: inventory_hostname.startswith('hnode-images')
node_text: inventory_hostname.startswith('hnode-text')
hnode_text: inventory_hostname.startswith('hnode-text')
node_page: inventory_hostname.startswith('hnode-page')
hnode_page: inventory_hostname.startswith('hnode-page')
node_favicons: inventory_hostname.startswith('hnode-favicons')
hnode_favicons: inventory_hostname.startswith('hnode-favicons')
# debugs: inventory_hostname.startswith('hdebug')
htask: inventory_hostname.startswith('htask')
task: inventory_hostname.startswith('htask')
celery: inventory_hostname.startswith('htask-celery')
work: inventory_hostname.startswith('htask-work')
staging: inventory_hostname.startswith('hstaging')
hdb: inventory_hostname.startswith('hdb')
db: inventory_hostname.startswith('hdb')
search: inventory_hostname.startswith('hdb-elasticsearch')
elasticsearch: inventory_hostname.startswith('hdb-elasticsearch')
redis: inventory_hostname.startswith('hdb-redis')
redis_story: inventory_hostname.startswith('hdb-redis-story')
redis_session: inventory_hostname.startswith('hdb-redis-session')
redis_user: inventory_hostname.startswith('hdb-redis-user')
postgres: inventory_hostname.startswith('hdb-postgres')
mongo: inventory_hostname.startswith('hdb-mongo') and not inventory_hostname.startswith('hdb-mongo-analytics')
mongo_analytics: inventory_hostname.startswith('hdb-mongo-analytics')
consul: inventory_hostname.startswith('hdb-consul')
hconsul: inventory_hostname.startswith('hdb-consul')
metrics: inventory_hostname.startswith('hdb-metrics')
sentry: inventory_hostname.startswith('hdb-sentry')

View file

@ -127,6 +127,9 @@
- name: Reload gunicorn
command: "kill -HUP {{ psaux.stdout }}"
when: not pulled.changed
rescue:
- name: Restart Docker Container
command: "docker restart newsblur_web"
tags:
- static

View file

@ -6,16 +6,23 @@
- ../env_vars/base.yml
tasks:
- name: Extract part of hostname to determine container name
set_fact:
redis_role: "{{ inventory_hostname.split('-')[2] }}"
tags:
- never
- replicaofnoone
- name: Turning off secondary for redis by deleting redis_replica.conf
copy:
dest: /srv/newsblur/docker/redis/redis_replica.conf
dest: "/srv/newsblur/docker/redis/redis_{{ redis_role }}_replica.conf"
content: ""
tags:
- never
- replicaofnoone
- name: Setting Redis REPLICAOF NO ONE
shell: docker exec redis redis-cli REPLICAOF NO ONE
shell: docker exec redis-{{ redis_role }} redis-cli REPLICAOF NO ONE
tags:
- never
- replicaofnoone

View file

@ -0,0 +1,6 @@
---
- hosts: all
become: yes
tasks:
- name: Restart the server
ansible.builtin.reboot:

View file

@ -8,16 +8,16 @@
- motd_role: db
roles:
- {role: 'base', tags: 'base'}
- {role: 'ufw', tags: 'ufw'}
- {role: 'docker', tags: 'docker'}
- {role: 'repo', tags: ['repo', 'pull']}
- {role: 'dnsmasq', tags: 'dnsmasq'}
- {role: 'consul', tags: 'consul'}
- {role: 'consul-client', tags: 'consul'}
- {role: 'mongo-exporter', tags: 'mongo-exporter'}
- {role: 'postgres-exporter', tags: 'postgres-exporter'}
- {role: 'redis-exporter', tags: 'redis-exporter'}
- {role: 'node-exporter', tags: ['node-exporter', 'metrics']}
- {role: 'prometheus', tags: ['prometheus', 'metrics']}
- {role: 'grafana', tags: ['grafana', 'metrics']}
# - {role: 'base', tags: 'base'}
# - {role: 'ufw', tags: 'ufw'}
# - {role: 'docker', tags: 'docker'}
# - {role: 'repo', tags: ['repo', 'pull']}
# - {role: 'dnsmasq', tags: 'dnsmasq'}
# - {role: 'consul', tags: 'consul'}
# - {role: 'consul-client', tags: 'consul'}
# - {role: 'mongo-exporter', tags: 'mongo-exporter'}
- { role: "postgres-exporter", tags: "postgres-exporter" }
- { role: "redis-exporter", tags: "redis-exporter" }
- { role: "node-exporter", tags: ["node-exporter", "metrics"] }
- { role: "prometheus", tags: ["prometheus", "metrics"] }
- { role: "grafana", tags: ["grafana", "metrics"] }

View file

@ -1,7 +1,6 @@
---
- name: SETUP -> node containers
hosts: node
become: true
vars_files:
- ../env_vars/base.yml
vars:

View file

@ -12,8 +12,8 @@
- {role: 'docker', tags: 'docker'}
- {role: 'repo', tags: ['repo', 'pull']}
- {role: 'dnsmasq', tags: 'dnsmasq'}
- {role: 'consul', tags: 'consul'}
- {role: 'consul-client', tags: 'consul'}
# - {role: 'consul', tags: 'consul'}
# - {role: 'consul-client', tags: 'consul'}
- {role: 'node-exporter', tags: ['node-exporter', 'metrics']}
- {role: 'redis', tags: 'redis'}
- {role: 'flask_metrics', tags: ['flask-metrics', 'metrics', 'flask_metrics']}

View file

@ -23,5 +23,5 @@
- {role: 'nginx', tags: 'nginx'}
- {role: 'node', tags: 'node'}
- {role: 'node-exporter', tags: ['node-exporter', 'metrics']}
- {role: 'prometheus', tags: ['prometheus', 'metrics']}
- {role: 'grafana', tags: ['grafana', 'metrics']}
# - {role: 'prometheus', tags: ['prometheus', 'metrics']}
# - {role: 'grafana', tags: ['grafana', 'metrics']}

View file

@ -8,13 +8,12 @@
- motd_role: task
roles:
- {role: 'base', tags: 'base'}
- {role: 'ufw', tags: 'ufw'}
- {role: 'docker', tags: 'docker'}
- {role: 'repo', tags: ['repo', 'pull']}
- {role: 'dnsmasq', tags: 'dnsmasq'}
- {role: 'consul', tags: 'consul'}
- {role: 'consul-client', tags: 'consul'}
- {role: 'apns', tags: 'apns'}
# - {role: 'netdata', tags: 'netdata'}
- {role: 'node-exporter', tags: ['node-exporter', 'metrics']}
- {role: 'celery_task', tags: 'celery'}
- {role: 'ufw', tags: 'ufw'}

View file

@ -103,14 +103,15 @@
become: yes
command:
docker run --rm --name=pg_basebackup --network=host -e POSTGRES_PASSWORD=newsblur -v /srv/newsblur/docker/volumes/postgres/data:/var/lib/postgresql/data postgres:13 pg_basebackup -h db-postgres.service.nyc1.consul -p 5432 -U newsblur -D /var/lib/postgresql/data -Fp -R -Xs -P -c fast
- name: Create Postgres docker volumes with correct permissions
become: yes
file:
path: "{{ item }}"
path: "{{ item }}"
state: directory
recurse: yes
owner: "{{ ansible_effective_user_id|int }}"
group: "{{ ansible_effective_group_id|int }}"
owner: 999
group: 999
with_items:
- /srv/newsblur/docker/volumes/postgres/archive
- /srv/newsblur/docker/volumes/postgres/backups
@ -129,7 +130,7 @@
- name: pg_ctl promote
become: yes
command:
docker exec -it postgres su - postgres -c "/usr/lib/postgresql/13/bin/pg_ctl -D /var/lib/postgresql/data promote"
docker exec postgres su - postgres -c "/usr/lib/postgresql/13/bin/pg_ctl -D /var/lib/postgresql/data promote"
# when: (inventory_hostname | regex_replace('[0-9]+', '')) in ['db-postgres-secondary']
tags:
- never

View file

@ -6,6 +6,17 @@
tags: packages
# ignore_errors: yes
- name: whoami
debug:
var: ansible_user_id
tags: whoami
- name: Set timezone
become: yes
ansible.builtin.timezone:
name: 'America/New_York'
tags: timezone
- name: Copy zshrc
template:
src: zshrc.txt.j2
@ -20,10 +31,20 @@
become: yes
- name: Cloning oh-my-zsh
git:
repo: https://github.com/robbyrussell/oh-my-zsh
dest: /home/nb/.oh-my-zsh
force: yes
block:
- name: Cloning oh-my-zsh
git:
repo: https://github.com/robbyrussell/oh-my-zsh
dest: /home/nb/.oh-my-zsh
force: yes
rescue:
- name: chown oh-my-zsh
become: yes
file:
path: /home/nb/.oh-my-zsh
owner: nb
group: nb
recurse: yes
- name: Copy toprc
copy: src=toprc.txt dest=~/.toprc

View file

@ -43,7 +43,7 @@ alias cd..='cd ..'
alias smtp='python -m smtpd -n -c DebuggingServer 127.0.0.1:1025'
alias tlnb='echo "----------------\n"; tail -f /srv/newsblur/logs/newsblur.log'
alias sp='sudo docker exec -it {% if 'task' in inventory_hostname %}{{ inventory_hostname|regex_replace('\d+', '') }}{% else %}newsblur_web{% endif %} python manage.py shell_plus'
alias sp='sudo docker exec -it {% if 'task' in inventory_hostname %}{{ inventory_hostname|regex_replace('\-?\d+', '')|regex_replace('htask', 'task')|regex_replace('happ', 'app') }}{% else %}newsblur_web{% endif %} python manage.py shell_plus'
alias dps='sudo docker ps -a'
alias cdnb='cd /srv/newsblur'
alias sshdo=/srv/newsblur/utils/ssh.sh

View file

@ -43,7 +43,7 @@
max-size: 100m
healthcheck:
# test: celery inspect ping -A newsblur_web -d celery@$HOSTNAME
test: bash -c "(($(date +%s) - $(stat /srv/newsblur/logs/newsblur.log -c %Y) < 120)) && exit 0 || exit 1"
test: bash -c "(($(date +%s) - $(stat /srv/newsblur/logs/newsblur.log -c %Y) < 600)) && exit 0 || exit 1"
interval: 60s
timeout: 10s
retries: 3

View file

@ -1,6 +1,6 @@
{
"service": {
"name": "{{ inventory_hostname|regex_replace('\d+', '') }}",
"name": "{{ inventory_hostname|regex_replace('\-?\d+', '')|regex_replace('htask', 'task') }}",
"id": "{{ inventory_hostname }}",
"tags": [
"celery_task"

View file

@ -1,11 +1,19 @@
{
{% if inventory_hostname.startswith("h") %}
"datacenter": "nyc1",
{% else %}
"datacenter": "nyc1",
{% endif %}
"data_dir": "/opt/consul",
"log_level": "INFO",
"log_file": "/var/log/consul/consul.log",
"enable_syslog": true,
"retry_join": [{{ consul_manager_ip.stdout|trim }}],
{% if inventory_hostname.startswith("hdb") and inventory_hostname|regex_replace("\-?\d+", "") not in ["hdb-mongo-analytics", "hdb-sentry"] %}
"advertise_addr": "{% raw %}{{ GetAllInterfaces | include \"name\" \"^enp\" | include \"flags\" \"forwardable|up\" | attr \"address\" }}{% endraw %}",
{% else %}
"advertise_addr": "{% raw %}{{ GetAllInterfaces | include \"name\" \"^eth\" | include \"flags\" \"forwardable|up\" | attr \"address\" }}{% endraw %}",
{% endif %}
"bind_addr": "0.0.0.0",
"ui_config": {"enabled": true},
"dns_config": {

View file

@ -1,12 +1,52 @@
#!/usr/bin/env python
import json
import os
import subprocess
import digitalocean
TOKEN_FILE = "/srv/secrets-newsblur/keys/digital_ocean.token"
OLD = False
# Uncomment below to allow existing servers to find the consul-manager
OLD = True
def get_host_ips_from_group(group_name):
"""
Fetches IP addresses of hosts from a specified group using ansible-inventory command across combined inventory.
:param group_name: The name of the group to fetch host IPs from.
:param inventory_base_path: Base path to the inventory directories. Defaults to the path in ansible.cfg.
:return: A list of IP addresses belonging to the specified group.
"""
cmd = ['ansible-inventory', '-i', '/srv/newsblur/ansible/inventories/hetzner.ini', '-i', '/srv/newsblur/ansible/inventories/hetzner.yml', '--list']
try:
# Execute the ansible-inventory command
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)
# Parse the JSON output from ansible-inventory
inventory_data = json.loads(result.stdout)
host_ips = []
# Check if the group exists
if group_name in inventory_data:
# Get the list of hosts in the specified group
if 'hosts' in inventory_data[group_name]:
for host in inventory_data[group_name]['hosts']:
# Fetch the host details, specifically looking for the ansible_host variable for the IP
host_vars = inventory_data['_meta']['hostvars'][host]
ip_address = host_vars.get('ansible_host', None)
if ip_address:
host_ips.append(ip_address)
else:
# If ansible_host is not defined, fallback to using the host's name
host_ips.append(host)
return host_ips
except subprocess.CalledProcessError as e:
print(f"Failed to execute ansible-inventory: {e.stderr}")
return []
except json.JSONDecodeError as e:
print(f"Failed to parse JSON output: {e}")
return []
TOKEN_FILE = "/srv/secrets-newsblur/keys/digital_ocean.token"
with open(TOKEN_FILE) as f:
token = f.read().strip()
@ -14,11 +54,12 @@ with open(TOKEN_FILE) as f:
manager = digitalocean.Manager(token=token)
my_droplets = manager.get_all_droplets()
consul_manager_droplets = [d for d in my_droplets if d.name.startswith("db-consul")]
if OLD:
consul_manager_ip_address = ','.join([f"\"{droplet.ip_address}\"" for droplet in consul_manager_droplets])
else:
consul_manager_ip_address = ','.join([f"\"{droplet.private_ip_address}\"" for droplet in consul_manager_droplets])
consul_manager_droplets = [d for d in my_droplets if "db-consul" in d.name]
# Use ansible-inventory to get the consul-manager ip
group_name = 'hconsul'
hetzner_hosts = get_host_ips_from_group(group_name)
consul_manager_ip_address = ','.join([f"\"{droplet.ip_address}\"" for droplet in consul_manager_droplets] + [f"\"{host}\"" for host in hetzner_hosts])
print(consul_manager_ip_address)

View file

@ -1,6 +1,8 @@
---
- name: Add the HashiCorp GPG key
become: yes
# Only warn if fail
ignore_errors: yes
apt_key:
url: https://apt.releases.hashicorp.com/gpg
state: present
@ -10,11 +12,21 @@
apt_repository:
repo: "deb [arch=amd64] https://apt.releases.hashicorp.com {{ ansible_distribution_release }} main"
- name: Add the official HashiCorp Linux repository.
become: yes
apt_repository:
repo: "deb [arch=arm64] https://apt.releases.hashicorp.com {{ ansible_distribution_release }} main"
- name: Update apt
become: yes
apt:
update_cache: yes
- name: Installing Consul
become: yes
apt:
allow_downgrades: yes
pkg: consul=1.10.4
pkg: consul=1.10.12-1
state: present
- name: Register Manager IP

View file

@ -19,7 +19,17 @@
- name: Bind dnsmasq to localhost
become: yes
lineinfile: dest=/etc/dnsmasq.conf state=present line='listen-address=127.0.0.1'
lineinfile:
dest: /etc/dnsmasq.conf
state: present
line: 'listen-address=127.0.0.1'
- name: Gather network facts
become: yes
setup:
gather_subset:
- '!all'
- network
- name: Check if resolv.conf exists
become: yes
@ -27,18 +37,23 @@
path: /etc/resolv.conf
register: resolvconf
# - debug: msg="{{resolvconf}}"
- name: Add localhost to resolv.conf
become: yes
lineinfile: dest=/etc/resolv.conf state=present line='nameserver 127.0.0.1' insertbefore=BOF
when: resolvconf.stat.readable
lineinfile:
dest: /etc/resolv.conf
state: present
line: 'nameserver 127.0.0.1'
insertbefore: BOF
when: resolvconf.stat.exists
- name: user=root dnsmasq
- name: Set dnsmasq user to root
become: yes
lineinfile: dest=/etc/dnsmasq.conf state=present line='user=root'
lineinfile:
dest: /etc/dnsmasq.conf
state: present
line: 'user=root'
- name: Stop systemd-resolved
- name: Stop and disable systemd-resolved
become: yes
systemd:
name: systemd-resolved
@ -54,8 +69,31 @@
owner: root
group: root
mode: 0644
vars:
network_interfaces: "{{ ansible_interfaces }}"
notify: restart dnsmasq
- name: Create systemd override directory for dnsmasq
become: yes
file:
path: /etc/systemd/system/dnsmasq.service.d
state: directory
mode: 0755
# - name: Add override for dnsmasq service
# become: yes
# copy:
# dest: /etc/systemd/system/dnsmasq.service.d/override.conf
# content: |
# [Unit]
# After=docker.service
- name: Remove override for dnsmasq service
become: yes
file:
path: /etc/systemd/system/dnsmasq.service.d/override.conf
state: absent
- name: Launch dnsmasq
become: yes
service:

View file

@ -8,11 +8,13 @@ server=/consul/127.0.0.1#8600
{# dnsmasq should not needlessly read /etc/resolv.conf #}
no-resolv
interface=lo
interface=eth0
interface=eth1
{% for interface in network_interfaces %}
{% if not interface.startswith('veth') and not interface.startswith('docker') and not interface.startswith('br') %}
# interface={{ interface }}
{% endif %}
{% endfor %}
bind-interfaces
# bind-interfaces # This will bind only to the interfaces that are up in interface= above
# log-dhcp
# log-queries
# log-facility=/var/log/dnsmasq.log

View file

@ -15,25 +15,37 @@
state: present
with_items: "{{ docker_prerequisite_packages_Ubuntu }}"
- name: Install prerequisite packages (for Ubuntu 14.04 only)
apt:
name: "{{ item.package }}"
state: present
with_items: "{{ docker_prerequisite_packages_Ubuntu_1404 }}"
when: ansible_distribution_version == '14.04'
- name: Import Docker CE repository gpg key
- name: Download Docker GPG key
become: yes
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
id: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88
- name: Add Docker CE repository
shell:
cmd: "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg"
creates: /usr/share/keyrings/docker-archive-keyring.gpg
- name: Set up the Docker repository with the correct GPG key
become: yes
apt_repository:
repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
ansible.builtin.apt_repository:
repo: "deb [arch={{ ansible_architecture }} signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
state: present
update_cache: yes
- name: Set up the Docker repository with the correct GPG key
become: yes
ansible.builtin.apt_repository:
repo: "deb [arch=arm64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
state: present
update_cache: yes
- name: Set up the Docker repository with the correct GPG key
become: yes
ansible.builtin.apt_repository:
repo: "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
state: present
update_cache: yes
- name: Update APT cache
become: yes
ansible.builtin.apt:
update_cache: yes
- name: Install Docker CE
become: yes

View file

@ -1,4 +1,18 @@
---
- name: Set facts for secondary elasticsearch servers
set_fact:
elasticsearch_secondary: no
tags:
- always
- name: Set facts for secondary elasticsearch servers
set_fact:
elasticsearch_secondary: yes
# when: inventory_hostname not in ["db-elasticsearch"]
when: inventory_hostname not in ["hdb-elasticsearch-1"]
tags:
- always
- name: Permissions for elasticsearch
become: yes
file:
@ -40,6 +54,7 @@
- name: newsblurnet
aliases:
- elasticsearch
user: "{{ ansible_effective_user_id|int }}:{{ ansible_effective_group_id|int }}"
volumes:
- /srv/newsblur/docker/volumes/elasticsearch:/usr/share/elasticsearch/data
- /var/log/elasticsearch/:/var/log/elasticsearch/

View file

@ -1,6 +1,10 @@
{
"service": {
{% if not elasticsearch_secondary %}
"name": "db-elasticsearch",
{% else %}
"name": "db-elasticsearch-staging",
{% endif %}
"tags": [
"db"
],

View file

@ -58,7 +58,7 @@
job: >-
[ $(date +\%d) -le 07 ] &&
docker pull certbot/dns-{{ dns_plugin }}:{{ certbot_version }};
docker run --rm -it
docker run --rm
-v /etc/letsencrypt:/etc/letsencrypt
-v /var/lib/letsencrypt:/var/lib/letsencrypt
-v /var/log/letsencrypt:/var/log/letsencrypt

View file

@ -1,4 +1,3 @@
- name: Start mongo-exporter container
become: yes
docker_container:
@ -12,9 +11,9 @@
- name: newsblurnet
env:
# MONGODB_URI: 'mongodb://{{ inventory_hostname }}.node.nyc1.consul:27017/admin?'
MONGODB_URI: 'mongodb://{{ mongodb_username }}:{{ mongodb_password }}@{{ inventory_hostname }}.node.nyc1.consul:27017/admin?authSource=admin'
MONGODB_URI: "mongodb://{{ mongodb_username }}:{{ mongodb_password }}@{{ inventory_hostname }}.node.nyc1.consul:27017/admin?authSource=admin"
ports:
- '9216:9216'
- "9216:9216"
- name: Register mongo-exporter in consul
tags: consul
@ -24,5 +23,5 @@
dest: /etc/consul.d/mongo-exporter.json
notify:
- reload consul
- name: Command to register mongo-exporter
command: "consul services register /etc/consul.d/mongo-exporter.json"
# - name: Command to register mongo-exporter
# command: "consul services register /etc/consul.d/mongo-exporter.json"

View file

@ -1,4 +1,20 @@
---
- name: Set mongo_analytics_secondary
set_fact:
mongo_analytics_secondary: no
tags:
- consul
- always
- name: Set mongo_analytics_secondary
set_fact:
mongo_analytics_secondary: yes
# when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) not in ['db-mongo-analytics']
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) not in ['hdb-mongo-analytics']
tags:
- consul
- always
- name: Permissions for mongo
become: yes
file:
@ -8,6 +24,17 @@
group: "{{ ansible_effective_group_id|int }}"
path: /var/log/mongodb
- name: Copy MongoDB keyfile
become: yes
copy:
content: "{{ mongodb_keyfile }}"
dest: /srv/newsblur/config/mongodb_keyfile.key
owner: "{{ ansible_effective_user_id|int }}"
group: "{{ ansible_effective_group_id|int }}"
mode: 0400
tags:
- keyfile
- name: Block for mongo volume
block:
- name: Get the volume name
@ -37,46 +64,47 @@
opts: defaults,discard
state: mounted
- name: Set permissions on mongo volume
# become: yes
file:
path: "/mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}"
state: directory
owner: "{{ ansible_effective_user_id|int }}"
group: "{{ ansible_effective_group_id|int }}"
recurse: yes
- name: Make backup directory
# become: yes
file:
path: "/mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}/backup/"
state: directory
owner: "{{ ansible_effective_user_id|int }}"
group: "{{ ansible_effective_group_id|int }}"
mode: 0755
- name: Create symlink to mounted volume for backups to live
file:
state: link
src: "/mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}/backup"
path: /srv/newsblur/backup
owner: "{{ ansible_effective_user_id|int }}"
group: "{{ ansible_effective_group_id|int }}"
force: yes
when: (inventory_hostname | regex_replace('[0-9]+', '')) in ['db-mongo-secondary', 'db-mongo-analytics']
- name: Copy MongoDB keyfile
become: yes
copy:
content: "{{ mongodb_keyfile }}"
dest: /srv/newsblur/config/mongodb_keyfile.key
owner: "{{ ansible_effective_user_id|int }}"
group: "{{ ansible_effective_group_id|int }}"
mode: 0400
tags:
- keyfile
- name: Set permissions on mongo volume
become: yes
file:
path: "/mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}"
state: directory
owner: "{{ ansible_effective_user_id|int }}"
group: "{{ ansible_effective_group_id|int }}"
recurse: yes
- name: Block for mongo volume on hetzner
block:
- name: Create backup directory
file:
path: "/srv/newsblur/docker/volumes/mongo/backup"
state: directory
owner: "{{ ansible_effective_user_id|int }}"
group: "{{ ansible_effective_group_id|int }}"
mode: 0755
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['hdb-mongo-secondary', 'hdb-mongo-analytics']
- name: Make backup directory
become: yes
file:
path: "/mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}/backup/"
state: directory
owner: "{{ ansible_effective_user_id|int }}"
group: "{{ ansible_effective_group_id|int }}"
mode: 0755
- name: Create symlink to mounted volume for backups to live
file:
state: link
src: "/mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}/backup"
path: /srv/newsblur/backup
owner: "{{ ansible_effective_user_id|int }}"
group: "{{ ansible_effective_group_id|int }}"
force: yes
- name: Start db-mongo docker container
become: yes
docker_container:
@ -103,7 +131,46 @@
- /srv/newsblur/config/mongodb_keyfile.key:/srv/newsblur/config/mongodb_keyfile.key
- /var/log/mongodb/:/var/log/mongodb/
- /mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}/backup/:/backup/
when: (inventory_hostname | regex_replace('[0-9]+', '')) in ['db-mongo', 'db-mongo-primary', 'db-mongo-secondary']
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['db-mongo', 'db-mongo-primary', 'db-mongo-secondary']
- name: Start mongo and set permissions
block:
- name: Start db-mongo docker container on hetzner
docker_container:
name: mongo
image: mongo:4.0
state: started
container_default_behavior: no_defaults
hostname: "{{ inventory_hostname }}"
restart_policy: unless-stopped
networks_cli_compatible: yes
network_mode: host
# network_mode: default
# networks:
# - name: newsblurnet
# aliases:
# - mongo
# ports:
# - "27017:27017"
command: --config /etc/mongod.conf
# user: 1000:1001
user: "{{ ansible_effective_user_id|int }}:{{ ansible_effective_group_id|int }}"
volumes:
- /srv/newsblur/docker/volumes/mongo:/data/db
- /srv/newsblur/ansible/roles/mongo/templates/mongo.conf:/etc/mongod.conf
- /srv/newsblur/config/mongodb_keyfile.key:/srv/newsblur/config/mongodb_keyfile.key
- /var/log/mongodb/:/var/log/mongodb/
- /srv/newsblur/docker/volumes/mongo/backup/:/backup/
- name: Set permissions on mongo volume
become: yes
file:
path: "/srv/newsblur/docker/volumes/"
state: directory
owner: "{{ ansible_effective_user_id|int }}"
group: "{{ ansible_effective_group_id|int }}"
recurse: yes
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['hdb-mongo-primary', 'hdb-mongo-secondary', 'hdb-mongo-analytics']
- name: Start db-mongo-analytics docker container
become: yes
@ -131,7 +198,35 @@
- /srv/newsblur/config/mongodb_keyfile.key:/srv/newsblur/config/mongodb_keyfile.key
- /var/log/mongodb/:/var/log/mongodb/
- /mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}/backup/:/backup/
when: (inventory_hostname | regex_replace('[0-9]+', '')) == 'db-mongo-analytics'
when: (inventory_hostname | regex_replace('[0-9]+', '')) in ['db-mongo-analytics']
- name: Start db-mongo-analytics docker container on hetzner
become: yes
docker_container:
name: mongo
image: mongo:4.0
state: started
container_default_behavior: no_defaults
hostname: "{{ inventory_hostname }}"
restart_policy: unless-stopped
networks_cli_compatible: yes
# network_mode: host
network_mode: default
networks:
- name: newsblurnet
aliases:
- mongo
ports:
- "27017:27017"
command: --config /etc/mongod.conf
user: 1000:1001
volumes:
- /srv/newsblur/docker/volumes/mongo:/data/db
- /srv/newsblur/ansible/roles/mongo/templates/mongo.analytics.conf:/etc/mongod.conf
- /srv/newsblur/config/mongodb_keyfile.key:/srv/newsblur/config/mongodb_keyfile.key
- /var/log/mongodb/:/var/log/mongodb/
- /srv/newsblur/docker/volumes/mongo/backup/:/backup/
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['hdb-mongo-analytics']
- name: Create mongo database user
shell:
@ -148,7 +243,6 @@
]
}
)' admin
when: (inventory_hostname | regex_replace('[0-9]+', '')) == 'db-mongo-analytics'
register: auth_result
changed_when:
- auth_result.rc == 0
@ -170,7 +264,7 @@
template:
src: consul_service.json
dest: /etc/consul.d/mongo.json
when: (inventory_hostname | regex_replace('[0-9]+', '')) in ['db-mongo-primary', 'db-mongo-secondary']
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['db-mongo-primary', 'db-mongo-secondary', 'hdb-mongo-primary', 'hdb-mongo-secondary']
notify:
- reload consul
@ -180,7 +274,7 @@
template:
src: consul_service.analytics.json
dest: /etc/consul.d/mongo.json
when: (inventory_hostname | regex_replace('[0-9]+', '')) == 'db-mongo-analytics'
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['db-mongo-analytics', 'hdb-mongo-analytics']
notify:
- reload consul
@ -214,7 +308,6 @@
register: app_changed
- name: Add mongo backup log
become: yes
file:
path: /var/log/mongo_backup.log
state: touch

View file

@ -1,6 +1,10 @@
{
"service": {
"name": "db-mongo-analytics",
{% if not mongo_analytics_secondary %}
"name": "db-mongo-analytics",
{% else %}
"name": "db-mongo-analytics-secondary",
{% endif %}
"id": "{{ inventory_hostname }}",
"tags": [
"db"

View file

@ -1,4 +1,13 @@
---
- name: Ensure 'nb' user owns /srv/ recursively
become: yes
file:
path: /srv
owner: nb
group: nb
recurse: yes
state: directory
- name: Copy node secrets
copy:
src: /srv/secrets-newsblur/settings/node_settings.env
@ -34,22 +43,22 @@
- name: Get the volume name
shell: ls /dev/disk/by-id/ | grep -v part
register: volume_name_raw
when: '"node-page" in inventory_hostname'
when: '"node-page" in inventory_hostname and not inventory_hostname.startswith("hnode")'
- set_fact:
volume_name: "{{ volume_name_raw.stdout }}"
when: '"node-page" in inventory_hostname'
when: '"node-page" in inventory_hostname and not inventory_hostname.startswith("hnode")'
- debug:
msg: "{{ volume_name }}"
when: '"node-page" in inventory_hostname'
when: '"node-page" in inventory_hostname and not inventory_hostname.startswith("hnode")'
- name: Create the mount point
become: yes
file:
path: "/mnt/{{ inventory_hostname | regex_replace('-', '') }}"
state: directory
when: '"node-page" in inventory_hostname'
when: '"node-page" in inventory_hostname and not inventory_hostname.startswith("hnode")'
- name: Mount volume read-write
become: yes
@ -59,7 +68,7 @@
fstype: xfs
opts: defaults,discard
state: mounted
when: '"node-page" in inventory_hostname'
when: '"node-page" in inventory_hostname and not inventory_hostname.startswith("hnode")'
- name: Symlink node-page volume from /srv/originals
become: yes
@ -67,10 +76,9 @@
dest: /srv/originals
src: "/mnt/{{ inventory_hostname | regex_replace('-', '') }}"
state: link
when: '"node-page" in inventory_hostname'
when: '"node-page" in inventory_hostname and not inventory_hostname.startswith("hnode")'
- name: Start node docker containers
become: yes
docker_container:
name: node
image: newsblur/newsblur_node
@ -104,7 +112,6 @@
when: item in inventory_hostname
- name: Start non-newsblur node docker containers
become: yes
docker_container:
name: "{{ item.container_name }}"
image: "{{ item.image }}"

View file

@ -1,6 +1,6 @@
{
"service": {
"name": "{{ inventory_hostname|regex_replace('\d+', '') }}",
"name": "{{ inventory_hostname|regex_replace('\-?\d+', '')|regex_replace('hnode', 'node') }}",
"id": "{{ inventory_hostname }}",
"tags": [
"web",

View file

@ -10,5 +10,5 @@
- name: reload postgres config
become: yes
command: docker exec -u postgres postgres pg_ctl reload
command: docker exec postgres pg_ctl reload
listen: reload postgres

View file

@ -1,4 +1,17 @@
---
- name: Set facts for all postgres servers
set_fact:
postgres_secondary: no
tags:
- always
- name: Set facts for secondary postgres servers
set_fact:
postgres_secondary: yes
# when: inventory_hostname not in ["db-postgres2"]
when: inventory_hostname not in ["hdb-postgres-1"]
tags:
- always
- name: Template postgresql-13.conf file
template:
@ -6,24 +19,67 @@
dest: /srv/newsblur/docker/postgres/postgres.conf
notify: reload postgres
register: updated_config
- name: Create Postgres docker volumes with correct permissions
become: yes
file:
path: "{{ item }}"
path: "{{ item }}"
state: directory
recurse: yes
owner: "{{ ansible_effective_user_id|int }}"
group: "{{ ansible_effective_group_id|int }}"
owner: 999
group: 999
with_items:
- /srv/newsblur/docker/volumes/postgres/archive
- /srv/newsblur/docker/volumes/postgres/backups
- /srv/newsblur/docker/volumes/postgres/data
- name: Template postgres secondaries with empty standby.signal file
become: yes
copy:
dest: /srv/newsblur/docker/volumes/postgres/data/standby.signal
content: ""
owner: 999
group: 999
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['db-postgres-secondary', 'hdb-postgres']
- name: Copy SSH private key
become: yes
copy:
src: /srv/secrets-newsblur/keys/postgres.key
dest: /home/nb/.ssh/id_rsa
owner: 999
group: 999
mode: "0600"
- name: Copy SSH public key
become: yes
copy:
src: /srv/secrets-newsblur/keys/postgres.key.pub
dest: /home/nb/.ssh/id_rsa.pub
owner: 999
group: 999
mode: "0600"
- name: Add SSH public key to authorized keys
authorized_key:
user: "nb"
state: present
key: "{{ lookup('file', '/srv/secrets-newsblur/keys/postgres.key.pub') }}"
- name: Build the custom postgres docker image
docker_image:
name: newsblur/postgres:13
source: build
build:
path: /srv/newsblur/docker/postgres/
force_tag: yes
state: present
- name: Start postgres docker containers
become: yes
docker_container:
name: postgres
image: postgres:13
image: newsblur/postgres:13
state: started
container_default_behavior: no_defaults
command: postgres -c config_file=/etc/postgresql/postgresql.conf
@ -35,11 +91,11 @@
network_mode: default
networks:
- name: newsblurnet
aliases:
aliases:
- postgres
user: "999:999"
ports:
- 5432:5432
user: 1000:1001
volumes:
- /srv/newsblur/docker/volumes/postgres/data:/var/lib/postgresql/data
- /srv/newsblur/docker/volumes/postgres/archive:/var/lib/postgresql/archive
@ -47,15 +103,16 @@
- /srv/newsblur/docker/postgres/postgres.conf:/etc/postgresql/postgresql.conf
- /srv/newsblur/docker/postgres/postgres_hba-13.conf:/etc/postgresql/pg_hba.conf
- /srv/newsblur/docker/postgres/postgres_ident-13.conf:/etc/postgresql/pg_ident.conf
- /home/nb/.ssh/id_rsa:/var/lib/postgresql/.ssh/id_rsa
restart_policy: unless-stopped
when: (inventory_hostname | regex_replace('[0-9]+', '')) in ['db-postgres-primary', 'db-postgres']
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['db-postgres-primary', 'db-postgres', 'hdb-postgres', 'hdb-postgres-secondary']
# - name: Change ownership in postgres docker container
# become: yes
# command: >
# docker exec postgres chown -fR postgres.postgres /var/lib/postgresql
# ignore_errors: yes
- name: Change ownership in postgres docker container
become: yes
command: >
docker exec postgres chown -fR postgres.postgres /var/lib/postgresql
ignore_errors: yes
- name: Ensure newsblur role in postgres
become: yes
shell: >
@ -63,9 +120,10 @@
docker exec postgres createuser -s newsblur -U postgres;
docker exec postgres createdb newsblur -U newsblur;
register: ensure_role
ignore_errors: yes
changed_when:
- "ensure_role.rc == 0"
failed_when:
- "ensure_role.rc == 0"
failed_when:
- "'already exists' not in ensure_role.stderr"
- "ensure_role.rc != 0"
@ -89,7 +147,9 @@
name: disk_usage_sanity_checker
minute: "0"
job: >-
OUTPUT=$(df / | head -n 2 | tail -1) docker run --rm -it -v /srv/newsblur:/srv/newsblur --network=newsblurnet --hostname {{ ansible_hostname }} newsblur/newsblur_python3 /srv/newsblur/utils/monitor_disk_usage.py $OUTPUT
OUTPUT=$(df / | head -n 2 | tail -1) docker run --rm -it -v /srv/newsblur:/srv/newsblur \
--network=newsblurnet --hostname {{ ansible_hostname }} newsblur/newsblur_python3 \
/srv/newsblur/utils/monitor_disk_usage.py $OUTPUT
tags: cron
- name: Add postgresql archive cleaner cronjob
@ -115,8 +175,8 @@
path: /var/log/postgres_backup.log
state: touch
mode: 0777
owner: 1000
group: 1001
owner: 999
group: 999
- name: Add postgres backup
cron:
@ -125,4 +185,3 @@
hour: "4"
job: /srv/newsblur/docker/postgres/backup_postgres.sh >> /var/log/postgres_backup.log 2>&1
tags: cron

View file

@ -1,6 +1,6 @@
{
"service": {
{% if inventory_hostname.startswith('db-postgres2') %}
{% if not postgres_secondary %}
"name": "db-postgres",
{% else %}
"name": "db-postgres-secondary",

View file

@ -6,7 +6,7 @@
restart_policy: unless-stopped
container_default_behavior: no_defaults
env:
REDIS_ADDR: "db-{{item.redis_target}}.service.nyc1.consul:6379"
REDIS_ADDR: "db-{{item.redis_target}}.service.nyc1.consul:{{ item.redis_port }}"
networks_cli_compatible: yes
network_mode: default
networks:
@ -16,12 +16,16 @@
with_items:
- port: 9121
redis_target: "redis-user"
redis_port: 6381
- port: 9122
redis_target: "redis-sessions"
redis_target: "redis-session"
redis_port: 6382
- port: 9123
redis_target: "redis-story"
redis_port: 6380
- port: 9124
redis_target: "redis-pubsub"
redis_port: 6383
- name: Register redis-exporters in consul
tags: consul
@ -35,7 +39,7 @@
- port: 9121
redis_target: "redis-user"
- port: 9122
redis_target: "redis-sessions"
redis_target: "redis-session"
- port: 9123
redis_target: "redis-story"
- port: 9124

View file

@ -8,7 +8,19 @@
state: reloaded
listen: reload consul
- name: restart redis
- name: restart redis user
become: yes
command: docker restart redis
listen: restart redis
command: docker restart redis-user
listen: restart redis_user
- name: restart redis story
become: yes
command: docker restart redis-story
listen: restart redis_story
- name: restart redis session
become: yes
command: docker restart redis-session
listen: restart redis_session
- name: restart redis pubsub
become: yes
command: docker restart redis-pubsub
listen: restart redis_pubsub

View file

@ -1,4 +1,31 @@
---
- name: Extract part of hostname to determine container name
set_fact:
redis_role: "{{ inventory_hostname.split('-')[2] }}"
redis_secondary: no
# redis_port: 6379
redis_ports:
story: 6380
user: 6381
session: 6382
pubsub: 6383
tags:
- always
- name: Set redis_port for redis servers
set_fact:
redis_port: "{{ redis_ports[redis_role] }}"
tags:
- always
- name: Set redis_secondary for secondary redis servers
set_fact:
redis_secondary: yes
# when: inventory_hostname not in ["db-redis-user", "db-redis-story1", "db-redis-session", "db-redis-pubsub"]
when: inventory_hostname not in ["hdb-redis-user-1", "hdb-redis-story-1", "hdb-redis-session-1", "hdb-redis-pubsub"]
tags:
- always
- name: Install sysfsutils for disabling transparent huge pages
become: yes
package:
@ -25,29 +52,42 @@
copy:
src: /srv/newsblur/docker/redis/redis.conf
dest: /srv/newsblur/docker/redis/redis.conf
notify: restart redis
notify: "restart redis_{{ redis_role }}"
register: updated_config
- name: Template redis_replica.conf file
template:
src: /srv/newsblur/docker/redis/redis_replica.conf.j2
dest: /srv/newsblur/docker/redis/redis_replica.conf
notify: restart redis
dest: "/srv/newsblur/docker/redis/redis_{{ redis_role }}_replica.conf"
notify: "restart redis_{{ redis_role }}"
register: updated_config
when: "'db-redis-story1' in inventory_hostname"
when: redis_secondary
- name: Remove redis_replica.conf file if not secondary
copy:
dest: "/srv/newsblur/docker/redis/redis_{{ redis_role }}_replica.conf"
content: ""
when: not redis_secondary
- name: Create Redis docker volume directory
file:
path: /srv/newsblur/docker/volumes
state: directory
recurse: yes
owner: "{{ ansible_effective_user_id|int }}"
group: "{{ ansible_effective_group_id|int }}"
- name: Create Redis docker volume with correct permissions
file:
path: /srv/newsblur/docker/volumes/redis
path: "/srv/newsblur/docker/volumes/redis_{{ redis_role }}"
state: directory
recurse: yes
owner: "{{ ansible_effective_user_id|int }}"
group: "{{ ansible_effective_group_id|int }}"
- name: Start redis docker containers
become: yes
docker_container:
name: redis
name: "redis-{{ redis_role }}"
image: redis:7
pull: true
state: started
@ -62,19 +102,19 @@
aliases:
- redis
ports:
- 6379:6379
- "{{ redis_port }}:6379"
restart_policy: unless-stopped
user: 1000:1001
user: "{{ ansible_effective_user_id|int }}:{{ ansible_effective_group_id|int }}"
volumes:
- /srv/newsblur/docker/volumes/redis:/data
- /srv/newsblur/docker/redis/redis.conf:/usr/local/etc/redis/redis_server.conf
- /srv/newsblur/docker/redis/redis_replica.conf:/usr/local/etc/redis/redis_replica.conf
- "/srv/newsblur/docker/volumes/redis_{{ redis_role }}:/data"
- "/srv/newsblur/docker/redis/redis.conf:/usr/local/etc/redis/redis_server.conf"
- "/srv/newsblur/docker/redis/redis_{{ redis_role }}_replica.conf:/usr/local/etc/redis/redis_replica.conf"
- name: Register redis in consul
become: yes
template:
src: consul_service.json
dest: /etc/consul.d/redis.json
dest: "/etc/consul.d/redis_{{ redis_role }}.json"
notify:
- reload consul
tags: consul
@ -123,7 +163,7 @@
job: >
docker run --rm
-v /srv/newsblur:/srv/newsblur
-v /srv/newsblur/docker/volumes/redis/dump.rdb:/data/dump.rdb
-v /srv/newsblur/docker/volumes/redis_{{ redis_role }}/dump.rdb:/data/dump.rdb
--network=newsblurnet
--hostname={{ ansible_hostname }}
newsblur/newsblur_python3 python /srv/newsblur/utils/backups/backup_redis.py

View file

@ -1,25 +1,25 @@
{
"service": {
{% if inventory_hostname in ["db-redis-user", "db-redis-story2", "db-redis-session", "db-redis-pubsub"] %}
"name": "{{ inventory_hostname|regex_replace('\d+', '') }}",
{% if not redis_secondary %}
"name": "{{ inventory_hostname|regex_replace('\-?\d+', '')|regex_replace("hdb", "db") }}",
{% else %}
"name": "{{ inventory_hostname|regex_replace('\d+', '') }}-staging",
"name": "db-redis-{{ redis_role }}-staging",
{% endif %}
"id": "{{ inventory_hostname }}",
"tags": [
"redis"
],
"port": 6379,
"port": {{ redis_port }},
"checks": [{
"id": "{{inventory_hostname}}-ping",
{% if inventory_hostname.startswith('db-redis-story') %}
"http": "http://{{ ansible_host }}:5579/db_check/redis_story?consul=1",
{% elif inventory_hostname.startswith('db-redis-user') %}
"http": "http://{{ ansible_host }}:5579/db_check/redis_user?consul=1",
{% elif inventory_hostname.startswith('db-redis-pubsub') %}
"http": "http://{{ ansible_host }}:5579/db_check/redis_pubsub?consul=1",
{% elif inventory_hostname.startswith('db-redis-sessions') %}
"http": "http://{{ ansible_host }}:5579/db_check/redis_sessions?consul=1",
{% if 'db-redis-story' in inventory_hostname %}
"http": "http://{{ ansible_host }}:5579/db_check/redis_story?consul=1&port={{ redis_port }}",
{% elif 'db-redis-user' in inventory_hostname %}
"http": "http://{{ ansible_host }}:5579/db_check/redis_user?consul=1&port={{ redis_port }}",
{% elif 'db-redis-pubsub' in inventory_hostname %}
"http": "http://{{ ansible_host }}:5579/db_check/redis_pubsub?consul=1&port={{ redis_port }}",
{% elif 'db-redis-session' in inventory_hostname %}
"http": "http://{{ ansible_host }}:5579/db_check/redis_sessions?consul=1&port={{ redis_port }}",
{% else %}
"http": "http://{{ ansible_host }}:5000/db_check/redis?consul=1",
{% endif %}

View file

@ -1,13 +1,26 @@
---
- name: Stop ufw and delete all rules
become: yes
ufw: state=reset
tags: ufw
- name: Set firewall default policy
- name: Set hosts
set_fact:
hetzner_hosts: "{{ groups['hall'] | map('extract', hostvars, ['ansible_host']) }}"
do_hosts: "{{ groups['NewsBlur_Docker'] | map('extract', hostvars, ['ansible_host']) }}"
- name: Generate UFW batch script
become: yes
ufw: state=disabled policy=reject
tags: ufw
template:
src: ufw_rules.sh.j2
dest: /tmp/ufw_rules.sh
mode: '0755'
# - name: Stop ufw and delete all rules
# become: yes
# ufw: state=reset
# tags: ufw
# - name: Set firewall default policy
# become: yes
# ufw: state=disabled policy=reject
# tags: ufw
#
# - name: Set ufw policy to deny all incoming connections
# ufw: policy=deny direction=incoming
@ -16,58 +29,87 @@
# - name: Set ufw policy to allow all ougoing connections
# ufw: policy=allow direction=outgoing
# tags: ufw
- name: Execute UFW batch script
become: yes
command: /tmp/ufw_rules.sh
# - name: Allow ssh
# become: yes
# ufw: rule=allow port=22
# tags: ufw
# - name: Allow all access from RFC1918 networks to this host
# become: yes
# ufw:
# rule: allow
# src: '{{ item }}'
# with_items:
# - 10.0.0.0/8
# - 172.18.0.0/16
# - 172.17.0.0/16
# tags:
# - firewall
# - ufw
- name: Allow ssh
become: yes
ufw: rule=allow port=22
tags: ufw
# - name: Allow all access from Hetzner inventory hosts
# become: yes
# ufw:
# rule: allow
# src: '{{ item }}'
# with_items: "{{ groups['hall'] | map('extract', hostvars, ['ansible_host']) }}"
# tags:
# - firewall
# - ufw
# - hetzner_firewall
# - hfirewall
- name: Allow all access from RFC1918 networks to this host
become: yes
ufw:
rule: allow
src: '{{ item }}'
with_items:
- 10.0.0.0/8
- 172.18.0.0/16
- 172.17.0.0/16
tags:
- firewall
- ufw
- name: Allow all access from inventory hosts old + new
become: yes
ufw:
rule: allow
src: '{{ item }}'
with_items: "{{ groups['oldandnew'] | map('extract', hostvars, ['ansible_host']) }}"
when: "'oldandnew' in groups"
tags:
- firewall
- ufw
# - name: Allow all access from Hetzner inventory hosts with docker
# become: yes
# ufw:
# rule: allow
# route: yes
# src: '{{ item }}'
# with_items: "{{ groups['hall'] | map('extract', hostvars, ['ansible_host']) }}"
# tags:
# - firewall
# - ufw
# - hetzner_firewall
# - hfirewall
- name: Allow all access from inventory hosts
become: yes
ufw:
rule: allow
src: '{{ item }}'
with_items: "{{ groups['NewsBlur_Docker'] | map('extract', hostvars, ['ansible_host']) }}"
when: "'oldandnew' not in groups"
tags:
- firewall
- ufw
# - name: Allow all access from inventory hosts old + new
# become: yes
# ufw:
# rule: allow
# src: '{{ item }}'
# with_items: "{{ groups['oldandnew'] | map('extract', hostvars, ['ansible_host']) }}"
# when: "'oldandnew' in groups"
# tags:
# - firewall
# - ufw
- name: Allow all access from inventory hosts with docker
become: yes
ufw:
rule: allow
route: yes
src: '{{ item }}'
with_items: "{{ groups['NewsBlur_Docker'] | map('extract', hostvars, ['ansible_host']) }}"
when: "'oldandnew' not in groups"
tags:
- firewall
- ufw
# - name: Allow all access from inventory hosts
# become: yes
# ufw:
# rule: allow
# src: '{{ item }}'
# with_items: "{{ groups['NewsBlur_Docker'] | map('extract', hostvars, ['ansible_host']) }}"
# when: "'oldandnew' not in groups"
# tags:
# - firewall
# - ufw
# - name: Allow all access from inventory hosts with docker
# become: yes
# ufw:
# rule: allow
# route: yes
# src: '{{ item }}'
# with_items: "{{ groups['NewsBlur_Docker'] | map('extract', hostvars, ['ansible_host']) }}"
# when: "'oldandnew' not in groups"
# tags:
# - firewall
# - ufw
# Code from https://stackoverflow.com/a/51741599/8717: "What is the best practice of docker + ufw under Ubuntu"
- name: Solving UFW and Docker issues by adding ufw after.rules

View file

@ -0,0 +1,43 @@
#!/bin/bash
# Script to apply UFW rules incrementally with regex matching for `ufw status verbose` output
# Fetch the current UFW status once and store it
CURRENT_UFW_STATUS=$(ufw status verbose)
# Function to check and apply a rule with regex for forward rules
apply_rule() {
local rule="$1"
local rule_type="$2" # "IN" for incoming, "FWD" for forwarded (route) rules
local ip_address="$3"
# Construct the regex pattern based on rule type
local regex_pattern
if [ "$rule_type" == "FWD" ]; then
regex_pattern="ALLOW FWD\\s+$ip_address"
else
regex_pattern="ALLOW IN\\s+$ip_address"
fi
# Use grep with -P for Perl-compatible regex, and -q to quietly check for a match
if echo "$CURRENT_UFW_STATUS" | grep -Pq -- "$regex_pattern"; then
echo "Rule already exists: $regex_pattern"
else
echo "Applying rule: $rule"
ufw $rule
fi
}
# Apply rules
# Example for direct allow rules
apply_rule "allow 22" "IN" "22" # IP address parameter is not used for this rule
# Example for forwarded (route) rules
{% for host in hetzner_hosts %}
apply_rule "allow from {{ host }}" "IN" "{{ host }}"
apply_rule "route allow from {{ host }}" "FWD" "{{ host }}"
{% endfor %}
{% for host in do_hosts %}
apply_rule "allow from {{ host }}" "IN" "{{ host }}"
apply_rule "route allow from {{ host }}" "FWD" "{{ host }}"
{% endfor %}

View file

@ -29,6 +29,14 @@
tags:
- static
- name: "Write out ips from ansible inventory of all servers with app/node/www/task in name into addresses.txt file"
template:
src: ip_addresses.txt.j2
dest: /srv/newsblur/apps/api/ip_addresses.txt
tags:
- inventory
- name: Prune docker
become: yes
community.docker.docker_prune:

View file

@ -1,6 +1,6 @@
{
"service": {
"name": "{{ inventory_hostname|regex_replace('\d+', '') }}",
"name": "{{ inventory_hostname|regex_replace('\-?\d+', '')|regex_replace('happ', 'app') }}",
"id": "{{ inventory_hostname }}-web",
"tags": [
"web"

View file

@ -0,0 +1,5 @@
{%- for item in hostvars %}
{% if "happ" in item or "hnode" in item or "hwww" in item or "htask" in item %}
{{ hostvars[item].ansible_host }}
{% endif %}
{%- endfor %}

View file

@ -26,4 +26,4 @@
- import_playbook: playbooks/setup_metrics.yml
when: "'metrics' in inventory_hostname"
- import_playbook: playbooks/setup_sentry.yml
when: "'sentry' in inventory_hostname"
when: "'sentry' in inventory_hostname or 'metrics' in inventory_hostname"

View file

@ -0,0 +1,34 @@
#!/usr/bin/env python
import os
import subprocess
import sys
import time
from hetzner.robot import Robot
TOKEN_FILE = "/srv/secrets-newsblur/keys/hetzner.yaml"
import requests
import yaml
# Load credentials from a YAML file
with open(TOKEN_FILE, "r") as file:
credentials = yaml.safe_load(file)
user = credentials["hetzner_robot"]["username"]
password = credentials["hetzner_robot"]["password"]
outfile = f"/srv/newsblur/ansible/inventories/hetzner.ini"
print(user, password)
robot = Robot(user, password)
# Check if the request was successful
if robot.servers:
with open(outfile, "w") as inventory_file:
inventory_file.write("[hetzner_servers]\n")
for server in robot.servers:
# Assuming the server IP is under 'server_ip' key
inventory_file.write(f"{server.ip}\n")
print(f"Inventory file 'hetzner_inventory.ini' created with {len(servers)} servers")
else:
print(f"Failed to fetch server data")

View file

@ -1,25 +1,28 @@
import os
import base64
import urllib.parse
import datetime
import os
import urllib.parse
import lxml.html
from django import forms
from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import render
from django.contrib.auth import login as login_user
from django.contrib.auth import logout as logout_user
from apps.reader.forms import SignupForm, LoginForm
from django.core.mail import mail_admins
from django.http import HttpResponse
from django.shortcuts import render
from apps.profile.models import Profile
from apps.social.models import MSocialProfile, MSharedStory, MSocialSubscription
from apps.rss_feeds.models import Feed, MStarredStoryCounts, MStarredStory
from apps.reader.forms import LoginForm, SignupForm
from apps.reader.models import RUserStory, UserSubscription, UserSubscriptionFolders
from apps.rss_feeds.models import Feed, MStarredStory, MStarredStoryCounts
from apps.rss_feeds.text_importer import TextImporter
from apps.reader.models import UserSubscription, UserSubscriptionFolders, RUserStory
from apps.social.models import MSharedStory, MSocialProfile, MSocialSubscription
from utils import json_functions as json
from utils import log as logging
from utils.feed_functions import relative_timesince
from utils.user_functions import ajax_login_required, get_user
from utils.view_functions import required_params
from utils.user_functions import get_user, ajax_login_required
@json.json_view
@ -511,8 +514,10 @@ def save_story(request, token=None):
return response
def ip_addresses(request):
import digitalocean
doapi = digitalocean.Manager(token=settings.DO_TOKEN_API_IPADDRESSES)
droplets = doapi.get_all_droplets()
addresses = '\n'.join([d.ip_address for d in droplets if any(name in d.name for name in ['task', 'staging', 'app', 'node'])])
# Read local file /srv/newsblur/apps/api/ip_addresses.txt and return that
with open('/srv/newsblur/apps/api/ip_addresses.txt', 'r') as f:
addresses = f.read()
mail_admins(f"IP Addresses accessed from {request.META['REMOTE_ADDR']} by {request.user}", addresses)
return HttpResponse(addresses, content_type='text/plain')

View file

@ -24,7 +24,7 @@ from utils.view_functions import is_true
from utils.story_functions import truncate_chars
from utils import log as logging
from utils import mongoengine_fields
from apns2.errors import BadDeviceToken, Unregistered
from apns2.errors import BadDeviceToken, Unregistered, DeviceTokenNotForTopic
from apns2.client import APNsClient
from apns2.payload import Payload
from bs4 import BeautifulSoup
@ -306,14 +306,15 @@ class MUserFeedNotification(mongo.Document):
tokens = MUserNotificationTokens.get_tokens_for_user(self.user_id)
# To update APNS:
# 0. Upgrade to latest openssl: brew install openssl
# 1. Create certificate signing request in Keychain Access
# 2. Upload to https://developer.apple.com/account/resources/certificates/list
# 3. Download to secrets/certificates/ios/aps.cer
# 4. Open in Keychain Access:
# 4. Open in Keychain Access, Under "My Certificates":
# - export "Apple Push Service: com.newsblur.NewsBlur" as aps.p12 (or just use aps.cer in #5)
# - export private key as aps_key.p12 WITH A PASSPHRASE (removed later)
# 5. openssl x509 -in aps.cer -inform DER -out aps.pem -outform PEM
# 6. openssl pkcs12 -nocerts -out aps_key.pem -in aps_key.p12
# 6. openssl pkcs12 -in aps_key.p12 -out aps_key.pem -nodes -legacy
# 7. openssl rsa -out aps_key.noenc.pem -in aps_key.pem
# 7. cat aps.pem aps_key.noenc.pem > aps.p12.pem
# 8. Verify: openssl s_client -connect gateway.push.apple.com:2195 -cert aps.p12.pem
@ -348,7 +349,7 @@ class MUserFeedNotification(mongo.Document):
)
try:
apns.send_notification(token, payload, topic="com.newsblur.NewsBlur")
except (BadDeviceToken, Unregistered):
except (BadDeviceToken, Unregistered, DeviceTokenNotForTopic):
logging.user(user, '~BMiOS token expired: ~FR~SB%s' % (token[:50]))
else:
confirmed_ios_tokens.append(token)

View file

@ -1598,7 +1598,7 @@ class Profile(models.Model):
self.save()
delta = datetime.datetime.now() - self.last_seen_on
months_ago = delta.days / 30
months_ago = round(delta.days / 30)
user = self.user
data = dict(user=user, months_ago=months_ago)
text = render_to_string('mail/email_premium_expire_grace.txt', data)
@ -1627,7 +1627,7 @@ class Profile(models.Model):
return
delta = datetime.datetime.now() - self.last_seen_on
months_ago = delta.days / 30
months_ago = round(delta.days / 30)
user = self.user
data = dict(user=user, months_ago=months_ago)
text = render_to_string('mail/email_premium_expire.txt', data)

View file

@ -1,81 +1,125 @@
import base64
import concurrent
import datetime
import random
import re
import socket
import ssl
import time
import urllib.error
import urllib.parse
import urllib.request
import zlib
import redis
import requests
import random
import zlib
import concurrent
import re
import ssl
import socket
import base64
import urllib.request, urllib.error, urllib.parse
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.template.loader import render_to_string
from django.db import IntegrityError
from django.db.utils import DatabaseError
from django.db.models import Q
from django.views.decorators.cache import never_cache
from django.urls import reverse
from django.conf import settings
from django.contrib.auth import login as login_user
from django.contrib.auth import logout as logout_user
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404, UnreadablePostError
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.mail import EmailMultiAlternatives
from django.core.validators import validate_email
from django.contrib.sites.models import Site
from django.db import IntegrityError
from django.db.models import Q
from django.db.utils import DatabaseError
from django.http import (
Http404,
HttpResponse,
HttpResponseForbidden,
HttpResponseRedirect,
UnreadablePostError,
)
from django.shortcuts import get_object_or_404, render
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils import feedgenerator
from django.utils.encoding import smart_str
from mongoengine.queryset import OperationError
from mongoengine.queryset import NotUniqueError
from apps.recommendations.models import RecommendedFeed
from apps.analyzer.models import MClassifierTitle, MClassifierAuthor, MClassifierFeed, MClassifierTag
from apps.analyzer.models import apply_classifier_titles, apply_classifier_feeds
from apps.analyzer.models import apply_classifier_authors, apply_classifier_tags
from apps.analyzer.models import get_classifiers_for_user, sort_classifiers_by_feed
from apps.profile.models import Profile, MCustomStyling, MDashboardRiver
from apps.reader.models import UserSubscription, UserSubscriptionFolders, RUserStory, RUserUnreadStory, Feature
from apps.reader.forms import SignupForm, LoginForm, FeatureForm
from apps.rss_feeds.models import MFeedIcon, MStarredStoryCounts, MSavedSearch
from django.views.decorators.cache import never_cache
from mongoengine.queryset import NotUniqueError, OperationError
from apps.analyzer.models import (
MClassifierAuthor,
MClassifierFeed,
MClassifierTag,
MClassifierTitle,
apply_classifier_authors,
apply_classifier_feeds,
apply_classifier_tags,
apply_classifier_titles,
get_classifiers_for_user,
sort_classifiers_by_feed,
)
from apps.notifications.models import MUserFeedNotification
from apps.profile.models import MCustomStyling, MDashboardRiver, Profile
from apps.reader.forms import FeatureForm, LoginForm, SignupForm
from apps.reader.models import (
Feature,
RUserStory,
RUserUnreadStory,
UserSubscription,
UserSubscriptionFolders,
)
from apps.recommendations.models import RecommendedFeed
from apps.rss_feeds.models import MFeedIcon, MSavedSearch, MStarredStoryCounts
from apps.search.models import MUserSearch
from apps.statistics.models import MStatistics, MAnalyticsLoader
from apps.statistics.models import MAnalyticsLoader, MStatistics
from apps.statistics.rstats import RStats
# from apps.search.models import SearchStarredStory
try:
from apps.rss_feeds.models import Feed, MFeedPage, DuplicateFeed, MStory, MStarredStory
from apps.rss_feeds.models import (
DuplicateFeed,
Feed,
MFeedPage,
MStarredStory,
MStory,
)
except:
pass
from apps.social.models import MSharedStory, MSocialProfile, MSocialServices
from apps.social.models import MSocialSubscription, MActivity, MInteraction
from apps.categories.models import MCategory
from apps.social.views import load_social_page
from apps.rss_feeds.tasks import ScheduleImmediateFetches
from utils import json_functions as json
from utils.user_functions import get_user, ajax_login_required
from utils.user_functions import extract_user_agent
from utils.feed_functions import relative_timesince
from utils.story_functions import format_story_link_date__short
from utils.story_functions import format_story_link_date__long
from utils.story_functions import strip_tags
from utils import log as logging
from utils.view_functions import get_argument_or_404, render_to, is_true
from utils.view_functions import required_params
from utils.ratelimit import ratelimit
from vendor.timezones.utilities import localtime_for_timezone
import tweepy
from apps.categories.models import MCategory
from apps.rss_feeds.tasks import ScheduleImmediateFetches
from apps.social.models import (
MActivity,
MInteraction,
MSharedStory,
MSocialProfile,
MSocialServices,
MSocialSubscription,
)
from apps.social.views import load_social_page
from utils import json_functions as json
from utils import log as logging
from utils.feed_functions import relative_timesince
from utils.ratelimit import ratelimit
from utils.story_functions import (
format_story_link_date__long,
format_story_link_date__short,
strip_tags,
)
from utils.user_functions import ajax_login_required, extract_user_agent, get_user
from utils.view_functions import (
get_argument_or_404,
is_true,
render_to,
required_params,
)
from vendor.timezones.utilities import localtime_for_timezone
BANNED_URLS = [
"brentozar.com",
]
ALLOWED_SUBDOMAINS = [
'dev',
'www',
'hwww',
'dwww',
# 'beta', # Comment to redirect beta -> www, uncomment to allow beta -> staging (+ dns changes)
'staging',
'hstaging',
'discovery',
'debug',
'debug3',
@ -671,10 +715,14 @@ def load_single_feed(request, feed_id):
if feed.is_newsletter and not usersub:
# User must be subscribed to a newsletter in order to read it
raise Http404
if feed.num_subscribers == 1 and not usersub and not user.is_staff:
# This feed could be private so user must be subscribed in order to read it
raise Http404
if page > 400:
logging.user(request, "~BR~FK~SBOver page 400 on single feed: %s" % page)
assert False
raise Http404
if query:
if user.profile.is_premium:
@ -874,7 +922,6 @@ def load_feed_page(request, feed_id):
raise Http404
feed = Feed.get_by_id(feed_id)
if feed and feed.has_page and not feed.has_page_exception:
if settings.BACKED_BY_AWS.get('pages_on_node'):
domain = Site.objects.get_current().domain
@ -883,8 +930,9 @@ def load_feed_page(request, feed_id):
feed.pk,
)
try:
page_response = requests.get(url)
except requests.ConnectionError:
page_response = requests.get(url, verify=not settings.DEBUG)
except requests.ConnectionError as e:
logging.user(request, f"~FR~SBError loading original page: {url} {e}")
page_response = None
if page_response and page_response.status_code == 200:
response = HttpResponse(page_response.content, content_type="text/html; charset=utf-8")

View file

@ -1,33 +1,36 @@
import urllib.request
import base64
import datetime
import gzip
import http.client
import operator
import struct
import urllib.error
import urllib.parse
import urllib.request
from io import BytesIO
from socket import error as SocketError
import boto3
import lxml.html
import numpy
import scipy
import scipy.misc
import scipy.cluster
import struct
import operator
import gzip
import datetime
import requests
import base64
import http.client
from PIL import BmpImagePlugin, PngImagePlugin, Image
from socket import error as SocketError
import boto3
from io import BytesIO
import scipy
import scipy.cluster
import scipy.misc
from django.conf import settings
from django.http import HttpResponse
from django.contrib.sites.models import Site
from apps.rss_feeds.models import MFeedPage, MFeedIcon
from utils.facebook_fetcher import FacebookFetcher
from utils import log as logging
from utils.feed_functions import timelimit, TimeoutError
from OpenSSL.SSL import Error as OpenSSLError, SESS_CACHE_NO_INTERNAL_STORE
from django.http import HttpResponse
from OpenSSL.SSL import SESS_CACHE_NO_INTERNAL_STORE
from OpenSSL.SSL import Error as OpenSSLError
from PIL import BmpImagePlugin, Image, PngImagePlugin
from pyasn1.error import PyAsn1Error
from requests.packages.urllib3.exceptions import LocationParseError
from apps.rss_feeds.models import MFeedIcon, MFeedPage
from utils import log as logging
from utils.facebook_fetcher import FacebookFetcher
from utils.feed_functions import TimeoutError, timelimit
class IconImporter(object):
@ -206,8 +209,10 @@ class IconImporter(object):
if self.page_data:
content = self.page_data
elif settings.BACKED_BY_AWS.get('pages_on_node'):
domain = Site.objects.get_current().domain
url = "https://%s/original_page/%s" % (
domain = "node-page.service.consul:8008"
if settings.DOCKERBUILD:
domain = "node:8008"
url = "http://%s/original_page/%s" % (
domain,
self.feed.pk,
)

View file

@ -462,6 +462,10 @@ class Feed(models.Model):
username = re.search('youtube.com/user/(\w+)', url).group(1)
url = "http://gdata.youtube.com/feeds/base/users/%s/uploads" % username
without_rss = True
if url and 'youtube.com/@' in url:
username = url.split('youtube.com/@')[1]
url = "http://gdata.youtube.com/feeds/base/users/%s/uploads" % username
without_rss = True
if url and 'youtube.com/channel/' in url:
channel_id = re.search('youtube.com/channel/([-_\w]+)', url).group(1)
url = "https://www.youtube.com/feeds/videos.xml?channel_id=%s" % channel_id

View file

@ -1,23 +1,28 @@
import requests
import re
import traceback
import feedparser
import time
import urllib.request, urllib.error, urllib.parse
import http.client
import re
import time
import traceback
import urllib.error
import urllib.parse
import urllib.request
import zlib
from socket import error as SocketError
import feedparser
import requests
from django.conf import settings
from django.contrib.sites.models import Site
from django.utils.encoding import smart_bytes
from mongoengine.queryset import NotUniqueError
from socket import error as SocketError
from django.conf import settings
from django.utils.text import compress_string as compress_string_with_gzip
from utils import log as logging
from apps.rss_feeds.models import MFeedPage
from utils.feed_functions import timelimit, TimeoutError
from mongoengine.queryset import NotUniqueError
from OpenSSL.SSL import Error as OpenSSLError
from pyasn1.error import PyAsn1Error
from sentry_sdk import capture_exception, flush
from apps.rss_feeds.models import MFeedPage
from utils import log as logging
from utils.feed_functions import TimeoutError, timelimit
# from utils.feed_functions import mail_feed_error_to_admin
BROKEN_PAGES = [
@ -127,6 +132,7 @@ class PageImporter(object):
return
except (ValueError, urllib.error.URLError, http.client.BadStatusLine, http.client.InvalidURL,
requests.exceptions.ConnectionError) as e:
logging.debug(' ***> [%-30s] Page fetch failed: %s' % (self.feed.log_title[:30], e))
self.feed.save_page_history(401, "Bad URL", e)
try:
fp = feedparser.parse(self.feed.feed_address)
@ -134,10 +140,8 @@ class PageImporter(object):
return html
feed_link = fp.feed.get('link', "")
self.feed.save()
logging.debug(' ***> [%-30s] Page fetch failed: %s' % (self.feed.log_title[:30], e))
except (urllib.error.HTTPError) as e:
self.feed.save_page_history(e.code, e.msg, e.fp.read())
except (http.client.IncompleteRead) as e:
logging.debug(' ***> [%-30s] Page fetch failed: %s' % (self.feed.log_title[:30], e))
self.feed.save_page_history(500, "IncompleteRead", e)
except (requests.exceptions.RequestException,
requests.packages.urllib3.exceptions.HTTPError) as e:
@ -305,8 +309,10 @@ class PageImporter(object):
return feed_page
def save_page_node(self, html):
domain = Site.objects.get_current().domain
url = "https://%s/original_page/%s" % (
domain = "node-page.service.consul:8008"
if settings.DOCKERBUILD:
domain = "node:8008"
url = "http://%s/original_page/%s" % (
domain,
self.feed.pk,
)

View file

@ -2,13 +2,16 @@ import datetime
import os
import shutil
import time
import redis
from newsblur_web.celeryapp import app
from celery.exceptions import SoftTimeLimitExceeded
from utils import log as logging
from django.conf import settings
from apps.profile.middleware import DBProfilerMiddleware
from newsblur_web.celeryapp import app
from utils import log as logging
from utils.redis_raw_log_middleware import RedisDumpMiddleware
FEED_TASKING_MAX = 10000
@app.task(name='task-feeds')
@ -45,6 +48,7 @@ def TaskFeeds():
else:
logging.debug(" ---> ~SN~FBToo many tasked feeds. ~SB%s~SN tasked." % tasked_feeds_size)
active_count = 0
feeds = []
logging.debug(" ---> ~SN~FBTasking %s feeds took ~SB%s~SN seconds (~SB%s~SN/~FG%s~FB~SN/%s tasked/queued/scheduled)" % (
active_count,

View file

@ -0,0 +1,75 @@
Migration from Digital Ocean to Hetzner, covering about 120 servers (20 db, 80 task, 20 app)
## Tweet
> "Time for a big server transition. Im moving the servers from Digital Ocean to Hetzner. Every database server (postgresql, mongo, redis x 4, elasticsearch, prometheus, consul, and sentry) is making the move. Im going to do it all at once, which means about an hour of downtime."
> "Afterwards, you shouldn't notice anything different, although these are bare metal servers, so theoretically they should be faster and more reliable."
```
make maintenance_on
make celery_stop
```
## Postgres
> Edit postgres/consul_service.json: db-postgres2 -> hdb-postgres-1
```
aps -l db-postgres2,hdb-postgres-1,hdb-postgres-2 -t consul
aps -l hdb-postgres-1 -t pg_promote
```
## Mongo
```
sshdo db-mongo-primary1
sudo docker exec -it mongo mongo
rs.config()
rs.reconfig()
```
## Mongo analytics
> Edit mongo/tasks/main.yml: mongo_analytics_secondary
```
aps -l db-mongo-analytics2,hdb-mongo-analytics-1 -t consul
```
## Redis
> Edit redis/tasks/main.yml: redis_secondary
```
aps -l hdb-redis-user-1,hdb-redis-user-2,db-redis-user -t consul
aps -l hdb-redis-session-1,hdb-redis-session-2,db-redis-sessions -t consul
aps -l hdb-redis-story-1,hdb-redis-story-2,db-redis-story1 -t consul
aps -l hdb-redis-pubsub,db-redis-pubsub -t consul
apd -l hdb-redis-user-1,hdb-redis-session-1,hdb-redis-story-1,hdb-redis-pubsub -t replicaofnoone
```
## Elasticsearch
> Edit elasticsearch/tasks/main.yml: elasticsearch_secondary
```
aps -l db-elasticsearch1,hdb-elasticsearch-1 -t consul
```
> Eventually `MUserSearch.remove_all()`
## Test hwww.newsblur.com
```
ansible-playbook ansible/deploy.yml -l happ-web-01 --tags maintenance_off
```
## Looks good? Launch
> Haproxy on DO to redirect to Hetzner
```
aps -l www -t haproxy
```
> Change DNS to point to Hetzner
```
make maintenance_off
```

View file

@ -1,20 +1,20 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0)
colorator (1.1.0)
concurrent-ruby (1.1.9)
em-websocket (0.5.2)
concurrent-ruby (1.2.2)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0)
http_parser.rb (~> 0)
eventmachine (1.2.7)
ffi (1.15.3)
ffi (1.16.3)
forwardable-extended (2.6.0)
http_parser.rb (0.6.0)
i18n (1.8.10)
http_parser.rb (0.8.0)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
jekyll (4.2.0)
jekyll (4.2.2)
addressable (~> 2.4)
colorator (~> 1.0)
em-websocket (~> 0.5)
@ -29,23 +29,23 @@ GEM
rouge (~> 3.0)
safe_yaml (~> 1.0)
terminal-table (~> 2.0)
jekyll-feed (0.15.1)
jekyll-feed (0.17.0)
jekyll (>= 3.7, < 5.0)
jekyll-paginate (1.1.0)
jekyll-redirect-from (0.16.0)
jekyll (>= 3.3, < 5.0)
jekyll-sass-converter (2.1.0)
jekyll-sass-converter (2.2.0)
sassc (> 2.0.1, < 3.0)
jekyll-seo-tag (2.7.1)
jekyll-seo-tag (2.8.0)
jekyll (>= 3.8, < 5.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
kramdown (2.3.1)
kramdown (2.4.0)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.3)
listen (3.5.1)
liquid (4.0.4)
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.4.0)
@ -55,23 +55,22 @@ GEM
jekyll-seo-tag (~> 2.1)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (4.0.6)
rb-fsevent (0.11.0)
public_suffix (5.0.4)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
rexml (3.2.5)
rouge (3.26.0)
rexml (3.2.6)
rouge (3.30.0)
safe_yaml (1.0.5)
sassc (2.4.0)
ffi (~> 1.9)
terminal-table (2.0.0)
unicode-display_width (~> 1.1, >= 1.1.1)
unicode-display_width (1.7.0)
webrick (1.7.0)
unicode-display_width (1.8.0)
webrick (1.8.1)
PLATFORMS
ruby
universal-darwin-20
arm64-darwin-23
DEPENDENCIES
jekyll (~> 4.2.0)
@ -85,4 +84,4 @@ DEPENDENCIES
webrick (~> 1.7)
BUNDLED WITH
2.2.20
2.4.22

View file

@ -27,6 +27,7 @@ description: > # this means to ignore newlines until "baseurl:"
A new sound of an old instrument.
baseurl: "" # the subpath of your site, e.g. /blog
url: "https://blog.newsblur.com" # the base hostname & protocol for your site, e.g. http://example.com
site.url: "https://blog.newsblur.com" # the base hostname & protocol for your site, e.g. http://example.com
permalink: pretty
twitter_username: newsblur
github_username: samuelclay

View file

@ -0,0 +1,20 @@
---
layout: post
title: Introducing the Grid view on iOS
tags: ['ios']
---
The Grid view is now on iOS. Read stories with large thumbnails in a magazine-like format, where you can see a customizable number of story previews at once. Works beautifully for both iPhone and iPad.
<img src="/assets/ipad-grid-1.png" style="width: 100%;border: 1px solid #A0A0A0;margin: 24px auto;display: block;">
Just like on the web, you can customize how many stories you see and how large each story is, giving you the freedom to read stories with large thumbnails or small image previews.
<img src="/assets/ipad-grid-2.png" style="width: 100%;border: 1px solid #A0A0A0;margin: 24px auto;display: block;">
It even works on iPhone!
<img src="/assets/iphone-grid.png" style="width: 100%;border: 1px solid #A0A0A0;margin: 24px auto;display: block;">
If you have any other ideas you'd like to see on iPad and iPhone, feel free to post an idea on the <a href="https://forum.newsblur.com">NewsBlur Forum</a>.
This is a huge release and has been a year in the making. Coming up soon: a new Mac app and intelligent feed discovery.

View file

@ -37,7 +37,7 @@ h1, h2, h3, h4, h5, h6,
p, blockquote, pre,
ul, ol, dl, figure,
%vertical-rhythm {
margin-bottom: $spacing-unit / 2;
margin-bottom: calc($spacing-unit / 2);
}
@ -138,7 +138,7 @@ a {
blockquote {
color: $grey-color;
border-left: 4px solid $grey-color-light;
padding-left: $spacing-unit / 2;
padding-left: calc($spacing-unit / 2);
@include relative-font-size(1.125);
letter-spacing: -1px;
font-style: italic;
@ -195,8 +195,8 @@ pre {
@include media-query($on-laptop) {
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit}));
max-width: calc(#{$content-width} - (#{$spacing-unit}));
padding-right: $spacing-unit / 2;
padding-left: $spacing-unit / 2;
padding-right: calc($spacing-unit / 2);
padding-left: calc($spacing-unit / 2);
}
}
@ -250,7 +250,7 @@ table {
}
}
th, td {
padding: ($spacing-unit / 3) ($spacing-unit / 2);
padding: calc(($spacing-unit / 3)) calc($spacing-unit / 2);
}
th {
background-color: lighten($grey-color-light, 3%);

View file

@ -94,8 +94,8 @@
@include media-query($on-palm) {
position: absolute;
top: $spacing-unit / 2;
right: $spacing-unit / 2;
top: calc($spacing-unit / 2);
right: calc($spacing-unit / 2);
background-color: $background-color;
border: 1px solid $grey-color-light;
border-radius: 5px;
@ -158,7 +158,7 @@
.footer-heading {
@include relative-font-size(1.125);
margin-bottom: $spacing-unit / 2;
margin-bottom: calc($spacing-unit / 2);
}
.contact-list,
@ -170,14 +170,14 @@
.footer-col-wrapper {
@include relative-font-size(0.9375);
color: $grey-color;
margin-left: -$spacing-unit / 2;
margin-left: calc($spacing-unit / -2);
@extend %clearfix;
}
.footer-col {
float: left;
margin-bottom: $spacing-unit / 2;
padding-left: $spacing-unit / 2;
margin-bottom: calc($spacing-unit / 2);
padding-left: calc($spacing-unit / 2);
}
.footer-col-1 {

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>A New Logo for a New Blog | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="A New Logo for a New Blog" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Weve come a long way, readers. What started as a fun project to scratch an itch has become a fun project that pays for its ever-increasing self. This week Im going to show how motivated I am about turning NewsBlur into a serious blog reader. And it starts with a collection of a circles:" />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="A New Logo for a New Blog" />
<script type="application/ld+json">
{"headline":"A New Logo for a New Blog","dateModified":"2011-03-15T08:39:00-04:00","datePublished":"2011-03-15T08:39:00-04:00","url":"https://blog.newsblur.com/2011/03/15/a-new-logo-for-a-new-blog/","@type":"BlogPosting","description":"Weve come a long way, readers. What started as a fun project to scratch an itch has become a fun project that pays for its ever-increasing self. This week Im going to show how motivated I am about turning NewsBlur into a serious blog reader. And it starts with a collection of a circles:","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/03/15/a-new-logo-for-a-new-blog/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2011-03-15T08:39:00-04:00","datePublished":"2011-03-15T08:39:00-04:00","description":"Weve come a long way, readers. What started as a fun project to scratch an itch has become a fun project that pays for its ever-increasing self. This week Im going to show how motivated I am about turning NewsBlur into a serious blog reader. And it starts with a collection of a circles:","headline":"A New Logo for a New Blog","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/03/15/a-new-logo-for-a-new-blog/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2011/03/15/a-new-logo-for-a-new-blog/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Explaining Intelligence | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Explaining Intelligence" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="If youre not using intelligence classifiers, youre only getting half the value out of NewsBlur. " />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Explaining Intelligence" />
<script type="application/ld+json">
{"headline":"Explaining Intelligence","dateModified":"2011-04-01T11:11:33-04:00","datePublished":"2011-04-01T11:11:33-04:00","url":"https://blog.newsblur.com/2011/04/01/explaining-intelligence/","@type":"BlogPosting","description":"If youre not using intelligence classifiers, youre only getting half the value out of NewsBlur. ","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/04/01/explaining-intelligence/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2011-04-01T11:11:33-04:00","datePublished":"2011-04-01T11:11:33-04:00","description":"If youre not using intelligence classifiers, youre only getting half the value out of NewsBlur. ","headline":"Explaining Intelligence","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/04/01/explaining-intelligence/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2011/04/01/explaining-intelligence/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Where We Are in April | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Where We Are in April" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Hi readers, I want to take a moment to share what Im working on for the month of April:" />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Where We Are in April" />
<script type="application/ld+json">
{"headline":"Where We Are in April","dateModified":"2011-04-23T14:57:02-04:00","datePublished":"2011-04-23T14:57:02-04:00","url":"https://blog.newsblur.com/2011/04/23/where-we-are-in-april/","@type":"BlogPosting","description":"Hi readers, I want to take a moment to share what Im working on for the month of April:","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/04/23/where-we-are-in-april/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2011-04-23T14:57:02-04:00","datePublished":"2011-04-23T14:57:02-04:00","description":"Hi readers, I want to take a moment to share what Im working on for the month of April:","headline":"Where We Are in April","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/04/23/where-we-are-in-april/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2011/04/23/where-we-are-in-april/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Make your own feed reader with NewsBlurs new API | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Make your own feed reader with NewsBlurs new API" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Please vote for this blog post on Hacker News: http://news.ycombinator.com/item?id=2485377." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Make your own feed reader with NewsBlurs new API" />
<script type="application/ld+json">
{"headline":"Make your own feed reader with NewsBlurs new API","dateModified":"2011-04-26T06:41:00-04:00","datePublished":"2011-04-26T06:41:00-04:00","url":"https://blog.newsblur.com/2011/04/26/make-your-own-feed-reader-with-newsblurs-new-api/","@type":"BlogPosting","description":"Please vote for this blog post on Hacker News: http://news.ycombinator.com/item?id=2485377.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/04/26/make-your-own-feed-reader-with-newsblurs-new-api/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2011-04-26T06:41:00-04:00","datePublished":"2011-04-26T06:41:00-04:00","description":"Please vote for this blog post on Hacker News: http://news.ycombinator.com/item?id=2485377.","headline":"Make your own feed reader with NewsBlurs new API","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/04/26/make-your-own-feed-reader-with-newsblurs-new-api/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2011/04/26/make-your-own-feed-reader-with-newsblurs-new-api/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Blar: A new Android app for NewsBlur | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Blar: A new Android app for NewsBlur" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="This Summer is shaping up to be the season for mobile apps. Blar, a new Android client for NewsBlur, has just been released. Its available on the Android Market here: https://market.android.com/details?id=bitwrit.Blar. It is created by Harris Munir, who you can contact through his site." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Blar: A new Android app for NewsBlur" />
<script type="application/ld+json">
{"headline":"Blar: A new Android app for NewsBlur","dateModified":"2011-08-09T09:44:00-04:00","datePublished":"2011-08-09T09:44:00-04:00","url":"https://blog.newsblur.com/2011/08/09/blar-a-new-android-app-for-newsblur/","@type":"BlogPosting","description":"This Summer is shaping up to be the season for mobile apps. Blar, a new Android client for NewsBlur, has just been released. Its available on the Android Market here: https://market.android.com/details?id=bitwrit.Blar. It is created by Harris Munir, who you can contact through his site.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/08/09/blar-a-new-android-app-for-newsblur/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2011-08-09T09:44:00-04:00","datePublished":"2011-08-09T09:44:00-04:00","description":"This Summer is shaping up to be the season for mobile apps. Blar, a new Android client for NewsBlur, has just been released. Its available on the Android Market here: https://market.android.com/details?id=bitwrit.Blar. It is created by Harris Munir, who you can contact through his site.","headline":"Blar: A new Android app for NewsBlur","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/08/09/blar-a-new-android-app-for-newsblur/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2011/08/09/blar-a-new-android-app-for-newsblur/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Customizing the reader, step 1: story titles | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Customizing the reader, step 1: story titles" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="The iPhone app is now only a few days away from launching. But it took 3 weeks of sitting around in the App Store approval queue before getting here. During that time, I started working on the new customizations that folks have been asking for." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Customizing the reader, step 1: story titles" />
<script type="application/ld+json">
{"headline":"Customizing the reader, step 1: story titles","dateModified":"2011-09-30T09:40:30-04:00","datePublished":"2011-09-30T09:40:30-04:00","url":"https://blog.newsblur.com/2011/09/30/customizing-the-reader-step-1-story-titles/","@type":"BlogPosting","description":"The iPhone app is now only a few days away from launching. But it took 3 weeks of sitting around in the App Store approval queue before getting here. During that time, I started working on the new customizations that folks have been asking for.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/09/30/customizing-the-reader-step-1-story-titles/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2011-09-30T09:40:30-04:00","datePublished":"2011-09-30T09:40:30-04:00","description":"The iPhone app is now only a few days away from launching. But it took 3 weeks of sitting around in the App Store approval queue before getting here. During that time, I started working on the new customizations that folks have been asking for.","headline":"Customizing the reader, step 1: story titles","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/09/30/customizing-the-reader-step-1-story-titles/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2011/09/30/customizing-the-reader-step-1-story-titles/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>A Social Feed Reader | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="A Social Feed Reader" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="NewsBlur was released exactly one year ago. You can read the initial reaction on Hacker News: http://news.ycombinator.com/item?id=1834305. Since then, so much has changed and all for the better. Usage is up—way, way up. Premium users are helping the site run. Load times are approaching the goal of less than 100 ms (0.10 sec) per page. In short, things couldnt be better." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="A Social Feed Reader" />
<script type="application/ld+json">
{"headline":"A Social Feed Reader","dateModified":"2011-10-26T11:41:23-04:00","datePublished":"2011-10-26T11:41:23-04:00","url":"https://blog.newsblur.com/2011/10/26/a-social-feed-reader/","@type":"BlogPosting","description":"NewsBlur was released exactly one year ago. You can read the initial reaction on Hacker News: http://news.ycombinator.com/item?id=1834305. Since then, so much has changed and all for the better. Usage is up—way, way up. Premium users are helping the site run. Load times are approaching the goal of less than 100 ms (0.10 sec) per page. In short, things couldnt be better.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/10/26/a-social-feed-reader/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2011-10-26T11:41:23-04:00","datePublished":"2011-10-26T11:41:23-04:00","description":"NewsBlur was released exactly one year ago. You can read the initial reaction on Hacker News: http://news.ycombinator.com/item?id=1834305. Since then, so much has changed and all for the better. Usage is up—way, way up. Premium users are helping the site run. Load times are approaching the goal of less than 100 ms (0.10 sec) per page. In short, things couldnt be better.","headline":"A Social Feed Reader","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/10/26/a-social-feed-reader/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2011/10/26/a-social-feed-reader/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>2011: Year in Review | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="2011: Year in Review" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Twelve months can be a quick flyby if you dont stop to write everything down. Luckily, a habit Ive kept since July 2009, when I started recording monthly goals for my project, is still going strong." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="2011: Year in Review" />
<script type="application/ld+json">
{"headline":"2011: Year in Review","dateModified":"2012-01-16T20:47:00-05:00","datePublished":"2012-01-16T20:47:00-05:00","url":"https://blog.newsblur.com/2012/01/16/2011-year-in-review/","@type":"BlogPosting","description":"Twelve months can be a quick flyby if you dont stop to write everything down. Luckily, a habit Ive kept since July 2009, when I started recording monthly goals for my project, is still going strong.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/01/16/2011-year-in-review/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-01-16T20:47:00-05:00","datePublished":"2012-01-16T20:47:00-05:00","description":"Twelve months can be a quick flyby if you dont stop to write everything down. Luckily, a habit Ive kept since July 2009, when I started recording monthly goals for my project, is still going strong.","headline":"2011: Year in Review","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/01/16/2011-year-in-review/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/01/16/2011-year-in-review/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>SSL &amp; Stripe.js | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="SSL &amp; Stripe.js" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Two big announcements today:" />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="SSL &amp; Stripe.js" />
<script type="application/ld+json">
{"headline":"SSL &amp; Stripe.js","dateModified":"2012-02-29T15:45:00-05:00","datePublished":"2012-02-29T15:45:00-05:00","url":"https://blog.newsblur.com/2012/02/29/ssl-stripejs/","@type":"BlogPosting","description":"Two big announcements today:","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/02/29/ssl-stripejs/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-02-29T15:45:00-05:00","datePublished":"2012-02-29T15:45:00-05:00","description":"Two big announcements today:","headline":"SSL &amp; Stripe.js","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/02/29/ssl-stripejs/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/02/29/ssl-stripejs/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>From project to profession: going indie on NewsBlur | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="From project to profession: going indie on NewsBlur" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Exactly four months ago, Jason Kottke found my project, NewsBlur, and tweeted:" />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="From project to profession: going indie on NewsBlur" />
<script type="application/ld+json">
{"headline":"From project to profession: going indie on NewsBlur","dateModified":"2012-03-01T11:48:00-05:00","datePublished":"2012-03-01T11:48:00-05:00","url":"https://blog.newsblur.com/2012/03/01/going-full-time/","@type":"BlogPosting","description":"Exactly four months ago, Jason Kottke found my project, NewsBlur, and tweeted:","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/03/01/going-full-time/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-03-01T11:48:00-05:00","datePublished":"2012-03-01T11:48:00-05:00","description":"Exactly four months ago, Jason Kottke found my project, NewsBlur, and tweeted:","headline":"From project to profession: going indie on NewsBlur","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/03/01/going-full-time/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/03/01/going-full-time/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>New mobile app for NewsBlur: Web Feeds for Nokia MeeGo | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="New mobile app for NewsBlur: Web Feeds for Nokia MeeGo" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="And what a gorgeous mobile app it is. App developer Róbert Márki just released Web Feeds, the first NewsBlur app for Nokia MeeGo. Take a look at these screenshots:" />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="New mobile app for NewsBlur: Web Feeds for Nokia MeeGo" />
<script type="application/ld+json">
{"headline":"New mobile app for NewsBlur: Web Feeds for Nokia MeeGo","dateModified":"2012-03-14T13:14:00-04:00","datePublished":"2012-03-14T13:14:00-04:00","url":"https://blog.newsblur.com/2012/03/14/mobile-app-web-feeds-nokia-meego/","@type":"BlogPosting","description":"And what a gorgeous mobile app it is. App developer Róbert Márki just released Web Feeds, the first NewsBlur app for Nokia MeeGo. Take a look at these screenshots:","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/03/14/mobile-app-web-feeds-nokia-meego/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-03-14T13:14:00-04:00","datePublished":"2012-03-14T13:14:00-04:00","description":"And what a gorgeous mobile app it is. App developer Róbert Márki just released Web Feeds, the first NewsBlur app for Nokia MeeGo. Take a look at these screenshots:","headline":"New mobile app for NewsBlur: Web Feeds for Nokia MeeGo","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/03/14/mobile-app-web-feeds-nokia-meego/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/03/14/mobile-app-web-feeds-nokia-meego/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Knight News Challenge: NewsBlur | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Knight News Challenge: NewsBlur" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Knight News Challenge: NewsBlur" />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Knight News Challenge: NewsBlur" />
<script type="application/ld+json">
{"headline":"Knight News Challenge: NewsBlur","dateModified":"2012-03-16T11:29:00-04:00","datePublished":"2012-03-16T11:29:00-04:00","url":"https://blog.newsblur.com/2012/03/16/knight-news-challenge/","@type":"BlogPosting","description":"Knight News Challenge: NewsBlur","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/03/16/knight-news-challenge/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-03-16T11:29:00-04:00","datePublished":"2012-03-16T11:29:00-04:00","description":"Knight News Challenge: NewsBlur","headline":"Knight News Challenge: NewsBlur","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/03/16/knight-news-challenge/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/03/16/knight-news-challenge/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Building real-time feed updates for NewsBlur with Redis and WebSockets | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Building real-time feed updates for NewsBlur with Redis and WebSockets" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Today, NewsBlur is going real-time. Blogs using the PubSubHubbub protocol (PuSH), which includes all Blogger, Tumblr, and many Wordpress blogs, will instantaneously show new updates to subscribers on NewsBlur. Making this happen, while not for the faint of heart, was straight-forward enough that Im sharing the recipe I used to get everything hooked up and running smoothly." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Building real-time feed updates for NewsBlur with Redis and WebSockets" />
<script type="application/ld+json">
{"headline":"Building real-time feed updates for NewsBlur with Redis and WebSockets","dateModified":"2012-04-02T17:52:00-04:00","datePublished":"2012-04-02T17:52:00-04:00","url":"https://blog.newsblur.com/2012/04/02/building-real-time-feed-updates-for-newsblur/","@type":"BlogPosting","description":"Today, NewsBlur is going real-time. Blogs using the PubSubHubbub protocol (PuSH), which includes all Blogger, Tumblr, and many Wordpress blogs, will instantaneously show new updates to subscribers on NewsBlur. Making this happen, while not for the faint of heart, was straight-forward enough that Im sharing the recipe I used to get everything hooked up and running smoothly.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/04/02/building-real-time-feed-updates-for-newsblur/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-04-02T17:52:00-04:00","datePublished":"2012-04-02T17:52:00-04:00","description":"Today, NewsBlur is going real-time. Blogs using the PubSubHubbub protocol (PuSH), which includes all Blogger, Tumblr, and many Wordpress blogs, will instantaneously show new updates to subscribers on NewsBlur. Making this happen, while not for the faint of heart, was straight-forward enough that Im sharing the recipe I used to get everything hooked up and running smoothly.","headline":"Building real-time feed updates for NewsBlur with Redis and WebSockets","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/04/02/building-real-time-feed-updates-for-newsblur/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/04/02/building-real-time-feed-updates-for-newsblur/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Introducing Blurblogs, Roy, and Y Combinator | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Introducing Blurblogs, Roy, and Y Combinator" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="What a difference a few months make. NewsBlur was a side-project of mine for two years. In March of this year, I committed myself full-time and went from developing NewsBlur almost entirely on the NYC subway to writing code every waking minute of the day. And now there are three big announcements to make. # 1. NewsBlur is now a *social* news reader The big news of the day is that you can now share stories on NewsBlur. When you share a story, your comments and the original story are posted to your blurblog. Your blurblog is a simple and customizable website. People can comment and reply directly on your blurblog, and you can follow your friends to read the news stories and blog posts that they care about.Since youre good at picking your friends, and your friends are good at picking their friends, you will see friends of friends show up, expanding your network with shared stories that you will enjoy. Its a new way of sharing the news. And because NewsBlur is already an easy to use news reader, its simple to find and share stories that your friends will care about. Every NewsBlur user has their own blurblog. All you have to do is signup for an account on www.newsblur.com and share interesting stories. # 2. Y Combinator For those of you who work with computer science, you may know that a Y-combinator generalizes recursion, abstracting its implementation, and thereby separating it from the actual work of the function in question.[^1] Im pleased as punch to announce an investment in NewsBlur by Y Combinator, the investment firm. Over the past two months, weve been humbled by the roster of experienced partners giving us candid advice. Its their tough love that is the catalyst for the next few months of transitioning NewsBlur from side project to world-class news reader. Expect NewsBlur to become simpler and more refined. # 3. Introducing Roy Yang When Y Combinator accepted me as a solo founder, their first piece of advice was to find a co-founder. Looking at every successful startup, a common pattern emerges. Every great startup has multiple people carrying the load when the company takes off. There is one person on this planet that I would trust as a co-founder. His name is Roy Yang and we have been friends since we met in New York four years ago. We worked together for nearly two years at Daylife, another news startup. I attended his wedding last year in Mexico, and he was the only person I called when I knew I needed somebody talented, focused, and able to complement me on a project that demands enormous time and effort.Roy is now responsible for both iOS apps and is instrumental in challenging me when I think Im right and am clearly not. Hes got the patience of a monk and the determination of a true New Yorker. Follow Roys blurblog to keep up with him. # A glimpse into the future of NewsBlur This summer marks the beginning of NewsBlur as a full-time startup. Look forward to new mobile apps, new designs, and new features. Heres a quick idea of what were working on for the next few weeks:" />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Introducing Blurblogs, Roy, and Y Combinator" />
<script type="application/ld+json">
{"headline":"Introducing Blurblogs, Roy, and Y Combinator","dateModified":"2012-07-30T11:22:00-04:00","datePublished":"2012-07-30T11:22:00-04:00","url":"https://blog.newsblur.com/2012/07/30/introducing-blurblogs-roy-and-y-combinator/","@type":"BlogPosting","description":"What a difference a few months make. NewsBlur was a side-project of mine for two years. In March of this year, I committed myself full-time and went from developing NewsBlur almost entirely on the NYC subway to writing code every waking minute of the day. And now there are three big announcements to make. # 1. NewsBlur is now a *social* news reader The big news of the day is that you can now share stories on NewsBlur. When you share a story, your comments and the original story are posted to your blurblog. Your blurblog is a simple and customizable website. People can comment and reply directly on your blurblog, and you can follow your friends to read the news stories and blog posts that they care about.Since youre good at picking your friends, and your friends are good at picking their friends, you will see friends of friends show up, expanding your network with shared stories that you will enjoy. Its a new way of sharing the news. And because NewsBlur is already an easy to use news reader, its simple to find and share stories that your friends will care about. Every NewsBlur user has their own blurblog. All you have to do is signup for an account on www.newsblur.com and share interesting stories. # 2. Y Combinator For those of you who work with computer science, you may know that a Y-combinator generalizes recursion, abstracting its implementation, and thereby separating it from the actual work of the function in question.[^1] Im pleased as punch to announce an investment in NewsBlur by Y Combinator, the investment firm. Over the past two months, weve been humbled by the roster of experienced partners giving us candid advice. Its their tough love that is the catalyst for the next few months of transitioning NewsBlur from side project to world-class news reader. Expect NewsBlur to become simpler and more refined. # 3. Introducing Roy Yang When Y Combinator accepted me as a solo founder, their first piece of advice was to find a co-founder. Looking at every successful startup, a common pattern emerges. Every great startup has multiple people carrying the load when the company takes off. There is one person on this planet that I would trust as a co-founder. His name is Roy Yang and we have been friends since we met in New York four years ago. We worked together for nearly two years at Daylife, another news startup. I attended his wedding last year in Mexico, and he was the only person I called when I knew I needed somebody talented, focused, and able to complement me on a project that demands enormous time and effort.Roy is now responsible for both iOS apps and is instrumental in challenging me when I think Im right and am clearly not. Hes got the patience of a monk and the determination of a true New Yorker. Follow Roys blurblog to keep up with him. # A glimpse into the future of NewsBlur This summer marks the beginning of NewsBlur as a full-time startup. Look forward to new mobile apps, new designs, and new features. Heres a quick idea of what were working on for the next few weeks:","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/07/30/introducing-blurblogs-roy-and-y-combinator/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-07-30T11:22:00-04:00","datePublished":"2012-07-30T11:22:00-04:00","description":"What a difference a few months make. NewsBlur was a side-project of mine for two years. In March of this year, I committed myself full-time and went from developing NewsBlur almost entirely on the NYC subway to writing code every waking minute of the day. And now there are three big announcements to make. # 1. NewsBlur is now a *social* news reader The big news of the day is that you can now share stories on NewsBlur. When you share a story, your comments and the original story are posted to your blurblog. Your blurblog is a simple and customizable website. People can comment and reply directly on your blurblog, and you can follow your friends to read the news stories and blog posts that they care about.Since youre good at picking your friends, and your friends are good at picking their friends, you will see friends of friends show up, expanding your network with shared stories that you will enjoy. Its a new way of sharing the news. And because NewsBlur is already an easy to use news reader, its simple to find and share stories that your friends will care about. Every NewsBlur user has their own blurblog. All you have to do is signup for an account on www.newsblur.com and share interesting stories. # 2. Y Combinator For those of you who work with computer science, you may know that a Y-combinator generalizes recursion, abstracting its implementation, and thereby separating it from the actual work of the function in question.[^1] Im pleased as punch to announce an investment in NewsBlur by Y Combinator, the investment firm. Over the past two months, weve been humbled by the roster of experienced partners giving us candid advice. Its their tough love that is the catalyst for the next few months of transitioning NewsBlur from side project to world-class news reader. Expect NewsBlur to become simpler and more refined. # 3. Introducing Roy Yang When Y Combinator accepted me as a solo founder, their first piece of advice was to find a co-founder. Looking at every successful startup, a common pattern emerges. Every great startup has multiple people carrying the load when the company takes off. There is one person on this planet that I would trust as a co-founder. His name is Roy Yang and we have been friends since we met in New York four years ago. We worked together for nearly two years at Daylife, another news startup. I attended his wedding last year in Mexico, and he was the only person I called when I knew I needed somebody talented, focused, and able to complement me on a project that demands enormous time and effort.Roy is now responsible for both iOS apps and is instrumental in challenging me when I think Im right and am clearly not. Hes got the patience of a monk and the determination of a true New Yorker. Follow Roys blurblog to keep up with him. # A glimpse into the future of NewsBlur This summer marks the beginning of NewsBlur as a full-time startup. Look forward to new mobile apps, new designs, and new features. Heres a quick idea of what were working on for the next few weeks:","headline":"Introducing Blurblogs, Roy, and Y Combinator","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/07/30/introducing-blurblogs-roy-and-y-combinator/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/07/30/introducing-blurblogs-roy-and-y-combinator/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Take it to the couch with the NewsBlur iPad app | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Take it to the couch with the NewsBlur iPad app" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Theres no wrong way to hold an iPad loaded with the brand new NewsBlur iPad app, provided its facing you and turned on." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Take it to the couch with the NewsBlur iPad app" />
<script type="application/ld+json">
{"headline":"Take it to the couch with the NewsBlur iPad app","dateModified":"2012-09-05T08:21:00-04:00","datePublished":"2012-09-05T08:21:00-04:00","url":"https://blog.newsblur.com/2012/09/05/newsblur-ipad-app/","@type":"BlogPosting","description":"Theres no wrong way to hold an iPad loaded with the brand new NewsBlur iPad app, provided its facing you and turned on.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/09/05/newsblur-ipad-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-09-05T08:21:00-04:00","datePublished":"2012-09-05T08:21:00-04:00","description":"Theres no wrong way to hold an iPad loaded with the brand new NewsBlur iPad app, provided its facing you and turned on.","headline":"Take it to the couch with the NewsBlur iPad app","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/09/05/newsblur-ipad-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/09/05/newsblur-ipad-app/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Giving Life to “The People Have Spoken” | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Giving Life to “The People Have Spoken”" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="For a long time, weve maintained The People Have Spoken, the blog of whats popular on NewsBlur, with a simple algorithm that measured how often something was shared. While thats a great way to see the stuff our users really like (Randall Munroe would probably win the NewsBlur equivalent of the Oscar), it makes it harder for everyone to find new stuff that they might not have seen or heard of before. So weve decided to throw a human into the mix. Ill let Allie introduce herself in her own words:" />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Giving Life to “The People Have Spoken”" />
<script type="application/ld+json">
{"headline":"Giving Life to “The People Have Spoken”","dateModified":"2012-10-01T17:52:00-04:00","datePublished":"2012-10-01T17:52:00-04:00","url":"https://blog.newsblur.com/2012/10/01/giving-life-to-popular/","@type":"BlogPosting","description":"For a long time, weve maintained The People Have Spoken, the blog of whats popular on NewsBlur, with a simple algorithm that measured how often something was shared. While thats a great way to see the stuff our users really like (Randall Munroe would probably win the NewsBlur equivalent of the Oscar), it makes it harder for everyone to find new stuff that they might not have seen or heard of before. So weve decided to throw a human into the mix. Ill let Allie introduce herself in her own words:","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/01/giving-life-to-popular/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-10-01T17:52:00-04:00","datePublished":"2012-10-01T17:52:00-04:00","description":"For a long time, weve maintained The People Have Spoken, the blog of whats popular on NewsBlur, with a simple algorithm that measured how often something was shared. While thats a great way to see the stuff our users really like (Randall Munroe would probably win the NewsBlur equivalent of the Oscar), it makes it harder for everyone to find new stuff that they might not have seen or heard of before. So weve decided to throw a human into the mix. Ill let Allie introduce herself in her own words:","headline":"Giving Life to “The People Have Spoken”","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/01/giving-life-to-popular/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/10/01/giving-life-to-popular/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Do the robot: the official NewsBlur Android app is here | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Do the robot: the official NewsBlur Android app is here" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Youve been bugging us for two years about it, and now its finally here: NewsBlurs expansion to mobile is complete, with our first-ever official Android app ready and waiting for your device. Thanks to the gifts of money and time from Y Combinator and the programming prowess of Papermill creator Ryan Bateman (otherwise known as @secretsquirrel), you can now join your iOS brethren on the couch with your daily dose of RSS goodness. Level of accompanying smugness is up to you." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Do the robot: the official NewsBlur Android app is here" />
<script type="application/ld+json">
{"headline":"Do the robot: the official NewsBlur Android app is here","dateModified":"2012-10-18T11:47:00-04:00","datePublished":"2012-10-18T11:47:00-04:00","url":"https://blog.newsblur.com/2012/10/18/do-the-robot-the-official-newsblur-android-app-is/","@type":"BlogPosting","description":"Youve been bugging us for two years about it, and now its finally here: NewsBlurs expansion to mobile is complete, with our first-ever official Android app ready and waiting for your device. Thanks to the gifts of money and time from Y Combinator and the programming prowess of Papermill creator Ryan Bateman (otherwise known as @secretsquirrel), you can now join your iOS brethren on the couch with your daily dose of RSS goodness. Level of accompanying smugness is up to you.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/18/do-the-robot-the-official-newsblur-android-app-is/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-10-18T11:47:00-04:00","datePublished":"2012-10-18T11:47:00-04:00","description":"Youve been bugging us for two years about it, and now its finally here: NewsBlurs expansion to mobile is complete, with our first-ever official Android app ready and waiting for your device. Thanks to the gifts of money and time from Y Combinator and the programming prowess of Papermill creator Ryan Bateman (otherwise known as @secretsquirrel), you can now join your iOS brethren on the couch with your daily dose of RSS goodness. Level of accompanying smugness is up to you.","headline":"Do the robot: the official NewsBlur Android app is here","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/18/do-the-robot-the-official-newsblur-android-app-is/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/10/18/do-the-robot-the-official-newsblur-android-app-is/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Extreme makeover: NewsBlur iOS app edition | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Extreme makeover: NewsBlur iOS app edition" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Now that NewsBlur has joined the wonderful world of Android, were turning our attention back to the iOS app, with a full-scale feature parity push for the new iPhone 5 and iOS 6. Its perfect for catching up on your reading when you realize that Apple Maps has sent you to the wrong address. Again." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Extreme makeover: NewsBlur iOS app edition" />
<script type="application/ld+json">
{"headline":"Extreme makeover: NewsBlur iOS app edition","dateModified":"2012-10-26T14:37:00-04:00","datePublished":"2012-10-26T14:37:00-04:00","url":"https://blog.newsblur.com/2012/10/26/ios-update-1-6/","@type":"BlogPosting","description":"Now that NewsBlur has joined the wonderful world of Android, were turning our attention back to the iOS app, with a full-scale feature parity push for the new iPhone 5 and iOS 6. Its perfect for catching up on your reading when you realize that Apple Maps has sent you to the wrong address. Again.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/26/ios-update-1-6/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-10-26T14:37:00-04:00","datePublished":"2012-10-26T14:37:00-04:00","description":"Now that NewsBlur has joined the wonderful world of Android, were turning our attention back to the iOS app, with a full-scale feature parity push for the new iPhone 5 and iOS 6. Its perfect for catching up on your reading when you realize that Apple Maps has sent you to the wrong address. Again.","headline":"Extreme makeover: NewsBlur iOS app edition","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/26/ios-update-1-6/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/10/26/ios-update-1-6/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Time for some free NewsBlur swag! | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Time for some free NewsBlur swag!" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Thats right, t-shirts, stickers, buttons, and magnets. Ive got a whole lot of good stuff to send out, so give me some critical info and Ill get you hooked up with the latest in startup love." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Time for some free NewsBlur swag!" />
<script type="application/ld+json">
{"headline":"Time for some free NewsBlur swag!","dateModified":"2012-10-29T15:38:00-04:00","datePublished":"2012-10-29T15:38:00-04:00","url":"https://blog.newsblur.com/2012/10/29/free-newsblur-swag-time/","@type":"BlogPosting","description":"Thats right, t-shirts, stickers, buttons, and magnets. Ive got a whole lot of good stuff to send out, so give me some critical info and Ill get you hooked up with the latest in startup love.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/29/free-newsblur-swag-time/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-10-29T15:38:00-04:00","datePublished":"2012-10-29T15:38:00-04:00","description":"Thats right, t-shirts, stickers, buttons, and magnets. Ive got a whole lot of good stuff to send out, so give me some critical info and Ill get you hooked up with the latest in startup love.","headline":"Time for some free NewsBlur swag!","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/29/free-newsblur-swag-time/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/10/29/free-newsblur-swag-time/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>The sharing bookmarklet: bringing your online explorations to NewsBlur | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="The sharing bookmarklet: bringing your online explorations to NewsBlur" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="There are lots of reasons not to post a cool article youve seen to your blurblog. Maybe you already follow too many blogs, and dont have room to add any more to your feed (in which case, may we humbly recommend a Premium account?) Maybe you dont want everyone to know just how crazy youve gotten about jai-alai or aerotrekking or My Little Pony: Friendship Is Magic. Or maybe you found a cool article on Facebook or Twitter or through an e-mail from a friend, and dont want to go through the hassle of adding the sites whole feed to your NewsBlur dashboard just to post one piece." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="The sharing bookmarklet: bringing your online explorations to NewsBlur" />
<script type="application/ld+json">
{"headline":"The sharing bookmarklet: bringing your online explorations to NewsBlur","dateModified":"2012-12-17T12:04:00-05:00","datePublished":"2012-12-17T12:04:00-05:00","url":"https://blog.newsblur.com/2012/12/17/sharing-bookmarklet/","@type":"BlogPosting","description":"There are lots of reasons not to post a cool article youve seen to your blurblog. Maybe you already follow too many blogs, and dont have room to add any more to your feed (in which case, may we humbly recommend a Premium account?) Maybe you dont want everyone to know just how crazy youve gotten about jai-alai or aerotrekking or My Little Pony: Friendship Is Magic. Or maybe you found a cool article on Facebook or Twitter or through an e-mail from a friend, and dont want to go through the hassle of adding the sites whole feed to your NewsBlur dashboard just to post one piece.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/12/17/sharing-bookmarklet/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-12-17T12:04:00-05:00","datePublished":"2012-12-17T12:04:00-05:00","description":"There are lots of reasons not to post a cool article youve seen to your blurblog. Maybe you already follow too many blogs, and dont have room to add any more to your feed (in which case, may we humbly recommend a Premium account?) Maybe you dont want everyone to know just how crazy youve gotten about jai-alai or aerotrekking or My Little Pony: Friendship Is Magic. Or maybe you found a cool article on Facebook or Twitter or through an e-mail from a friend, and dont want to go through the hassle of adding the sites whole feed to your NewsBlur dashboard just to post one piece.","headline":"The sharing bookmarklet: bringing your online explorations to NewsBlur","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/12/17/sharing-bookmarklet/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/12/17/sharing-bookmarklet/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>A blurblog of ones own: new privacy controls | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="A blurblog of ones own: new privacy controls" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Here at NewsBlur HQ, we love greeting each new day by seeing what everyone posts on their blurblogs, but we understand that not everyone might want to have their reading preferences broadcast to the public (or have the public broadcast its opinions on said preferences). So were introducing a special new service for premium account holders that allows you to protect your posts from prying eyes." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="A blurblog of ones own: new privacy controls" />
<script type="application/ld+json">
{"headline":"A blurblog of ones own: new privacy controls","dateModified":"2013-01-03T00:00:00-05:00","datePublished":"2013-01-03T00:00:00-05:00","url":"https://blog.newsblur.com/2013/01/03/privacy-controls/","@type":"BlogPosting","description":"Here at NewsBlur HQ, we love greeting each new day by seeing what everyone posts on their blurblogs, but we understand that not everyone might want to have their reading preferences broadcast to the public (or have the public broadcast its opinions on said preferences). So were introducing a special new service for premium account holders that allows you to protect your posts from prying eyes.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/01/03/privacy-controls/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-01-03T00:00:00-05:00","datePublished":"2013-01-03T00:00:00-05:00","description":"Here at NewsBlur HQ, we love greeting each new day by seeing what everyone posts on their blurblogs, but we understand that not everyone might want to have their reading preferences broadcast to the public (or have the public broadcast its opinions on said preferences). So were introducing a special new service for premium account holders that allows you to protect your posts from prying eyes.","headline":"A blurblog of ones own: new privacy controls","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/01/03/privacy-controls/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/01/03/privacy-controls/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Three Months to Scale NewsBlur | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Three Months to Scale NewsBlur" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="At 4:16pm last Wednesday I got a short and to-the-point email from Nilay Patel at The Verge with only a link that started with the host “googlereader.blogspot.com”. The sudden spike in NewsBlurs visitors immediately confirmed — Google was shutting down Reader." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Three Months to Scale NewsBlur" />
<script type="application/ld+json">
{"headline":"Three Months to Scale NewsBlur","dateModified":"2013-03-17T17:24:00-04:00","datePublished":"2013-03-17T17:24:00-04:00","url":"https://blog.newsblur.com/2013/03/17/three-months-to-scale-newsblur/","@type":"BlogPosting","description":"At 4:16pm last Wednesday I got a short and to-the-point email from Nilay Patel at The Verge with only a link that started with the host “googlereader.blogspot.com”. The sudden spike in NewsBlurs visitors immediately confirmed — Google was shutting down Reader.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/03/17/three-months-to-scale-newsblur/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-03-17T17:24:00-04:00","datePublished":"2013-03-17T17:24:00-04:00","description":"At 4:16pm last Wednesday I got a short and to-the-point email from Nilay Patel at The Verge with only a link that started with the host “googlereader.blogspot.com”. The sudden spike in NewsBlurs visitors immediately confirmed — Google was shutting down Reader.","headline":"Three Months to Scale NewsBlur","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/03/17/three-months-to-scale-newsblur/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/03/17/three-months-to-scale-newsblur/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>The NewsBlur Redesign | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="The NewsBlur Redesign" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Not to say that NewsBlur was ugly before today, but it certainly didnt have the loving embrace of a talented designer. So without waiting another moment (or month) I proudly present the NewsBlur redesign." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="The NewsBlur Redesign" />
<script type="application/ld+json">
{"headline":"The NewsBlur Redesign","dateModified":"2013-05-20T22:47:00-04:00","datePublished":"2013-05-20T22:47:00-04:00","url":"https://blog.newsblur.com/2013/05/20/the-newsblur-redesign/","@type":"BlogPosting","description":"Not to say that NewsBlur was ugly before today, but it certainly didnt have the loving embrace of a talented designer. So without waiting another moment (or month) I proudly present the NewsBlur redesign.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/05/20/the-newsblur-redesign/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-05-20T22:47:00-04:00","datePublished":"2013-05-20T22:47:00-04:00","description":"Not to say that NewsBlur was ugly before today, but it certainly didnt have the loving embrace of a talented designer. So without waiting another moment (or month) I proudly present the NewsBlur redesign.","headline":"The NewsBlur Redesign","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/05/20/the-newsblur-redesign/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/05/20/the-newsblur-redesign/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Keyboard Shortcuts Manager | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Keyboard Shortcuts Manager" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Hot on the heels of the redesign launch, Im already putting out new features. There are a number of post-redesign priorities on my list, but one of the most requested features is to customize the keyboard shortcuts." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Keyboard Shortcuts Manager" />
<script type="application/ld+json">
{"headline":"Keyboard Shortcuts Manager","dateModified":"2013-05-23T09:01:00-04:00","datePublished":"2013-05-23T09:01:00-04:00","url":"https://blog.newsblur.com/2013/05/23/keyboard-shortcuts-manager/","@type":"BlogPosting","description":"Hot on the heels of the redesign launch, Im already putting out new features. There are a number of post-redesign priorities on my list, but one of the most requested features is to customize the keyboard shortcuts.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/05/23/keyboard-shortcuts-manager/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-05-23T09:01:00-04:00","datePublished":"2013-05-23T09:01:00-04:00","description":"Hot on the heels of the redesign launch, Im already putting out new features. There are a number of post-redesign priorities on my list, but one of the most requested features is to customize the keyboard shortcuts.","headline":"Keyboard Shortcuts Manager","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/05/23/keyboard-shortcuts-manager/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/05/23/keyboard-shortcuts-manager/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Read NewsBlur on your Mac with the new ReadKit | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Read NewsBlur on your Mac with the new ReadKit" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="ReadKit, a native Mac app for reading Instapaper, Pocket, and NewsBlur on your desktop, completes the RSS reading trifecta. NewsBlur has a web app, native iOS app, and now a native Mac app." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Read NewsBlur on your Mac with the new ReadKit" />
<script type="application/ld+json">
{"headline":"Read NewsBlur on your Mac with the new ReadKit","dateModified":"2013-05-30T12:12:16-04:00","datePublished":"2013-05-30T12:12:16-04:00","url":"https://blog.newsblur.com/2013/05/30/read-newsblur-on-your-mac-with-the-new-readkit/","@type":"BlogPosting","description":"ReadKit, a native Mac app for reading Instapaper, Pocket, and NewsBlur on your desktop, completes the RSS reading trifecta. NewsBlur has a web app, native iOS app, and now a native Mac app.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/05/30/read-newsblur-on-your-mac-with-the-new-readkit/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-05-30T12:12:16-04:00","datePublished":"2013-05-30T12:12:16-04:00","description":"ReadKit, a native Mac app for reading Instapaper, Pocket, and NewsBlur on your desktop, completes the RSS reading trifecta. NewsBlur has a web app, native iOS app, and now a native Mac app.","headline":"Read NewsBlur on your Mac with the new ReadKit","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/05/30/read-newsblur-on-your-mac-with-the-new-readkit/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/05/30/read-newsblur-on-your-mac-with-the-new-readkit/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Text view comes to the NewsBlur iOS app | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Text view comes to the NewsBlur iOS app" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="The iOS apps are finding themselves host to a whole slew of additions and enhancements. Today I get to tell you about the iOS apps newest feature: the Text view." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Text view comes to the NewsBlur iOS app" />
<script type="application/ld+json">
{"headline":"Text view comes to the NewsBlur iOS app","dateModified":"2013-06-04T08:31:18-04:00","datePublished":"2013-06-04T08:31:18-04:00","url":"https://blog.newsblur.com/2013/06/04/text-view-comes-to-the-newsblur-ios-app/","@type":"BlogPosting","description":"The iOS apps are finding themselves host to a whole slew of additions and enhancements. Today I get to tell you about the iOS apps newest feature: the Text view.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/06/04/text-view-comes-to-the-newsblur-ios-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-06-04T08:31:18-04:00","datePublished":"2013-06-04T08:31:18-04:00","description":"The iOS apps are finding themselves host to a whole slew of additions and enhancements. Today I get to tell you about the iOS apps newest feature: the Text view.","headline":"Text view comes to the NewsBlur iOS app","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/06/04/text-view-comes-to-the-newsblur-ios-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/06/04/text-view-comes-to-the-newsblur-ios-app/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>NewsBlur Puzzle T-shirt 2013 | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="NewsBlur Puzzle T-shirt 2013" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Last year I was proud to be able to send a free t-shirt and handwritten note to every single user who requested one. It took a few days of writing, stuffing, and mailing to send out a couple hundred t-shirts." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="NewsBlur Puzzle T-shirt 2013" />
<script type="application/ld+json">
{"headline":"NewsBlur Puzzle T-shirt 2013","dateModified":"2013-07-24T15:49:00-04:00","datePublished":"2013-07-24T15:49:00-04:00","url":"https://blog.newsblur.com/2013/07/24/newsblur-puzzle-t-shirt-2013/","@type":"BlogPosting","description":"Last year I was proud to be able to send a free t-shirt and handwritten note to every single user who requested one. It took a few days of writing, stuffing, and mailing to send out a couple hundred t-shirts.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/07/24/newsblur-puzzle-t-shirt-2013/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-07-24T15:49:00-04:00","datePublished":"2013-07-24T15:49:00-04:00","description":"Last year I was proud to be able to send a free t-shirt and handwritten note to every single user who requested one. It took a few days of writing, stuffing, and mailing to send out a couple hundred t-shirts.","headline":"NewsBlur Puzzle T-shirt 2013","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/07/24/newsblur-puzzle-t-shirt-2013/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/07/24/newsblur-puzzle-t-shirt-2013/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Simple Search for Feeds, Saved Stories, and Blurblogs | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Simple Search for Feeds, Saved Stories, and Blurblogs" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Search, which can easily be considered one of the most important features of a world-class news reader, is also one of the most difficult features to build." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Simple Search for Feeds, Saved Stories, and Blurblogs" />
<script type="application/ld+json">
{"headline":"Simple Search for Feeds, Saved Stories, and Blurblogs","dateModified":"2013-07-30T12:38:23-04:00","datePublished":"2013-07-30T12:38:23-04:00","url":"https://blog.newsblur.com/2013/07/30/simple-search-for-feeds-saved-stories-and-blurblogs/","@type":"BlogPosting","description":"Search, which can easily be considered one of the most important features of a world-class news reader, is also one of the most difficult features to build.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/07/30/simple-search-for-feeds-saved-stories-and-blurblogs/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-07-30T12:38:23-04:00","datePublished":"2013-07-30T12:38:23-04:00","description":"Search, which can easily be considered one of the most important features of a world-class news reader, is also one of the most difficult features to build.","headline":"Simple Search for Feeds, Saved Stories, and Blurblogs","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/07/30/simple-search-for-feeds-saved-stories-and-blurblogs/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/07/30/simple-search-for-feeds-saved-stories-and-blurblogs/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Mark as read by number of days and other improvements | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Mark as read by number of days and other improvements" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Heres a few big improvements for the NewsBlur website." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Mark as read by number of days and other improvements" />
<script type="application/ld+json">
{"headline":"Mark as read by number of days and other improvements","dateModified":"2013-09-13T17:05:55-04:00","datePublished":"2013-09-13T17:05:55-04:00","url":"https://blog.newsblur.com/2013/09/13/mark-as-read-by-number-of-days-and-other/","@type":"BlogPosting","description":"Heres a few big improvements for the NewsBlur website.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/09/13/mark-as-read-by-number-of-days-and-other/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-09-13T17:05:55-04:00","datePublished":"2013-09-13T17:05:55-04:00","description":"Heres a few big improvements for the NewsBlur website.","headline":"Mark as read by number of days and other improvements","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/09/13/mark-as-read-by-number-of-days-and-other/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/09/13/mark-as-read-by-number-of-days-and-other/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Upping unread stories to 30 days for premium accounts | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Upping unread stories to 30 days for premium accounts" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="While I love shipping new features and fixing bugs, the single largest user request was neither a feature nor a bug. NewsBlur allows for two weeks of unread stories. Once a story is more than 14 days old, it would no longer show up as unread. The justification for this was simple: you have a week to read a story, and have a second week as a grace period." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Upping unread stories to 30 days for premium accounts" />
<script type="application/ld+json">
{"headline":"Upping unread stories to 30 days for premium accounts","dateModified":"2013-09-16T17:09:14-04:00","datePublished":"2013-09-16T17:09:14-04:00","url":"https://blog.newsblur.com/2013/09/16/upping-unread-stories-to-30-days-for-premium/","@type":"BlogPosting","description":"While I love shipping new features and fixing bugs, the single largest user request was neither a feature nor a bug. NewsBlur allows for two weeks of unread stories. Once a story is more than 14 days old, it would no longer show up as unread. The justification for this was simple: you have a week to read a story, and have a second week as a grace period.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/09/16/upping-unread-stories-to-30-days-for-premium/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-09-16T17:09:14-04:00","datePublished":"2013-09-16T17:09:14-04:00","description":"While I love shipping new features and fixing bugs, the single largest user request was neither a feature nor a bug. NewsBlur allows for two weeks of unread stories. Once a story is more than 14 days old, it would no longer show up as unread. The justification for this was simple: you have a week to read a story, and have a second week as a grace period.","headline":"Upping unread stories to 30 days for premium accounts","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/09/16/upping-unread-stories-to-30-days-for-premium/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/09/16/upping-unread-stories-to-30-days-for-premium/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Offline reading with the NewsBlur iOS app | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Offline reading with the NewsBlur iOS app" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Today Im launching version 3.0 of the iPhone and iPad app for NewsBlur. This major update brings loads of big features that combine to make the worlds best iOS news reader with the fastest sync in town." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Offline reading with the NewsBlur iOS app" />
<script type="application/ld+json">
{"headline":"Offline reading with the NewsBlur iOS app","dateModified":"2013-09-17T12:46:00-04:00","datePublished":"2013-09-17T12:46:00-04:00","url":"https://blog.newsblur.com/2013/09/17/offline-reading-with-the-newsblur-ios-app/","@type":"BlogPosting","description":"Today Im launching version 3.0 of the iPhone and iPad app for NewsBlur. This major update brings loads of big features that combine to make the worlds best iOS news reader with the fastest sync in town.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/09/17/offline-reading-with-the-newsblur-ios-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-09-17T12:46:00-04:00","datePublished":"2013-09-17T12:46:00-04:00","description":"Today Im launching version 3.0 of the iPhone and iPad app for NewsBlur. This major update brings loads of big features that combine to make the worlds best iOS news reader with the fastest sync in town.","headline":"Offline reading with the NewsBlur iOS app","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/09/17/offline-reading-with-the-newsblur-ios-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/09/17/offline-reading-with-the-newsblur-ios-app/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>The NewsBlur iPhone and iPad app meets iOS 7 | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="The NewsBlur iPhone and iPad app meets iOS 7" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Apples latest operating system for iOS is a departure from their old aesthetic. So Ive decided to give the NewsBlur iOS app a slightly new look. But even more than how the app looks is how the app works. Tons of new features made it into this mega-release." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="The NewsBlur iPhone and iPad app meets iOS 7" />
<script type="application/ld+json">
{"headline":"The NewsBlur iPhone and iPad app meets iOS 7","dateModified":"2013-10-28T09:53:00-04:00","datePublished":"2013-10-28T09:53:00-04:00","url":"https://blog.newsblur.com/2013/10/28/the-newsblur-iphone-and-ipad-app-meets-ios-7/","@type":"BlogPosting","description":"Apples latest operating system for iOS is a departure from their old aesthetic. So Ive decided to give the NewsBlur iOS app a slightly new look. But even more than how the app looks is how the app works. Tons of new features made it into this mega-release.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/10/28/the-newsblur-iphone-and-ipad-app-meets-ios-7/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-10-28T09:53:00-04:00","datePublished":"2013-10-28T09:53:00-04:00","description":"Apples latest operating system for iOS is a departure from their old aesthetic. So Ive decided to give the NewsBlur iOS app a slightly new look. But even more than how the app looks is how the app works. Tons of new features made it into this mega-release.","headline":"The NewsBlur iPhone and iPad app meets iOS 7","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/10/28/the-newsblur-iphone-and-ipad-app-meets-ios-7/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/10/28/the-newsblur-iphone-and-ipad-app-meets-ios-7/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Version 3.0 of the NewsBlur Android App | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Version 3.0 of the NewsBlur Android App" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Hot on the heels of version 3.0 of the NewsBlur iOS app comes the next version of the Android app. A bunch of new features have made it into this release, including the new story navigation pane and the text view." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Version 3.0 of the NewsBlur Android App" />
<script type="application/ld+json">
{"headline":"Version 3.0 of the NewsBlur Android App","dateModified":"2013-11-18T16:51:00-05:00","datePublished":"2013-11-18T16:51:00-05:00","url":"https://blog.newsblur.com/2013/11/18/version-3-0-of-the-newsblur-android-app/","@type":"BlogPosting","description":"Hot on the heels of version 3.0 of the NewsBlur iOS app comes the next version of the Android app. A bunch of new features have made it into this release, including the new story navigation pane and the text view.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/11/18/version-3-0-of-the-newsblur-android-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-11-18T16:51:00-05:00","datePublished":"2013-11-18T16:51:00-05:00","description":"Hot on the heels of version 3.0 of the NewsBlur iOS app comes the next version of the Android app. A bunch of new features have made it into this release, including the new story navigation pane and the text view.","headline":"Version 3.0 of the NewsBlur Android App","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/11/18/version-3-0-of-the-newsblur-android-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/11/18/version-3-0-of-the-newsblur-android-app/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Background updates and dynamic font sizing on the NewsBlur iOS app | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Background updates and dynamic font sizing on the NewsBlur iOS app" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="This week brings us a minor, but major, update for the NewsBlur iOS app. Several new features, some due to new APIs in iOS 7, have made it into the app." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Background updates and dynamic font sizing on the NewsBlur iOS app" />
<script type="application/ld+json">
{"headline":"Background updates and dynamic font sizing on the NewsBlur iOS app","dateModified":"2013-12-19T10:38:00-05:00","datePublished":"2013-12-19T10:38:00-05:00","url":"https://blog.newsblur.com/2013/12/19/background-updates-and-dynamic-font-sizing-on-the/","@type":"BlogPosting","description":"This week brings us a minor, but major, update for the NewsBlur iOS app. Several new features, some due to new APIs in iOS 7, have made it into the app.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/12/19/background-updates-and-dynamic-font-sizing-on-the/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-12-19T10:38:00-05:00","datePublished":"2013-12-19T10:38:00-05:00","description":"This week brings us a minor, but major, update for the NewsBlur iOS app. Several new features, some due to new APIs in iOS 7, have made it into the app.","headline":"Background updates and dynamic font sizing on the NewsBlur iOS app","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/12/19/background-updates-and-dynamic-font-sizing-on-the/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/12/19/background-updates-and-dynamic-font-sizing-on-the/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Faster parallel network requests for version 3.5 of the NewsBlur Android app | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Faster parallel network requests for version 3.5 of the NewsBlur Android app" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="The single biggest criticism Ive heard of the Android app is that it can be slow when loading feeds and then loading stories. That changes today with the release of version 3.5 of the NewsBlur Android app." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Faster parallel network requests for version 3.5 of the NewsBlur Android app" />
<script type="application/ld+json">
{"headline":"Faster parallel network requests for version 3.5 of the NewsBlur Android app","dateModified":"2014-01-03T11:35:00-05:00","datePublished":"2014-01-03T11:35:00-05:00","url":"https://blog.newsblur.com/2014/01/03/faster-parallel-network-requests-for-version-35/","@type":"BlogPosting","description":"The single biggest criticism Ive heard of the Android app is that it can be slow when loading feeds and then loading stories. That changes today with the release of version 3.5 of the NewsBlur Android app.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2014/01/03/faster-parallel-network-requests-for-version-35/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2014-01-03T11:35:00-05:00","datePublished":"2014-01-03T11:35:00-05:00","description":"The single biggest criticism Ive heard of the Android app is that it can be slow when loading feeds and then loading stories. That changes today with the release of version 3.5 of the NewsBlur Android app.","headline":"Faster parallel network requests for version 3.5 of the NewsBlur Android app","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2014/01/03/faster-parallel-network-requests-for-version-35/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2014/01/03/faster-parallel-network-requests-for-version-35/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

View file

@ -8,9 +8,9 @@
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
<link rel="alternate" type="application/rss+xml"
title="The NewsBlur Blog RSS feed"
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
<title>Three new features for the web: syntax highlighting for source code, adjustable video widths, and footnotes | The NewsBlur Blog</title>
<meta name="generator" content="Jekyll v4.2.0" />
<meta name="generator" content="Jekyll v4.2.2" />
<meta property="og:title" content="Three new features for the web: syntax highlighting for source code, adjustable video widths, and footnotes" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="A few small new features to get your first full week of the new year started off right." />
@ -23,7 +23,7 @@
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Three new features for the web: syntax highlighting for source code, adjustable video widths, and footnotes" />
<script type="application/ld+json">
{"headline":"Three new features for the web: syntax highlighting for source code, adjustable video widths, and footnotes","dateModified":"2014-01-06T12:46:00-05:00","datePublished":"2014-01-06T12:46:00-05:00","url":"https://blog.newsblur.com/2014/01/06/three-new-features-for-the-web-syntax/","@type":"BlogPosting","description":"A few small new features to get your first full week of the new year started off right.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2014/01/06/three-new-features-for-the-web-syntax/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2014-01-06T12:46:00-05:00","datePublished":"2014-01-06T12:46:00-05:00","description":"A few small new features to get your first full week of the new year started off right.","headline":"Three new features for the web: syntax highlighting for source code, adjustable video widths, and footnotes","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2014/01/06/three-new-features-for-the-web-syntax/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2014/01/06/three-new-features-for-the-web-syntax/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />

Some files were not shown because too many files have changed in this diff Show more